Wired the Soroban escrow contract into the production payout path. Prize pools are now settled atomically via smart contract.
- Contract existed but was never called
- Payout service used direct hot-wallet transfers (custodial)
- No wrapper library for contract interaction
- Deployment scripts missing
- Created
EscrowClientwrapper for clean contract interaction - Modified payout service to use escrow settlement
- Added deployment scripts
- Implemented graceful fallback to direct transfers
File: packages/stellar/src/escrow.ts
Clean API for contract interaction:
initialize()— Set up contract for challengedeposit()— Brand funds poolsettle()— Atomic distribution to winnersrefund()— Return funds if cancelledgetBalance()— View balanceisSettled()— View settlement status
File: apps/api/src/services/payout.ts
Modified to:
- Check if
SOROBAN_CONTRACT_IDconfigured - If yes → use
EscrowClient.settle()(atomic) - If no → fall back to direct transfers
- Log which path used
File: contracts/contracts/escrow/Makefile
Added:
make deploy-testnet— Deploy to testnetmake deploy-mainnet— Deploy to mainnet
File: .env.example
Added:
SOROBAN_CONTRACT_ID— Contract ID after deploymentSTELLAR_RPC_URL— Soroban RPC endpoint
File: docs/adr/003-escrow-implementation.md
Documents decision, rationale, deployment, monitoring
File: packages/stellar/src/escrow.test.ts
Tests wrapper and contract interaction
Challenge ends
↓
Payout worker processes
↓
If SOROBAN_CONTRACT_ID set:
→ Use EscrowClient.settle()
→ Atomic distribution
→ Non-custodial
↓
Else:
→ Fall back to direct transfers
→ Custodial (temporary)
↓
Update challenge to "settled"
Not just high-value challenges because:
- Simplifies logic (one path)
- Maximizes non-custodial benefits
- Atomic settlement prevents bugs
- Graceful fallback if needed
If contract has issues:
- Unset
SOROBAN_CONTRACT_ID - Payout service automatically uses direct transfers
- No code changes needed
- Payouts continue normally
- Deploy:
make deploy-testnet - Set
SOROBAN_CONTRACT_IDin test env - Run integration tests
- Monitor 24h
- Deploy to staging Soroban
- Set
SOROBAN_CONTRACT_ID - Run full E2E test
- Monitor 7 days
- External security audit
- Deploy:
make deploy-mainnet - Set
SOROBAN_CONTRACT_ID - Canary: one challenge
- Monitor 24h
- Gradual rollout
✅ Non-custodial: Platform never holds funds
✅ Atomic: All-or-nothing distribution
✅ Auditable: On-chain proof
✅ Graceful: Works without contract
✅ Testable: Can disable for testing
✅ ADR docs/adr/003-escrow-implementation.md
✅ packages/stellar/src/escrow.ts wrapper
✅ Payout worker uses settle()
✅ Deployment script: make deploy-testnet/mainnet
✅ Integration test: initialize, deposit, settle
✅ CONTRACT_ID env var documented
- ✅
packages/stellar/src/escrow.ts— NEW - ✅
packages/stellar/src/escrow.test.ts— NEW - ✅
apps/api/src/services/payout.ts— Modified - ✅
contracts/contracts/escrow/Makefile— Modified - ✅
docs/adr/003-escrow-implementation.md— NEW - ✅
.env.example— Modified
- Deploy contract to testnet
- Set
SOROBAN_CONTRACT_IDin test env - Run integration tests
- Monitor payout service
- Schedule external audit
- Deploy to mainnet after audit