Contract Flow
This page walks through the on-chain contract lifecycle of a Privacy Channel: the exact contract calls behind setup, deposit, transfer, and withdrawal. It is the contract-level companion to Deposit and Withdrawal Flows, which covers the same operations from the user and provider perspective.
Components
privacy-channelcontract: the vault. Custodies the SAC asset, trackssupply, and stores UTXOs. A UTXO is aBytesN<65>secp256r1 public key plus an amount; the matching secret key owns it.channel-authcontract: a Soroban custom account. Its__check_authgates every bundle, and it holds the provider registry.- Single state-changing entry:
transact(op: ChannelOperation), whereop = { spend, create, deposit, withdraw }.
Setup (one-time)
- Deploy
channel-auth; its owner is the council (set viaownableat construction). - The council calls
add_provider(provider_pubkey)onchannel-auth(owner-gated byownable::enforce_owner_auth, emitsProviderAdded). Without this,require_providerreturnsProviderThresholdNotMetand nothing can move. - Deploy
privacy-channelvia__constructor(admin, auth_contract, asset), binding it to thatchannel-authand the SAC asset (one channel per asset).
Deposit (Alice puts money in)
op:deposit=[(Alice, amt, [Create(U1, amt)])],create=[(U1, amt)];spend/withdrawempty.- Provider submits
transact(op). process_bundlecallsauth().require_auth_for_args(req)->__check_auth->require_provider(provider Ed25519, threshold 1). No spends, sohandle_utxo_authchecks nothing.process_bundlerunsstore.create(U1, amt); assertsΣdeposit == Σcreate.execute_external_operations:Alice.require_auth_for_args([conds]),asset.transfer(Alice -> contract, amt),increase_supply(amt).
Send (Alice pays Bob)
- Bob derives
U_bob, gives Alice its pubkey. op:spend=[(U_alice, [Create(U_bob, amt), Create(U_change, chg)])],create=[(U_bob, amt),(U_change, chg)].- Provider submits
transact(op). __check_auth:require_provider+handle_utxo_authverifies Alice's secp256r1 signature overU_alice's burn payload ("BURN" || pubkey || amount).assert_signed_effects_are_executed(MOON-01): the signedCreateconditions equal the executedcreates.process_bundle:store.spend(U_alice),store.create(U_bob),store.create(U_change); assertsΣspent == Σcreate. No SAC call,supplyunchanged.
Receive (Bob)
- No
transact.U_bobwas created in Alice's bundle; Bob holds an unspent UTXO under his key and spends it later by producing the secp256r1 signature forU_bob.
Withdraw (Bob cashes out)
op:spend=[(U_bob, [ExtWithdraw(Bob, amt), Create(U_change, chg)])],withdraw=[(Bob, amt, conds)],create=[(U_change, chg)].- Provider submits
transact(op). __check_auth:require_provider+handle_utxo_auth(Bob's secp256r1 overU_bob).assert_signed_effects_are_executed(MOON-01): signedExtWithdraw+Createequal the executedwithdraw+create.process_bundle:store.spend(U_bob),store.create(U_change); assertsΣspent == Σwithdraw + Σcreate.execute_external_operations:authorize_as_current_contract(...),asset.transfer(contract -> Bob, amt),decrease_supply(amt).
Invariant
Across every bundle: Σdeposit + Σspent == Σcreate + Σwithdraw, every spend carries the owner's secp256r1 signature, and a registered provider co-signs the whole thing.