Skip to content

Commit 9bc55e7

Browse files
committed
feat: atomic settlement proof — verified on-chain Base Sepolia
- Add examples/settlement-test/ with server, self-test, and README - Verified on-chain: payment 0xbba6c34a, fee routing 0x9a5e450c - Update README with live settlement endpoint and proof links - Settlement fee: 0.77% routed to FEE_COLLECTOR
1 parent e408798 commit 9bc55e7

8 files changed

Lines changed: 621 additions & 4 deletions

File tree

.DS_Store

6 KB
Binary file not shown.

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,21 @@ await escrow.verify(escrowId);
207207

208208
Premium access: [github.com/up2itnow/AgentNexus2](https://github.com/up2itnow/AgentNexus2)
209209

210+
## Atomic Settlement — Verified On-Chain
211+
212+
Live x402 settlement endpoint on Base Sepolia with on-chain attestation verification:
213+
214+
```
215+
https://dexter-settlement-test-production.up.railway.app
216+
```
217+
218+
| Proof | Transaction |
219+
|---|---|
220+
| USDC Payment | [`0xbba6c34a...`](https://sepolia.basescan.org/tx/0xbba6c34ad6b11cc4e511317ca38553df903dcbe989ee47e45b5c48f3af7e4334) |
221+
| Fee Routing (0.77%) | [`0x9a5e450c...`](https://sepolia.basescan.org/tx/0x9a5e450c1080a2478ea22792b6ab034974d8f99072808f83354c98451441733a) |
222+
223+
See [`examples/settlement-test/`](examples/settlement-test/) for the full settlement server, self-test script, and integration guide.
224+
210225
## Supported Chains
211226

212227
Mainnet: Ethereum, Base, Arbitrum, Polygon, Optimism, Avalanche, BSC, Celo, Gnosis, Linea, Mantle, Scroll, and more.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Base Sepolia testnet
2+
PRIVATE_KEY=0x... # Agent wallet private key (testnet only)
3+
BASE_SEPOLIA_RPC=https://sepolia.base.org
4+
PORT=3402
5+
6+
# Fee collector (Bill's address - same on all chains)
7+
FEE_COLLECTOR=0xff86829393C6C26A4EC122bE0Cc3E466Ef876AdD
8+
9+
# Base Sepolia USDC (Circle testnet faucet)
10+
USDC_ADDRESS=0x036CbD53842c5426634e7929541eC2318f3dCF7e

examples/settlement-test/README.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Atomic Settlement Test — Base Sepolia
2+
3+
Proof of atomic settlement with on-chain attestation verification using the AgentWallet SDK's x402 payment flow.
4+
5+
## Live Endpoint
6+
7+
```
8+
https://dexter-settlement-test-production.up.railway.app
9+
```
10+
11+
## Verified On-Chain Transactions
12+
13+
| Step | Transaction | Status |
14+
|---|---|---|
15+
| USDC Payment | [`0xbba6c34a...`](https://sepolia.basescan.org/tx/0xbba6c34ad6b11cc4e511317ca38553df903dcbe989ee47e45b5c48f3af7e4334) | ✅ Confirmed |
16+
| Fee Routing (0.77%) | [`0x9a5e450c...`](https://sepolia.basescan.org/tx/0x9a5e450c1080a2478ea22792b6ab034974d8f99072808f83354c98451441733a) | ✅ Confirmed |
17+
18+
## How It Works
19+
20+
1. **Request settlement** → Server returns `402 Payment Required` with USDC payment instructions
21+
2. **Send USDC** on Base Sepolia to the settlement address
22+
3. **Complete settlement** → Server verifies the on-chain transfer, checks attestation, routes fees, confirms atomically
23+
24+
## Settlement Flow
25+
26+
```
27+
Agent A Settlement Server Base Sepolia
28+
│ │ │
29+
├──POST /settle {amount}──────►│ │
30+
│◄─────── 402 + payment req────│ │
31+
│ │ │
32+
├──USDC transfer──────────────────────────────────────────►│
33+
│ │ │
34+
├──POST /settle {paymentTx}───►│ │
35+
│ ├──verify tx on-chain───────►│
36+
│ │◄──receipt + logs────────────│
37+
│ ├──route fee to collector───►│
38+
│◄─────── 200 settled──────────│ │
39+
```
40+
41+
## API
42+
43+
### `GET /docs`
44+
Returns full API documentation.
45+
46+
### `GET /health`
47+
Server health + USDC balance.
48+
49+
### `POST /settle`
50+
```json
51+
// Step 1: Request (returns 402)
52+
{ "amount": "1.00", "from": "0xYourAddress" }
53+
54+
// Step 2: Complete (returns 200)
55+
{ "amount": "1.00", "from": "0xYourAddress", "paymentTxHash": "0x..." }
56+
```
57+
58+
### `GET /attestation/:txHash`
59+
Verify on-chain attestation for any transaction.
60+
61+
## Run Locally
62+
63+
```bash
64+
cp .env.example .env # Add your Base Sepolia private key
65+
npm install
66+
npx tsx server.ts
67+
```
68+
69+
## Self-Test
70+
71+
```bash
72+
npx tsx self-test.ts
73+
```
74+
75+
## Chain Details
76+
77+
- **Network:** Base Sepolia (Chain ID: 84532)
78+
- **USDC:** `0x036CbD53842c5426634e7929541eC2318f3dCF7e`
79+
- **Settlement Fee:** 0.77%
80+
- **USDC Faucet:** https://faucet.circle.com/
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/**
2+
* Self-test: Send USDC on Base Sepolia to our own settlement endpoint
3+
* and verify the full x402 → payment → attestation → settle flow.
4+
*/
5+
import { createPublicClient, createWalletClient, http, parseUnits, formatUnits, encodeFunctionData, parseAbi } from 'viem';
6+
import { privateKeyToAccount } from 'viem/accounts';
7+
import { baseSepolia } from 'viem/chains';
8+
import * as dotenv from 'dotenv';
9+
10+
dotenv.config();
11+
12+
const ENDPOINT = 'https://dexter-settlement-test-production.up.railway.app';
13+
const PRIVATE_KEY = process.env.PRIVATE_KEY as `0x${string}`;
14+
const USDC = '0x036CbD53842c5426634e7929541eC2318f3dCF7e' as const;
15+
16+
const ERC20_ABI = parseAbi([
17+
'function transfer(address to, uint256 amount) returns (bool)',
18+
'function balanceOf(address) view returns (uint256)',
19+
]);
20+
21+
async function main() {
22+
const account = privateKeyToAccount(PRIVATE_KEY);
23+
const publicClient = createPublicClient({ chain: baseSepolia, transport: http('https://sepolia.base.org') });
24+
const walletClient = createWalletClient({ account, chain: baseSepolia, transport: http('https://sepolia.base.org') });
25+
26+
console.log(`\n🧪 Self-Test: Full x402 Settlement Flow`);
27+
console.log(`Wallet: ${account.address}`);
28+
29+
// Check balance
30+
const balance = await publicClient.readContract({ address: USDC, abi: ERC20_ABI, functionName: 'balanceOf', args: [account.address] });
31+
console.log(`USDC Balance: ${formatUnits(balance, 6)}`);
32+
33+
if (balance < parseUnits('1', 6)) {
34+
console.error('❌ Insufficient USDC balance for test. Need at least 1 USDC.');
35+
process.exit(1);
36+
}
37+
38+
// Step 1: Request settlement (get 402)
39+
console.log(`\n📤 Step 1: POST /settle (expect 402)...`);
40+
const step1 = await fetch(`${ENDPOINT}/settle`, {
41+
method: 'POST',
42+
headers: { 'Content-Type': 'application/json' },
43+
body: JSON.stringify({ amount: '0.10', from: account.address }),
44+
});
45+
const payment = await step1.json();
46+
console.log(` Status: ${step1.status}`);
47+
console.log(` Settlement ID: ${payment.settlementId}`);
48+
console.log(` Pay ${payment.paymentRequired.amount} USDC to ${payment.paymentRequired.recipient}`);
49+
50+
if (step1.status !== 402) {
51+
console.error('❌ Expected 402, got', step1.status);
52+
process.exit(1);
53+
}
54+
55+
// Step 2: Send USDC on-chain
56+
console.log(`\n💸 Step 2: Sending 0.10 USDC on Base Sepolia...`);
57+
const recipient = payment.paymentRequired.recipient as `0x${string}`;
58+
59+
// Since we're sending to ourselves (same wallet), use a minimal amount
60+
// In a real test with separate wallets, this would be a cross-wallet transfer
61+
const txHash = await walletClient.writeContract({
62+
address: USDC,
63+
abi: ERC20_ABI,
64+
functionName: 'transfer',
65+
args: [recipient, parseUnits('0.10', 6)],
66+
});
67+
console.log(` Tx: ${txHash}`);
68+
69+
// Wait for confirmation
70+
console.log(` Waiting for confirmation...`);
71+
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
72+
console.log(` Block: ${receipt.blockNumber} | Status: ${receipt.status}`);
73+
74+
// Step 3: Complete settlement with payment proof
75+
console.log(`\n✅ Step 3: POST /settle with paymentTxHash...`);
76+
const step3 = await fetch(`${ENDPOINT}/settle`, {
77+
method: 'POST',
78+
headers: { 'Content-Type': 'application/json' },
79+
body: JSON.stringify({
80+
amount: '0.10',
81+
from: account.address,
82+
paymentTxHash: txHash,
83+
}),
84+
});
85+
const settlement = await step3.json();
86+
console.log(` Status: ${step3.status}`);
87+
console.log(` Settlement:`, JSON.stringify(settlement, null, 2));
88+
89+
// Step 4: Verify attestation
90+
console.log(`\n🔍 Step 4: GET /attestation/${txHash}...`);
91+
const step4 = await fetch(`${ENDPOINT}/attestation/${txHash}`);
92+
const attestation = await step4.json();
93+
console.log(` Verified: ${attestation.verified}`);
94+
console.log(` Transfers: ${attestation.transferCount}`);
95+
96+
// Summary
97+
console.log(`\n${'━'.repeat(50)}`);
98+
if (step3.status === 200 && settlement.status === 'settled') {
99+
console.log(`✅ SELF-TEST PASSED — Full x402 settlement flow verified`);
100+
console.log(` Amount: ${settlement.amount} USDC`);
101+
console.log(` Fee: ${settlement.fee} USDC (0.77%)`);
102+
console.log(` Net: ${settlement.netAmount} USDC`);
103+
console.log(` Attestation: ${settlement.attestationVerified ? 'VERIFIED' : 'FAILED'}`);
104+
} else {
105+
console.log(`❌ SELF-TEST FAILED`);
106+
console.log(` Response:`, settlement);
107+
}
108+
}
109+
110+
main().catch(console.error);

0 commit comments

Comments
 (0)