One contract. Five states.
Everything HodL does is a call on one Soroban contract. Status only moves forward, and every function checks it first, which is what stops a release or refund from happening twice.
The state machine
- Created → Funded · fund · payer
- Funded → Released · release · payer
- Funded → Refunded · refund · recipient
- Funded → Expired · claim_expired · payer
- Created → Expired · claim_expired, nothing to return
A Funded escrow past its deadline stays stored as Funded until someone acts; reads report it as Expired, so no cron job is needed. The payer can still release, or the recipient refund, until it is claimed.
The live contract
CCBBKXKT2CMMAU7CUQJBFLIECPJQKN3IGDK6EGOS5PQH56HXF4FNLLDB- Escrows created
- Code version
- Network
- USDC allowed
- Upgrades
- Admin
No admin function can move, freeze or redirect funds. While upgrades are enabled the admin can replace the code, UUPS-style; every upgrade is public on-chain, and lock_upgrades makes the code final.
Functions
| Function | Signer | Allowed when | Effect |
|---|---|---|---|
| create_and_fund(...) | Payer | Amount > 0, not to yourself, deadline in the future | Creates and locks in one signature |
| create(...) | Payer | Same checks | Saves the terms, locks nothing |
| fund(id) | Payer | Not funded, before the deadline | USDC: payer → contract |
| release(id) | Payer | Funded, any time | USDC: contract → recipient |
| refund(id) | Recipient | Funded, any time | USDC: contract → payer |
| claim_expired(id) | Payer | Not settled, at or after the deadline | Locked USDC back to the payer |
| get_escrow(id) | Anyone | Always | The escrow plus its effective status |
| upgrade(hash) / lock_upgrades() | Admin | Until upgrades are locked | Swaps the code; address and funds stay |
Public API
Read-only, keyless and CORS-open JSON, cached for a few seconds. The app itself reads the chain directly, so it works without these.
- GET /api/escrow/:idOne escrow and its phase
- GET /api/escrow/:id/eventsIts receipts with transaction hashes
- GET /api/escrows?ids=1,2,3Batch read
- GET /api/users/:address/escrowsPaying and receiving ids; ?expand=1, ?activity=1
- GET /api/statsContract id, escrow count, version, upgrade lock
- GET /api/healthSoroban RPC and Horizon status
- POST /api/friendbotFunds a Testnet account with XLM
Verify it yourself
- The contract on Stellar Expert
- Call it from Stellar Lab
- Testnet USDC (Circle)
- Source code and TRANSACTIONS.md
Every escrow event carries topics ("hodl", action, id), so any escrow's history is one RPC getEvents filter.