This contract implements a smart-wallet-style Soroban contract account with:
- Owner-controlled actions authorized via a nonce-bound payload + optional signature (
tx_signature) - Passkey / web key storage & retrieval
- BLS key aggregation (reset/rotation)
- Per-token limits + approvals / allowances
- Dapp invocation with optional embedded auth rules (
auth_vec)
Most privileged functions do not rely on Address::require_auth() directly.
Instead they do:
- Build a canonical payload using:
- function name (
String) - arguments serialized as XDR (
ToXdr) - a nonce binding (
compute_tx_nonce)
- function name (
- Enforce owner authorization via:
owner_require_auth(env, payload, tx_signature)
This design enables:
- account-like UX (sign once off-chain, submit tx on-chain)
- replay protection via nonce
- consistent signing payload across calls
One-time setup. Fails with AlreadyInitialized if called again.
Stores:
- username
- web passkey
- BLS keys (vector)
- factory address (master contract)
- installed version hash
Sets allowance expiry offset (must be > 0), else InvalidExpiration.
Updates the linked external wallet (owner address).
Updates default per-tx spending/approval limit. limit < 0 → InvalidLimit.
Rotates / replaces aggregated BLS keys.
Updates the factory/master contract address.
- Requires
from.require_auth() - Transfers tokens into the wallet contract via
take_token amount <= 0→InvalidAmount
amount <= 0→InvalidAmountamount > read_limit(token)→ExceedMaxAllowance- Uses
send_tokento transfer out
Sets a custom token limit (per token). limit < 0 → InvalidLimit.
Writes allowance:
amount < 0→InvalidAmountamount > read_limit(token)→ExceedMaxAllowance
- Requires
spender.require_auth() - Spends from the allowance bucket using
spend_token amount <= 0→InvalidAmount
Reads current allowance.
Allows wallet to invoke another contract function:
- Optionally processes additional auth rules via
dapp_invoke_auth(auth_vec) - Executes:
env.invoke_contract(&contract_id, &func, args.unwrap_or(vec![&env]))
This is useful for routing interactions through the wallet while applying extra auth constraints.
Returns:
max_allowance(default spend limit)g_account(optional owner if set)
Reads stored web/passkey details.
Returns installed contract version hash.
Returns current nonce used for payload binding.
Helper to compute the exact payload that must be authorized.
Reads the wallet’s token balance.
Returns owner external wallet address.
Returns factory/master address.
- Reads installed version
- If already equal →
AlreadyLatest - Calls
env.deployer().update_current_contract_wasm(new_version) - Persists new version via
write_installed_version
AlreadyInitialized: initializing twiceInvalidAmount: non-positive amount where positive is requiredInvalidLimit: negative limitInvalidExpiration: expiration offset is zeroExceedMaxAllowance: amount exceeds token limitAlreadyLatest: upgrade target equals installed version
For owner-gated calls, the payload is built like:
args: Vec<Bytes> = vec![&env, <each_arg>.to_xdr(&env), ...]payload = compute_tx_nonce(&env, "<function_name>", args)?owner_require_auth(env, payload, tx_signature)?
If you’re building a client:
- call
get_tx_payload(func, args_as_bytes)(or mirror the same rules off-chain) - sign the returned
BytesN<32> - submit the transaction with
tx_signature
Important: the payload depends on XDR encoding; clients must match encoding exactly.
Owner-gated:
- update_allowance_expiration
- set_external_wallet
- update_default_limit
- reset_account
- update_factory
- withdraw
- dapp_invoker
- add_limit
- approve
- upgrade
Require caller auth:
- deposit (from)
- spend (spender)
Read-only:
- get_account_parameters, get_passkey, get_version, get_allowance, get_nonce, get_tx_payload, get_balance, get_owner, get_factory