Guide · FDC
Verify a cross-chain payment in 20 lines of Solidity
Flare Data Connector (FDC) lets your contract answer questions like "did this wallet pay that wallet 100 XRP last week?" without a bridge, without wrapping, and without a trusted intermediary. A committee of Flare validators signs off on a Merkle root of off-chain facts every voting round; your contract checks one Merkle branch against the committed root and uses the result. This guide walks through the verification snippet, the off-chain request flow, and the mock-proof shortcut we shipped so you can develop without waiting for a real attestation round.
Verify a Payment proof in Solidity
The whole integration is three imports, one verifycall, and a couple of sanity checks on the decoded fields. Caller supplies the proof as calldata; your contract validates and acts.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {IPayment} from
"@flarenetwork/flare-periphery-contracts/flare/IPayment.sol";
import {ContractRegistry} from
"@flarenetwork/flare-periphery-contracts/flare/ContractRegistry.sol";
contract InvoiceSettler {
mapping(bytes32 => bool) public settled;
function settle(IPayment.Proof calldata proof) external {
require(
ContractRegistry.auxiliaryGetIFdcVerification()
.verifyPayment(proof),
"FDC: invalid Merkle proof"
);
require(proof.data.responseBody.status == 0, "payment not successful");
bytes32 reference = proof.data.responseBody.standardPaymentReference;
require(!settled[reference], "already settled");
settled[reference] = true;
// ... release goods, mint tokens, unlock escrow, etc.
}
}verifyPayment hashes the response body against the supplied Merkle branch and checks it against the on-chain voting round root. Anyone can submit the proof; only genuinely-attested payments pass. standardPaymentReference is the off-chain-controlled 32-byte reference the sender attaches to the payment, which is how you idempotently link the on-chain settle to a specific invoice.
The full request flow
Getting from "I want to verify this payment" to "I have a proof" is a four-step off-chain dance:
- Submit request. Call
FdcHub.requestAttestation(bytes requestBody)on Flare. Pay the fee, which is currently tiny but varies by type. This emits anAttestationRequestevent that FlareForge indexes. - Wait one voting round. Rounds are 90 seconds. The committee sees your request, checks the source chain, and signs.
- Fetch proof off-chain. Pull the signed response + Merkle branch from any Flare attestation client HTTP API once the round finalizes. You pass the request hash and round id.
- Submit to your contract. Pass the proof struct as calldata to whatever function uses it (the
settle(...)above, or your own flow).
The typical attestation-client endpoint looks like this. Replace the host with your preferred provider (Flare Foundation runs one, several node operators run their own):
curl -X POST https://attestation-client.example/attestation-client/api/proof/get-specific-proof \
-H "Content-Type: application/json" \
-d '{
"votingRoundId": 1000042,
"requestBytes": "0x5061796d656e7400..."
}'Mock proofs for local development
Waiting 90 seconds per round while you iterate on a contract is slow. FlareForge Sandbox generates structurally-identical mock proofs offline so you can unit test the verification flow without network round-trips. The Merkle root is deterministic and self-consistent inside the payload, so your contract's verifyPayment will accept or reject based on the same logic as real FDC, except no on-chain round is involved.
curl -X POST https://flareforge.io/api/v1/sandbox/generate-proof \
-H "Content-Type: application/json" \
-d '{
"type_id": "Payment",
"source_id": "XRP",
"fields": {
"transactionId": "0x...",
"inUtxo": 0,
"utxo": 0
}
}'The response is a { merkleLeaf, merkleProof[], response }tuple that maps directly onto the IPayment.Proofstruct. Plug it into a Foundry test and you can fuzz your settlement logic without any RPC calls.
EVMTransaction in one screen
Same shape, different response body. Use this when you care about an on-chain event on Ethereum, Polygon, Arbitrum, or any other EVM source:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {IEVMTransaction} from
"@flarenetwork/flare-periphery-contracts/flare/IEVMTransaction.sol";
import {ContractRegistry} from
"@flarenetwork/flare-periphery-contracts/flare/ContractRegistry.sol";
contract CrossChainClaim {
function claim(IEVMTransaction.Proof calldata proof) external {
require(
ContractRegistry.auxiliaryGetIFdcVerification()
.verifyEVMTransaction(proof),
"FDC: invalid proof"
);
require(proof.data.responseBody.status == 1, "source tx reverted");
// Walk proof.data.responseBody.events[] for the log you care about.
}
}Attestation types at a glance
| Type | What it proves |
|---|---|
| Payment | Assert that an account paid another account a specific amount on an external chain. Most common for escrow release, token bridges, invoice settlement. |
| EVMTransaction | Prove an EVM transaction happened on a source chain (Ethereum, Polygon, BSC, etc.) and read its logs or return value inside your Flare contract. |
| ReferencedPaymentNonexistence | Prove that an expected payment did NOT happen before a deadline. Drives escrow timeouts and slashing. |
| ConfirmedBlockHeightExists | Prove a block with a given height is finalized on the source chain, which is how you finalize 'past a given depth' logic. |
| AddressValidity | Check that an off-chain address (XRP, BTC, etc.) is syntactically valid before accepting a user-supplied destination. |
| BalanceDecreasingTransaction | Prove a balance-decreasing outflow from a specific address. Used to detect unauthorized spends by escrow agents. |
Live feed of every AttestationRequest on Flare mainnet, filterable by type and source chain, is on the FDC Explorer.
Things that bite on the first integration
- Wrong source_id casing. Source IDs are ASCII padded to 32 bytes.
XRPworks;xrpdoes not. Same forBTC,ETH,DOGE. - Forgetting the fee.
requestAttestationtakesmsg.value. It reverts silently in some wallet UIs if you forget. - Relying on one proof forever. Proofs are tied to a specific voting round. They are not re-verifiable in a later round; if you need to re-prove, request again.
- Not storing the payment reference. Two contracts calling the same
settlewith the same proof must be idempotent. StorestandardPaymentReferencein a mapping and reject replays, as in the snippet above.
Related on the site
- FDC Attestation Explorer — every AttestationRequest on Flare mainnet, filter by type and source chain, 24h throughput chart.
- CrossChain Sandbox — generate mock proofs for your Foundry tests without a round trip.
- FTSOv2 in 10 lines — same series, for block-latency price feeds.
- FAssets risk in Python — same series, for FAssets agent risk monitoring.