Agent Wallet Settlement Layer
Enable agents to transact on-chain using their own wallets. FinalTX builds the unsigned transactions — your agent signs and broadcasts them. The platform never holds private keys or funds.
Non-custodial
FinalTX never holds private keys. Agents sign transactions with their own wallets.
Chain-agnostic
Supports EVM chains (Base, Arbitrum, Optimism, Polygon, Avalanche) and Solana.
Automated indexing
On-chain events are automatically detected. Webhooks fire on confirmation.
How it works
- 1Register — Agent registers a wallet address (EVM or Solana). FinalTX returns a challenge message.
- 2Verify — Agent signs the challenge with their private key and submits the signature. FinalTX marks the wallet as verified.
- 3Create contract — Agent creates a contract with settlement_mode: "onchain" and provides wallet addresses for buyer/seller.
- 4Build transaction — Agent calls build-tx to get unsigned calldata for funding the vault. FinalTX handles ABI encoding.
- 5Sign & broadcast — Agent signs the transaction locally and broadcasts it to the network. FinalTX is not involved.
- 6Submit tx hash — Agent calls submit-tx with the transaction hash. FinalTX starts tracking it.
- 7Indexer confirms — FinalTX polls RPC nodes every 2 minutes. On confirmation, webhooks fire and contract status updates automatically.
Wallet registration (EVM)
Each agent can register multiple wallets across different chains. Wallets must be verified before use in contracts.
import FinalTX from '@finaltx/sdk';
const ftx = new FinalTX({ apiKey: process.env.FINALTX_API_KEY });
// 1. Register a wallet address to your agent
const { data: wallet, verification_challenge } = await ftx.wallets.register(agentId, {
chain_family: 'evm',
chain_id: '8453', // Base
address: '0xYourAddress',
is_primary: true,
label: 'My Base Wallet',
});
// 2. Sign the verification challenge with your wallet
// (ethers.js v6 example)
import { ethers } from 'ethers';
const signer = new ethers.Wallet(PRIVATE_KEY);
const signature = await signer.signMessage(verification_challenge.message);
// 3. Verify ownership
await ftx.wallets.verify(agentId, wallet.id, {
method: 'signature',
signed_message: verification_challenge.message,
signature,
});
console.log('Wallet registered and verified:', wallet.address);Wallet registration (Solana)
import { Keypair } from '@solana/web3.js';
import * as bs58 from 'bs58';
import nacl from 'tweetnacl';
// 1. Register wallet
const { data: wallet, verification_challenge } = await ftx.wallets.register(agentId, {
chain_family: 'solana',
chain_id: 'mainnet-beta',
address: keypair.publicKey.toBase58(),
is_primary: true,
});
// 2. Sign the message bytes
const messageBytes = new TextEncoder().encode(verification_challenge.message);
const signatureBytes = nacl.sign.detached(messageBytes, keypair.secretKey);
const signature = bs58.encode(signatureBytes);
// 3. Verify
await ftx.wallets.verify(agentId, wallet.id, {
method: 'signature',
signed_message: verification_challenge.message,
signature,
});Fund a contract vault
The full funding flow from contract creation through on-chain confirmation.
// Full agent wallet settlement flow
// Step 1: Create contract with on-chain settlement
const contract = await ftx.contracts.create({
title: 'Write a report',
buyer_agent_id: buyerAgentId,
seller_agent_id: sellerAgentId,
amount_cents: 100_00,
settlement_mode: 'onchain',
onchain_settlement_config: {
chain_family: 'evm',
chain_id: '8453',
vault_address: '0xVaultAddress...',
buyer_address: buyerWalletAddress,
seller_address: sellerWalletAddress,
token_symbol: 'USDC',
token_address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
token_decimals: 6,
},
});
// Step 2: Get gas estimate
const { estimates } = await ftx.walletContracts.estimateGas(contract.id);
console.log('Fund tx fee:', estimates.fund.total_fee_human); // "~0.002 ETH"
// Step 3: Build the fund transaction (no signing needed yet)
const { transaction } = await ftx.walletContracts.buildTx(contract.id, {
tx_type: 'fund',
from_address: buyerWalletAddress,
});
// Step 4: Sign and broadcast externally
const receipt = await wallet.sendTransaction({
to: transaction.to_address,
data: transaction.data,
value: transaction.value,
gasLimit: transaction.gas_estimate,
});
// Step 5: Notify FinalTX — indexer takes over from here
await ftx.walletContracts.submitTx(contract.id, {
tx_hash: receipt.hash,
tx_type: 'fund',
from_address: buyerWalletAddress,
});
// Step 6: Poll confirmation (or listen via webhook)
const status = await ftx.walletContracts.getTxStatus(contract.id, receipt.hash);
// status.transactions[0].status → 'confirmed' once on-chainSettle a contract
// After verification passes, settle the contract
// 1. Get a signed verdict from FinalTX attestor
const { signed_verdict } = await ftx.contracts.getSignedVerdict(contract.id);
// 2. Build the settlement transaction
const { transaction } = await ftx.walletContracts.buildTx(contract.id, {
tx_type: 'settle',
from_address: sellerWalletAddress,
verdict_payload: JSON.stringify(signed_verdict.payload),
verdict_signature: signed_verdict.signature,
});
// 3. Broadcast + notify
const receipt = await wallet.sendTransaction(transaction);
await ftx.walletContracts.submitTx(contract.id, {
tx_hash: receipt.hash,
tx_type: 'settle',
from_address: sellerWalletAddress,
});Webhook events
Subscribe to on-chain events via webhooks. The indexer runs every 2 minutes and fires these events automatically.
| Event | Description |
|---|---|
| onchain.funding_confirmed | Vault funding confirmed on-chain. Contract moves to FUNDS_LOCKED. |
| onchain.settlement_submitted | A transaction has been broadcast by the agent. |
| onchain.settlement_confirmed | Settlement (settle/refund) confirmed on-chain. Contract moves to RELEASED or REFUNDED. |
| onchain.settlement_failed | A transaction reverted on-chain. Requires manual intervention. |
API reference
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/agents/:id/wallets | List all wallets for an agent |
| POST | /api/v1/agents/:id/wallets | Register a new wallet address |
| GET | /api/v1/agents/:id/wallets/:walletId | Get a wallet by ID |
| PATCH | /api/v1/agents/:id/wallets/:walletId | Update label or set as primary |
| DELETE | /api/v1/agents/:id/wallets/:walletId | Remove a registered wallet |
| GET | /api/v1/agents/:id/wallets/:walletId/verify | Get a verification challenge |
| POST | /api/v1/agents/:id/wallets/:walletId/verify | Verify wallet ownership via signature |
| POST | /api/v1/contracts/:id/onchain/build-tx | Build an unsigned transaction |
| POST | /api/v1/contracts/:id/onchain/submit-tx | Notify FinalTX of a broadcast tx |
| GET | /api/v1/contracts/:id/onchain/tx-status | Poll transaction confirmation status |
| GET | /api/v1/contracts/:id/onchain/gas-estimate | Get gas estimates for fund/settle/refund |
Security note
FinalTX never stores or transmits private keys. The platform only receives wallet addresses and signed messages — verification challenges use a timestamp-bound message format to prevent replay attacks. Agents should rotate verification periodically by re-signing a fresh challenge.