The Trustless Work escrow contract uses a trustline field to specify which Stellar asset is locked and transferred during the escrow lifecycle. To date, all testing has been done exclusively with USDC.
The contract is asset-agnostic — it accepts any valid Stellar Asset Contract (SAC) address as the trustline. However, native XLM has not been tested and behaves differently from issued assets:
- XLM does not require a
changeTrust operation before receiving funds.
- Its SAC address must be resolved via CLI before using it (see Step 2).
- XLM uses 7 decimal places:
1 XLM = 10_000_000 stroops. All amount values in the contract are in the asset's smallest unit.
This issue tracks the community effort to validate the complete escrow lifecycle end-to-end on Testnet using native XLM, interacting directly with the deployed contract via the Stellar CLI — no API or dApp required.
Goal
Confirm that all escrow contract functions work correctly when XLM is used as the trustline asset, and surface any edge cases or errors specific to XLM.
Prerequisites
Install and configure everything before starting.
1. Install Rust and the wasm32 target
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32v1-none
2. Install the Stellar CLI
# macOS/Linux via Homebrew
brew install stellar-cli
# or via cargo
cargo install --locked stellar-cli --features opt
3. Configure Testnet
stellar network add \
--global testnet \
--rpc-url https://soroban-testnet.stellar.org:443 \
--network-passphrase "Test SDF Network ; September 2015"
4. Create and fund test identities
You need at least two identities. We'll use alice (service provider / receiver) and bob (approver / release signer / platform / dispute resolver).
stellar keys generate --global alice --network testnet
stellar keys generate --global bob --network testnet
# Fund both accounts via Friendbot
stellar keys fund alice --network testnet
stellar keys fund bob --network testnet
Check their public keys with:
stellar keys address alice
stellar keys address bob
Step-by-Step Tasks
Step 1 — Clone the repo and build the contract
git clone https://github.com/Trustless-Work/Trustless-Work-Smart-Escrow.git
cd Trustless-Work-Smart-Escrow
stellar contract build
This generates the WASM at target/wasm32v1-none/release/escrow.wasm.
Step 2 — Resolve the native XLM SAC address
The native XLM asset has a Soroban contract representation. Resolve it with:
stellar contract id asset \
--asset native \
--network testnet \
--source alice
Save this address — you will use it as the trustline.address in the next step.
# Example: store it in a variable
XLM_SAC=$(stellar contract id asset --asset native --network testnet --source alice)
echo $XLM_SAC
Step 3 — Deploy the escrow contract
# Upload the WASM and get its hash
WASM_HASH=$(stellar contract install \
--network testnet \
--source alice \
--wasm target/wasm32v1-none/release/escrow.wasm)
echo "WASM hash: $WASM_HASH"
# Deploy a new contract instance
CONTRACT_ID=$(stellar contract deploy \
--wasm-hash $WASM_HASH \
--source alice \
--network testnet)
echo "Contract ID: $CONTRACT_ID"
Save CONTRACT_ID — every subsequent command uses it.
Step 4 — Initialize the escrow
Replace <ALICE_ADDRESS> and <BOB_ADDRESS> with the outputs of stellar keys address alice/bob. Replace <XLM_SAC> with the value from Step 2.
stellar contract invoke \
--id $CONTRACT_ID \
--source alice \
--network testnet \
-- \
initialize_escrow \
--escrow_properties '{
"engagement_id": "xlm-test-001",
"title": "XLM Escrow Test",
"description": "Testing native XLM as trustline",
"amount": "20000000",
"platform_fee": 300,
"receiver_memo": "0",
"roles": {
"approver": "<BOB_ADDRESS>",
"service_provider": "<ALICE_ADDRESS>",
"platform_address": "<BOB_ADDRESS>",
"release_signer": "<BOB_ADDRESS>",
"dispute_resolver": "<BOB_ADDRESS>",
"receiver": "<ALICE_ADDRESS>"
},
"milestones": [
{
"description": "Milestone 1: Setup",
"status": "Pending",
"evidence": "",
"approved": false
},
{
"description": "Milestone 2: Delivery",
"status": "Pending",
"evidence": "",
"approved": false
}
],
"flags": {
"disputed": false,
"released": false,
"resolved": false
},
"trustline": {
"address": "<XLM_SAC>"
}
}'
amount: 20000000 = 2 XLM in stroops. platform_fee: 300 = 3% in basis points.
Step 5 — Fund the escrow
Alice sends 2 XLM to the contract. The amount must match what was set in initialize_escrow.
stellar contract invoke \
--id $CONTRACT_ID \
--source alice \
--network testnet \
-- \
fund_escrow \
--signer <ALICE_ADDRESS> \
--expected_escrow '{
"engagement_id": "xlm-test-001",
"title": "XLM Escrow Test",
"description": "Testing native XLM as trustline",
"amount": "20000000",
"platform_fee": 300,
"receiver_memo": "0",
"roles": {
"approver": "<BOB_ADDRESS>",
"service_provider": "<ALICE_ADDRESS>",
"platform_address": "<BOB_ADDRESS>",
"release_signer": "<BOB_ADDRESS>",
"dispute_resolver": "<BOB_ADDRESS>",
"receiver": "<ALICE_ADDRESS>"
},
"milestones": [
{"description": "Milestone 1: Setup", "status": "Pending", "evidence": "", "approved": false},
{"description": "Milestone 2: Delivery", "status": "Pending", "evidence": "", "approved": false}
],
"flags": {"disputed": false, "released": false, "resolved": false},
"trustline": {"address": "<XLM_SAC>"}
}' \
--amount 20000000
Verify the contract holds the XLM:
stellar contract invoke \
--id $CONTRACT_ID \
--source alice \
--network testnet \
-- \
get_escrow
Step 6 — Update milestone status
Alice (service provider) marks each milestone as completed and provides evidence.
# Milestone 0
stellar contract invoke \
--id $CONTRACT_ID \
--source alice \
--network testnet \
-- \
change_milestone_status \
--milestone_index 0 \
--new_status "Completed" \
--new_evidence "https://github.com/your-repo/pull/1" \
--service_provider <ALICE_ADDRESS>
# Milestone 1
stellar contract invoke \
--id $CONTRACT_ID \
--source alice \
--network testnet \
-- \
change_milestone_status \
--milestone_index 1 \
--new_status "Completed" \
--new_evidence "https://github.com/your-repo/pull/2" \
--service_provider <ALICE_ADDRESS>
Step 7 — Approve milestones
Bob (approver) approves each milestone.
# Approve milestone 0
stellar contract invoke \
--id $CONTRACT_ID \
--source bob \
--network testnet \
-- \
approve_milestone \
--milestone_index 0 \
--approver <BOB_ADDRESS>
# Approve milestone 1
stellar contract invoke \
--id $CONTRACT_ID \
--source bob \
--network testnet \
-- \
approve_milestone \
--milestone_index 1 \
--approver <BOB_ADDRESS>
Step 8 — Release funds
Bob (release signer) triggers the release. The contract will split:
0.30% → Trustless Work protocol address
3% → Bob (platform fee)
- Remainder → Alice (receiver)
stellar contract invoke \
--id $CONTRACT_ID \
--source bob \
--network testnet \
-- \
release_funds \
--release_signer <BOB_ADDRESS> \
--trustless_work_address <TRUSTLESS_WORK_PROTOCOL_ADDRESS>
The Trustless Work protocol address for Testnet can be found in the official documentation.
Step 9 (Optional) — Test the dispute flow
This step is optional but highly valuable. Start fresh with a new deployed contract and funded escrow, then:
# 1. Open a dispute (alice or bob, but NOT the dispute_resolver)
stellar contract invoke \
--id $CONTRACT_ID \
--source alice \
--network testnet \
-- \
dispute_escrow \
--signer <ALICE_ADDRESS>
# 2. Resolve the dispute — distribute the full balance between parties
# The sum of all values in distributions MUST equal the contract's XLM balance
stellar contract invoke \
--id $CONTRACT_ID \
--source bob \
--network testnet \
-- \
resolve_dispute \
--dispute_resolver <BOB_ADDRESS> \
--trustless_work_address <TRUSTLESS_WORK_PROTOCOL_ADDRESS> \
--distributions '{"<ALICE_ADDRESS>": "12000000", "<BOB_ADDRESS>": "8000000"}'
Evidence Submission
When commenting on this issue to report your results, paste the following template filled with your real data:
## XLM Flow Testing — Evidence Report
**Tester:** @your-github-handle
**Date:** YYYY-MM-DD
**Network:** Stellar Testnet
**Contract ID:** C...
**XLM SAC address used:** C...
### Transaction Hashes
| Step | Function | TX Hash | Stellar Expert Link |
|---|---|---|---|
| 3 | Deploy | `...` | [link](https://stellar.expert/explorer/testnet/tx/...) |
| 4 | `initialize_escrow` | `...` | [link](...) |
| 5 | `fund_escrow` | `...` | [link](...) |
| 6a | `change_milestone_status` (0) | `...` | [link](...) |
| 6b | `change_milestone_status` (1) | `...` | [link](...) |
| 7a | `approve_milestone` (0) | `...` | [link](...) |
| 7b | `approve_milestone` (1) | `...` | [link](...) |
| 8 | `release_funds` | `...` | [link](...) |
| 9 (opt) | `dispute_escrow` | `...` | [link](...) |
| 9 (opt) | `resolve_dispute` | `...` | [link](...) |
### Fee Verification (release_funds)
| Recipient | Expected (stroops) | Received (stroops) |
|---|---|---|
| Alice (receiver) | `20000000 - 60000 - 600000 = 19340000` | `?` |
| Trustless Work (0.30%) | `60000` | `?` |
| Bob / platform (3%) | `600000` | `?` |
### Observations
[Describe any errors, unexpected behavior, or XLM-specific issues encountered.]
Acceptance Criteria
The Trustless Work escrow contract uses a
trustlinefield to specify which Stellar asset is locked and transferred during the escrow lifecycle. To date, all testing has been done exclusively with USDC.The contract is asset-agnostic — it accepts any valid Stellar Asset Contract (SAC) address as the trustline. However, native XLM has not been tested and behaves differently from issued assets:
changeTrustoperation before receiving funds.1 XLM = 10_000_000 stroops. Allamountvalues in the contract are in the asset's smallest unit.This issue tracks the community effort to validate the complete escrow lifecycle end-to-end on Testnet using native XLM, interacting directly with the deployed contract via the Stellar CLI — no API or dApp required.
Goal
Confirm that all escrow contract functions work correctly when XLM is used as the trustline asset, and surface any edge cases or errors specific to XLM.
Prerequisites
Install and configure everything before starting.
1. Install Rust and the wasm32 target
2. Install the Stellar CLI
3. Configure Testnet
stellar network add \ --global testnet \ --rpc-url https://soroban-testnet.stellar.org:443 \ --network-passphrase "Test SDF Network ; September 2015"4. Create and fund test identities
You need at least two identities. We'll use
alice(service provider / receiver) andbob(approver / release signer / platform / dispute resolver).stellar keys generate --global alice --network testnet stellar keys generate --global bob --network testnet # Fund both accounts via Friendbot stellar keys fund alice --network testnet stellar keys fund bob --network testnetCheck their public keys with:
Step-by-Step Tasks
Step 1 — Clone the repo and build the contract
git clone https://github.com/Trustless-Work/Trustless-Work-Smart-Escrow.git cd Trustless-Work-Smart-Escrow stellar contract buildThis generates the WASM at
target/wasm32v1-none/release/escrow.wasm.Step 2 — Resolve the native XLM SAC address
The native XLM asset has a Soroban contract representation. Resolve it with:
Save this address — you will use it as the
trustline.addressin the next step.Step 3 — Deploy the escrow contract
Save
CONTRACT_ID— every subsequent command uses it.Step 4 — Initialize the escrow
Replace
<ALICE_ADDRESS>and<BOB_ADDRESS>with the outputs ofstellar keys address alice/bob. Replace<XLM_SAC>with the value from Step 2.Step 5 — Fund the escrow
Alice sends 2 XLM to the contract. The amount must match what was set in
initialize_escrow.Verify the contract holds the XLM:
stellar contract invoke \ --id $CONTRACT_ID \ --source alice \ --network testnet \ -- \ get_escrowStep 6 — Update milestone status
Alice (service provider) marks each milestone as completed and provides evidence.
Step 7 — Approve milestones
Bob (approver) approves each milestone.
Step 8 — Release funds
Bob (release signer) triggers the release. The contract will split:
0.30%→ Trustless Work protocol address3%→ Bob (platform fee)Step 9 (Optional) — Test the dispute flow
This step is optional but highly valuable. Start fresh with a new deployed contract and funded escrow, then:
Evidence Submission
When commenting on this issue to report your results, paste the following template filled with your real data:
Acceptance Criteria