Developer API Reference

The public HTTP surface of Final DeFi: intent status and receipts, fee quotes, Launch Sale reads, oracle marks and Final Chain account views.

Last updated 2026-09-12 · maintained in the Final Wiki

The public HTTP surface of Final DeFi, precisely: what each route takes, what it answers, and how it refuses. Base URLs https://api.final-de.fi (production) and https://api-test.final-de.fi (test). Every route answers JSON. Submitting intents is done through Final Wallet clients (the dashboard and, next, the extension); the submission format is not a public interface and is deliberately not documented here.

Final Chain is still in development; the state plane in service today is the Pre-Final Chain, a temporary build that is not open to external transactions. The reads below are the supported way to consume its state, and they will keep answering the same way after the move to Final Chain.

#Conventions

  • Chain reference. Chains are identified by a 32-byte reference, the same value the settlement and Launch Sale contracts key on. GET /final-chain/chains lists the enabled set with references and ids.
  • Amounts are decimal strings in base units. Times are Unix seconds unless a field says otherwise.
  • Refusals are named: a 4xx body carries error (a stable code) and message (human text). Match on the code, never on the message.
  • Rate limits are per client and per route family; 429 carries Retry-After.
  • Read routes are unauthenticated. Operator routes are not part of this surface.

#Environments and endpoints

Two environments, the same routes and the same contract addresses on every chain (the addresses are deployed deterministically; read them live from GET /system/config).

TestProduction
APIhttps://api-test.final-de.fihttps://api.final-de.fi
Wallet dashboardhttps://test-wallet.final-de.fi (register: /#register)https://wallet.final-de.fi
Explorerhttps://test-explorer.final-de.fihttps://explorer.final-de.fi
Execution chainsSepolia 11155111, OP Sepolia 11155420Ethereum 1, Optimism 10, Polygon 137, Base 8453, Arbitrum 42161
Final Gateway0x0000000000885A9026278be27079584142eC7ef6 on every chainsame
Final Wallet factory0x00000000000f5907cc946b140BfCA6d85B4dd7A2 on every chainsame

Plainly: the test and production environments have the same shape — one API, one wallet dashboard, one explorer each — and the gateway and the wallet factory sit at the same address on every chain of both.

  • The test environment runs the same code as production on the test chains; build against it first. tPHI, the test token, comes from the faucet (below).
  • The state plane's own RPC is not a public endpoint in either environment; its state is served by the reads on this page and by the explorer.
  • Browser calls are accepted from https://final-de.fi only (CORS). Integrate server-side, or through your own backend.

#Developer tools

  • tPHI faucet: 1,000 tPHI per address, per test chain, per calendar day (resets at 00:00 UTC), open to everyone, on Sepolia, OP Sepolia, Base Sepolia and Arbitrum Sepolia. The contract sends the tPHI itself when an address calls drip(); anyone may call dripTo(address) for another address. Use the faucet page on the website (final-de.fi/faucet) or call the contract from your wallet. Contracts — Sepolia 0x1e554b4C28734a02312Ad33a5E2fEC520B794970, OP Sepolia 0x3c0B973eb89aE60eFb7D09D92B19841e8918b840, Base Sepolia 0x71ae7E3D53dd33aB35eE36d324faA01bD19A95D5, Arbitrum Sepolia 0x41f428a84f20A5EF1db99F1e3ac1e1BE38509C22; the token is tPHI 0x00000000E76d2EdE04ce47C88Cbc9D058e2Efbb4 on all four. Views open(), nextAt(address) (0 = can draw, else the next midnight UTC), remaining(), amount(). Each faucet was funded with 50,000 tPHI; the page shows the remaining level. Polygon Amoy is observed only: no tPHI there.
  • Mock assets on the test chains for vAsset and fee testing: USDF (a USD-pegged mock stablecoin) and fETH, with a public faucet function on the token itself.

#Intent status and receipts

#GET /intents/{id} (also /v01/intents/{id})

The lifecycle of one intent. id is the identifier your wallet client shows after submission (posted intents are pq_ followed by the intent's leaf hash).

Answer 200:

{
  "intentId": "pq_0x…",
  "status": "INCLUDED",
  "receipt": {
    "status": "INCLUDED",
    "chainId": 11155420,
    "txHash": "0x…",
    "includedAtBlock": "48325955",
    "includedAt": "2026-09-03T16:54:20.000Z"
  }
}

status values and what they mean:

StatusMeaningFinal?
QUEUED / PENDINGaccepted, not yet executedno
INCLUDEDthe bundle carrying the intent is in a block on the target chainno (finality pending)
EXECUTEDthe intent's row executed; observed at the chain's finalityyes
ROW_REVERTEDthe bundle landed but this intent's own call revertedyes
REVERTEDthe whole bundle revertedyes
RECLAIMEDstaged funds were reclaimed; nothing executedyes
EXPIREDthe deadline passed with no executionyes
REJECTED_GAS_DRIFTgas moved past what the intent's terms cover; retried a bounded number of times, then droppedyes
REJECTED_EXECUTED_NO_EFFECTa bundle with these exact terms already executed and this row took no effect; not retriedyes

Plainly: an intent is pending until it is included and included until the chain finalizes it; then it is one of executed, reverted on its own row, reverted with its bundle, reclaimed, expired, or rejected because gas drifted or an identical bundle had already executed — every final status comes with a receipt.

404 { "error": "Intent not found" } means the id was never submitted here.

#GET /v01/stream

Server-sent events of lifecycle transitions: one event per status change, data = the same object GET /intents/{id} answers.

#Preflight

#POST /preflight

Ask whether an intent would be admitted, before a user signs it. Body: the intent row as your client composes it (wallet, target calldata, value, deadline, fee fields, target chain). Answer 200 { "ok": true, … } or 200 { "ok": false, "failure": "<code>", "details": [ … ] }. Preflight mirrors the admission gate exactly; a false here is a refusal there.

#Fees

#POST /fees/compose

Compose a fee for a set of legs. boundedMaxWei — the most, in wei, the payer accepts for the whole intent — is required; every other field but targetChainId is optional.

Body:

{
  "targetChainId": 11155420,
  "boundedMaxWei": "5000000000000000",
  "legs": [{ "chainId": 11155420, "gasLimit": "400000" }],
  "feeToken": "0x0000000000000000000000000000000000000000",
  "intent": { "privacy": "private" },
  "executeNotBefore": 1788470000
}

Up to eight legs, each with chainId and gasLimit (a creation leg needs no gasLimit; kind, tipPerGas and calldataBytes are optional). feeToken zero means the native coin. intent.privacy is visible or private; intent.payInPhi asks for the PHI price. wallet lets the quote read that wallet's fee deposit. executeNotBefore is optional; more than a minute ahead marks the quote as scheduled and retains 50 % instead of 15 %.

Answer: headroom (held, reason, headroomWei, neededWei — whether the target chain can carry the intent now) and quote: ok, totalWei (= feeAmount), its parts costBasisWei, markupWei, floorWei, serviceFeeWei, postingFeeWei, delayFeeWei, privacyFeeWei, nonRefundableWei, depositRequiredWei; tier (name, index, markup in basis points, discount); retained (basis points, scheduled, envelopeWei, bufferWei); privacy (option, premiumWei, laneAvailable, slotIndex, slotSeconds); delay (horizonSeconds, leadMs, opensAtMs); admission (the gas frame, the floor, the price source and its age); one entry per leg (boundedMaxWei, gasCostWei, markupWei, premiumWei); composedAt. Refusals: INVALID_COMPOSE_REQUEST (with details naming the field), INVALID_EXECUTE_NOT_BEFORE, COMPOSE_UNAVAILABLE (503); a quote that cannot be built answers ok: false with a reason such as INVALID_LEGS or CHAIN_NOT_SERVED.

The retained buffer is why a settled charge can differ from a quote: every intent is charged a gas margin on top of its bounded maximum — 15 %, or 50 % for a scheduled intent — every time. It is the payer's money held at that gateway: at settlement, the difference between what was paid and what the executions cost is saved to the payer's fee deposit there, and the gateway draws that deposit first on the payer's next intent. Nothing is transferred back; unused margin is gas already paid. A retry executes the terms that were admitted; it is not re-priced against the floor.

#GET /v01/fee-quote?chainId=… and GET /v01/fee-tokens?chainId=…

An indicative quote for one chain, and the fee tokens accepted there with decimals and symbols.

#GET /v01/subsidy/{owner}

The gasless-lane subsidy budget available to an owner.

#Launch Sale

#GET /presale/summary

phiSold, remainingPhi, capPhi, priceUsdMicros, the Final Chain block, and the most recent admissions (source chain reference, sequence, PHI amount, delivery chain).

#GET /presale/quote

Query: chainRef (payment chain), token (native or an address), amount (base units), targetChainRef (delivery chain), beneficiary (address).

Answer: phiAmount, phiAmountWei, usdMicros, markUsdMicros, pricedAtBlock, fee (amount in the payment asset, usdMicros, legs, tier, beneficiary, targetChainRef), totalAmount (what to send: amount plus fee), minimumPhiWei. Refusals: INVALID_CHAIN_REF, INVALID_AMOUNT, INVALID_TOKEN, INVALID_TARGET_CHAIN_REF, UNKNOWN_CHAIN, UNKNOWN_TARGET_CHAIN, ASSET_NOT_ACCEPTED, QUOTE_UNAVAILABLE (503, the fee cannot be priced yet), RPC_UNCONFIGURED (503).

#GET /presale/status/{chainRef}/{seq}

{ "state": "pending" }, or admitted with phiAmount, phiAmountWei, usdMicros, targetChainRef, moveSeq, or delivered.

#POST /presale/rpc/{chainId}

A read relay onto the fleet's pooled RPC for the Launch Sale page: JSON-RPC body, allow-listed read methods only (eth_call with the page's selectors, eth_getBalance, eth_feeHistory up to 20 blocks, eth_blockNumber, eth_chainId), no batches, its own rate bucket. Upstream errors are not echoed.

#Searchers

Searcher flow and user intents are auctioned together in one lane. Searcher rows sit strictly after every user row — backrun only — and a bid whose inclusion would worsen any user intent's outcome is refused at submission. Only an enforceable bid counts: one paid atomically inside the bundle, or one backed by collateral the searcher has deposited with the gateway. The private order-flow ingress publishes the terms and the rejection codes.

#State-plane reads (/final-chain/*)

These routes read the state plane — the Pre-Final Chain today, Final Chain at release — and keep their paths across that move.

RouteAnswer
GET /final-chain/account/{address}the wallet's published account record with its inclusion proof
GET /final-chain/wallet-of/{owner}wallets opened for an owner
GET /final-chain/tree/{tree}/leaf/{key}one tree leaf by key, with proof
GET /final-chain/treescurrent tree roots
GET /final-chain/chainsthe enabled chain set: id, reference, name, block time
GET /final-chain/open-policythe account-open policy in force: the chains an account is created on — the enabled chains that carry the protocol's contracts, from the registry tree, with its epoch — and the opening terms every new account starts with; an open request composed over any other chain table is refused

Plainly: everything the state plane knows about an account, a tree leaf, the tree roots, the enabled chains and the opening policy is readable by lookup — the account record and the leaves with their proofs — with no scanning and no trust in the answering server.

Proofs answer against the roots the execution chains hold; they are what a client presents when it needs to prove state, and they are size-specific.

#Oracle

RouteAnswer
GET /oracle/{asset}the current mark for an asset

#Accounts and recovery

  • POST /accounts/open — submit an account-open request for a new identity; the body is the certificate-backed request the wallet client produces.
  • POST /recovery — submit a recovery request for a wallet.
  • POST /recovery/attest/jwt — the JWT/passkey attestor lane (Google issuer set today).
  • POST /recovery/validator-context — context for recovery validators.
  • GET /recovery/attestors — the armed attestor set.

#System

  • GET /system/config — non-secret runtime configuration a client needs: chain set, gateway and factory addresses, accepted fee tokens.
  • GET /v01/treasury/state — the treasury's public state.
  • GET /health answers liveness for the process; GET /ready answers whether it can serve, and why not.

#Error codes, common

CodeHTTPMeaning
INVALID_*400a parameter is malformed
UNKNOWN_CHAIN, CHAIN_NOT_SERVED400the chain is not on the enabled set
RATE_LIMITED429slow down; Retry-After is set
RPC_UNCONFIGURED, QUOTE_UNAVAILABLE503a dependency is not ready; retry later
*_FAILED502an upstream read failed; retry later

Plainly: a 400 means the request is wrong, a 429 means you are too fast, a 502 or 503 means something behind the API is not ready — fix the first, retry the others.

#See also

Protocol integration guide · Intents · Fees and PHI · Glossary