Project: Build a Cross-Border Settlement Service
A complete, runnable project implementing a two-country settlement: mint on the sending side, transfer on-chain, then burn-and-credit on the receiving side — three calls, with each side handled by its own local backend. See the Assets API for the endpoint reference.
What you'll build
A settle.js script covering one full transfer: a Kenyan sender's KES deposit becomes a stable on-chain asset, moves to a Nigerian recipient's wallet, and is burned-and-credited to NGN on arrival — idempotent end-to-end via one shared externalId.
Prerequisites
- Node.js 18+ (for global
fetch) - A running comet-engine with a tenant API key/secret — see Authentication
Project setup
mkdir cross-border && cd cross-border && npm init -y && npm install dotenv.env:
COMET_BASE_URL=http://localhost:8100
COMET_API_KEY=your-tenant-api-key
COMET_API_SECRET=your-tenant-api-secretCreate settle.js with this shared helper at the top — every step below adds to the same file:
import 'dotenv/config';
const base = process.env.COMET_BASE_URL;
const headers = {
'Content-Type': 'application/json',
'X-API-Key': process.env.COMET_API_KEY,
'X-API-Secret': process.env.COMET_API_SECRET,
};
async function comet(method, path, body) {
const res = await fetch(base + path, { method, headers, body: body ? JSON.stringify(body) : undefined });
const data = await res.json();
if (!res.ok) throw new Error(`${method} ${path} -> ${res.status}: ${JSON.stringify(data)}`);
return data;
}The third call differs by chain, not just the chain param
burn-from (relayer-initiated burn against a prior approval) is EVM only — Stellar has no allowance concept, so its equivalent flow uses burn-by-holder instead. Everything else about the settlement is identical either way.
Step 1: Sender deposits and receives a stable asset
curl -X POST "http://localhost:8100/api/v1/assets/usdc/mint" \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-API-Secret: YOUR_API_SECRET" \
-d '{
"userId": 24,
"amountBase": "50000000",
"externalId": "cross-001"
}'{
"amount": 50,
"chain": "celo",
"symbol": "USDC",
"txHash": "0x..."
}Step 2: Send it on-chain to the recipient
curl -X POST "http://localhost:8100/api/v1/assets/usdc/send" \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-API-Secret: YOUR_API_SECRET" \
-d '{
"userId": 24,
"to": "0x176d431dbd00639e13177ef0c397a9fa2eb77a7b",
"amountBase": "50000000"
}'{
"txHash": "0x...",
"chain": "celo",
"symbol": "USDC",
"amount": 50
}Step 3: Recipient's backend settles to local currency
curl -X POST "http://localhost:8100/api/v1/assets/usdc/burn-from" \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-API-Secret: YOUR_API_SECRET" \
-d '{
"userId": 56789,
"amountBase": "50000000",
"externalId": "cross-001"
}'{
"txHash": "0x...",
"chain": "celo",
"symbol": "USDC",
"amount": 50
}Requires prior approval
burn-from only works once the recipient's wallet has approved the relayer to spend (see Approve Spender) — this is the one EVM-specific step in an otherwise chain-agnostic flow.
The same externalId across the mint and the final burn keeps the whole transfer idempotent end-to-end, even though it spans two separate local backends and currencies.
Step 4: Run the full settlement as one script
Add main() to settle.js — only the asset symbol, recipient address format, and final settlement call (per the warning above) change per chain:
async function main() {
const externalId = `cross-${Date.now()}`;
const recipientAddress = '0x176d431dbd00639e13177ef0c397a9fa2eb77a7b'; // recipient's EVM wallet
const minted = await comet('POST', '/api/v1/assets/usdc/mint', { userId: 24, amountBase: '50000000', externalId });
console.log('sender funded:', minted.txHash);
const sent = await comet('POST', '/api/v1/assets/usdc/send', { userId: 24, to: recipientAddress, amountBase: '50000000' });
console.log('sent on-chain:', sent.txHash);
// Requires the recipient's wallet to have approved the relayer first —
// see the warning above. In a real app, the recipient's own backend
// calls this after their wallet has approved.
const settled = await comet('POST', '/api/v1/assets/usdc/burn-from', { userId: 56789, amountBase: '50000000', externalId });
console.log('settled, NGN credited:', settled.txHash);
}
main().catch((e) => { console.error(e); process.exit(1); });node settle.jsWhat you built
- A real two-country settlement: mint, on-chain transfer, and settlement to local currency, in one idempotent flow.
- The one EVM-vs-Stellar divergence (
burn-fromvsburn-by-holder) handled explicitly rather than papered over.
Next steps: wire Step 1 to your own deposit-confirmation webhook and Step 3 to your own local-currency payout, or add Approvals in front of the settlement call if a single credential settling large transfers unilaterally is a risk for your business.