Skip to main content
This recipe gives a card-processor role the ability to place holds on a wallet’s funds, the way a card network authorizes a payment before settling it. Read Authorization Locks for the full rules. The flow:
  1. The owner adds a delegated role with ManageAuthorizationLocks and a spending limit.
  2. The delegate locks part of the wallet’s balance. Every other role, the owner included, can now spend only the rest.
  3. The delegate captures the hold by spending through SignV2, which lowers the lock by the amount spent. It can release whatever is left. Otherwise the hold expires.
  4. Once the delegate’s holds are inactive, the owner can remove its role, which also deletes its lock records.

Rust Protocol SDK

These examples target the auth-lock draft of swig-protocol-sdk/solana/rust/sdk. Use that branch through Git or a local checkout while reviewing it. The published swig-sdk, swig-interface, and swig-state 3.0.0 crates do not yet include these lock APIs. The direct swig_interface examples also require wallet revision 4628591963a1f6d9196bae67b25fba6c4a9fb0ab. The hosted Developer SDK has separate TypeScript, Python, and Rust APIs for preparing backend-built transactions.

Grant the delegated role

The delegate can lock an asset up to its remaining spending allowance for that mint, minus its existing active holds. The new hold must also fit the asset account’s unreserved, lockable balance; native SOL excludes the rent-exempt minimum. Each hold lasts at most max_duration_slots. A role with All or AllButManageAuthority also needs ManageAuthorizationLocks to lock. Its cap is the asset account’s unreserved, lockable balance, still within max_duration_slots. A non-root role cannot set or change its own duration bound; the root role can. To let the root role lock, have it grant itself the permission:

Place a hold

With the wallet switched to the delegate’s role:
To hold tokens instead, first grant the delegate a matching token spending permission with capacity for at least 600 base units. The SOL-only grant above cannot authorize this hold. SPL Token and Token-2022 mints are supported:
For a token account that is not the wallet’s ATA, call add_authorization_lock(asset_account, mint.to_bytes(), amount, expiry_slot) directly. For native SOL, pass the wallet address and an all-zero mint.

Read holds

display_swig() also prints an AUTHORIZATION LOCKS section and shows each role’s ManageAuthorizationLocks bound.

Capture a hold

The holder captures by spending. With the wallet switched to the delegate’s role, spend from the locked account through SignV2. No other instruction is needed:
After each SignV2 the delegate signs, the net amount that left each locked account is taken off its locks on that account, oldest first. Spending more than the hold takes the rest from free balance, within the role’s limits. The role’s spending limits are charged here, not when it locked. A lock captured to 0 stays stored with amount == 0. It no longer binds and get_active_authorization_locks skips it. Other roles, the owner’s All role included, can only spend the balance minus the delegate’s remaining hold. If they try to spend into it, the transaction fails with AuthorizationLockViolated.

Release or prune a hold

Set lock_id to the existing hold you selected from get_authorization_locks() or the active-hold output above. Creation returns a transaction signature, not the lock ID.
The delegate can release its lock at any time, which voids whatever is still reserved. After a lock expires or is fully captured, any role with All, AllButManageAuthority, ManageAuthority, or ManageAuthorizationLocks can prune it to reclaim its rent.

Remove the delegate

Once all of the delegate’s holds are inactive (expired or fully captured), the owner can remove its role. The removal also deletes the delegate’s lock records and refunds their rent with the role’s:
While any of its holds is still active, removal fails with AuthorizationLockActive, even if its other holds have expired. Release the remaining holds as the delegate, or wait for them to expire.

Lock from a program (ProgramExec)

A ProgramExec role can hold ManageAuthorizationLocks too, so an on-chain program can place and release holds. The program spends a hold through SignV2 as usual. The SDK lock helpers work with a ProgramExecClientRole. To build the instructions directly, use the swig_interface builders. The program’s instruction must pass the Swig config and the wallet address as its first two accounts. The authorizing program must also validate the exact Swig lock instruction and its account metas before approving it: add/remove operation, acting role, asset/mint, amount and expiry for add, or lock ID and rent recipient for remove. Bind those values to the caller’s authorization, for example by inspecting the following instruction through the instructions sysvar. A matching prefix and wallet pair alone do not authorize a particular hold; a generic approval could otherwise be reused with a changed amount, expiry, or lock ID. The builder example assumes authorize_hold_data and the authorizing program implement that binding:
The Ed25519, Secp256k1, and Secp256r1 remove builders also take the wallet address, right after the authority arguments:

CLI

These commands require a CLI build containing wallet PR #240, which has merged. Build that revision or a later one until a CLI release includes it. The CLI remains in swig-wallet; the Rust SDK lives in the Protocol SDK repository. The swig CLI has the same operations. Each command also takes --authority-type, --authority, and --authority-kp, or falls back to the saved default authority. Grant the delegated role. Repeat --permissions once per permission:
The token permission covers the mint used below and limits its spending to the merchant’s token account. Replace both placeholders with that mint and destination. Place, list, and release holds as the delegate:
The CLI’s command mode has no SignV2 spend command, so capture holds by spending through the SDK as shown above. list-authorization-locks --all shows fully captured holds as fully captured. Revoke the delegate’s ability to place new holds. This is allowed even while it holds active locks:
In interactive mode (swig -i), use the Authorization Locks menu.

Common errors

See Error Types for codes.