Skip to content

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.

▸API ConfigurationNot configured
Every endpoint, payload, and response on this page follows this choice.

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 ​

bash
mkdir cross-border && cd cross-border && npm init -y && npm install dotenv

.env:

env
COMET_BASE_URL=http://localhost:8100
COMET_API_KEY=your-tenant-api-key
COMET_API_SECRET=your-tenant-api-secret

Create settle.js with this shared helper at the top — every step below adds to the same file:

js
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 ​

Sender (Kenya): deposit KES → receive USDC
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"
}'
Response200
{
  "amount": 50,
  "chain": "celo",
  "symbol": "USDC",
  "txHash": "0x..."
}

Step 2: Send it on-chain to the recipient ​

Sender: send USDC to 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"
}'
Response200
{
  "txHash": "0x...",
  "chain": "celo",
  "symbol": "USDC",
  "amount": 50
}

Step 3: Recipient's backend settles to local currency ​

Recipient (Nigeria) backend: burn USDC → credit NGN
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"
}'
Response200
{
  "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:

js
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); });
bash
node settle.js

What 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-from vs burn-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.

Released under the MIT License.