A payment facilitator for the x402 protocol on Canton Network. This facilitator enables everyday payment transactions on banking-grade infrastructure, bringing retail payment volume to Canton's institutional-grade distributed ledger.
This facilitator implements the x402 payment protocol for Canton Network, allowing:
- Everyday payments: Person-to-person FIAT transfers
- API monetization: Pay-per-use for services
- Micropayments: Low-value, high-volume transactions
- Banking integration: Regulated, compliant payment infrastructure
- β Canton Ledger API integration via gRPC
- β Daml smart contract for payments
- β x402 protocol compliance
- β Local development with Canton sandbox
- β Simple HTTP API (verify and settle endpoints)
Before you begin, ensure you have:
- Node.js v20+ (install via nvm)
- Java 21 (required for Canton and Daml SDK)
- Daml SDK 3.4.0+ (installation instructions)
- Canton SDK (automatically downloaded by setup script)
- curl or wget (for downloading Canton)
git clone <repository-url>
cd canton-x402-facilitator
npm installDownload and extract Canton SDK:
chmod +x scripts/setup-canton.sh
./scripts/setup-canton.shThis will download Canton SDK v3.4.0 and extract it to canton-release/.
cd daml
daml build
cd ..This creates the Payment.dar file in daml/.daml/dist/.
In a separate terminal, start Canton with the Payment contract:
./canton-release/bin/canton sandbox \
--ledger-api-port 6865 \
--json-api-port 7575 \
--dar daml/.daml/dist/canton-x402-payment-1.0.0.darCanton will start and load the Payment contract. You should see:
INFO c.d.c.p.s.SandboxServer - Listening on 0.0.0.0:6865
INFO c.d.c.p.s.SandboxServer - Listening on 0.0.0.0:7575 over HTTP
Create a .env file (or copy from env.example):
cp env.example .envEdit .env if needed:
CANTON_LEDGER_HOST=localhost
CANTON_LEDGER_PORT=6865
CANTON_JSON_API_PORT=7575
FACILITATOR_PORT=3000npm run devYou should see:
π Canton x402 Facilitator
==========================
π‘ Initializing Canton client...
Host: localhost:6865
JSON API: localhost:7575
β
Connected to Canton Ledger API at localhost:6865
β
Facilitator server running on http://localhost:3000
Endpoints:
GET /health - Health check
GET /supported - Supported payment kinds
POST /verify - Verify payment
POST /settle - Settle payment
Check facilitator and Canton connection status.
Response:
{
"status": "healthy",
"facilitator": "ok",
"canton": "connected",
"version": "0.1.0"
}Returns the payment kinds supported by this facilitator.
Response:
{
"kinds": [
{
"x402Version": 1,
"scheme": "exact-canton",
"network": "canton-local",
"extra": {
"facilitatorVersion": "0.1.0",
"features": ["verify", "settle"]
}
}
]
}Verifies an x402 payment payload without settling it.
Request:
{
"paymentPayload": {
"x402Version": 1,
"scheme": "exact-canton",
"network": "canton-local",
"payload": {
"command": {
"payer": "Alice::12207...abc",
"payee": "Bob::12207...def",
"amount": "10.00",
"currency": "USD",
"resourceId": "https://api.example.com/resource/123",
"nonce": "550e8400-e29b-41d4-a716-446655440000"
}
}
},
"paymentRequirements": {
"scheme": "exact-canton",
"network": "canton-local",
"maxAmountRequired": "10.00",
"resource": "https://api.example.com/resource/123",
"payTo": "Bob::12207...def",
"asset": "USD"
}
}Response (Success):
{
"isValid": true,
"payer": "Alice::12207...abc"
}Response (Failure):
{
"isValid": false,
"invalidReason": "insufficient_amount",
"payer": "Alice::12207...abc"
}Settles an x402 payment by submitting it to Canton.
Request: Same format as /verify
Response (Success):
{
"success": true,
"transaction": "12207d6f70656e2d736f757263652d6c6564676572",
"network": "canton-local",
"payer": "Alice::12207...abc"
}Response (Failure):
{
"success": false,
"errorReason": "settlement_failed",
"network": "canton-local",
"payer": "Alice::12207...abc"
}- Client requests a resource from a resource server
- Server responds with 402 Payment Required and payment requirements
- Client creates a payment payload with Canton command
- Client submits request with X-PAYMENT header containing the payload
- Server calls facilitator /verify to check payment validity
- Server delivers the resource if payment is valid
- Server calls facilitator /settle to execute the payment on Canton
- Canton creates Payment contract on the ledger
- Server returns X-PAYMENT-RESPONSE header with settlement confirmation
canton-x402-facilitator/
βββ src/
β βββ index.ts # Express server
β βββ canton/
β β βββ client.ts # Canton gRPC client
β β βββ verify.ts # Payment verification
β β βββ settle.ts # Payment settlement
β βββ types/
β βββ index.ts # TypeScript types
βββ daml/
β βββ Payment.daml # Payment smart contract
β βββ daml.yaml # Daml project config
βββ scripts/
β βββ setup-canton.sh # Canton setup script
βββ README.md
Compile TypeScript to JavaScript:
npm run buildRun the compiled version:
npm startTo test the facilitator, you'll need:
- Canton running with the Payment contract deployed
- Canton parties created (Alice, Bob, etc.)
- Test payloads that match the Payment contract structure
You can use curl to test endpoints:
# Check health
curl http://localhost:3000/health
# Check supported kinds
curl http://localhost:3000/supported
# Verify a payment (with test payload)
curl -X POST http://localhost:3000/verify \
-H "Content-Type: application/json" \
-d @test-payload.jsonThe facilitator connects to Canton via its gRPC Ledger API:
- gRPC Connection: Connects to Canton's CommandService on port 6865
- Command Submission: Submits Daml commands to create Payment contracts
- Transaction Confirmation: Waits for Canton to confirm the transaction
- Event Streaming: Can optionally stream transaction events
The Daml Payment contract has these fields:
template Payment
with
payer: Party -- Who is paying
payee: Party -- Who receives payment
amount: Decimal -- Amount (e.g., 10.50)
currency: Text -- Currency (USD, EUR, etc.)
resourceId: Text -- What is being paid for
nonce: Text -- Unique ID (prevents replay)
When settled, the Payment can be converted to a Settlement contract (immutable record).
The facilitator supports:
- canton-local: Local Canton sandbox for development
- canton-testnet: Canton testnet (when available)
- canton-mainnet: Canton mainnet (production)
If Canton fails to start:
# Check Java version (need 21)
java -version
# Try starting Canton directly
./canton-release/bin/canton --versionIf the facilitator can't connect to Canton:
- Ensure Canton is running:
curl http://localhost:7575/livez - Check ports:
lsof -i :6865andlsof -i :7575 - Check PROTO_PATH in
.envpoints to Canton proto files
If you see proto loading errors, set the CANTON_PROTO_PATH:
export CANTON_PROTO_PATH=/path/to/canton/community/ledger-api/src/main/protobufOr in .env:
CANTON_PROTO_PATH=/path/to/canton/community/ledger-api/src/main/protobuf
If daml build fails:
# Ensure Daml SDK is installed
daml version
# Reinstall if needed
curl -sSL https://get.daml.com/ | shFor production use:
- Use production Canton network (not sandbox)
- Enable TLS for gRPC connections
- Add authentication (JWT tokens)
- Implement rate limiting on API endpoints
- Add monitoring (Prometheus, DataDog, etc.)
- Configure proper logging (structured logs)
- Set up database for tracking payment state
- Implement idempotency for settlement requests
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Submit a pull request
Apache 2.0 - See LICENSE file
For issues and questions:
- Open an issue on GitHub
- Join the x402 Discord
- Contact the maintainers
Built with β€οΈ for the future of banking-grade micropayments