Soroban smart contracts powering the DupDub crypto-to-fiat settlement platform on Stellar.
This repository contains the Soroban (Stellar smart contract platform) contracts that back DupDub's on-chain settlement flow — payment escrow, merchant registry, fee handling, and related infrastructure. It does not contain the backend API or frontend application; those live in their own repositories:
- Backend (Settlement API) —
dupdap-backend - Frontend (Merchant & Customer Portal) —
dupdap-frontend
API work tracked in this repository is implemented in the backend; see the backend API implementation note.
All contracts live under dabdub_contracts/contracts/:
| Contract | Purpose | Coverage |
|---|---|
| admin_timelock | Timelocked admin actions | |
|
batch_payments | Batched payment processing | |
|
fee_calculator | Fee computation logic | |
|
fee_distributor | Fee distribution to recipients | |
|
liquidity_router | Liquidity routing for settlements | |
|
merchant_registry | Merchant registration and metadata | |
|
multisig_admin | Multi-signature admin control | |
|
payment_escrow | Escrow for customer payments (approve → deposit → settle) | |
|
payment_request | Payment request lifecycle | |
|
rbac_access | Role-based access control | |
|
reconciliation | Settlement reconciliation | |
|
settlement_ledger | On-chain settlement record-keeping | |
|
slippage_protection | Slippage guards for conversions | |
|
stellar_confirmations | Confirmation tracking for Stellar transactions | |
DEFAULT_MAX_TTL_LEDGERS is 518,400 ledgers: approximately 30 days at an
assumed five seconds per ledger. It is a default upper bound on a deposit's
requested lifetime, balancing a settlement window against locked capital and
operational exposure. It is not a regulatory retention requirement or an exact
wall-clock deadline. Choose shorter payment TTLs where the business permits;
compliance and settlement policies must be defined separately.
A deposit at ledger L with TTL T records expiry = L + T. Release is allowed
through that ledger; refund/expire becomes available only when the sequence is
strictly greater than expiry. Time passing alone does not transfer tokens or
change Pending status: someone must invoke settlement. An open dispute blocks
normal expiry/refund and requires admin resolution, so TTL is not a guarantee
that all funds leave escrow within 30 days.
Admin configuration already exists: get_max_ttl_ledgers reads the default
or override, and admin-authorized set_max_ttl_ledgers(caller, value) changes the
cap for future deposits and emits a CONFIG/max_ttl event. Existing payments keep
their original expiry. Keeping this configurable allows business-specific
settlement periods, but a compromised admin can lengthen future capital lockup;
operators should restrict and monitor admin access and review cap changes against
their policies. This change introduces no additional admin authority.
Soroban storage TTL is separate from payment expiry. Deposit extends the
payment's persistent entry and the escrow instance/code through expiry + 1,
including the first refundable ledger. A shorter subsequent deposit cannot
shorten those lifetimes. A requested TTL must leave that extra ledger within
env.storage().max_ttl(); deposits exceeding network retention capacity (or
integer capacity) fail before transferring funds, even if the admin cap is larger.
Network TTL limits can change, so clients should simulate deposits.
After the retained lifetime, unpaid or disputed records can be archived. Operators must extend TTLs or restore archived payment, escrow instance/code, and any token entries before settlement; archival does not refund or erase entitlement. Token contracts have independent storage maintenance. Existing payments deposited before this change need TTL extension/restoration too: updating the WASM does not rewrite their stored entries. This retention policy is not an audit-history retention SLA.
Run the lifetime regressions with:
cd dabdub_contracts
cargo test -p payment-escrow test_max_ttl- Rust with the
wasm32v1-nonetarget stellar-cli(for deployment)wasm-opt(binaryen), for optimised release builds
rustup target add wasm32v1-nonemake build # debug build of all contracts
make build-optimised # release build + wasm-opt -Oz
make check-wasm-size # enforce the 64 KB per-contract WASM limit
make test # cargo test across the workspaceOr directly with cargo, from dupdapp_contract/:
cd dupdapp_contract
cargo build --target wasm32v1-none --release
cargo testDependent contracts verify each other's version before calling across contract
boundaries. payment_escrow reads merchant_registry's version() and panics
with a clear error if the registry is older than the escrow's
MIN_COMPATIBLE_VERSION constant. This prevents a major registry upgrade that
changes the is_approved ABI from silently breaking escrow settlement.
When you change a contract's public ABI (function signatures, argument types, return types, or storage layout that other contracts read):
- Bump the contract's
version()— increment the value returned by the contract'sversion()entry point (e.g.merchant_registry). - Update dependents'
MIN_COMPATIBLE_VERSION— for every contract that calls into the changed contract, raise itsMIN_COMPATIBLE_VERSIONconstant to the new version if the change is breaking. Leave it unchanged for backward-compatible additions. - Update the version check tests — adjust the dependent contract's tests so they cover both the compatible and incompatible paths.
- Deploy in dependency order — deploy the upgraded dependency first, then the dependents, so the version check passes at runtime.
- Document the change — note the version bump and any ABI changes in the
PR description and, for breaking changes, in
docs/escrow_upgrade_procedure.md.
- CI (
.github/workflows/ci.yml) — runscargo test, coverage (tarpaulin), and a WASM build on every push/PR. - Deploy (
.github/workflows/deploy.yml) — builds and optimises WASMs, then deploys to Stellar testnet on merges tomainand to mainnet on version tags (manual approval required).
Grafana/Loki/Promtail configs under grafana/ and docker-compose.yml are provided for local observability tooling; see monitoring/ for uptime/status-page setup.
- Mainnet deployment, recovery and on-call runbook
- Contract ABIs (JSON) — machine-readable call signatures for each contract
- Escrow upgrade procedure
- Security docs
- Website: https://dupdub.xyz
- Documentation: https://docs.dupdub.xyz
- #1094: merchant_registry and payment_escrow: a merchant suspended after deposit is still paid out by release/release_partial
- #1107: payment_escrow::dispute's _reason parameter is misleadingly underscore-prefixed despite being used, and has no validation
- #1108: payment_escrow: dispute resolution is all-or-nothing on the remainder even after a merchant has already delivered via partial release
- #1113: payment_escrow: the highest-risk functions (upgrade, emergency_drain's XLM path, set_registry(None)) have the thinnest test coverage
- #1214: [P1] payment_escrow: add
release_to_customerpath for admin-approved refunds
- #1215: [P1] merchant_registry: add merchant metadata fields (website, contact, country)