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) andhttps://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/chainslists 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
4xxbody carrieserror(a stable code) andmessage(human text). Match on the code, never on the message. - Rate limits are per client and per route family;
429carriesRetry-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).
| Test | Production | |
|---|---|---|
| API | https://api-test.final-de.fi | https://api.final-de.fi |
| Wallet dashboard | https://test-wallet.final-de.fi (register: /#register) | https://wallet.final-de.fi |
| Explorer | https://test-explorer.final-de.fi | https://explorer.final-de.fi |
| Execution chains | Sepolia 11155111, OP Sepolia 11155420 | Ethereum 1, Optimism 10, Polygon 137, Base 8453, Arbitrum 42161 |
| Final Gateway | 0x0000000000885A9026278be27079584142eC7ef6 on every chain | same |
| Final Wallet factory | 0x00000000000f5907cc946b140BfCA6d85B4dd7A2 on every chain | same |
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.fionly (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 calldripTo(address)for another address. Use the faucet page on the website (final-de.fi/faucet) or call the contract from your wallet. Contracts — Sepolia0x1e554b4C28734a02312Ad33a5E2fEC520B794970, OP Sepolia0x3c0B973eb89aE60eFb7D09D92B19841e8918b840, Base Sepolia0x71ae7E3D53dd33aB35eE36d324faA01bD19A95D5, Arbitrum Sepolia0x41f428a84f20A5EF1db99F1e3ac1e1BE38509C22; the token is tPHI0x00000000E76d2EdE04ce47C88Cbc9D058e2Efbb4on all four. Viewsopen(),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:
| Status | Meaning | Final? |
|---|---|---|
QUEUED / PENDING | accepted, not yet executed | no |
INCLUDED | the bundle carrying the intent is in a block on the target chain | no (finality pending) |
EXECUTED | the intent's row executed; observed at the chain's finality | yes |
ROW_REVERTED | the bundle landed but this intent's own call reverted | yes |
REVERTED | the whole bundle reverted | yes |
RECLAIMED | staged funds were reclaimed; nothing executed | yes |
EXPIRED | the deadline passed with no execution | yes |
REJECTED_GAS_DRIFT | gas moved past what the intent's terms cover; retried a bounded number of times, then dropped | yes |
REJECTED_EXECUTED_NO_EFFECT | a bundle with these exact terms already executed and this row took no effect; not retried | yes |
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.
| Route | Answer |
|---|---|
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/trees | current tree roots |
GET /final-chain/chains | the enabled chain set: id, reference, name, block time |
GET /final-chain/open-policy | the 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
| Route | Answer |
|---|---|
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 /healthanswers liveness for the process;GET /readyanswers whether it can serve, and why not.
#Error codes, common
| Code | HTTP | Meaning |
|---|---|---|
INVALID_* | 400 | a parameter is malformed |
UNKNOWN_CHAIN, CHAIN_NOT_SERVED | 400 | the chain is not on the enabled set |
RATE_LIMITED | 429 | slow down; Retry-After is set |
RPC_UNCONFIGURED, QUOTE_UNAVAILABLE | 503 | a dependency is not ready; retry later |
*_FAILED | 502 | an 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