Step-by-step guide for calling COMEBACKHERE Protocol contracts via soroban-cli and the backend API.
- Funded Stellar account on the target network
- Deployed contracts — IDs available in
artifacts/addresses.json - Configured
.env(copy from.env.local.exampleor.env.testnet.example) soroban-cliinstalled:cargo install soroban-cli
Placeholder values used throughout:
| Placeholder | Replace with |
|---|---|
$INVOICE_CONTRACT |
Invoice contract ID from artifacts/addresses.json |
$TREASURY_CONTRACT |
Treasury contract ID |
$COMPLIANCE_CONTRACT |
Compliance contract ID |
$SOURCE_ACCOUNT |
Your funded Stellar public key |
$SECRET_KEY |
Your Stellar secret key |
$RPC_URL |
Soroban RPC endpoint (e.g. http://localhost:8000/soroban/rpc) |
$NETWORK_PASSPHRASE |
Network passphrase from your .env |
soroban contract invoke \
--id $INVOICE_CONTRACT \
--source $SECRET_KEY \
--rpc-url $RPC_URL \
--network-passphrase "$NETWORK_PASSPHRASE" \
-- create_invoice \
--merchant $SOURCE_ACCOUNT \
--customer GCUSTOMER... \
--amount 1000000 \
--token CUSDC... \
--expires_at 1750000000 \
--nonce 1curl -X POST http://localhost:3000/invoices \
-H "Content-Type: application/json" \
-d '{
"merchant_address": "$SOURCE_ACCOUNT",
"token": "USDC",
"amount": 1000000,
"due_date": 1750000000
}'Response includes invoice_id to use in subsequent calls.
soroban contract invoke \
--id $INVOICE_CONTRACT \
--source $SECRET_KEY \
--rpc-url $RPC_URL \
--network-passphrase "$NETWORK_PASSPHRASE" \
-- get_invoice_status \
--invoice_id 1curl http://localhost:3000/invoices/1soroban contract invoke \
--id $INVOICE_CONTRACT \
--source $SECRET_KEY \
--rpc-url $RPC_URL \
--network-passphrase "$NETWORK_PASSPHRASE" \
-- mark_paids \
--invoice_ids '[1]'Calling raise_dispute on the invoice contract atomically calls
raise_dispute on the treasury contract, placing the referenced
settlement OnHold.
soroban contract invoke \
--id $INVOICE_CONTRACT \
--source $SECRET_KEY \
--rpc-url $RPC_URL \
--network-passphrase "$NETWORK_PASSPHRASE" \
-- raise_dispute \
--invoice_id 1 \
--settlement_id 3 \
--claimant $SOURCE_ACCOUNT \
--reason 1curl -X POST http://localhost:3000/disputes \
-H "Content-Type: application/json" \
-d '{
"claimant_address": "$SOURCE_ACCOUNT",
"settlement_id": "3",
"reason": "Goods not delivered"
}'soroban contract invoke \
--id $INVOICE_CONTRACT \
--source $SECRET_KEY \
--rpc-url $RPC_URL \
--network-passphrase "$NETWORK_PASSPHRASE" \
-- set_treasury \
--caller $SOURCE_ACCOUNT \
--treasury $TREASURY_CONTRACTsoroban contract invoke \
--id $TREASURY_CONTRACT \
--source $SECRET_KEY \
--rpc-url $RPC_URL \
--network-passphrase "$NETWORK_PASSPHRASE" \
-- propose_settlement \
--signer $SOURCE_ACCOUNT \
--token CUSDC... \
--amount 5000000 \
--merchant GMERCHANT...Returns the settlement_id.
soroban contract invoke \
--id $TREASURY_CONTRACT \
--source $SECRET_KEY \
--rpc-url $RPC_URL \
--network-passphrase "$NETWORK_PASSPHRASE" \
-- approve_settlement \
--signer $SOURCE_ACCOUNT \
--settlement_id 1curl -X POST http://localhost:3000/api/treasury/approve-settlement \
-H "Content-Type: application/json" \
-d '{ "settlement_id": 1 }'soroban contract invoke \
--id $TREASURY_CONTRACT \
--source $SECRET_KEY \
--rpc-url $RPC_URL \
--network-passphrase "$NETWORK_PASSPHRASE" \
-- execute_settlement \
--signer $SOURCE_ACCOUNT \
--settlement_id 1 \
--token_contract CUSDC...The execute endpoint validates the treasury USDC balance before submitting.
curl -X POST http://localhost:3000/api/treasury/execute-settlement \
-H "Content-Type: application/json" \
-d '{ "settlement_id": 1 }'Calls hold_settlement on the treasury contract, preventing execution until
explicitly released or escalated.
soroban contract invoke \\
--id $TREASURY_CONTRACT \\
--source $SECRET_KEY \\
--rpc-url $RPC_URL \\
--network-passphrase "$NETWORK_PASSPHRASE" \\
-- hold_settlement \\
--signer $SOURCE_ACCOUNT \\
--settlement_id 7 \\
--reason "Awaiting KYC confirmation"Calls release_hold on the treasury contract, returning the settlement to Pending
so the normal approval and execution flow can resume.
soroban contract invoke \\
--id $TREASURY_CONTRACT \\
--source $SECRET_KEY \\
--rpc-url $RPC_URL \\
--network-passphrase "$NETWORK_PASSPHRASE" \\
-- release_hold \\
--signer $SOURCE_ACCOUNT \\
--settlement_id 7curl -X POST http://localhost:3000/api/treasury/release-hold \\
-H "Content-Type: application/json" \\
-d '{ "settlement_id": 7 }'Calls raise_dispute on the treasury contract, escalating the hold to the
governance dispute-resolution flow and beginning a multi-sig vote.
soroban contract invoke \\
--id $TREASURY_CONTRACT \\
--source $SECRET_KEY \\
--rpc-url $RPC_URL \\
--network-passphrase "$NETWORK_PASSPHRASE" \\
-- raise_dispute \\
--signer $SOURCE_ACCOUNT \\
--settlement_id 7 \\
--reason "Merchant disputes the invoice amount"curl -X POST http://localhost:3000/api/treasury/escalate-hold \\
-H "Content-Type: application/json" \\
-d '{ "settlement_id": 7, "reason": "Merchant disputes the invoice amount" }'soroban contract invoke \
--id $TREASURY_CONTRACT \
--source $SECRET_KEY \
--rpc-url $RPC_URL \
--network-passphrase "$NETWORK_PASSPHRASE" \
-- get_thresholdsoroban contract invoke \
--id $TREASURY_CONTRACT \
--source $SECRET_KEY \
--rpc-url $RPC_URL \
--network-passphrase "$NETWORK_PASSPHRASE" \
-- update_threshold \
--admin $SOURCE_ACCOUNT \
--new_threshold 3curl http://localhost:3000/api/treasury/thresholdcurl -X POST http://localhost:3000/api/treasury/threshold \
-H "Content-Type: application/json" \
-d '{ "threshold": 3 }'soroban contract invoke \
--id $COMPLIANCE_CONTRACT \
--source $SECRET_KEY \
--rpc-url $RPC_URL \
--network-passphrase "$NETWORK_PASSPHRASE" \
-- allow_address \
--admin $SOURCE_ACCOUNT \
--address GTARGET...soroban contract invoke \
--id $COMPLIANCE_CONTRACT \
--source $SECRET_KEY \
--rpc-url $RPC_URL \
--network-passphrase "$NETWORK_PASSPHRASE" \
-- block_address \
--admin $SOURCE_ACCOUNT \
--address GTARGET...is_allowed returns true only when an address has been explicitly allowed
and its allowance has not expired. Three distinct scenarios produce a
false result, and integrators must not treat them as equivalent — the
appropriate response differs in each case.
The address has never been passed to allow_address. is_allowed returns
false immediately. The correct action is to route the payer through your
KYC/onboarding flow before retrying.
# The address GNEW... has never been allowed.
soroban contract invoke \
--id $COMPLIANCE_CONTRACT \
--source $SECRET_KEY \
--rpc-url $RPC_URL \
--network-passphrase "$NETWORK_PASSPHRASE" \
-- is_allowed \
--address GNEWADDRESSNEVERALLOWED...
# → falseExpected response: false
The backend maps this to error code COMPLIANCE_NOT_ALLOWED. Return HTTP 403
to the caller with a message indicating that the address must complete
onboarding before transacting.
allow_address was called with an expires_at timestamp that has since
passed. is_allowed returns false because the allowance window closed.
The address is not blocked — it simply needs to be re-allowed (e.g., after
a periodic compliance re-check).
# GEXPIRED... was allowed until ledger time 1700000000, which has passed.
soroban contract invoke \
--id $COMPLIANCE_CONTRACT \
--source $SECRET_KEY \
--rpc-url $RPC_URL \
--network-passphrase "$NETWORK_PASSPHRASE" \
-- is_allowed \
--address GEXPIREDADDRESS...
# → false (allowance window closed)Expected response: false
The backend maps this to error code COMPLIANCE_ALLOWANCE_EXPIRED. Return
HTTP 403 with a message telling the caller that their compliance approval has
lapsed and they must renew it. Do not present this to the user as a block
— it is a renewal prompt.
block_address was called for this address. is_allowed returns false
regardless of any prior allowance. A blocked address must not transact until
the block is explicitly lifted by a compliance admin.
# GBLOCKED... was explicitly blocked via block_address.
soroban contract invoke \
--id $COMPLIANCE_CONTRACT \
--source $SECRET_KEY \
--rpc-url $RPC_URL \
--network-passphrase "$NETWORK_PASSPHRASE" \
-- is_allowed \
--address GBLOCKEDADDRESS...
# → false (explicit block in effect)Expected response: false
The backend maps this to error code COMPLIANCE_BLOCKED. Return HTTP 403
with a message indicating that the address is blocked from transacting. Do
not expose details about why it was blocked to the end-user; log the event
for compliance audit purposes and direct the user to your support channel.
| State | is_allowed result |
Error code | Suggested HTTP status |
|---|---|---|---|
| Never allowed | false |
COMPLIANCE_NOT_ALLOWED |
403 |
| Allowance expired | false |
COMPLIANCE_ALLOWANCE_EXPIRED |
403 |
| Explicitly blocked | false |
COMPLIANCE_BLOCKED |
403 |
| Allowed and within window | true |
— | proceed |
soroban contract invoke \
--id $INVOICE_CONTRACT \
--source $SECRET_KEY \
--rpc-url $RPC_URL \
--network-passphrase "$NETWORK_PASSPHRASE" \
-- get_grace_windowcurl http://localhost:3000/api/invoice/grace-windowsoroban contract invoke \
--id $INVOICE_CONTRACT \
--source $SECRET_KEY \
--rpc-url $RPC_URL \
--network-passphrase "$NETWORK_PASSPHRASE" \
-- set_grace_window \
--caller $SOURCE_ACCOUNT \
--window 172800curl -X POST http://localhost:3000/api/invoice/grace-window \
-H "Content-Type: application/json" \
-d '{ "grace_window_seconds": 172800 }'- All contract write operations require
--sourceto be a funded account with sufficient XLM for fees. - Use
--network testnetinstead of--rpc-url/--network-passphraseflags when targeting Testnet via the CLI default configuration. - Contract IDs and addresses are exported to
artifacts/addresses.jsonafter running a deployment script.