v4 (F-SOR-011) — Contract constructor. Runs exactly once, atomically, as
part of the deploy operation. This closes the front-run / unauthorized-init
gap a separate initialize() had: there is no window in which an attacker
can set the admin before the deployer does.
admin.require_auth() ensures the admin address consents to being set
(prevents installing an admin the deployer does not control). The deploy
transaction must therefore carry the admin's authorization.
fn __constructor(env: soroban_sdk::Env, admin: soroban_sdk::Address)
Upgrade contract WASM. Admin only (high-privilege).
fn upgrade(
env: soroban_sdk::Env,
new_wasm_hash: soroban_sdk::BytesN<32>,
) -> Result<(), DistributeError>
Pause the contract. Admin only. Blocks all distribute() calls.
fn pause(env: soroban_sdk::Env) -> Result<(), DistributeError>
Resume the contract. Admin only. Unblocks distribute() calls.
fn resume(env: soroban_sdk::Env) -> Result<(), DistributeError>
Transfer admin role in one step. Current admin must authorize.
Deprecated in v3 — prefer the 2-step propose_admin + accept_admin
flow which requires the new admin to prove they hold the keypair
(prevents transfer-to-typo'd-address footgun).
fn set_admin(
env: soroban_sdk::Env,
new_admin: soroban_sdk::Address,
) -> Result<(), DistributeError>
v3 — Step 1 of admin rotation. Current admin proposes a new admin. The new admin must call accept_admin() to take ownership. Overwrites any prior pending proposal.
fn propose_admin(
env: soroban_sdk::Env,
new_admin: soroban_sdk::Address,
) -> Result<(), DistributeError>
v3 — Step 2 of admin rotation. Pending admin accepts ownership. The pending admin must sign — proves they hold the keypair.
fn accept_admin(env: soroban_sdk::Env) -> Result<(), DistributeError>
Extend contract instance TTL. Anyone can call (allows cron jobs).
fn extend_ttl(env: soroban_sdk::Env)
Distribute USDC from payer to multiple recipients in a single transaction.
Stateful-minimal: stores admin + paused flag only. The contract's primary purpose is to batch SAC.transfer() calls under one require_auth(payer) invocation tree — enabling one passkey prompt for N investor payments.
Company Wallet (payer)
│
├──► Investor 1 (amounts[0])
├──► Investor 2 (amounts[1])
├──► ...
├──► Investor N (amounts[N-1])
│
└──► Treasury (fee_amount) ← skipped if fee == 0
Auth model:
payer.require_auth() is called once at the top.token.transfer(payer, recipient, amount) creates a
sub-invocation automatically included in the auth tree.payer: Company smart wallet (C... address). Signs via passkey.token: USDC SAC contract address.recipients: Investor addresses (C... or G...).amounts: Per-investor USDC amounts in stroops (i128).fn distribute(
env: soroban_sdk::Env,
payer: soroban_sdk::Address,
token: soroban_sdk::Address,
recipients: soroban_sdk::Vec,
amounts: soroban_sdk::Vec,
fee_recipient: soroban_sdk::Address,
fee_amount: i128,
) -> Result<(), DistributeError>
Returns the contract version.
fn version(env: soroban_sdk::Env) -> u32
Returns the admin address.
fn get_admin(env: soroban_sdk::Env) -> Result
Returns whether the contract is paused.
fn get_paused(env: soroban_sdk::Env) -> bool
v3 — Returns the pending admin if propose_admin() was called and not yet accepted.
fn get_pending_admin(env: soroban_sdk::Env) -> Option