Smart contracts powering the MarketX decentralized marketplace.
This repository contains Soroban smart contracts written in Rust for handling escrow, payments, and core on-chain marketplace logic on the Stellar network.
MarketX leverages Stellar's Soroban smart contract platform to provide:
- Secure escrow between buyers and sellers
- Controlled fund release and refunds
- Authorization-based state transitions
- On-chain validation of marketplace operations
- Event emission for off-chain indexing and monitoring
The contract layer is designed to be secure, deterministic, and minimal.
- Rust (stable toolchain)
- Soroban Smart Contracts (soroban-sdk v25)
- stellar-cli v25
- Stellar Testnet (initial deployment target)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup update# Legacy target (used for cargo test / dev builds)
rustup target add wasm32-unknown-unknown
# New Soroban target (used by stellar contract build)
rustup target add wasm32v1-nonecargo install stellar-cliVerify installation:
stellar --versionThis repository is a Cargo workspace — every directory under contracts/ is automatically included as a workspace member. Adding a new contract requires no changes to the root Cargo.toml.
.
├── Cargo.toml # Workspace manifest & shared dependencies
├── Cargo.lock # Locked dependency versions (committed)
├── Makefile # Workspace-wide shortcuts (build, test, fmt, check)
└── contracts/
└── marketx/ # Placeholder contract (replace with real logic)
├── Cargo.toml # Inherits versions from workspace
├── Makefile # Per-contract shortcuts
└── src/
├── lib.rs # Contract entrypoints & module-level docs
├── errors.rs # ContractError variants
├── types.rs # Escrow, EscrowStatus, DataKey
└── test.rs # Unit & snapshot tests
stellar contract init . --name <contract-name>This scaffolds contracts/<contract-name>/ and automatically adds it to the workspace.
Shared dependency versions (e.g. soroban-sdk) are inherited from [workspace.dependencies] in the root Cargo.toml.
Build all contracts as optimized WASM artifacts:
make build
# or directly:
stellar contract buildArtifacts land at:
target/wasm32v1-none/release/<contract-name>.wasm
make test
# or directly:
cargo testAll contract logic must be covered by unit tests.
Generate a keypair and fund it via Friendbot:
stellar keys generate --global deployer --network testnet
stellar keys fund deployer --network testnetVerify the account address:
stellar keys address deployerstellar contract deploy \
--wasm target/wasm32v1-none/release/marketx.wasm \
--source deployer \
--network testnetOn success, the CLI outputs a contract ID:
CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Export it for use in subsequent commands:
export CONTRACT_ID=CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXstellar contract invoke \
--id $CONTRACT_ID \
--source deployer \
--network testnet \
-- \
create_escrow \
--buyer GBUYERADDRESSXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX \
--seller GSELLERADDRESSXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX \
--amount 1000000 \
--token CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSCNote: Amounts are in stroops (1 XLM = 10,000,000 stroops).
stellar contract info \
--id $CONTRACT_ID \
--network testnetAll state is stored in persistent ledger entries (minimum TTL: 4,096 ledgers on testnet, ~5.7 hours at 5 s/ledger). There are three key types:
| Key | Type | Description |
|---|---|---|
Escrow(u64) |
Escrow |
One record per escrow, keyed by caller-assigned ID |
EscrowCount |
u64 |
Monotonic counter reserved for future auto-ID generation |
InitialValue |
u32 |
Arbitrary value set at initialization; defaults to 0 |
The Escrow struct has five fields: buyer: Address, seller: Address, token: Address, amount: i128 (in the token's base unit, e.g. stroops for XLM), and status: EscrowStatus.
An escrow moves through a strict state machine. Released and Refunded are terminal — no further transitions are permitted once either is reached.
Pending ──► Released buyer confirms delivery
Pending ──► Disputed dispute raised
Pending ──► Refunded direct cancellation
Disputed ──► Released resolved in seller's favour
Disputed ──► Refunded resolved in buyer's favour
All transitions except Disputed → Released require buyer authorization (require_auth).
Stores an initial u32 value in persistent storage. Can be called multiple times; subsequent calls overwrite the previous value.
Returns the value set by initialize, or 0 if initialize has not been called.
Writes an Escrow record to persistent storage under escrow_id. Silently overwrites any existing record — callers are responsible for ID uniqueness.
Returns the escrow record for escrow_id. Traps (panics) if the ID does not exist. Use try_get_escrow when the ID may be absent.
Safe variant of get_escrow. Returns ContractError::EscrowNotFound instead of trapping on a missing ID.
The primary state-mutation entrypoint. Loads the escrow, enforces buyer authorization for buyer-initiated moves, validates the transition against the state graph, and persists the updated record.
| Error | Condition |
|---|---|
EscrowNotFound |
No record exists for escrow_id |
InvalidTransition |
Move not permitted from the current state |
Convenience wrapper that releases funds to the seller. Validates that the escrow is in Pending state before delegating to transition_status, surfacing EscrowNotFunded as a clearer error than the generic InvalidTransition.
| Error | Condition |
|---|---|
EscrowNotFound |
No record exists for escrow_id |
EscrowNotFunded |
Escrow is not in Pending state |
InvalidTransition |
Transition rejected by state graph (propagated from transition_status) |
| Variant | Value | Meaning |
|---|---|---|
EscrowNotFound |
1 |
No escrow stored for the given ID |
InvalidTransition |
2 |
State move not in the valid transition graph |
EscrowNotFunded |
3 |
Escrow is not in Pending state |
Error discriminant values are part of the on-chain ABI — they must not be renumbered.
- Use explicit authorization checks (
require_auth) - Validate all inputs
- Avoid unnecessary storage writes
- Keep state transitions clear and deterministic
- Format and check before opening a PR:
make fmt
make check- Ensure no warnings before opening a PR
- Initial deployment target: Stellar Testnet
- Mainnet deployment will follow thorough testing and review.
MIT