Skip to content

Repository files navigation

DupDub Stellar Contracts

Soroban smart contracts powering the DupDub crypto-to-fiat settlement platform on Stellar.

Overview

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.

Contracts

All contracts live under dabdub_contracts/contracts/:

| Contract | Purpose | Coverage | |---|---| | admin_timelock | Timelocked admin actions | admin_timelock | | batch_payments | Batched payment processing | batch_payments | | fee_calculator | Fee computation logic | fee_calculator | | fee_distributor | Fee distribution to recipients | fee_distributor | | liquidity_router | Liquidity routing for settlements | liquidity_router | | merchant_registry | Merchant registration and metadata | merchant_registry | | multisig_admin | Multi-signature admin control | multisig_admin | | payment_escrow | Escrow for customer payments (approve → deposit → settle) | payment_escrow | | payment_request | Payment request lifecycle | payment_request | | rbac_access | Role-based access control | rbac_access | | reconciliation | Settlement reconciliation | reconciliation | | settlement_ledger | On-chain settlement record-keeping | settlement_ledger | | slippage_protection | Slippage guards for conversions | slippage_protection | | stellar_confirmations | Confirmation tracking for Stellar transactions | stellar_confirmations |

Payment escrow lifetime and storage

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

Development

Prerequisites

  • Rust with the wasm32v1-none target
  • stellar-cli (for deployment)
  • wasm-opt (binaryen), for optimised release builds
rustup target add wasm32v1-none

Build & test

make 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 workspace

Or directly with cargo, from dupdapp_contract/:

cd dupdapp_contract
cargo build --target wasm32v1-none --release
cargo test

Contract versioning

Dependent 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.

Bumping a contract version

When you change a contract's public ABI (function signatures, argument types, return types, or storage layout that other contracts read):

  1. Bump the contract's version() — increment the value returned by the contract's version() entry point (e.g. merchant_registry).
  2. Update dependents' MIN_COMPATIBLE_VERSION — for every contract that calls into the changed contract, raise its MIN_COMPATIBLE_VERSION constant to the new version if the change is breaking. Leave it unchanged for backward-compatible additions.
  3. Update the version check tests — adjust the dependent contract's tests so they cover both the compatible and incompatible paths.
  4. Deploy in dependency order — deploy the upgraded dependency first, then the dependents, so the version check passes at runtime.
  5. 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/CD

  • CI (.github/workflows/ci.yml) — runs cargo 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 to main and to mainnet on version tags (manual approval required).

Monitoring

Grafana/Loki/Promtail configs under grafana/ and docker-compose.yml are provided for local observability tooling; see monitoring/ for uptime/status-page setup.

Docs

Links

Handsoff notes

  • #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_customer path for admin-approved refunds
  • #1215: [P1] merchant_registry: add merchant metadata fields (website, contact, country)

Releases

Packages

Contributors

Languages