This directory contains the Soroban smart contract workspace for ManageHub.
The NestJS backend interacts with the payment_escrow contract via a hand-written client at
backend/src/payments/soroban/escrow-contract.client.ts.
The backend expects exactly these four public functions on the deployed contract:
fn create(escrow_id: BytesN<32>, payer: Address, beneficiary: Address, amount: i128)
fn release(escrow_id: BytesN<32>)
fn refund(escrow_id: BytesN<32>)
fn get_status(escrow_id: BytesN<32>) -> u32
| Method | Backend caller | Purpose |
|---|---|---|
create |
buildCreateTx() |
Lock funds in a new escrow |
release |
buildReleaseTx() |
Release escrowed funds to the beneficiary |
refund |
buildRefundTx() |
Refund escrowed funds to the payer |
get_status |
getEscrowStatus() (simulate) |
Read-only status check, never submitted |
The get_status return value maps to the backend's EscrowStatus enum
(escrow-status.enum.ts):
Raw u32 |
Contract State | Backend EscrowStatus |
|---|---|---|
1 |
Locked | LOCKED |
2 |
Released | RELEASED |
3 |
Refunded | REFUNDED |
4 |
Completed (fully released in instalments) | - |
A missing escrow is not a status code: get_status returns
Err(Error::NotFound), which surfaces to clients as Error(Contract, #1). The
backend maps that error to NOT_FOUND.
| Code | Error | Meaning |
|---|---|---|
1 |
NotFound |
No escrow exists for the id |
2 |
AlreadyResolved |
The escrow is no longer Locked |
3 |
InvalidAmount |
Amount must be positive |
4 |
AlreadyExists |
An escrow with this id already exists |
5 |
ArithmeticOverflow |
Checked arithmetic failed |
20 |
AmountExceedsEscrow |
A partial release would exceed the locked amount |
21 |
NothingToRefund |
Nothing remains to refund |
amount is an i128 in the escrowed token's smallest unit, not fiat minor
units. The backend stores fiat amounts in minor units (cents); it must convert
to token units before building the transaction:
token_units = round_half_up(fiat_minor_units * 10^token_decimals / 10^fiat_decimals / rate)
Rounding is applied once, on the backend, to the token amount; the contract never rounds. Most Stellar assets use 7 decimals.
Worked example: 50.00 USD paid in a token pegged 1:1 to USD with 7 decimals.
The payment record holds 5000 (cents); 5000 * 10^7 / 10^2 = 500_000_000
token units, which is the amount passed to create. Partial releases use the
same unit and satisfy released <= amount.
These environment variables bind the backend to the on-chain contract
(see soroban-config.ts):
| Variable | Description |
|---|---|
SOROBAN_ENABLED |
"true" to activate the Stellar escrow rail; any other value disables it |
STELLAR_ESCROW_CONTRACT_ID |
StrKey-encoded address of the deployed payment_escrow contract |
STELLAR_NETWORK |
Stellar network passphrase (e.g. Test SDF Network ; September 2015) |
STELLAR_RPC_URLS |
Comma-separated Soroban RPC endpoints |
STELLAR_SECRET_KEY |
Treasury/operator secret key for signing releases and refunds |
STELLAR_BENEFICIARY_ADDRESS |
Default beneficiary Stellar address |
The backend derives escrow IDs as the SHA-256 hash of the Payment entity UUID,
passed to the contract as BytesN<32>. See escrow-submission.processor.ts for the
exact derivation.
Amount-handling paths must use checked arithmetic (checked_add / checked_sub) rather than raw + / -. revenue_distribution already does this for its basis-point sum. payment_escrow stores a single amount per escrow and performs no arithmetic on it today; when partial releases or batch totals are added they must use the same checked pattern inline.
| Crate | Purpose |
|---|---|
payment_escrow |
Escrow lifecycle (create → release / refund) |
Additional crates (e.g. resource_credits, staking_rewards) should be added
as workspace members in Cargo.toml as they are developed.
make build/make test/make deploy/make clean— see the rootMakefile(CT-70).make audit— CI-enforced authorization audit; seescripts/audit_require_auth.sh(CT-72).
SECURITY.md— trust model and pre-deployment checklist (CT-68).UPGRADE.md— contract upgrade/migration path (CT-69).
- Settlement batches (execute / retry / abandon) are modelled off-chain only, in the backend
settlement-batch.entity.ts. There is no on-chain batch contract, so the placeholdersettlement_batch_lifecycletests were removed. - Split allocation property tests and the escrow-to-distribution integration test were placeholders with no assertions. They are removed until the behaviour they describe exists on-chain; add them back with real assertions (a
proptestdev-dependency and both contracts registered in oneEnv) together with that feature. cargo test --workspacenow runs natively in CI together withcargo fmt --check,cargo clippy -- -D warningsand a WASM build.