Soroban smart contracts for the BettaPay payment infrastructure on Stellar.
get_payment_reference and get_payments are intentionally public so
off-chain settlement indexers and composing contracts can verify a payment
without a merchant signature. A payment reference is a 32-byte value supplied
by the merchant and must be generated with cryptographically secure randomness;
it functions as a bearer capability for lookup. Indexers may consume
payment_stored events for discovery and use these endpoints for targeted
verification; raw Soroban state reads remain an equivalent alternative.
BettaPay-Contract/
├── Cargo.toml # Rust workspace root (both contracts)
├── settlement_contract/ # Merchant registration, fee splits, payment references
│ ├── Cargo.toml
│ └── src/lib.rs
├── governance_contract/ # Fee config, anchor registry, system params
│ ├── Cargo.toml
│ └── src/lib.rs
├── bettapay_common/ # Shared constants, types, error codes, and storage keys
├── docs/ # Core protocol architecture and lifecycle specifications
│ ├── scheduled-operation-lifecycle.md # Timelocked queue, collision rules, and 9 operations
│ ├── recovery-lifecycle.md # Emergency recovery flow, delays, and failure modes
│ ├── bootstrap-fallback.md # 4-tier settlement rule fallback chain and emission rules
│ ├── storage-key-ownership.md # Storage key matrix, ownership, and TTL policies
│ ├── contract-limits.md # Protocol constants and bounds reference
│ └── archive-and-tombstones.md # Merchant archiving and tombstone semantics
├── scripts/
│ ├── deploy_testnet.sh # Build + deploy both contracts + init admin
│ ├── simulate.sh # Simulate contract calls locally
│ ├── export_events.sh # Dump contract events over a ledger range
│ ├── list_pending_ops.sh # Show pending timelocked operations
│ ├── verify_wasm_hash.sh # Compare deployed Wasm hash to local build
│ └── check_interface_parity.sh # Fail when the two contracts' interface versions drift
└── DEVELOPMENT.md # Upgrade and storage-migration guide
- Scheduled Operation Lifecycle: State machine, allowed transitions, duplicate vs. genuine hash collision (codes
12and316), minimum 7-day timelock delay, emergency recovery veto, permissionless execution, and reference for all nineOperationvariants. - Admin Recovery Lifecycle: End-to-end recovery sequence, actors, 7-day
RECOVERY_DELAY_SECONDS, cancellation rules, failure modes (RecoveryNotPending8,RecoveryDelayActive9,RecoveryAlreadyPending15), and scheduled operation veto semantics. - Bootstrap Fallback Semantics: The 4-tier resolution chain (merchant → default → governance → bootstrap), trigger conditions, field values (
100platform bps,5network bps,0delay,auto_settlefalse), and event emission rules. - Storage Key Ownership and TTL Policies: Authoritative mapping of all settlement, governance, and
CommonDataKeykeys to owning crates, storage kinds, and TTL bump policies. - Contract Limits: Numeric bounds, fee limits, batch limits, and their enforcement points.
| Contract | Address |
|---|---|
| Settlement | CBGBGKJSUY7XYB6HWW4CVAU6MW2KD25FSF45E5KCP53TKUK374MBZNFB |
| Governance | CDPFWUTIXF5BC6BKNDLSQOZSDQCXAJNZFCZWHBE2RRHANRN25T3ILPZ7 |
| Admin | GCCHHKNI7GRA5QWC7RCTT3OHO7SKAUMKQA6IBWEQEO2SXI3GF376UHDD |
Network: Test SDF Network ; September 2015
- Rust (MSRV): 1.85.0. The minimum supported Rust version is the channel
pinned in
rust-toolchain.toml. CI builds and tests the workspace on it in a separate MSRV job; reproduce it locally withmake msrv. - soroban-cli: 21.5.0. Pinned once as
SOROBAN_VERSIONin theMakefile. Install it withmake install_sorobanand check it withmake check_soroban.
See CONTRIBUTING.md for details.
# Run all tests
cargo test
# Build WASM release binaries
cargo build --target wasm32-unknown-unknown --release
# Deploy to testnet (requires soroban CLI)
bash scripts/deploy_testnet.shAll examples below assume you have the Soroban CLI installed and a funded testnet identity.
# Build all contracts (release WASM)
cargo build --target wasm32-unknown-unknown --release
# Build a specific contract
cargo build --target wasm32-unknown-unknown --release -p settlement_contract
cargo build --target wasm32-unknown-unknown --release -p governance_contract# Run all tests
cargo test
# Run tests for a specific contract
cargo test -p settlement_contract
cargo test -p governance_contract
# Run a specific test by name
cargo test merchant_lifecycle_uses_canonical_topics -p settlement_contract# One-command deployment (builds + deploys + initializes both contracts)
bash scripts/deploy_testnet.sh
# Or deploy step-by-step:
# 1. Generate and fund a key
soroban keys generate bettapay-admin --fund
# 2. Build and optimize WASM
make optimize
# 3. Deploy settlement contract
SETTLEMENT_ID=$(soroban contract deploy \
--wasm target/optimized/settlement_contract_opt.wasm \
--source-account bettapay-admin \
--rpc-url https://soroban-testnet.stellar.org \
--network-passphrase "Test SDF Network ; September 2015")
# 4. Deploy governance contract
GOVERNANCE_ID=$(soroban contract deploy \
--wasm target/optimized/governance_contract_opt.wasm \
--source-account bettapay-admin \
--rpc-url https://soroban-testnet.stellar.org \
--network-passphrase "Test SDF Network ; September 2015")
# 5. Initialize both contracts
ADMIN=$(soroban keys address bettapay-admin)
RECOVERY_ADDRESS="$ADMIN"
soroban contract invoke \
--id "$SETTLEMENT_ID" \
--source-account bettapay-admin \
--rpc-url https://soroban-testnet.stellar.org \
--network-passphrase "Test SDF Network ; September 2015" \
-- \
init --admins "[\"$ADMIN\"]" --threshold 1 --governance "$GOVERNANCE_ID" --recovery-address "$RECOVERY_ADDRESS"
soroban contract invoke \
--id "$GOVERNANCE_ID" \
--source-account bettapay-admin \
--rpc-url https://soroban-testnet.stellar.org \
--network-passphrase "Test SDF Network ; September 2015" \
-- \
init --admins "[\"$ADMIN\"]" --threshold 1 --recovery-address "$RECOVERY_ADDRESS"# Set vars (adjust IDs as needed)
SETTLEMENT_ID=CBGBGKJSUY7XYB6HWW4CVAU6MW2KD25FSF45E5KCP53TKUK374MBZNFB
ADMIN=GCCHHKNI7GRA5QWC7RCTT3OHO7SKAUMKQA6IBWEQEO2SXI3GF376UHDD
MERCHANT=GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
RPC=https://soroban-testnet.stellar.org
PASS="Test SDF Network ; September 2015"
# Check admin
soroban contract invoke --id "$SETTLEMENT_ID" --source-account bettapay-admin \
--rpc-url "$RPC" --network-passphrase "$PASS" -- \
get_admin
# Register a merchant
soroban contract invoke --id "$SETTLEMENT_ID" --source-account bettapay-admin \
--rpc-url "$RPC" --network-passphrase "$PASS" -- \
register_merchant --merchant "$MERCHANT"
# Check if merchant is registered
soroban contract invoke --id "$SETTLEMENT_ID" --source-account bettapay-admin \
--rpc-url "$RPC" --network-passphrase "$PASS" -- \
is_merchant_registered --merchant "$MERCHANT"
# Set a settlement rule (250 bps platform, 50 bps network)
soroban contract invoke --id "$SETTLEMENT_ID" --source-account bettapay-admin \
--rpc-url "$RPC" --network-passphrase "$PASS" -- \
set_settlement_rule \
--merchant "$MERCHANT" \
--rule '{"platform_fee_bps": 250, "network_fee_bps": 50, "settlement_delay_ledger": 0, "auto_settle": false}'
# Calculate fee split without storing
soroban contract invoke --id "$SETTLEMENT_ID" --source-account bettapay-admin \
--rpc-url "$RPC" --network-passphrase "$PASS" -- \
calculate_fee_split --merchant "$MERCHANT" --amount 10000
# Store a payment reference (32-byte hex hash)
soroban contract invoke --id "$SETTLEMENT_ID" --source-account "$MERCHANT" \
--rpc-url "$RPC" --network-passphrase "$PASS" -- \
store_payment_reference \
--merchant "$MERCHANT" \
--reference "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890" \
--amount 10000
# Fetch a stored payment
soroban contract invoke --id "$SETTLEMENT_ID" --source-account bettapay-admin \
--rpc-url "$RPC" --network-passphrase "$PASS" -- \
get_payment_reference \
--reference "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
# Set a global default rule
soroban contract invoke --id "$SETTLEMENT_ID" --source-account bettapay-admin \
--rpc-url "$RPC" --network-passphrase "$PASS" -- \
set_default_rule \
--new_rule '{"platform_fee_bps": 100, "network_fee_bps": 0, "settlement_delay_ledger": 0, "auto_settle": false}'
# Pause/unpause
soroban contract invoke --id "$SETTLEMENT_ID" --source-account bettapay-admin \
--rpc-url "$RPC" --network-passphrase "$PASS" -- \
pause
soroban contract invoke --id "$SETTLEMENT_ID" --source-account bettapay-admin \
--rpc-url "$RPC" --network-passphrase "$PASS" -- \
unpauseGOVERNANCE_ID=CDPFWUTIXF5BC6BKNDLSQOZSDQCXAJNZFCZWHBE2RRHANRN25T3ILPZ7
ASSET=GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
ANCHOR=GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# Get admin
soroban contract invoke --id "$GOVERNANCE_ID" --source-account bettapay-admin \
--rpc-url "$RPC" --network-passphrase "$PASS" -- \
get_admin
# Set fee config (platform 120 bps, network 35 bps)
soroban contract invoke --id "$GOVERNANCE_ID" --source-account bettapay-admin \
--rpc-url "$RPC" --network-passphrase "$PASS" -- \
set_fee_config \
--caller "$ADMIN" \
--config '{"platform_fee_bps": 120, "network_fee_bps": 35}'
# Read fee config
soroban contract invoke --id "$GOVERNANCE_ID" --source-account bettapay-admin \
--rpc-url "$RPC" --network-passphrase "$PASS" -- \
get_fee_config
# Update a system parameter
soroban contract invoke --id "$GOVERNANCE_ID" --source-account bettapay-admin \
--rpc-url "$RPC" --network-passphrase "$PASS" -- \
update_system_param --caller "$ADMIN" --key '"max_settle"' --value 1440
# Read a system parameter
soroban contract invoke --id "$GOVERNANCE_ID" --source-account bettapay-admin \
--rpc-url "$RPC" --network-passphrase "$PASS" -- \
get_system_param --key '"max_settle"'
# Register an anchor for an asset
soroban contract invoke --id "$GOVERNANCE_ID" --source-account bettapay-admin \
--rpc-url "$RPC" --network-passphrase "$PASS" -- \
upsert_anchor --caller "$ADMIN" --asset "$ASSET" --anchor "$ANCHOR"
# Read an anchor
soroban contract invoke --id "$GOVERNANCE_ID" --source-account bettapay-admin \
--rpc-url "$RPC" --network-passphrase "$PASS" -- \
get_anchor --asset "$ASSET"
# Remove an anchor
soroban contract invoke --id "$GOVERNANCE_ID" --source-account bettapay-admin \
--rpc-url "$RPC" --network-passphrase "$PASS" -- \
remove_anchor --caller "$ADMIN" --asset "$ASSET"
# Transfer admin to a new address
soroban contract invoke --id "$GOVERNANCE_ID" --source-account bettapay-admin \
--rpc-url "$RPC" --network-passphrase "$PASS" -- \
transfer_admin --caller "$ADMIN" --new_admin "GYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY"For local development and testing without deploying to testnet, use the simulate.sh script:
# Run complete local simulation (builds, deploys, and initializes both contracts)
bash scripts/simulate.sh
# This outputs contract IDs and source identity:
# Source identity: bettapay-sim
# Source address: GYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
# Settlement contract ID: CYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
# Governance contract ID: CYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYAfter running simulate.sh, use the printed contract IDs for local testing:
# Example: Query settlement contract locally
SETTLEMENT_ID=CYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
GOVERNANCE_ID=CYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
SOURCE=bettapay-sim
# Get settlement contract admin
soroban contract invoke --id "$SETTLEMENT_ID" --source-account "$SOURCE" \
--rpc-url https://soroban-testnet.stellar.org \
--network-passphrase "Test SDF Network ; September 2015" -- \
get_admin
# Register a test merchant
TEST_MERCHANT=GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
soroban contract invoke --id "$SETTLEMENT_ID" --source-account "$SOURCE" \
--rpc-url https://soroban-testnet.stellar.org \
--network-passphrase "Test SDF Network ; September 2015" -- \
register_merchant --merchant "$TEST_MERCHANT"For repetitive testing, create a local .env file or set these variables:
# Testnet Configuration
export SOROBAN_RPC_URL="https://soroban-testnet.stellar.org"
export SOROBAN_NETWORK_PASSPHRASE="Test SDF Network ; September 2015"
export SOROBAN_ACCOUNT="bettapay-admin"
# Contract Addresses (update after deployment)
# After deployment, update the contract IDs in:
# - [BettaPay-Frontend](https://github.com/org/BettaPay-Frontend) `.env`
# - [BettaPay-Backend](https://github.com/org/BettaPay-Backend) `.env`
export SETTLEMENT_CONTRACT_ID="CBGBGKJSUY7XYB6HWW4CVAU6MW2KD25FSF45E5KCP53TKUK374MBZNFB"
export GOVERNANCE_CONTRACT_ID="CDPFWUTIXF5BC6BKNDLSQOZSDQCXAJNZFCZWHBE2RRHANRN25T3ILPZ7"
export ADMIN_ADDRESS="GCCHHKNI7GRA5QWC7RCTT3OHO7SKAUMKQA6IBWEQEO2SXI3GF376UHDD"
# Use variables in commands:
soroban contract invoke --id "$SETTLEMENT_CONTRACT_ID" --source-account "$SOROBAN_ACCOUNT" \
--rpc-url "$SOROBAN_RPC_URL" --network-passphrase "$SOROBAN_NETWORK_PASSPHRASE" -- \
get_admin# Generate a new key
soroban keys generate test-merchant
# Fund it (Friendbot for testnet)
soroban keys fund test-merchant
# Get its address
MERCHANT_ADDR=$(soroban keys address test-merchant)
echo "Merchant address: $MERCHANT_ADDR"SETTLEMENT_ID=CBGBGKJSUY7XYB6HWW4CVAU6MW2KD25FSF45E5KCP53TKUK374MBZNFB
ADMIN_ACCOUNT="bettapay-admin"
MERCHANT_ADDR=$(soroban keys address test-merchant)
RPC="https://soroban-testnet.stellar.org"
PASS="Test SDF Network ; September 2015"
# Register merchant
soroban contract invoke --id "$SETTLEMENT_ID" --source-account "$ADMIN_ACCOUNT" \
--rpc-url "$RPC" --network-passphrase "$PASS" -- \
register_merchant --merchant "$MERCHANT_ADDR"
# Set settlement rule (e.g., 250 bps platform fee, 50 bps network fee, immediate settlement)
soroban contract invoke --id "$SETTLEMENT_ID" --source-account "$ADMIN_ACCOUNT" \
--rpc-url "$RPC" --network-passphrase "$PASS" -- \
set_settlement_rule \
--merchant "$MERCHANT_ADDR" \
--rule '{"platform_fee_bps": 250, "network_fee_bps": 50, "settlement_delay_ledger": 0, "auto_settle": false}'
# Verify merchant is registered
soroban contract invoke --id "$SETTLEMENT_ID" --source-account "$ADMIN_ACCOUNT" \
--rpc-url "$RPC" --network-passphrase "$PASS" -- \
is_merchant_registered --merchant "$MERCHANT_ADDR"SETTLEMENT_ID=CBGBGKJSUY7XYB6HWW4CVAU6MW2KD25FSF45E5KCP53TKUK374MBZNFB
MERCHANT_ADDR=$(soroban keys address test-merchant)
# Calculate fees for a 10,000 stroops payment
soroban contract invoke --id "$SETTLEMENT_ID" --source-account bettapay-admin \
--rpc-url "$RPC" --network-passphrase "$PASS" -- \
calculate_fee_split --merchant "$MERCHANT_ADDR" --amount 10000
# Example output: {platform: 2500, network: 500, merchant: 7000}Operator-facing helper scripts (full flags documented in each script's --help):
# Verify a deployed contract matches the local optimized Wasm artifact
bash scripts/verify_wasm_hash.sh --contract-id "$SETTLEMENT_ID" \
--wasm target/optimized/settlement_contract_opt.wasm
# Export contract events over a ledger range to JSON or CSV
# (re-uses soroban_events.py helpers; --from-file replays a saved response)
bash scripts/export_events.sh --contract-id "$SETTLEMENT_ID" \
--start-ledger 1000 --end-ledger 3000 --format csv --output events.csv
# List pending timelocked operations (from op_scheduled/op_executed/op_cancelled events)
bash scripts/list_pending_ops.sh --contract-id "$SETTLEMENT_ID" --format table
# Fail when the two contracts advertise different interface versions
make check_interface_parityEvent topic names are decoded against the constants in bettapay_common/src/events.rs
so exporters and indexers cannot drift from the contracts.
This section documents the public entry points, parameter types, return values, authorization rules, and error states for each contract. All financial amounts and fees (bps) are handled using integer types (i128 and u32 respectively) to maintain determinism on-chain.
Handles merchant onboarding, rules-based fee splits, and payment references anchor logging.
Represents the fee distribution and delay configuration for a specific merchant or as a global default.
pub struct SettlementRule {
pub platform_fee_bps: u32, // Platform fee in basis points (1 bps = 0.01%)
pub network_fee_bps: u32, // Network fee in basis points
pub settlement_delay_ledger: u32, // Ledger count delay before funds settle
pub auto_settle: bool, // Automatic settlement toggle
}JSON CLI representation:
{
"platform_fee_bps": 250,
"network_fee_bps": 50,
"settlement_delay_ledger": 0,
"auto_settle": false
}Calculated output for fee distributions.
pub struct FeeSplit {
pub gross_amount: i128, // Total paid amount
pub platform_fee_amount: i128, // Platform fee share (rounded up)
pub network_fee_amount: i128, // Network fee share (rounded up)
pub merchant_amount: i128, // Net merchant payout (remnants)
}Stored record representation for logged payment references.
pub struct PaymentRecord {
pub merchant: Address,
pub amount: i128,
pub platform_fee_amount: i128,
pub network_fee_amount: i128,
pub merchant_amount: i128,
pub platform_fee_bps: u32,
pub network_fee_bps: u32,
pub ledger: u32,
pub settlement_delay_ledger: u32,
pub auto_settle: bool,
}| Function | Inputs | Output | Auth / Guard | Error Panics (SettlementError) |
Description |
|---|---|---|---|---|---|
init |
admin: Address, governance: Address, recovery_address: Address
|
() |
admin |
AlreadyInitialized, InvalidGovernance, InvalidRecoveryAddress
|
Initializes the contract and stores the administrator, governance contract, and backup recovery address. Can only be called once. |
get_admin |
None | Address |
None | NotInitialized |
Returns the current admin address. |
get_governance |
None | Address |
None | NotInitialized |
Returns the configured governance contract address. |
update_governance |
new_governance: Address |
() |
Stored Admin, not paused |
NotInitialized, Paused, InvalidGovernance
|
Updates the governance contract address after validating get_fee_config. |
initiate_recovery |
new_admin: Address |
() |
Recovery Address |
NotInitialized, InvalidAdmin, RecoveryAlreadyPending
|
Starts delayed admin recovery. Vetoes all pending scheduled operations. See Recovery Lifecycle. |
cancel_recovery |
signers: Vec<Address> |
() |
Initiator or Admin Multisig |
NotInitialized, Unauthorized, RecoveryNotPending
|
Cancels a pending recovery during the 7-day delay. See Recovery Lifecycle. |
execute_recovery |
None | () |
None (Permissionless) |
RecoveryNotPending, RecoveryDelayActive
|
Applies pending recovery after 7-day delay elapses. Consumes pending state atomically. See Recovery Lifecycle. |
transfer_admin |
signers: Vec<Address>, new_admins: Vec<Address>, new_threshold: u32 |
() |
Stored Admin multisig |
NotInitialized, Unauthorized, InvalidAdmin, InvalidThreshold, SameAdmin
|
Transfers administrative control and multisig threshold. |
pause |
None | () |
Stored Admin |
NotInitialized, Unauthorized
|
Halts mutating operations (e.g. register merchant, store payment reference). |
unpause |
None | () |
Stored Admin |
NotInitialized, Unauthorized
|
Resumes mutating contract operations. |
is_paused |
None | bool |
None | None | Returns whether the contract is currently paused. |
schedule |
signers: Vec<Address>, operation: Operation, execute_in: u64 |
() |
Stored Admin multisig threshold |
Paused, Unauthorized, ExecutionNotReady, GovernanceCallFailed, OperationAlreadyScheduled, OperationHashCollision
|
Schedules a timelocked administrative operation. Enforces minimum 7-day delay (MIN_TIMELOCK_DELAY_SECONDS). See Scheduled Operation Lifecycle. |
execute |
executor: Address, operation: Operation |
() |
None (Permissionless) |
Paused, OperationNotScheduled, ExecutionNotReady, RecoveryDelayActive
|
Permissionlessly executes a scheduled operation after timelock delay. Vetoed if recovery is pending (except CancelRecovery). See Scheduled Operation Lifecycle. |
cancel |
signers: Vec<Address>, operation: Operation |
() |
Stored Admin multisig threshold |
Paused, Unauthorized, OperationNotScheduled
|
Cancels a scheduled operation prior to execution. See Scheduled Operation Lifecycle. |
register_merchant |
merchant: Address |
() |
Stored Admin |
Paused, InvalidAddress, MerchantExists
|
Registers a new merchant. The merchant must not already exist and cannot be the zero address. |
unregister_merchant |
merchant: Address |
() |
Stored Admin |
Paused, MerchantMissing
|
Removes a merchant from the registry, deletes their specific rule, and orphans their payment records — post-unregister reads of those records fail with PaymentOrphaned. |
set_settlement_rule |
merchant: Address, rule: SettlementRule
|
() |
Stored Admin |
Paused, MerchantMissing, InvalidFeeBps, InvalidSettlementDelay
|
Sets merchant-specific override fees and delay parameters. Sum of bps must be |
clear_settlement_rule |
merchant: Address |
() |
Stored Admin | MerchantRuleNotSet |
Deletes a merchant-specific rule override, forcing a fallback to default → governance → bootstrap. |
set_default_rule |
new_rule: SettlementRule |
() |
Stored Admin |
InvalidFeeBps, InvalidSettlementDelay
|
Configures the global fallback rule applied when no merchant-specific rule exists. |
get_default_rule |
None | Option<SettlementRule> |
None | None | Fetches the global default settlement rule configuration, if any. |
store_payment_reference |
merchant: Address, reference: BytesN<32>, amount: i128
|
FeeSplit |
merchant |
Paused, MerchantMissing, InvalidPaymentReference, InvalidAmount, DuplicatePaymentReference
|
Records a payment hash on-chain and returns the fee split. Emits bootstrap_fallback when falling back to unconfigured bootstrap defaults. See Bootstrap Fallback Semantics. |
is_merchant_registered |
merchant: Address |
bool |
None | None | Checks if a merchant is present in the registry. |
get_settlement_rule |
merchant: Address |
Option<SettlementRule> |
None | None | Reads the merchant-specific settlement rule override. |
calculate_fee_split |
merchant: Address, amount: i128
|
FeeSplit |
None |
MerchantMissing, InvalidAmount
|
Performs a dry-run calculation of fees for the merchant without mutating storage. Read-only and never emits events. See Bootstrap Fallback Semantics. |
get_payment_reference |
merchant: Address, reference: BytesN<32>
|
Option<PaymentRecord> |
None | None | Returns the merchant's logged payment record for the reference. Refreshes the entry's TTL when queried. |
get_payments |
merchant: Address, references: Vec<BytesN<32>>
|
Vec<PaymentRecord> |
None | None | Batch retrieves the merchant's logged payment records. Missing references are ignored. |
prune_payments |
merchant: Address, references: Vec<BytesN<32>>
|
u32 |
Stored Admin | BatchTooLarge |
Removes up to 100 of one merchant's payment records in one call. Missing or repeated references are skipped, so cleanup can be retried. Emits one payments_pruned event with the removed count. |
Controls protocol-wide parameters, fee limits, upgraded code hashes, and trust anchors.
Represents the default protocol fees.
pub struct FeeConfig {
pub platform_fee_bps: u32, // Bounded by MIN_FEE_BPS (5) and MAX_FEE_BPS (5,000)
pub network_fee_bps: u32, // Combined sum must be <= 10,000 bps
}Event payload structure for administrative transfer tracking.
pub struct AdminTransferred {
pub old_admin: Address,
pub new_admin: Address,
}| Function | Inputs | Output | Auth / Guard | Error Panics (GovernanceError) |
Description |
|---|---|---|---|---|---|
init |
admin: Address, recovery_address: Address
|
() |
admin |
AlreadyInitialized, InvalidRecoveryAddress
|
Initializes the governance contract and stores the administrator and backup recovery address. |
is_initialized |
None | bool |
None | None | Returns true if initialization has been completed. |
get_admin |
None | Address |
None | NotInitialized |
Returns the current admin address. |
get_recovery_address |
None | Address |
None | NotInitialized |
Returns the configured backup recovery address. |
initiate_recovery |
new_admin: Address |
() |
Recovery Address |
NotInitialized, InvalidAdmin, RecoveryAlreadyPending
|
Starts delayed admin recovery. See Recovery Lifecycle. |
cancel_recovery |
None | () |
Stored Admin |
NotInitialized, RecoveryNotPending
|
Cancels a pending recovery during the 7-day delay. See Recovery Lifecycle. |
execute_recovery |
None | () |
None (Permissionless) |
RecoveryNotPending, RecoveryDelayActive
|
Applies pending recovery after the 7-day delay. See Recovery Lifecycle. |
upgrade |
caller: Address, new_wasm_hash: BytesN<32>
|
() |
caller == admin |
Unauthorized |
Replaces the contract Wasm code with a new binary, leaving storage unchanged. Changing a stored type needs a migration — see DEVELOPMENT.md. |
transfer_admin |
_caller: Address, new_admin: Address
|
() |
Stored Admin | InvalidAdmin |
Assigns the admin role to new_admin. Address cannot be zero or the current admin. |
pause |
caller: Address |
() |
caller == admin |
Unauthorized |
Halts mutating governance config operations. |
unpause |
caller: Address |
() |
caller == admin |
Unauthorized |
Resumes governance operations. |
is_paused |
None | bool |
None | None | Returns whether the governance modifications are halted. |
update_system_param |
caller: Address, key: Symbol, value: i128
|
() |
caller == admin |
Unauthorized, InvalidParamValue
|
Stores or overrides a numeric parameter value (must be key. |
get_system_param |
key: Symbol |
Option<i128> |
None | None | Fetches the system parameter value. Refreshes storage TTL on reads. |
set_fee_config |
caller: Address, config: FeeConfig
|
() |
caller == admin |
Unauthorized, InvalidFeeBps
|
Updates the global default fee configuration. Limits: |
get_fee_config |
None | Option<FeeConfig> |
None | None | Fetches the active system fee config. |
upsert_anchor |
caller: Address, asset: Address, anchor: Address
|
() |
caller == admin |
Unauthorized |
Registers or updates a trusted off-chain anchor address for a token asset. |
remove_anchor |
caller: Address, asset: Address
|
() |
caller == admin |
Unauthorized, AnchorMissing
|
Removes the trusted anchor config for a token asset. |
get_anchor |
asset: Address |
Option<Address> |
None | None | Reads the trusted anchor address associated with an asset. |
flowchart LR
subgraph Clients
Admin[Admin / Operators]
Merchant[Merchant Services]
Backend[Backend Services]
end
subgraph Contracts
Governance[Governance Contract]
Settlement[Settlement Contract]
end
Admin --> Governance
Merchant --> Settlement
Backend --> Governance
Backend --> Settlement
Governance -->|Fee config and anchors| Settlement
Settlement -->|Payments and settlement events| Backend
This diagram highlights the main interaction pattern: the backend and operators call the contracts directly, while the settlement contract consumes governance configuration and emits settlement-related events back to the application layer.
soroban-sdk = "21.7.7"
No cross-contract calls. Both contracts are independently deployable and stateless across each other. The backend services call them via Stellar RPC.
Found a security vulnerability? Please do not open a public GitHub issue.
See SECURITY.md for the reporting process (GitHub Security
Advisories preferred, email fallback), the vulnerability report template, the
list of report owners, and our 90-day responsible disclosure window.