This guide explains how to configure and verify the three contract addresses that the
callora-vault contract uses to route deducted USDC after each API call.
When a backend operator calls deduct or batch_deduct, the vault reduces the
caller's on-chain balance and transfers the corresponding USDC to a downstream
contract. The destination is determined by two configurable addresses stored in the
vault:
| Address slot | Storage key | Purpose |
|---|---|---|
settlement |
Settlement |
callora-settlement contract; tracks per-developer balances |
revenue_pool |
RevenuePool |
callora-revenue-pool contract; simple admin-controlled distribution |
Priority rule: when both are configured, settlement takes priority and
revenue_pool is not used in the same deduct call.
If neither address is set, the deducted amount stays inside the vault (balance is
reduced but no USDC transfer occurs).
callora-vault
├── usdc_token ← set at init(); never changes
├── settlement ← set via set_settlement(); read via get_settlement()
└── revenue_pool ← set via set_revenue_pool(); read via get_revenue_pool()
All three can be read in one call with get_contract_addresses().
On Stellar testnet, USDC is available at:
stellar contract id asset \
--asset USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5 \
--network testnetOn mainnet, use the canonical Circle USDC issuer:
Issuer: GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN
Asset: USDC
Record the resulting contract ID — this is your usdc_token argument for init.
stellar contract deploy \
--wasm target/wasm32-unknown-unknown/release/callora_settlement.wasm \
--source <OPERATOR_KEY> \
--network testnet
# → SETTLEMENT_CONTRACT_IDInitialize it:
stellar contract invoke \
--id <SETTLEMENT_CONTRACT_ID> \
--source <OPERATOR_KEY> \
--network testnet \
-- init \
--admin <ADMIN_ADDRESS> \
--vault_address <VAULT_CONTRACT_ID>stellar contract deploy \
--wasm target/wasm32-unknown-unknown/release/callora_revenue_pool.wasm \
--source <OPERATOR_KEY> \
--network testnet
# → REVENUE_POOL_CONTRACT_IDInitialize it:
stellar contract invoke \
--id <REVENUE_POOL_CONTRACT_ID> \
--source <OPERATOR_KEY> \
--network testnet \
-- init \
--admin <ADMIN_ADDRESS> \
--usdc_token <USDC_TOKEN_ID>stellar contract deploy \
--wasm target/wasm32-unknown-unknown/release/callora_vault.wasm \
--source <OPERATOR_KEY> \
--network testnet
# → VAULT_CONTRACT_ID
stellar contract invoke \
--id <VAULT_CONTRACT_ID> \
--source <OPERATOR_KEY> \
--network testnet \
-- init \
--owner <OWNER_ADDRESS> \
--usdc_token <USDC_TOKEN_ID> \
--revenue_pool <REVENUE_POOL_CONTRACT_ID>Note:
settlementis not passed toinit; register it withset_settlementafter deployment (see step 5).
Only the vault admin may call set_settlement:
stellar contract invoke \
--id <VAULT_CONTRACT_ID> \
--source <ADMIN_KEY> \
--network testnet \
-- set_settlement \
--caller <ADMIN_ADDRESS> \
--settlement_address <SETTLEMENT_CONTRACT_ID>stellar contract invoke \
--id <VAULT_CONTRACT_ID> \
--source <OPERATOR_KEY> \
--network testnet \
-- get_contract_addressesExpected output (JSON):
[
"C<USDC_TOKEN_ID>",
"C<SETTLEMENT_CONTRACT_ID>",
"C<REVENUE_POOL_CONTRACT_ID>"
]null appears for any slot that has not been configured yet.
You can also query each slot individually:
# Settlement address
stellar contract invoke --id <VAULT_CONTRACT_ID> -- get_settlement
# Revenue pool address
stellar contract invoke --id <VAULT_CONTRACT_ID> -- get_revenue_poolAll setters are admin-only and can be called at any time after init.
| Goal | Function |
|---|---|
| Change / set the settlement address | set_settlement(caller, settlement_address) |
| Change / set the revenue pool | set_revenue_pool(caller, Some(new_address)) |
| Remove revenue pool routing | set_revenue_pool(caller, None) |
⚠️ Address changes take effect immediately on the nextdeductcall. Coordinate with your monitoring stack before switching in production.
import { Contract, SorobanRpc, xdr, scValToNative } from "@stellar/stellar-sdk";
const server = new SorobanRpc.Server("https://soroban-testnet.stellar.org");
const vault = new Contract(VAULT_CONTRACT_ID);
const sim = await server.simulateTransaction(
buildTransaction(vault.call("get_contract_addresses"))
);
const [usdcToken, settlement, revenuePool] =
scValToNative(sim.result.retval);
console.log({ usdcToken, settlement, revenuePool });
if (!settlement) {
console.warn("settlement address not configured — USDC stays in vault");
}- Admin key:
set_settlementandset_revenue_poolboth callrequire_auth()and check the stored admin address. Use a hardware wallet or multisig for the admin. - Address validation: The vault does not verify that configured addresses are
valid contracts. Before calling
set_settlement, confirm the settlement contract is deployed, initialized, and has the vault address registered viaset_vault. - Atomicity: Each address change is a single storage write; no partial update is observable by concurrent callers.
- Testnet vs mainnet: USDC token IDs differ across networks. Always confirm with
get_contract_addressesafter deployment.
docs/ACCESS_CONTROL.md— role matrix for all privileged functionsSECURITY.md— security checklist and threat modelEVENT_SCHEMA.md— events emitted byset_settlement/set_revenue_poolSETTLEMENT_IMPLEMENTATION.md— end-to-end settlement flow