# Create an API token Source: https://shield.fi/docs/api-reference/api-tokens/create-api-token openapi.json POST /api-tokens Create an API token whose secret is returned once. # List API tokens Source: https://shield.fi/docs/api-reference/api-tokens/list-api-tokens openapi.json GET /api-tokens List API tokens owned by the authenticated wallet without returning their secrets. # Revoke an API token Source: https://shield.fi/docs/api-reference/api-tokens/revoke-api-token openapi.json DELETE /api-tokens/{id} Revoke one API token owned by the authenticated wallet. # Admin revoke sessions Source: https://shield.fi/docs/api-reference/authentication/admin-revoke-sessions openapi.json POST /admin/sessions/revoke Revoke sessions through the protected administrator operation. # Create a WebSocket ticket Source: https://shield.fi/docs/api-reference/authentication/create-websocket-ticket openapi.json GET /auth/ws-ticket Create the short-lived ticket used in the first WebSocket authentication frame. # Get the current session Source: https://shield.fi/docs/api-reference/authentication/get-session openapi.json GET /auth/session Retrieve the current wallet session and reseed its CSRF token. # List wallet sessions Source: https://shield.fi/docs/api-reference/authentication/list-sessions openapi.json GET /auth/sessions List active sessions for the authenticated wallet. # Refresh a session Source: https://shield.fi/docs/api-reference/authentication/refresh-session openapi.json POST /auth/refresh Rotate the refresh token and issue fresh browser session cookies. # Request a sign-in challenge Source: https://shield.fi/docs/api-reference/authentication/request-sign-in-challenge openapi.json POST /auth/challenge Create the nonce and message a wallet must sign to start a session. # Revoke a wallet session Source: https://shield.fi/docs/api-reference/authentication/revoke-session openapi.json POST /auth/sessions/{session_id}/revoke Revoke one session owned by the authenticated wallet. # Sign out all sessions Source: https://shield.fi/docs/api-reference/authentication/sign-out-all-sessions openapi.json POST /auth/logout-all Revoke every session for the authenticated wallet. # Sign out this session Source: https://shield.fi/docs/api-reference/authentication/sign-out-session openapi.json POST /auth/logout Revoke the current browser session and clear its authentication cookies. # Verify a wallet signature Source: https://shield.fi/docs/api-reference/authentication/verify-wallet-signature openapi.json POST /auth/verify Verify the signed challenge and establish the browser session cookies. # Get pair controls Source: https://shield.fi/docs/api-reference/compliance/get-pair-controls openapi.json GET /compliance/pairs/{token0}/{token1} Retrieve pause state for a token pair in either token order. # Get protocol controls Source: https://shield.fi/docs/api-reference/compliance/get-protocol-controls openapi.json GET /compliance Retrieve global pause and pool-creation control state. # Get token controls Source: https://shield.fi/docs/api-reference/compliance/get-token-controls openapi.json GET /compliance/tokens/{token_id} Retrieve allowlist and pause state for one token. # Get token market Source: https://shield.fi/docs/api-reference/explore/get-token-market openapi.json GET /explore/tokens/{token_id} Read market statistics and pools for one token. # List market transactions Source: https://shield.fi/docs/api-reference/explore/list-market-transactions openapi.json GET /explore/transactions Read public market transactions with cursor pagination. # List pool markets Source: https://shield.fi/docs/api-reference/explore/list-pool-markets openapi.json GET /explore/pools List pool market statistics with offset pagination. # List token markets Source: https://shield.fi/docs/api-reference/explore/list-token-markets openapi.json GET /explore/tokens List token prices and market statistics with offset pagination. # Batch pool statistics Source: https://shield.fi/docs/api-reference/pools/batch-pool-statistics openapi.json GET /pools/stats Retrieve rolling 24-hour statistics for up to 100 pools in one request. # Get liquidity distribution Source: https://shield.fi/docs/api-reference/pools/get-liquidity-distribution openapi.json GET /pools/{key}/liquidity-distribution Retrieve the liquidity distributed across a pool's initialized ranges. # Get a pool Source: https://shield.fi/docs/api-reference/pools/get-pool openapi.json GET /pools/{key} Retrieve one pool by its canonical pool key. # Get pool OHLCV Source: https://shield.fi/docs/api-reference/pools/get-pool-ohlcv openapi.json GET /pools/{key}/ohlcv Retrieve indexed open, high, low, close, and volume candles for a pool. # Get pool oracle Source: https://shield.fi/docs/api-reference/pools/get-pool-oracle openapi.json GET /pools/{key}/oracle Read an indexed pool oracle snapshot for a selected time window. # Get pool statistics Source: https://shield.fi/docs/api-reference/pools/get-pool-statistics openapi.json GET /pools/{key}/stats Retrieve rolling 24-hour statistics for one pool. # Get rebalance state Source: https://shield.fi/docs/api-reference/pools/get-rebalance-state openapi.json GET /pools/{key}/rebalance-state Read pool state and tick data for a position rebalance. # List initialized ticks Source: https://shield.fi/docs/api-reference/pools/list-initialized-ticks openapi.json GET /pools/{key}/initialized-ticks List the initialized tick boundaries for a pool. # List pool trades Source: https://shield.fi/docs/api-reference/pools/list-pool-trades openapi.json GET /pools/{key}/trades List indexed trades for a pool. # List pools Source: https://shield.fi/docs/api-reference/pools/list-pools openapi.json GET /pools List registered pools with token metadata, fees, enabled state, and pagination. # List positions Source: https://shield.fi/docs/api-reference/positions/list-positions openapi.json GET /positions List indexed liquidity positions available to the authenticated client. # Get protocol state Source: https://shield.fi/docs/api-reference/protocol/get-protocol-state openapi.json GET /protocol/state Get a revisioned snapshot of protocol controls and routing configuration. # List fee tiers Source: https://shield.fi/docs/api-reference/protocol/list-fee-tiers openapi.json GET /fee-tiers List the fee tiers registered for the configured deployment. # Generate referral codes Source: https://shield.fi/docs/api-reference/referrals/generate-referral-code openapi.json POST /referral/generate Generate referral codes for an authorized administrator. # Get my referral code Source: https://shield.fi/docs/api-reference/referrals/get-my-referral-code openapi.json GET /referral/my-code Get the referral code for the authenticated wallet. # Get referral administration Source: https://shield.fi/docs/api-reference/referrals/get-referral-admin openapi.json GET /referral/admin Get referral administration data for an authorized administrator. # Get referral settings Source: https://shield.fi/docs/api-reference/referrals/get-referral-settings openapi.json GET /referral/settings Get the current referral program settings. # Get referral status Source: https://shield.fi/docs/api-reference/referrals/get-referral-status openapi.json GET /referral/status Get referral and reward status for the authenticated wallet. # List referral codes Source: https://shield.fi/docs/api-reference/referrals/list-referral-codes openapi.json GET /referral/codes List referral codes for an authorized administrator. # Redeem a referral code Source: https://shield.fi/docs/api-reference/referrals/redeem-referral-code openapi.json POST /referral/redeem Redeem a referral code for the authenticated wallet. # Update referral settings Source: https://shield.fi/docs/api-reference/referrals/update-referral-settings openapi.json PUT /referral/settings Update referral program settings with administrator authorization. # Find a route Source: https://shield.fi/docs/api-reference/routing/find-route openapi.json GET /route Find an indexed Shield Swap route between two tokens. # Get route topology Source: https://shield.fi/docs/api-reference/routing/get-route-topology openapi.json GET /route/topology Get the current token and pool graph used for route discovery. # Inspect pool state Source: https://shield.fi/docs/api-reference/schemas/inspect-pool-state openapi.json GET /debug/pool Retrieve the raw on-chain pool slot and optional tick status for debugging. Select **Testnet** before you use this endpoint. The current mainnet deployment does not enable it. # List unclaimed outputs Source: https://shield.fi/docs/api-reference/swaps/list-unclaimed-outputs openapi.json GET /unclaimed List swap outputs and position proceeds the authenticated wallet can still claim. # Get a test-token request Source: https://shield.fi/docs/api-reference/test-tokens/get-test-token-request openapi.json GET /airdrop/{job_id} Retrieve the current state and per-token results for a test-token request. Select **Testnet** before you use this endpoint. The current mainnet deployment does not enable it. # Request test tokens Source: https://shield.fi/docs/api-reference/test-tokens/request-test-tokens openapi.json POST /airdrop Start a background request for the configured test token set. Select **Testnet** before you use this endpoint. The current mainnet deployment does not enable it. # List tokens Source: https://shield.fi/docs/api-reference/tokens/list-tokens openapi.json GET /tokens List registered Shield Swap tokens and wrapper metadata. # Concentrated liquidity Source: https://shield.fi/docs/concepts/concentrated-liquidity How Shield Swap allocates liquidity across price ranges. Shield Swap assigns each LP position a lower tick and an upper tick. The position contributes active liquidity only while the pool's current tick is inside the half-open interval: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} tick_lower <= current_tick < tick_upper ``` An LP can concentrate capital near the current market price. Inventory composition and fee production change as price crosses the position boundaries. ## Position state The public `Position` struct stores: * Pool identifier * Lower and upper ticks * Liquidity amount * Last fee-growth checkpoint for token0 and token1 * Token0 and token1 amounts owed The `PositionNFT` ownership record proves who may operate that position. Public market state can show the range and liquidity for a `token_id` without storing the owner's address in the `positions` mapping. ## Boundary accounting Each initialized boundary stores both `liquidity_gross` and signed `liquidity_net`. * `liquidity_gross` is the total position liquidity that references the boundary. * `liquidity_net` describes the change to active liquidity when price crosses the boundary upward. A lower boundary adds liquidity on an upward crossing. An upper boundary removes it. For a downward crossing, the contract applies the opposite sign. When `liquidity_gross` falls to zero, the contract removes the boundary from the initialized-tick list. This keeps the tick walk from stopping at an empty boundary. ## In-range and out-of-range inventory The liquidity formulas produce different token requirements depending on the current square-root price: * Below the range, the position is funded with token0. * Inside the range, the position uses both tokens. * Above the range, the position is funded with token1. Mint and increase transfer the full native base-unit desired amounts into AMM custody, calculate the maximum supported liquidity, and then calculate the amounts actually used. The position receives `desired - used` as public `tokens_owed`; token change records contain only input-record value above the desired transfer. ## Fee eligibility Only active liquidity receives fees for a swap step. The position's fee entitlement is derived from cumulative global fee growth and the fee-growth values stored outside its lower and upper ticks. Each step's fractional fee growth is recorded immediately using the liquidity active during that step. ## Contract-specific limits Shield Swap combines tick ranges and Q128.128 per-liquidity fee growth with fixed unrolled tick walks, wide arithmetic, `PositionNFT` ownership records, public finalizer mappings, freezelist proofs, and a separate claim transaction. Quotes and range-management tools must reproduce these rules. # Trading fees and protocol share Source: https://shield.fi/docs/concepts/fees-and-protocol-share Fee tiers, LP fee growth, and protocol fee collection. Each pool has one fee tier stored in `PoolState.fee`. The swap step interprets that value in parts per million. Common configured values are 100, 500, 3000, and 10000, corresponding to 0.01 percent, 0.05 percent, 0.30 percent, and 1.00 percent. These defaults come from the bootstrap scripts. The contract allows the administrator to register other `u16` fee values and bind them to approved tick spacings. ## Fee split For each swap step, `compute_swap_step` returns a fee amount. The program calculates: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} protocol_fee = fee * fee_protocol / 16 lp_fee = fee - protocol_fee ``` `fee_protocol` may be zero or an integer from 4 through 10. A nonzero setting therefore assigns 4/16 through 10/16 of the trading fee to the protocol. ## LP accounting The LP portion is divided by active liquidity and added to the cumulative fee-growth value for the input token. That cumulative value uses 128 fractional bits in a two-limb wrapping structure. When price crosses an initialized tick, both token fee-growth-outside values are flipped against their corresponding global values. A position's inside growth is derived from global growth and its two boundary values. Fees become an amount owed when a position settles during increase, decrease, collect, or freeze. The `fee_owed` helper multiplies the growth delta by position liquidity and shifts out 128 fractional bits. ## Protocol accounting The protocol share accumulates in `Slot.protocol_fees0` or `Slot.protocol_fees1`, depending on swap direction. These values use native token base units. `collect_protocol` accepts requested native base-unit amounts, verifies that the pool has enough recorded protocol fees, executes public transfers, and subtracts the amounts from the slot. Only the administrator may collect protocol fees. The recipient can be a separate address, but the contract does not enforce a treasury policy or revenue split. ## Rounding Integer arithmetic leaves a fraction below one Q128 unit when fee growth is computed. The implementation discards that sub-Q128 remainder for the step. It is not carried into a later step, which prevents dust earned under one active-liquidity set from being credited after the set changes. Read [Q128 fee growth](../math/q128-fee-growth) and [Fee entitlements](../liquidity/fee-entitlements) before implementing independent fee reconciliation. # Initialized-tick index Source: https://shield.fi/docs/concepts/initialized-tick-index The per-pool linked list used to walk liquidity boundaries. Each pool stores initialized ticks in a doubly linked list. This avoids asking the swap caller to supply a sequence of tick hints and gives the contract a deterministic next boundary in either direction. ## Sentinels Pool creation writes two sentinel `Tick` entries: * Minimum sentinel at `-400001` * Maximum sentinel at `400001` At pool birth, the minimum sentinel points forward to the maximum sentinel, and the maximum sentinel points back to the minimum sentinel. The pool `Slot` also stores: * `next_init_below`, the nearest initialized tick at or below the current tick * `next_init_above`, the nearest initialized tick above the current tick ## Inserting a boundary Mint and increase accept `tick_lower_hint` and `tick_upper_hint`. A hint is the initialized predecessor of the new boundary. For an uninitialized boundary, finalize verifies: * The hinted tick belongs to the pool. * It is the minimum sentinel or has positive gross liquidity. * It is below the new tick. * Its `next` pointer is above the new tick. The contract then splices the new tick between the predecessor and successor. If both position boundaries are new, the upper insertion re-reads its predecessor because the lower insertion may have changed the list. ## Removing a boundary Decrease and freeze call the shared liquidity-removal logic. When a boundary's `liquidity_gross` becomes zero, the program links its predecessor directly to its successor and removes the tick mapping entry. Slot pointers are advanced if they referred to the removed boundary. ## Walking during swaps A single-hop swap starts from the slot's nearest pointer and performs five swap iterations. Each iteration reads the next boundary, moves price toward that tick or the user limit, applies fees, crosses when appropriate, and updates the pointers. Each multi-hop leg performs three iterations. Route design must account for this bounded walk. A large input can remain partially unspent when the walk or price limit stops progress. ## Exact-limit crossing The crossing condition treats exact arrival at an initialized tick price as a crossing, including when the price equals `sqrt_price_limit`. Otherwise, the price could park at a boundary while the previous range's liquidity remained active. This condition applies together with the current Q128 fee-growth types. See [Exact-limit tick crossing](../math/exact-limit-tick-crossing) for the combined invariant. ## Indexer checks An indexer can audit list integrity by checking reciprocal `prev` and `next` links, sorted order, pool identity, positive gross liquidity for non-sentinels, and agreement between slot pointers and the current tick. These checks detect corrupted or stale routing state earlier than a failed swap. # Pools and identifiers Source: https://shield.fi/docs/concepts/pools-and-keys Canonical token ordering, pool keys, pair keys, tick keys, and position identifiers. Shield Swap uses deterministic hashes for pools, ticks, swaps, and positions. Integrators must reproduce the same preimages and canonical ordering. ## Pool key `create_pool` accepts two token identifiers, a fee tier, an initial square-root price, a tick spacing, and an initial tick. The program sorts the token identifiers before hashing: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} pool_key = BHP256(PoolKey { token0: min(input_token_a, input_token_b), token1: max(input_token_a, input_token_b), fee }) ``` The same token pair can have multiple pools when the fee tier differs. Reversing the input token order produces the same key because sorting happens before the hash. ## Pair key `PairKey` contains only sorted token0 and token1. It is used by `pair_paused`, so one pair pause affects every fee tier for that pair. ## Tick key Each tick mapping entry is keyed by the hash of: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} TickKey { pool, tick } ``` Tick keys are pool-specific. The same tick number in two pools has a different mapping key. ## Position token ID Mint computes the position identifier from: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} TokenIDPreimage { request: MintPositionRequest, recipient, nonce } ``` The request and resulting `token_id` are public. The recipient and nonce are private inputs. The token ID is an opaque public position handle. Increase, decrease, collect, and burn use the same token ID. Rebalance removes that position and creates a replacement with a new token ID. The token ID is not derived from the confidential address formula used for swaps. It is also not, by itself, proof of ownership. The `PositionNFT` ownership record is the authority consumed by position entry points. ## Swap ID Single-hop swap hashes a `SwapKey` that includes pool, direction, amount, price limit, recipient, nonce, and caller. The recipient and caller slots both contain the same public confidential address. Its relationship to the signer is not disclosed in ordinary public state. Multi-hop swap hashes the complete `SwapMultiHopRequest`. The contract rejects creation when `swap_outputs` already contains the resulting ID. Nonces should therefore be unique for the relevant request and address. ## Token identifiers Token identifiers are `field` values used for dynamic dispatch through `IARC20`. The token-seeding script encodes a bare program name into a field by packing its UTF-8 bytes little-endian. That script is an API database utility, not a separate on-chain identifier rule. Production integrations should obtain token identifiers from an approved registry or deployment manifest and verify that the field decodes to the intended program. Do not infer asset identity from a ticker symbol. # Positions and ownership records Source: https://shield.fi/docs/concepts/positions-and-nfts The split between public position economics and confidential ownership. Position state is split between a public `Position` mapping entry and a `PositionNFT` ownership record. ## Public position The `positions` mapping stores a `Position` under `token_id`. It contains the pool, range, liquidity, fee-growth checkpoints, and owed token amounts. This data is public because swaps, liquidity changes, and fee settlement need consensus on the same values. An observer can follow the economic lifecycle of a position identifier. The observer can see its range, active amount, principal withdrawal, fee collection, freeze effects, and removal after burn. ## PositionNFT ownership record `PositionNFT` is an Aleo record with these fields: * `owner` * `withdrawal` * `token_id` * `token0_id` * `token1_id` * `pool` * `tick_lower` * `tick_upper` The owner field and record contents are encrypted for the record owner under ordinary record handling. Most position operations return a replacement with the same identifying fields. Rebalance is different. It consumes the old record, removes the old public position, and returns a record for a new token ID and range. The program relies on record ownership for authorization. It does not store an owner address in the public `Position` struct. ## Owner and withdrawal are different roles Mint accepts two independent private addresses: | Address | Role | | - | - | | Position owner, supplied as `recipient` | Owns and decrypts the `PositionNFT`. This address authorizes increase, rebalance, decrease, collect, and burn by spending the current ownership record. | | Withdrawal address, supplied as `withdrawal` | Owns collect payouts and nonzero rebalance refunds. It does not need to be the position owner or mint signer. | The withdrawal address is embedded in the first `PositionNFT` and copied into each replacement record. Rebalance also keeps it in the new position. Collect and Rebalance do not accept an alternate payout address. To use a different address, fully close the position and mint a new one. Both addresses are private inputs to mint and are absent from the public `positions` mapping. Mint validates freezelist non-inclusion proofs for the signer, position owner, and withdrawal address. Collect validates the current record owner and the embedded withdrawal address. ## Lifecycle * `mint` creates the public mapping row and the first `PositionNFT` ownership record. * `increase_liquidity` consumes the record and returns an updated record. * `rebalance_position` removes the full old position and returns a new record with a new token ID. * `decrease_liquidity` returns an updated record and moves principal into public `tokens_owed` accounting. * `collect` returns an updated ownership record and token payout records. * `burn` consumes the record and removes the mapping row once liquidity and owed balances are zero. ## Linkability The ownership record hides owner and immutable withdrawal addresses from the public mapping. A token ID links increase, decrease, collect, and burn activity for that position. Rebalance creates a new token ID. The rebalance transaction links the removed position to its replacement, and the new `MintComplianceRecord` attributes the replacement. ## Freeze behavior A freeze is applied to the public `token_id`. If the position has liquidity, the contract removes it from the pool and settles principal plus fees into `tokens_owed`. The freeze then blocks increase, decrease, collect, and burn. Unfreeze removes the blocking entry. It does not restore liquidity. The holder can collect settled value and may add liquidity again. ## Integration rule Do not reconstruct a `PositionNFT` from public fields. A valid record includes cryptographic record metadata and must be discovered, decrypted, and tracked by the owner. After any position operation, persist the returned replacement record before discarding the spent input. # Price, ticks, and square-root price Source: https://shield.fi/docs/concepts/price-ticks-and-sqrt-price Interpret the current Q128.128 pool price and tick domain. Each tick represents a base-unit price ratio of `1.0001^tick`. The contract stores the square root of that ratio in Q128.128 form: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} sqrt_price_x128(tick) = floor(sqrt(1.0001^tick) * 2^128) ``` The implementation uses a fixed integer cascade, not floating-point exponentiation. ## Domain * User ticks: `-400000` through `400000`. * Linked-list sentinels: `-400001` and `400001`. * Minimum square-root ratio: `{ hi: 0, lo: 702075911466779181339691826087 }`. * Maximum square-root ratio: `{ hi: 484680305, lo: 8756686347225649145659787327114459760 }`. Pool creation requires its initial tick and square-root price to agree. Position ticks must align with pool spacing. Swap price limits must be on the correct side of the current price and inside the allowed ratio domain. ## Display conversion The raw squared ratio compares native base units. To display token1 per token0, adjust for the two token decimals. Reverse the result when the interface displays the opposite token order. Never derive a signed transaction limit with binary floating point. Use integer Q128.128 helpers and verify the limit against the current slot. # Swap and claim lifecycle Source: https://shield.fi/docs/concepts/swap-and-claim-lifecycle Why swap execution and confidential token delivery are separate transactions. Shield Swap separates pool execution from delivery of confidential token records. A completed swap therefore has an accepted swap transaction and an accepted claim transaction. ```mermaid theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} sequenceDiagram participant Wallet participant AMM as Shield Swap participant Token as Token program participant State as Public mappings participant Investigator Wallet->>AMM: Submit swap with input record and private factor AMM->>Token: Transfer input amount from record to public custody AMM-->>Investigator: Return encrypted compliance record AMM->>State: Update pool, ticks, fees, and SwapOutput AMM-->>Wallet: Return swap ID and change record Wallet->>State: Read authoritative SwapOutput Wallet->>AMM: Submit claim with same signer and factor AMM->>Token: Transfer output and refund into records AMM->>State: Remove SwapOutput AMM-->>Wallet: Return output and refund records ``` ## Stage 1: prepare The client selects an input token record, chooses trade parameters, generates a nonce, chooses a private blinding factor, and derives a public confidential address bound to the signer. The address is submitted publicly, while its relationship to the signer is not disclosed in ordinary public state. The client must retain the blinding factor. The claim uses the same factor and signer to prove the confidential address relationship. ## Stage 2: execute The `swap` function verifies the signer-bound confidential address relation and calls the input token program to transfer the requested amount from private to public custody. It also creates an encrypted compliance record for the investigator. The finalize stage: * Checks deadline, pause state, pool identity, direction, amount, and price limit * Normalizes the raw input amount * Walks initialized ticks * Updates price, liquidity, fee growth, protocol fees, ticks, and slot pointers * Checks `amount_out_min` * Writes a public `SwapOutput` * Writes a permanent execution header and one receipt for each executed hop * Marks the confidential address as used The stored output includes the output amount and any unspent input in raw token units. ## Stage 3: observe After acceptance, the client reads `swap_outputs[swap_id]`. These mapping values are the authoritative claim arguments. A client should not rely only on a pre-trade quote because the actual output and remainder depend on finalized state. ## Stage 4: claim `claim_swap_output` verifies the same signer and blinding factor, requests public-to-private token transfers for output and refund, and passes the exact public values into finalize. Finalize checks every field against the stored output and removes the mapping entry. Removal prevents a second claim. The execution header and hop receipts remain after claim. They supply the accepted route result used by the History page and indexer. ## Multi-hop variation Multi-hop uses the same split settlement model and the same `SwapOutput` shape. Its pending output contains final output and any unspent original route input. `claim_swap_output` returns one output record and one input-refund record. ## Operational consequences * A wallet must surface pending claims and retry them safely. * Pauses do not block claim functions, but ordinary validation and token transfer behavior still apply. * Anyone monitoring public state can see the pending output values and later removal. * Losing the blinding factor can prevent the signer from constructing the required claim proof. * Changing the signer also fails confidential address verification. Wallets should present swap and claim as one workflow with two on-chain transactions. # Native token amounts Source: https://shield.fi/docs/concepts/token-amount-normalization How the current AMM uses base units and applies display decimals off chain. The current AMM does not normalize token amounts to a shared decimal scale. It calculates swaps, liquidity, fees, protocol balances, and owed amounts in each token program's native base units. ## Contract behavior * `PoolState` has no `scale0` or `scale1`. * The program has no `token_decimals` mapping or `set_token_decimals` function. * Desired amounts, minimums, swap amounts, refunds, owed balances, and protocol fees use native base units. * The contract does not require high-decimal token inputs to be divisible by an AMM scale. ## Display conversion The deployment registry and API token metadata hold display decimals. A client converts only at its display and input boundary: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} human amount = base units / 10^decimals base units = exact decimal input * 10^decimals ``` Never use floating-point arithmetic to build a signed base-unit amount. ## Price display The on-chain square-root price represents a ratio of native base units. A human token1-per-token0 quote must account for canonical token order and the difference in display decimals. Keep these concepts separate: * Contract amount: exact native base units. * Contract price: Q128.128 square-root ratio of base units. * Display amount: decimal-formatted base units. * Display price: base-unit ratio adjusted for token decimals and display order. ## Wrapped assets The registry distinguishes the AMM token program from the underlying record program. ALEO uses wrapped credits. USAD and USDCx use ARC-22 wrappers. Router input record amounts can therefore use `u64` or `u128`, while the core AMM amount fields remain `u128`. # Caller and signer Source: https://shield.fi/docs/confidentiality/caller-and-signer How immediate callers, top-level signers, record recipients, and confidential addresses differ across direct and routed execution. Leo exposes two execution identities in the proof context: * The caller is the account or program that invoked the current entry function. * The signer is the top-level account that signed the transaction. They often match in a direct wallet call. They differ when another program invokes Shield Swap on the user's behalf. All three compliance records retain both values so routed execution does not lose top-level account attribution. ## Direct invocation In a direct swap submitted by a wallet: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} signer = wallet address caller = wallet address blinded_address = public pseudonym derived from wallet and private factor ``` The public swap mappings store the confidential address, not the signer or caller values captured in the compliance record. In a direct mint, the signer and caller normally match. The private position owner and immutable withdrawal address can each be different because `mint` accepts both inputs separately. ## Routed invocation When a router or custody program calls Shield Swap: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} signer = top-level user account caller = intermediary program address blinded_address = public pseudonym bound to signer ``` Storing only `caller` would identify the integration but not the top-level account. Storing only `signer` would lose the immediate routing context. The current schema stores both. ## Four relevant roles Direct and routed calls can contain four distinct identities. | Role | Source | Function | | - | - | - | | Signer | Signer context | Top-level account authorizing the transaction | | Caller | Caller context | Direct account or program invoking the AMM | | Recipient | `recipient`, `withdrawal`, or a record's `owner` field | Address that receives a `PositionNFT` or token record | | Confidential address | Public derived address | One-use swap pseudonym stored in public swap fields | These roles can differ. ### Mint example An institution signs a transaction through a custody program and assigns the position to a segregated wallet: * Signer: institution's transaction account * Caller: custody program * Recipient: segregated position wallet * Token ID: public position handle `MintComplianceRecord` captures signer, caller, owner recipient, immutable withdrawal, nonce, and token ID. ### Swap example A user signs through a router: * Signer: user account * Caller: router program * Confidential address: public one-use pseudonym * Claim recipient: signer, because the claim creates token records for the signer `SwapComplianceRecord` captures signer, caller, and the confidential address. ### Collect example An LP consumes a `PositionNFT` whose immutable withdrawal address differs from its owner: * Signer and record owner authorize the collect * The NFT withdrawal address receives the token records * Public state shows amounts and position update * The original mint compliance record contains the withdrawal address The caller cannot replace that address during collect. ### Rebalance example The current record owner signs through the rebalance router. The router becomes the immediate caller, while the owner remains the signer. The replacement `PositionNFT` belongs to the signer. It keeps the old withdrawal address and receives a new public token ID. ## Contract use of caller Administrative functions generally capture the caller and pass it to finalization. There, `assert_admin` compares the caller with the public admin mapping. This means a normal admin account must invoke the function directly. If an admin wants to operate through a governance program, that program address must itself be the configured admin and must complete the two-step transfer process. The imported multisig check governs program upgrades at editions greater than zero. Routine admin authorization uses the caller and the `admin` mapping. ## Contract use of signer The core program uses the signer to: * Derive and verify public confidential addresses * Direct single-hop claim records * Direct all swap-claim output and refund records * Populate compliance records with caller and signer attribution * Own the replacement `PositionNFT` after Rebalance * Return the pool creator address publicly from `create_pool` `burn` does not return the signer as a public output. ## Signer is not legal identity The signer proves which Aleo account authorized the top-level transaction. It does not establish: * The named person controlling the account * Beneficial ownership of assets * Authority to act for an institution * Jurisdiction or location * Screening or KYC status A compliance system must maintain an external binding between the Aleo address and its verified subject. The compliance record can then connect the verified account to a specific public swap or position. ## Integration requirements Routers and custody programs should document: * Which program address appears as `caller` * Which account remains `signer` * Whether mint recipient can differ from signer * How blinding material is generated and returned to the signer * Who initiates claim * How compliance records are associated with an off-chain customer or account ID Compliance parsers must preserve `caller` and `signer` as separate fields and must not assume equality. ## Related pages * [Confidential addresses](./confidential-addresses) * [Compliance records](./compliance-records) * [Trust and key boundaries](../security/trust-and-key-boundaries) # Compliance records Source: https://shield.fi/docs/confidentiality/compliance-records Schemas, recipients, lifecycle coverage, integration requirements, and limits of encrypted attribution records. Shield Swap emits encrypted attribution records for position creation and swaps. Rebalance creates a replacement position and emits another mint record. Compliance records supply transaction-attribution data. Participant eligibility, sanctions screening, transaction approval, reporting, and lawful disclosure remain operator functions. ## Recipient Every compliance record sets: ```leo theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} owner: SWAP_INVESTIGATOR_KEY ``` `SWAP_INVESTIGATOR_KEY` is a compile-time address constant. It contains a different address from `DEPLOYER`. The recipient is not read from the `admin` mapping and cannot be changed by an admin transition. Transferring administration does not transfer investigator access. ## SwapComplianceRecord | Field | Meaning | | - | - | | `owner` | Hardcoded investigator recipient | | `swap_id` | Public identifier for the pending output | | `token_in` | Input token program ID | | `token_out` | Output token program ID | | `request` | Complete `SwapRequest` | | `caller` | Immediate account or program that invoked `swap` | | `signer` | Top-level account that signed the transaction | | `blinded_address` | Public swap pseudonym bound to the signer and private factor | The embedded `SwapRequest` contains: * Pool * Direction * Input amount * Minimum output * Square-root price limit * Confidential recipient address * Nonce * Deadline ## MultiHopSwapComplianceRecord | Field | Meaning | | - | - | | `owner` | Hardcoded investigator recipient | | `swap_id` | Public identifier for the routed output | | `request` | Complete `SwapMultiHopRequest` | | `caller` | Immediate account or program that invoked the AMM | | `signer` | Top-level account that signed the transaction | | `blinded_address` | Public routed-swap pseudonym | The embedded request contains: * Input and output token IDs * Input amount and minimum output * Confidential recipient address * Up to three hop structs * Pool, direction, and price limit for every hop * Hop count * Nonce and deadline * Request-level confidential caller address ## MintComplianceRecord | Field | Meaning | | - | - | | `owner` | Hardcoded investigator recipient | | `token_id` | Public position identifier | | `token0_id` | Pool token 0 program ID | | `token1_id` | Pool token 1 program ID | | `nonce` | Unique value used to derive the position token ID | | `request` | Complete `MintPositionRequest` | | `caller` | Immediate caller of the transition that emitted the record | | `signer` | Top-level account that signed the transaction | | `recipient` | Confidential owner selected for the PositionNFT | | `withdrawal` | Immutable owner of later payout and rebalance refund records | The embedded request contains pool, tick range, desired amounts, minimum amounts, and insertion hints. Mint can have three distinct actors: * Signer that funds and authorizes the transaction * Caller program that routes the operation * Recipient that owns the position record The record retains all three. Rebalance also emits `MintComplianceRecord`. Its signer and recipient are the rebalance signer, and its withdrawal value comes from the old `PositionNFT`. ## Parser requirements The current records retain both `caller` and `signer`. This gives investigators the immediate integration path and the top-level account attribution for routed calls. Clients that decrypt and parse these records must match the current field sequence. A parser that omits `signer` can fail or silently mislabel data. The supplied TypeScript types include: * `SwapComplianceRecord` * `MultiHopSwapComplianceRecord` The supplied TypeScript types do not include `MintComplianceRecord`. Compliance integrations must provide and test a typed parser for that record. ## Emission coverage | Action | Compliance record emitted | | - | - | | Initial mint | Yes | | Rebalance | Yes | | Single-hop swap | Yes | | Multi-hop swap | Yes | | Increase liquidity | No | | Decrease liquidity | No | | Collect | No | | Burn | No | | Claim | No | | Freeze or unfreeze | No | | Pool creation | No | | Protocol-fee collection | No | Each mint record gives the investigator the token ID, position owner, and immutable withdrawal address. A rebalance record identifies the replacement position. The public rebalance transaction links the removed token ID to the replacement token ID. Every collect and nonzero rebalance refund pays the recorded withdrawal address. ## Storage and discovery Compliance records are encrypted record outputs, not mapping rows. The AMM does not maintain an investigator inbox or a public index of record commitments by case. An operator needs to: 1. Scan accepted `mint`, `rebalance_position`, `swap`, and `swap_multi_hop` transitions. 2. Identify record outputs owned by the investigator account. 3. Preserve ciphertext, transaction ID, block reference, program edition, and schema version. 4. Decrypt inside an approved key boundary. 5. Normalize the record into a controlled evidence store. 6. Link the public token ID or swap ID to public execution state. 7. Apply access, retention, and disclosure policy outside the contract. The AMM exposes no transition to acknowledge, consume, rotate, re-encrypt, or classify a compliance record. ## Data interpretation The `signer` field identifies an Aleo account. It does not prove: * A person's civil identity * Beneficial ownership * Institutional mandate * Geographic location * KYC status * Sanctions status * Source of funds An external identity system must bind an Aleo address to those claims. The record provides transaction attribution once that binding exists. The `caller` field identifies the immediate integration path. It is useful for distinguishing a direct invocation from a router, custody program, or other intermediary. It should not be treated as the funding account when it is a program address. ## Related pages * [Caller and signer](./caller-and-signer) * [Compliance controls](./regulator-and-compliance-review) # Confidential addresses Source: https://shield.fi/docs/confidentiality/confidential-addresses Construction, use, recovery, one-time enforcement, and correlation limits for confidential swap addresses. Public swap and claim fields contain a signer-bound confidential address instead of the signer wallet. The blinding factor used to derive it is private. Swap amount, route, token identifiers, price limits, timing, and pool movement remain public. ## Contract derivation The contract defines one claim-or-swap domain constant and verifies: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} blinded_address = Poseidon8( program_address, claim_or_swap_domain, signer_address, blinding_factor ) ``` The program calls `Poseidon8::hash_to_address_raw` over these values in `verify_blinded_address`. Including the program address limits cross-program reuse. Including the domain distinguishes this use from unrelated address constructions. Including the signer prevents another account from claiming the relationship with only a copied factor. ## Client-side factor derivation The TypeScript client derives a blinding factor from: * Program address * A client-side blinding-factor domain * Account view-key scalar * Local counter The contract does not verify that the factor came from this view-key and counter scheme. It only verifies the final signer-bound confidential address. Production clients may use another secure factor-generation method if they preserve claim recovery and uniqueness. The contract stores only the public confidential address and a used-address marker. Wallets must preserve the factor or the inputs required to derive it. ## Swap sequence For a single-hop swap: 1. The wallet selects or derives a private blinding factor. 2. The wallet computes the public confidential address using the AMM program address, domain, signer, and factor. 3. The wallet submits the token record and private factor. 4. The wallet submits the confidential address and market parameters publicly. 5. The contract recomputes the expected address and rejects a mismatch. 6. The request stores the confidential address as its recipient. 7. The swap ID also commits to the confidential address. 8. Finalization stores the confidential address as both recipient and caller in `SwapOutput`. 9. Finalization marks the confidential address used. The multi-hop path stores the confidential address as request recipient and request caller, then applies the same one-use check. ## Claim sequence The claimant supplies: * Private blinding factor * Public confidential address * Public swap ID * Public token identifiers * Public output and refund amounts The contract recomputes the expected address using the claim transaction's signer. Finalization requires the supplied confidential address to match the stored caller and recipient. It also requires each amount and token ID to match `SwapOutput`. Token records are created for the signer. The claimant cannot provide a different payout recipient. Successful finalization removes the pending output. ## One-time enforcement `used_blinded_addresses` is a public mapping. A successful swap rejects an address that is already present and then sets it to `true`. The entry is not removed after claim. A confidential address is therefore one-use for successful swaps in this program. One-time enforcement does not replace wallet-side concurrency control. Two transactions prepared with the same factor can race. At most one can finalize successfully, and the other incurs a rejected execution path. ## Recovery requirements The claim depends on the same signer and factor relationship used for the swap. A production wallet needs to preserve enough state to reconstruct or retrieve: * Program ID or program address * Domain version * Signer account * Blinding factor, or its deterministic derivation inputs * Confidential address * Swap ID * Output and refund values read from public state A backup that restores the account key but not the factor derivation state can leave an output difficult or impossible to claim. If factors are derived from a counter, restoration and concurrent submission must not cause counter reuse. The public `used_blinded_addresses` mapping can help detect reuse, but it is not a wallet state store and cannot reveal the private factor. ## Security properties The construction provides: * A public pseudonym that is bound to the transaction signer * A private secret required to reproduce the relation * Claim restriction to the signer that matches the confidential address * One-use enforcement for successful swaps * Separation from the program's other address domains The claim finalizer also verifies all public token and amount fields and removes the output, preventing cross-user and double-claim paths. ## Limits The construction does not provide: * Confidential amounts * Confidential route selection * Confidential pool or token identifiers * Confidential claim timing * Protection from all statistical correlation * Recovery of lost blinding material * A legal identity assertion * A general stealth-address system for mint or collect The current TypeScript derivation uses the account view key. A party that receives that view key for wallet indexing may also be able to reproduce derived factors if it knows or can enumerate the counter scheme. Production design should evaluate whether factor derivation should use a separate wallet secret. Different factors generate different confidential addresses, but distinct public operations can still be correlated by amount, route, price movement, network timing, client behavior, or information outside the AMM. ## Compliance disclosure The public sees only the confidential address in swap attribution fields. The encrypted compliance record includes: * Confidential address * Immediate caller * Top-level signer * Swap ID and request details The investigator can link the Aleo signer address to the public pseudonym. Linking that address to a legal identity requires an external account binding. See [Caller and signer](./caller-and-signer) and [Compliance records](./compliance-records). ## Related pages * [Confidentiality model](./model) * [Confidential data limitations](../security/confidential-data-limitations) # Confidential data Source: https://shield.fi/docs/confidentiality/confidential-data The Aleo private inputs and encrypted records used for participant ownership, funding, payouts, and selective disclosure. Shield Swap uses Aleo private inputs and encrypted records to keep participant ownership information out of ordinary public AMM state. Record contents, private inputs, and direct wallet attribution are confidential. Public market parameters and finalization state remain visible. ## Private proof inputs The core program declares the following private inputs: | Transition | Private input | Purpose | | - | - | - | | `mint` | `nonce` | Adds confidential entropy to the public token ID | | `mint` | `recipient` | Selects the owner of the returned PositionNFT | | `mint` | `withdrawal` | Binds the immutable owner of later payout records | | `mint` | freezelist proofs | Proves signer, owner recipient, and withdrawal non-inclusion | | `rebalance_position` | `nonce`, owner proofs, and withdrawal proofs | Creates the successor token ID and verifies both protected addresses | | `swap` | `blinding_factor` | Binds the signer to a public one-use confidential address without publishing the factor | | `swap_multi_hop` | `blinding_factor` | Applies the same address construction to a routed swap | | `claim_swap_output` | `blinding_factor`, freezelist proofs | Authorizes single-hop or routed output settlement | | `collect` | owner and withdrawal freezelist proofs | Validates both NFT authority and immutable payout address | A private input is not the same as an encrypted record. The value participates in proof construction, while a record is a persistent unit of encrypted Aleo state that can later be consumed. ## PositionNFT records `PositionNFT` contains: * Owner * Immutable withdrawal address * Token ID * Token program identifiers * Pool * Lower and upper ticks All declared record fields use Leo's default visibility and are encrypted for the owner. The owner can decrypt the record and use it to authorize position operations. `PositionNFT` is the confidential ownership capability for public position state. Current liquidity and owed amounts remain in the public `positions` mapping. Mint creates the first PositionNFT. Increase, decrease, and collect return an updated record with the same identifying fields. Rebalance removes the complete old position. It returns a PositionNFT with a new token ID and range, the same pool and withdrawal, and the signer as owner. Burn consumes the final record and returns no replacement. ## Token records Dynamic token records fund and settle the confidential path. ### Funding records Mint and increase consume two token records. Swap consumes one token record. Rebalance can consume zero, one, or two funding records through its router. The token program moves the requested amount into the AMM's public balance and returns an encrypted change record for any unspent balance. The exact record schema and authorization rules belong to the dynamically selected token program. The AMM depends on a compatible `IARC20` implementation. Asset onboarding must confirm that the production token program enforces the intended ownership, amount, freeze, and transfer rules. ### Payout records Collect converts public AMM balances into token records owned by `PositionNFT.withdrawal`. A nonzero rebalance refund also goes to that immutable address. Swap claims convert the output and any original-input refund into token records owned by the signer. The owner of a token record is confidential from ordinary public state. The amount transferred by the AMM is still public because collect and claim amounts are public arguments and because pending swap amounts are stored publicly before claim. ## Compliance records The program defines three encrypted compliance record types: * `MintComplianceRecord` * `SwapComplianceRecord` * `MultiHopSwapComplianceRecord` Each record is owned by `SWAP_INVESTIGATOR_KEY`, not by the participant. The record contents are confidential from the public and available to the corresponding investigator-key holder. All three records include the signer and caller. This preserves the top-level account and the immediate integration path for a routed call. See [Compliance records](./compliance-records) and [Caller and signer](./caller-and-signer). ## What each record owner can see ### Position recipient The mint recipient can decrypt the PositionNFT. If the recipient differs from the signer, the signer should not be assumed to own or decrypt that position record. ### Token-record owner The owner of a token change, output, refund, or collect record can decrypt its token and amount fields according to the token program's schema. ### Investigator The investigator can decrypt compliance records addressed to `SWAP_INVESTIGATOR_KEY`. The investigator does not gain general authority over participant PositionNFTs or token records from this AMM contract. ### Administrator The admin has no special decryption function in `shield_swap.aleo`. The program assigns separate addresses to the deployer and investigator roles. An admin transfer does not move the investigator role. ## Mint confidentiality differs from swap confidentiality Mint does not accept a `blinded_address`. Its token ID is computed as: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} hash(MintPositionRequest, recipient, nonce) ``` The request is public, while the recipient and nonce are private. Their hash produces the public token ID used to index position accounting. Swaps use a public confidential address derived from the signer and a private blinding factor. Claims prove the same relation and direct records to the signer. Mint derives a token ID from private inputs. Swaps derive a signer-bound public pseudonym. The two mechanisms have different lifecycle and recovery requirements. ## Information not covered by encrypted records The confidential path does not hide: * Swap amount or direction * Minimum output or price limit * Pool or multi-hop route * Position range * Desired or minimum mint amounts * Position token ID * Liquidity and owed balances * Claim amount or timing * Administrative control activity The public and confidential layers must be evaluated together. See [Public data](./public-data). ## Lifecycle coverage Each mint compliance record links signer, caller, owner recipient, immutable withdrawal address, nonce, and token ID. Rebalance creates a new mint record for its successor. The contract does not issue new compliance records for: * Increase liquidity * Decrease liquidity * Collect * Burn * Claims * Freeze or unfreeze The listed operations do not create new compliance records. Every collect pays the withdrawal address that the investigator can read from the applicable mint record. ## Related pages * [Confidentiality model](./model) * [Public data](./public-data) * [Trust and key boundaries](../security/trust-and-key-boundaries) # Confidentiality model Source: https://shield.fi/docs/confidentiality/model How Shield Swap separates public market state, confidential participant data, encrypted records, and selective disclosure. Shield Swap combines a public concentrated-liquidity market with confidential participant records. Prices, liquidity, routes, amounts, and accounting state remain available for consensus and market verification. Wallet ownership and token-record ownership are carried through Aleo private inputs and encrypted records. Public observers can inspect prices, liquidity, routes, amounts, and accounting changes. The intended confidential paths omit participant wallet addresses from public AMM fields. Investigator disclosure and correlation through public data remain possible. The protocol also creates encrypted compliance records for position creation and swaps. A rebalance creates a new position and emits another mint record. ## Four data layers ### Public market state The program stores pools, slots, ticks, positions, pending swap outputs, administrative state, pause state, token eligibility, and position freezes in public mappings. These mappings are the shared ledger used by finalization. Public state includes: * Pool token identifiers, fee tier, and enabled state * Current price, active liquidity, tick, fee growth, and protocol fees * Initialized tick liquidity and linked-list pointers * Position range, liquidity, fee checkpoints, and owed amounts, keyed by `token_id` * Swap output and remaining original input, keyed by `swap_id`, until claim * Administrative and control state See [Public data](./public-data) for the operation-level inventory. ### Private proof inputs Leo inputs marked `private` are supplied to the proof without becoming ordinary public arguments. The core program uses private inputs for: * The mint nonce * The mint owner recipient and immutable withdrawal address * The rebalance nonce and owner and withdrawal proofs * Swap and claim blinding factors * Mint, collect, and claim freezelist proofs Aleo records persist on chain as encrypted state. Traders and LPs supply token or `PositionNFT` records to the prover when funding or authorizing an operation. ### Encrypted records Records carry confidential ownership and value. Record fields in this program do not declare explicit visibility, so their values are encrypted for the record owner. The core program returns: * `PositionNFT` records to position owners * Dynamic token change records to token owners * Token output and refund records to claim signers * Token payout records to immutable position withdrawal addresses * Compliance records to `SWAP_INVESTIGATOR_KEY` An encrypted record is different from a public mapping entry. The record owner can decrypt and spend the record. Public finalization still updates the shared accounting state required by the AMM. ### Public pseudonyms Swaps use a public `blinded_address` as both the stored caller and recipient. The confidential address is derived from the program address, a domain constant, the signer, and a private blinding factor. The confidential address is a public, one-use execution pseudonym that omits the signer wallet from public swap fields while remaining bound to the signer and private factor for claims. See [Confidential addresses](./confidential-addresses) for the derivation and claim flow. ## Participant and disclosure views Different parties see different parts of the same operation. | Party | Available information | | - | - | | Public observer | Public inputs, transaction timing, mapping state, pool changes, routes, amounts, token IDs, position IDs, confidential addresses, and claim activity | | Trader | Public market data, the trader's input and change records, blinding material, and claimed token records | | LP | Public position accounting and the LP's encrypted `PositionNFT` and token records | | Investigator-key holder | Public data plus decrypted mint, rebalance, and swap compliance records owned by `SWAP_INVESTIGATOR_KEY` | | Administrator | Public control state and admin authority. The admin has no extra record visibility unless it also controls the investigator key | | Token program | The token-specific transfer state and restrictions enforced by that program | `DEPLOYER` initializes the administrative state. A separate `SWAP_INVESTIGATOR_KEY` receives compliance records. An admin transfer changes the `admin` mapping but does not change the hardcoded investigator key. ## Trading path A single-hop confidential swap follows this sequence: 1. The trader supplies a token record and a private blinding factor. 2. The trader supplies public market parameters, including pool, direction, amount, limits, deadline, and token identifiers. 3. The contract verifies the public confidential address against the program, signer, domain, and private factor. 4. The token program transfers the input amount from a record into the AMM's public balance. 5. Finalization updates public pool and tick state and writes a public `SwapOutput`. 6. The transition emits an encrypted `SwapComplianceRecord` to the investigator key. 7. A later claim proves the same signer-bound confidential address and supplies the exact public output values. 8. The token program creates token records for the signer. 9. Finalization removes the pending `swap_outputs` entry. Multi-hop execution follows the same model. The route, hop directions, hop price limits, and refund values remain public. ## Position path Position confidentiality uses records rather than swap-style confidential addresses. 1. Mint consumes two token records. 2. The mint owner, immutable withdrawal address, and nonce are private inputs. 3. The program computes a public `token_id` from the public request, private owner, and private nonce. 4. Finalization creates public position accounting under that token ID. 5. The owner receives an encrypted `PositionNFT` that binds the withdrawal address. 6. The investigator receives an encrypted `MintComplianceRecord` containing token ID, nonce, signer, caller, owner, and withdrawal address. 7. Increase, decrease, and collect consume and reissue the PositionNFT while retaining the same token ID. 8. Rebalance consumes the complete old position and creates a new token ID and PositionNFT for the signer. 9. Rebalance emits another `MintComplianceRecord` and keeps the immutable withdrawal address. 10. Burn consumes the PositionNFT and removes the public position row after liquidity and owed balances reach zero. `burn` does not return the signer as a public output. The public token ID remains. Position activity is linkable to one pseudonymous position identifier. The burn output does not directly publish the signing wallet. ## Compliance boundary The program supplies selective-disclosure records and operational controls. Legal identity, jurisdiction, participant eligibility, disclosure authority, and retention remain off-chain responsibilities. Compliance records are created only for: * `mint` * `rebalance_position` * `swap` * `swap_multi_hop` They are not created for increase, decrease, collect, burn, claim, freeze, unfreeze, pool creation, or protocol-fee collection. See [Compliance records](./compliance-records) for lifecycle coverage. ## Confidentiality and compliance boundaries Participant attribution and record ownership are confidential in the intended execution paths. Trade amounts, routes, prices, timing, and public position accounting remain observable. Investigator-owned records provide selective attribution, while legal compliance depends on deployment-specific policies and controls. See [Confidential data limitations](../security/confidential-data-limitations) for the threat and correlation boundaries. # Public data Source: https://shield.fi/docs/confidentiality/public-data The market, position, transaction, and control information visible through public inputs and mappings. Public pool mappings let wallets, routers, market makers, and LP tools reconstruct state and verify finalization. Trade size, route, price movement, position range, and timing remain observable. ## Public mappings The program exposes the following public mappings. ### Market state `pools` stores: * Sorted token program identifiers * Fee tier * Enabled state * Wrapper and underlying token relationships `slots` stores: * Current tick * Tick spacing * Current square-root price * Protocol-fee setting * Active liquidity * Global fee growth for both tokens * Maximum liquidity per tick * Accrued protocol fees * Nearest initialized ticks above and below `ticks` stores, for each initialized boundary: * Pool identifier * Net and gross liquidity * Tick index * Fee growth outside the tick * Previous and next initialized ticks `initialized_pools`, `tick_spacings`, `fee_tiers`, and `fee_to_tick_spacing` expose pool and configuration availability. `pool_creators` stores the immediate `create_pool` caller for each pool key. ### Position state `positions` stores a public row keyed by `token_id`. The row contains: * Pool * Lower and upper ticks * Liquidity * Last fee-growth checkpoints * Owed amounts for both tokens The mapping does not contain the LP wallet address. Ownership is carried by the encrypted `PositionNFT`. A token ID gives observers a pseudonymous position handle. Mint, increase, and decrease return the token ID as a public output. Burn returns the token ID and removes the position row. Rebalance removes the complete old position and creates a replacement with a new public token ID. The transaction links both position identifiers. ### Pending swap state `swap_outputs` stores a public `SwapOutput` until claim. It includes: * Public confidential recipient address * Public confidential caller address * Input and output token identifiers * Exact output amount * Exact unspent input amount * Up to two additional input-token and remainder slots for multi-hop execution The claim transition repeats these values as public inputs and removes the mapping entry after successful validation. Removal changes current state, but it does not erase the transaction from chain history. ### Swap execution receipts `swap_execution_headers` stores the execution height and hop count for each accepted swap. `swap_execution_hops` stores the public result for each hop. Each hop exposes the pool, direction, input, output, gross fee, protocol fee, final price, final liquidity, and final tick. These receipts remain after claim. ### Control state The public control mappings expose: * Current and pending admin * Used confidential addresses through `used_blinded_addresses` * Registered token decimals * Whether pool creation is open * Global pause state * Token eligibility for pool creation * Token pause state * Pair pause state * Position freeze state and its current stored block height An operator that needs a durable administrative history should archive state changes. `pending_admin`, `swap_outputs`, `positions`, and `frozen_position` entries can be removed. ## Public inputs by operation ### Pool creation `create_pool` takes public token IDs, fee, initial square-root price, tick spacing, and initial tick. It returns the pool key and signer address as public outputs. Pool creation is not a confidential participant path. The return value explicitly exposes the creator's signer address. ### Mint The full `MintPositionRequest` is public: * Pool * Tick range * Desired token amounts * Minimum token amounts * Tick insertion hints The token program identifiers and resulting token ID are also public. The nonce and recipient are private inputs, and the PositionNFT is encrypted. Finalization publishes the resulting liquidity and owed balances through the position and pool mappings. ### Increase liquidity The following are public: * Desired token amounts * Minimum token amounts * Token program identifiers * Tick insertion hints * Returned token ID The replacement `PositionNFT` and token change records are encrypted to their owners. Public position, tick, and slot state changes reveal the economic effect. ### Decrease liquidity The token ID, liquidity removed, and minimum amounts are public. Principal and accrued fees are credited into the public `tokens_owed` fields. No token transfer occurs until collect. ### Rebalance The public request includes the old liquidity, recovered amounts, funded amounts, refunds, target liquidity, replacement mint request, deadline, and asset identifiers. The transaction uses the old token ID and returns the new token ID publicly. The private nonce, ownership record, and freezelist proofs do not become ordinary public inputs. Finalization removes the old position and publishes the replacement range, liquidity, fee checkpoints, and owed amounts. ### Collect Requested amounts and token identifiers are public. The recipient is a private input, and payout records are encrypted. The position mapping shows the reduction in owed amounts. ### Burn The token ID is public. The signer address is not returned in the public outputs. The removal of the position mapping entry is public. ### Single-hop swap The public inputs include: * Confidential address * Pool * Direction * Input amount * Minimum output * Square-root price limit * Nonce * Deadline * Both pool token identifiers The swap ID is a public output. Finalization exposes the exact output and remainder in `swap_outputs` and updates public pool and tick state. ### Multi-hop swap The public inputs include: * Confidential address * Endpoint token identifiers * Input amount and minimum output * Two or three pool identifiers * Direction and square-root price limit for every hop * Hop count * Nonce and deadline The route is public. Each hop's pool-state effect is public. The final output and per-hop remainders are public in the pending output mapping. ### Claims Claims keep the blinding factor private but take the following values publicly: * Confidential address * Swap ID * Output and refund token identifiers * Exact output and refund amounts The resulting token records are encrypted to the signer. The public claim still reveals timing and confirms that a particular pending output was consumed. ## Market-data consequences Public state supports: * Deterministic quote simulation * Independent fee and liquidity accounting * Route validation * Market surveillance based on amounts and price movement * Public verification of pause and freeze controls * Reconciliation of protocol fees It also creates correlation surfaces. A distinctive amount, narrow tick range, unusual route, or closely timed claim may be recognizable even when the wallet address remains confidential. Market makers should assume competitors can observe executed size and route. Institutions should assume transaction economics are public. Compliance reviewers should treat the encrypted compliance record as an attribution layer over data that is already largely public. ## Current state is not complete history Mappings are mutable state, not immutable event tables. * `swap_outputs` is removed after claim. * `positions` is removed after burn. * `pending_admin` is removed after acceptance. * `frozen_position` is removed on unfreeze. The current swap execution receipts remain after claim. They do not replace complete transaction history for rejected actions. Historical reconstruction requires transaction and state-diff indexing. A live mapping read alone cannot prove when prior values were set or removed. The freeze block height illustrates the distinction. While a freeze is active, the mapping records the height at which it was set. Unfreeze removes that entry. The chain transaction remains available to an indexer, but the current mapping does not retain a permanent freeze stamp. ## Related pages * [Confidentiality model](./model) * [Confidential data](./confidential-data) * [Confidential addresses](./confidential-addresses) * [Confidential data limitations](../security/confidential-data-limitations) # Compliance controls Source: https://shield.fi/docs/confidentiality/regulator-and-compliance-review How confidentiality, attribution, intervention controls, and operating responsibilities work. Shield Swap combines public market state, confidential ownership records, encrypted attribution records, and administrative intervention controls. Each layer provides different capabilities and requires different operating safeguards. Compliance depends on off-chain identity, eligibility, disclosure, governance, and jurisdiction-specific controls in addition to the contract features described here. ## Operating responsibilities Operators must define and maintain: * Deployed program ID, edition, checksum, and release * Admin address and governance process * Investigator address and key custody * Token admission and wrapper behavior * Public and confidential data inventory * Compliance-record schemas and coverage * Disclosure authorization and evidence handling * Pause and position-freeze procedures * Monitoring and incident response * Identity binding outside the chain * Jurisdiction-specific obligations Publish deployment-specific addresses, custodians, approved assets, and operating jurisdiction separately from the protocol documentation. ## Confidentiality summary The public can observe: * Pool, tick, and position accounting * Swap amounts and directions * Multi-hop routes * Limits, deadlines, and token identifiers * Public confidential addresses * Position token IDs * Claim amounts and timing * Administrative state changes The intended confidential path omits the following from public AMM fields: * PositionNFT owner * Token-record owner * Mint recipient * Immutable collect withdrawal address * Swap blinding factor * Signer and immediate caller stored inside encrypted compliance records The last item is selectively disclosed to the hardcoded investigator account. ## Attribution capability The encrypted compliance records give the investigator both: * `signer`: top-level account authorizing the transaction * `caller`: direct account or program invoking the AMM `signer` preserves top-level account attribution when `caller` is an intermediary. Linking that address to a natural person or legal entity requires an external identity registry. Mint records additionally disclose the private position owner and immutable withdrawal address. Swap records disclose the public confidential address. Each record includes the identifiers and request data needed for correlation. ## Coverage limits Compliance records exist for mint, rebalance, and swaps. They do not cover: * Position increases * Position decreases * Collections * Burns * Claims * Freeze or unfreeze * Pool creation * Protocol-fee collection Each mint token ID permits later public position tracking. A public rebalance transaction links the old token ID to the replacement token ID. The encrypted mint records contain the immutable address used by each later collect and nonzero rebalance refund. ## Investigator governance The investigator address is hardcoded. The source defines a separate address for the initial deployer. Operators must define: 1. Who controls the account view key? 2. Is the account private key also retained? 3. Which personnel and systems can request decryption? 4. Which can perform decryption? 5. Are record-specific view keys used for limited disclosure? 6. How are accesses logged and reviewed? 7. How is key recovery tested? 8. What happens after compromise or loss? 9. How will future records be redirected if the key must change? 10. How are historical records handled after a rotation? The contract has no investigator-key rotation transition. Admin transfer does not change the investigator. ## Administrative intervention ### Pause controls The admin can: * Disable one pool * Pause one token across pools * Pause all fee tiers of a token pair * Pause the venue globally These controls block pool creation where applicable, new mints, liquidity increases, and trading. They do not pause decreases, collections, burns, or output claims. Pause does not gate these exit paths. Record ownership, balances, token transfers, and other operation-specific assertions still apply. ### Position freeze The admin can freeze a public position token ID. Freeze removes its active liquidity, settles principal and fees into owed balances, and blocks increase, decrease, collect, and burn until unfreeze. Freeze does not transfer value to the admin. It immobilizes the position and removes its market depth. Unfreeze removes the freeze entry and unlocks collection. It does not restore liquidity. The mapping's block-height value is present only while the freeze is active. Historical freeze evidence requires transaction indexing because unfreeze removes the current mapping entry. ### Missing intervention controls The core program does not provide: * User or wallet allowlist * User or wallet freezelist * Address-specific swap block * Pending-output freeze * Compliance pre-approval hook * Risk-score input * Automatic sanctions screening * Administrative seizure An existing swap output remains claimable during pause states if the claimant satisfies the normal proof and amount checks. ## Token eligibility The token allowlist controls which token programs can be used to create pools. It does not screen participants and does not prove that a token preserves issuer controls. The included wrapper targets a test stablecoin, and its internal ARC-20 transfers do not consult the underlying stablecoin freezelist on every transfer. Verify how issuer controls behave after wrapping and during AMM transfers before enabling a production market. Asset onboarding must cover: * Exact token program and edition * Issuer freezelist behavior * Private transfer requirements * Wrapper reserve and redemption controls * Upgrade authority * Decimal registration * Record schema * Failure behavior during pause or freeze * Whether AMM custody changes the applicable control path ## Control matrix | Control objective | Protocol support | Operating requirement | | - | - | - | | Keep wallet attribution confidential from public AMM state | Confidential swap fields and encrypted records | Threat model, wallet implementation, correlation analysis | | Attribute swaps to top-level account | `signer` in encrypted swap records | Investigator parser, identity registry, decryption audit | | Attribute routed integration | `caller` retained with `signer` | Registered router and program-address inventory | | Attribute initial position | Mint record includes signer, caller, recipient, and token ID | Mint parser and identity binding | | Attribute replacement position | Rebalance emits a mint record for the new token ID | Rebalance parser and old-to-new token ID correlation | | Halt trading broadly | Pool, token, pair, and global controls | Authorized runbook, alerting, test evidence | | Immobilize one position | Position freeze | Case approval, transaction archive, unfreeze procedure | | Preserve exit during market pause | No pause checks on exit and claim paths | Monitoring and tests, plus freeze-status review | | Rotate disclosure recipient | Not supported by admin transition | Upgrade or redeployment plan | | Maintain durable case history | Not provided by mutable mappings | Independent chain and state-diff archive | | Enforce participant eligibility | Not implemented in core | External access or policy layer | ## Claims and limitations Acceptable technical claims include: * Confidential participant identity with public market state * Encrypted compliance records for selective disclosure * Publicly verifiable concentrated-liquidity accounting * Compliance-supporting pause, token eligibility, and position-freeze controls * Signer attribution retained across routed program calls Claims requiring separate legal and operational evidence include: * The venue is compliant * The venue satisfies a named regulation * All participants are screened * All position payouts are attributable * The system is anonymous or untraceable * USDCx issuer restrictions are preserved end to end * A freeze creates a permanent in-contract audit record ## Required operating records Maintain: * Deployed program registry * Admin and investigator custody policies * Compliance-record parser tests, including routed `caller != signer` * A typed `MintComplianceRecord` implementation * Disclosure runbook and sample redacted evidence package * Access-log review procedure * Key-loss and compromise exercise results * Pause and freeze exercise results * Historical state index design * Token and wrapper due-diligence reports * Identity-binding procedure * Data-retention schedule * Incident and regulator-notification process ## Related pages * [Confidentiality model](./model) * [Compliance records](./compliance-records) * [Protocol boundaries](../start/protocol-boundaries) * [Trust and key boundaries](../security/trust-and-key-boundaries) # Architecture Source: https://shield.fi/docs/developers/architecture How the Shield Swap program, token programs, records, public mappings, API, and clients interact. Shield Swap has seven integration layers. A production application normally touches at least four of them. ## Layers | Layer | Role | Integration concern | | - | - | - | | Shield Swap program | Executes AMM state transitions and controls | Entrypoints, finalizers, mappings, structs, and exact math | | Swap, LP, and rebalance routers | Adapt wrapped records and funded rebalances to core AMM functions | Asset kind, wrapper direction, imports, and router address binding | | Dynamic token programs | Move token value into and out of the AMM | Concrete transfer semantics, decimals, record shape, and upgrade authority | | Freezelist and multisig programs | Supply non-inclusion proofs and upgrade authorization | Root freshness, proof construction, signer authority, and editions | | Encrypted records | Carry token value, position authority, and investigator attribution | Discovery, decryption, spent state, replacement, backup, and custody | | Public mappings | Hold current market, position, pending-output, and control state | Direct reads, parsing, indexing, and reconciliation | | API and client services | Expose indexed data and build user workflows | Authentication, routing, persistence, proving, submission, and recovery | ## Program execution `shield_swap.aleo` contains the AMM state machine. User functions validate inputs and construct records or a `Final`. Finalizers execute token futures and update public mappings. The program imports multisig, freezelist, and a pinned `IARC20` interface. A field identifier selects the concrete token program at runtime. The core accepts protected calls from only the hardcoded swap, LP, and rebalance router addresses. Successful interface dispatch does not prove that a token is safe or operationally approved. Asset support depends on the deployed token program's code, state, transfer behavior, and authority model. ## Public state and record ownership Mappings contain pools, slots, ticks, positions, pending swap outputs, configuration, and controls. They support quoting, indexing, and independent accounting. They also expose amounts, routes, ranges, liquidity, fees, timing, and control actions. Records carry a different kind of state: * token records carry spendable value * `PositionNFT` carries position authority * investigator records carry encrypted mint, rebalance, or swap attribution A public position row does not prove wallet ownership. A `PositionNFT` alone does not contain the position's latest public economics. Clients must reconcile both. ## Swap data flow 1. The client reads current pool, tick, token, and control state. 2. The client preflights a revisioned protocol snapshot and the wallet selects an input token record. 3. The client selects direct `swap`, `swap_from_wrapped`, or `swap_mh_from_wrapped` from the asset registry. 4. The program moves input value into public custody and updates AMM mappings. 5. Finalization writes `swap_outputs[swap_id]`. 6. The client reads that exact output and constructs the current core or router claim with signer proofs. 7. The program returns output and refund token records. 8. The wallet stores the records and reconciles the final public state. A pending `swap_outputs` entry is an unsettled user obligation until claim removes it. ## Position data flow Mint creates a public position and a `PositionNFT` that binds owner and immutable withdrawal address. Mint, rebalance, collect, and claim require current non-inclusion proofs. Increase, decrease, and collect return a replacement record for the same token ID. Burn removes the public row and consumes the final ownership record. Rebalance removes the complete old position and creates a new token ID and record. It keeps the pool and withdrawal address and makes the signer the owner. Custody systems must update record inventory only after transaction acceptance is known. ## Production client boundary The TypeScript harness demonstrates mapping reads, parsing, record handling, submission, and persistence. It is test-oriented. A production client still needs: * a quote engine that matches contract rounding and iteration caps * a record store with reserved, spent, and unknown states * a pending-claim queue * deployment-versioned parsers * a chain indexer or API client * bounded retry and finality handling * durable reconciliation and monitoring Continue with [Swap](../trading/swap#developers) to submit and claim a trade, or [Protocol boundaries](../start/protocol-boundaries) for ownership across systems. # Integrate asset bridging Source: https://shield.fi/docs/developers/bridging Bridge supported assets, receive USDCx confidentially, and connect browser or embedded wallets. This guide shows how to add bridging to your application, choose a supported pair, and connect the wallets that authorize each transfer. ## Choose how funds arrive Your application can send funds to a public balance or, for USDCx, directly to a balance held in token records. The [Fund guide](../trading/fund-and-bridge#how-funding-works) explains which balances you can use for trading. You authorize the transfer with the source wallet. Some routes also need the receiving wallet to finish delivery. A confirmed source transaction means the transfer has started; check destination delivery before treating the funds as available. ## Supported pairs The following **mainnet** pairs support transfers in both directions. Each row connects the external asset to its Aleo counterpart. The interface column distinguishes executable SDK routes from direct Hyperlane routes exposed by the wallet services API. | External asset | External network | Aleo asset | Bridge | Interface | | - | - | - | - | - | | USDC | Ethereum or Arc | USDCx | Circle xReserve | TypeScript, Python. | | ETH | Ethereum | ETH | Hyperlane | TypeScript, Python, REST. | | WBTC | Ethereum | WBTC | Hyperlane | TypeScript, Python, REST. | | USDT | Ethereum | USDT | Hyperlane | TypeScript, Python, REST. | | USDT | BNB Smart Chain | USDT | Hyperlane | REST. | | SOL | Solana | SOL | Hyperlane | TypeScript, Python, REST. | | BAT | Ethereum or Solana | BAT | Hyperlane | TypeScript, Python. | | USDG | Ethereum or Solana | USDG | Hyperlane | TypeScript, Python. | | ZEC | Solana | ZEC | Hyperlane | TypeScript, Python. | | ALEO | Ethereum, Base, or Solana | ALEO | Hyperlane | REST. | USDC from Base or Arbitrum can also reach USDCx through a composed SDK route: CCTP moves USDC to Arc, then xReserve bridges it to Aleo. The reverse route returns USDC through Arc. The [packaged examples](#run-packaged-examples) cover these transfers. ZEC in the table is a Solana token, not a transfer from the Zcash network. Check [route discovery](../trading/fund-and-bridge#2-discover-assets-and-routes) before requesting a quote. SDK routes marked `metadata-required` cannot execute. The REST catalog can also return providers that exchange different assets; those quotes are separate from the direct bridge pairs above. A listed route still needs a valid quote for your amount and addresses. ## Bridge SOL between Solana and Aleo **Prerequisites:** Your mainnet Aleo account from [Setup](../trading/configure), a Solana wallet, and a Solana mainnet RPC endpoint. Keep enough SOL for the transfer, network fees, delivery payment, and any account rent shown in the quote. The service examples use Node.js 22 or later or Python 3.11 or later. ### 1. Connect the accounts For a service or agent, supply credentials through your application's configuration or secret manager. These examples read them from environment variables. A [browser application](#integrate-bridging-with-a-frontend) or [embedded wallet](#use-embedded-wallets-for-bridging) supplies a wallet connection instead of a source private key. | Input | Value supplied by your application | | - | - | | `ALEO_PRIVATE_KEY` | Your mainnet trading account's private key. | | `SOLANA_RPC_URL` | Your Solana mainnet RPC endpoint. | | `SOLANA_KEYPAIR_JSON` | TypeScript: a JSON array containing the Solana wallet's 64-byte keypair. | | `SOLANA_PRIVATE_KEY` | Python: the Solana wallet's base58-encoded private key. | | `SOURCE_ADDRESS`, `DESTINATION_ADDRESS` | REST: the sending Solana address and receiving Aleo address. | Install the bridge packages and Solana signing dependency: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} npm install --legacy-peer-deps @provablehq/aleo-bridge-sdk@0.12.1 @provablehq/veil-aleo-sdk@0.12.1 @provablehq/veil-aleo-devnode@0.12.1 @solana/kit@8 ``` ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import { loadNetwork } from "@provablehq/veil-aleo-sdk" import { createAleoClient, createBridgeClient, createSolanaClient, solanaHttp, solanaKeyPair, } from "@provablehq/aleo-bridge-sdk" const network = await loadNetwork("mainnet") const nativeAleo = network.createAleoClient({ privateKey: process.env.ALEO_PRIVATE_KEY! }) const solana = createSolanaClient({ transport: solanaHttp(process.env.SOLANA_RPC_URL!), account: solanaKeyPair(Uint8Array.from(JSON.parse(process.env.SOLANA_KEYPAIR_JSON!))), }) const solanaBridge = createBridgeClient({ environment: "mainnet", clients: { solana, aleo: createAleoClient({ publicClient: nativeAleo.publicClient, account: nativeAleo.walletClient }), }, }) ``` `solanaBridge` can request quotes and authorize transfers with either account. Creating it does not move funds. ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} python -m pip install "aleo-bridge-sdk==0.6.1" ``` Register the Aleo record scanner so the client can find token records when you later unshield funds for a withdrawal. Registration shares your account's view key with the hosted scanner, which can read records belonging to that account. ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import os from aleo import Aleo, HTTPProvider from aleo_bridge import Bridge, FileCheckpointStore, Solana aleo = Aleo(HTTPProvider("https://edge.provable.com/api", network="mainnet")) aleo.default_account = aleo.account.from_private_key(os.environ["ALEO_PRIVATE_KEY"]) registration = aleo.records.register(aleo.default_account) if not registration.get("ok"): raise RuntimeError(registration.get("error", "Record scanner registration failed")) solana = Solana(os.environ["SOLANA_RPC_URL"], private_key=os.environ["SOLANA_PRIVATE_KEY"]) solana_bridge = Bridge( aleo, solana=solana, checkpoints=FileCheckpointStore(".bridge/checkpoints"), ) ``` `solana_bridge` uses your accounts to authorize transfers. Its checkpoint store retains transfer progress so you can recover after a restart. The API prepares the transfer; your wallet signs it. Use `curl` and `jq` with the source and destination addresses supplied by your application: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} export WALLET_API=https://wallet.api.provable.com : "${SOURCE_ADDRESS:?Set the Solana sender address}" : "${DESTINATION_ADDRESS:?Set the Aleo recipient address}" ``` Keep the source private key in your wallet. The API needs the addresses, not the private key. ### 2. Review a SOL quote **Prerequisites:** The Solana connections from [Connect the accounts](#1-connect-the-accounts). These examples request **0.01 SOL in display units**, not lamports. The recipient is your configured Aleo account. The quote identifies the route, expected delivery, and fees before you authorize a transfer. The SDK examples select Hyperlane explicitly; review the provider in the REST response before choosing a quote. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const solanaQuote = await solanaBridge.quote({ source: { chain: "solana", asset: "sol" }, destination: { chain: "aleo", asset: "sol" }, bridgeProtocol: "hyperlane", amount: "0.01", sender: await solana.walletClient!.getAddress(), recipient: String(nativeAleo.account.address), }) ``` Review `solanaQuote.plan` before submitting it. For a withdrawal, reverse the source and destination, use your Aleo address as `sender`, and set `recipient` to the receiving Solana address. ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} solana_quote = solana_bridge.quote( source_chain="solana", source_asset="sol", destination_chain="aleo", destination_asset="sol", bridge_protocol="hyperlane", amount="0.01", sender=solana.address, recipient=solana_bridge.aleo_address(), ) ``` Review `solana_quote.amount_out` and `solana_quote.fees`. For a withdrawal, reverse the chains, use `solana_bridge.aleo_address()` as `sender`, and supply the receiving Solana address as `recipient`. ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} QUOTES=$(curl --fail-with-body --silent --show-error --get "$WALLET_API/bridge/quotes" \ -H 'X-Client-Type: programmatic' \ --data-urlencode 'srcChain=SOLANA' --data-urlencode 'srcAsset=SOL_SOLANA' \ --data-urlencode 'destChain=ALEO' --data-urlencode 'destAsset=WSOL_ALEO' \ --data-urlencode 'amountIn=0.01' --data-urlencode 'slippageBps=50' \ --data-urlencode "fromAddress=$SOURCE_ADDRESS" \ --data-urlencode "refundAddress=$SOURCE_ADDRESS" \ --data-urlencode "recipientAddress=$DESTINATION_ADDRESS") printf '%s\n' "$QUOTES" | jq '.data | to_entries[] | {index: .key, quote: .value}' ``` Each result contains the provider, output amount, and fees. For a withdrawal, reverse the chain and asset identifiers and use the Aleo wallet as the source. `WSOL_ALEO` is the API code for the Aleo SOL asset. The SDK field names and REST parameters follow the [quote parameter tables](../trading/fund-and-bridge#3-request-and-review-a-quote). Before withdrawing, [unshield the withdrawal amount](../trading/fund-and-bridge#shield-public-balances) and wait for acceptance. Hyperlane withdrawals spend a public balance. ### 3. Submit and check delivery **Prerequisites:** Your reviewed quote from [Review a SOL quote](#2-review-a-sol-quote). For TypeScript, supply `saveCheckpoint`, your application callback that durably saves each `BridgeCheckpoint` before returning. It may return `void` or `Promise`. **Recover the original transfer after an uncertain submission.** A timeout does not prove that funds stayed in your source wallet; sending again can create a second transfer. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const solanaExecution = await solanaBridge.execute({ plan: solanaQuote.plan, onCheckpoint: saveCheckpoint, }) const solanaProgress = await solanaBridge.wait({ progress: { next: "wait", plan: solanaQuote.plan, receipt: solanaExecution.receipt }, }) ``` `solanaProgress.next === "done"` confirms delivery. A pending result needs monitoring or [recovery](../trading/fund-and-bridge#6-recover-an-interrupted-transfer), not a new transfer. ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} solana_progress = solana_bridge.execute(solana_quote.plan, mode="signer") if solana_progress.next == "wait": solana_progress = solana_bridge.wait(solana_progress) ``` `solana_progress.next == "done"` confirms delivery. `mode="signer"` also supports the return transfer by spending your public Aleo balance. The checkpoint store retains the original transfer if monitoring stops. Continue with `QUOTES` in [Submit the reviewed transfer](../trading/fund-and-bridge#4-submit-the-reviewed-transfer), then [Check delivery](../trading/fund-and-bridge#5-check-delivery). The same order flow supports Solana. Signed Solana transactions use base64; if the wallet broadcasts, report its original transaction signature instead of submitting the bytes again. Fund the Solana source account before fetching unsigned transactions. The API simulates the transfer and can reject an unfunded, newly generated address with `AccountNotFound`, even when quote and order creation succeeded. After inbound delivery, [shield the received SOL](../trading/fund-and-bridge#shield-public-balances), using `sol` instead of `eth` in those examples. After outbound delivery, the receiving Solana wallet holds native SOL. ## Receive USDCx confidentially **Prerequisites:** The mainnet Ethereum and Aleo clients from [Configure access](../trading/fund-and-bridge#1-configure-access), USDC plus ETH for source fees, and the recipient's Aleo wallet with destination fee payment configured. The examples below use that section's TypeScript `bridge` and `account`, or Python `bridge` and `ethereum`. Circle xReserve offers two ways to receive USDCx directly in token records. They differ in whether the Ethereum deposit exposes the Aleo recipient and who completes delivery. | Delivery mode | What stays public | How you receive USDCx | | - | - | - | | `record` | The Ethereum sender, amount, and Aleo recipient. | A provider delivers the token record without a separate recipient claim. | | `private` | The Ethereum sender, amount, and timing. The deposit contains a commitment instead of the Aleo recipient address. | The recipient uses the original secret nonce to authorize a destination mint after Circle attests the deposit. | Both modes avoid a separate shielding transaction. Neither conceals the source transaction. Destination fee payment can also be public; the TypeScript completion example below uses a public fee. See [public data and confidentiality boundaries](../confidentiality/public-data) for trading disclosures. ### 1. Keep the secret needed for completion The `private` flow commits to the recipient and a secret nonce, a random value needed again to receive the funds. Generate a nonzero Aleo scalar once and save it in your application's secret store **before** depositing. Load that same value for quoting, submission, and completion. Bridge checkpoints omit it. These examples load the saved scalar string, such as a decimal integer followed by `scalar`, from `BRIDGE_MINT_SECRET_NONCE`. Use a cryptographically random value, not a fixed sample or `0scalar`. ### 2. Review a private-mint quote **Prerequisites:** The clients named in [Receive USDCx confidentially](#receive-usdcx-confidentially) and the saved nonce from [Keep the secret needed for completion](#1-keep-the-secret-needed-for-completion). Each example requests **2 USDC in display units**; the quote supplies the current constraints and fees. Your Ethereum account must already hold the requested USDC amount. Both SDKs check that balance while quoting, before any deposit is submitted. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const nonce = process.env.BRIDGE_MINT_SECRET_NONCE if (!nonce || nonce === "0scalar") throw new Error("Load the saved nonzero mint nonce") const privateQuote = await bridge.quote({ source: { chain: "ethereum", asset: "usdc" }, destination: { chain: "aleo", asset: "usdcx" }, bridgeProtocol: "xreserve", amount: "2", recipient: String(account.address), mintMode: "private", privateMintSecretNonce: nonce, }) ``` The plan selects `private` delivery for the configured recipient. Review the amount, allowance, approval requirement, and fees before execution. ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import os from aleo import mainnet nonce = os.environ["BRIDGE_MINT_SECRET_NONCE"] if str(mainnet.Scalar.from_string(nonce)) == "0scalar": raise ValueError("Load the saved nonzero mint nonce") private_quote = bridge.quote( source_chain="ethereum", source_asset="usdc", destination_chain="aleo", destination_asset="usdcx", bridge_protocol="xreserve", amount="2", sender=ethereum.require_address(), recipient=bridge.aleo_address(), mint_mode="private", secret_nonce=nonce, ) ``` Review `private_quote.amount_out` and `private_quote.fees`. The configured Aleo account is the recipient that must later complete this mint. `mintMode` in TypeScript and `mint_mode` in Python are optional strings with a `public` default; select `private` explicitly for this flow. The corresponding nonce fields are scalar strings required by this tutorial. To use provider-delivered records instead, select `record`, omit the nonce, and follow the [packaged record-delivery example](#run-packaged-examples). A Circle attestation alone does not confirm that the provider has delivered the record. The wallet services REST examples do not expose these xReserve mint-mode controls. Use an SDK for this flow; a REST quote for USDC to USDCx may select a different provider. ### 3. Deposit and complete the mint **Prerequisites:** The quote and saved nonce from [Review a private-mint quote](#2-review-a-private-mint-quote). TypeScript also needs the durable `saveCheckpoint` callback described in [Submit and check delivery](#3-submit-and-check-delivery). Python uses the checkpoint store configured in the Fund guide. **Keep the original nonce and checkpoint until delivery completes.** Losing the nonce prevents this flow from completing the mint. If submission is interrupted, recover the saved transfer instead of making another deposit. Run the source submission after reviewing the quote: ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const privateExecution = await bridge.execute({ plan: privateQuote.plan, privateMintSecretNonce: nonce, onCheckpoint: saveCheckpoint, }) let privateProgress = await bridge.wait({ progress: { next: "wait", plan: privateQuote.plan, receipt: privateExecution.receipt }, }) ``` `privateProgress.next === "complete"` means Circle's attestation is ready and the recipient can authorize the destination mint. It does not mean USDCx has arrived. ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} private_progress = bridge.execute(private_quote.plan, secret_nonce=nonce) if private_progress.next == "wait": private_progress = bridge.wait(private_progress) ``` `private_progress.next == "complete"` means the recipient can authorize the destination mint with the saved nonce. If the result requests `resume`, recover and resume the remaining source action with the same nonce; an approval may have confirmed before the deposit was sent. A `wait` result needs further monitoring. The [recovery guide](../trading/fund-and-bridge#6-recover-an-interrupted-transfer) explains these states. When the result is `complete`, approve the destination mint with the recipient's Aleo wallet: ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} if (privateProgress.next === "complete") { const mint = await bridge.complete({ progress: privateProgress, privateMintSecretNonce: nonce, privateFee: false, onCheckpoint: saveCheckpoint, }) privateProgress = await bridge.wait({ progress: { next: "wait", plan: privateQuote.plan, receipt: mint.receipt }, }) } ``` ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} if private_progress.next == "complete": private_progress = bridge.complete(private_progress, secret_nonce=nonce) if private_progress.next == "wait": private_progress = bridge.wait(private_progress) ``` Only `next: "done"` confirms destination delivery. The resulting USDCx record can be used for trading without another shielding step. ## Integrate bridging with a frontend **Prerequisites:** A TypeScript browser application, the connected Aleo `client` from [Browser client setup](../trading/configure#browser-client-setup), an Ethereum wallet provider, and a connected Solana Wallet Standard account when offering Solana routes. Each wallet and RPC endpoint must target mainnet. Your frontend requests quotes through public RPC connections and asks a connected wallet to authorize fund movements. Private keys stay in the wallets. Your application still needs to retain bridge progress so a page reload does not lose the transfer reference. ### 1. Connect the bridge to browser wallets Install the bridge and Solana packages alongside the wallet packages from Setup: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} npm install --legacy-peer-deps @provablehq/aleo-bridge-sdk@0.12.1 @provablehq/veil-core@0.12.1 @solana/kit@8 ``` The function below accepts connections from your wallet selection UI. The Solana wallet must expose `solana:signAndSendTransaction`; an Ethereum connector supplies an EIP-1193 provider after account access is approved. | Argument | Type | Required or default | | - | - | - | | `ethereumProvider` | EIP-1193 provider from your Ethereum wallet connector. | Required; no default. | | `solanaAccount` | Object containing the connected Wallet Standard `wallet`, selected `account`, and `chain: "solana:mainnet"`. | Required; no default. | | `aleoWalletClient` | The connected Aleo `client` from Setup. | Required; no default. | | `ethereumRpcUrl`, `solanaRpcUrl` | Public mainnet RPC URL strings. | Required; no default. | ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import { createPublicClient, http } from "@provablehq/veil-core" import { createAleoClient, createBridgeClient, createEvmClient, createSolanaClient, evmHttp, evmProvider, solanaHttp, solanaWallet, } from "@provablehq/aleo-bridge-sdk" function createBrowserBridge(options: { ethereumProvider: Parameters[0] solanaAccount: Parameters[0] aleoWalletClient: NonNullable[0]["account"]> ethereumRpcUrl: string solanaRpcUrl: string }) { const publicClient = createPublicClient({ transport: http("https://edge.provable.com/api/v2", { network: "mainnet" }), }) return createBridgeClient({ environment: "mainnet", clients: { ethereum: createEvmClient({ transport: evmHttp(options.ethereumRpcUrl), account: evmProvider(options.ethereumProvider), }), solana: createSolanaClient({ transport: solanaHttp(options.solanaRpcUrl), account: solanaWallet(options.solanaAccount), }), aleo: createAleoClient({ publicClient, account: options.aleoWalletClient }), }, }) } ``` The returned bridge supports the quote and execution steps on this page. Remove an unused source-chain client if your application offers only Ethereum or Solana. Recreate the bridge and request a fresh quote when a selected account or network changes. ### 2. Separate quote review from authorization Use the returned bridge to request a quote when the amount or destination changes. Show the recipient, source amount, expected output, and fees before asking for approval. For USDCx, also show the selected delivery mode and whether a destination claim is required. Call this function from your confirmation handler with the reviewed `BridgePlan`. Supply `saveCheckpoint` from your application's durable storage; it receives a serializable `BridgeCheckpoint` and resolves only after saving it. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import type { BridgeCheckpoint, BridgePlan } from "@provablehq/aleo-bridge-sdk" async function submitReviewedPlan( bridge: ReturnType, plan: BridgePlan, saveCheckpoint: (checkpoint: BridgeCheckpoint) => Promise, privateMintSecretNonce?: string, ) { if (plan.mintMode === "private" && (!privateMintSecretNonce || privateMintSecretNonce === "0scalar")) { throw new Error("Load the saved nonzero mint nonce before submitting") } const execution = await bridge.execute({ plan, onCheckpoint: saveCheckpoint, privateMintSecretNonce }) return bridge.wait({ progress: { next: "wait", plan, receipt: execution.receipt } }) } ``` This handler depends on `createBrowserBridge` from [Connect the bridge to browser wallets](#1-connect-the-bridge-to-browser-wallets). The optional nonce is required for a `private` USDCx mint and must come from the saved secret, not from a new generation on each click. Disable repeat submission while the request is in progress. The returned `next` value tells your UI what the transfer needs: | `next` | UI action | | - | - | | `wait` | Show the transfer as pending and keep monitoring. | | `resume` | Request authorization for the remaining source action. | | `complete` | Request authorization for destination completion. | | `done` | Show confirmed delivery. | | `failed` | Show the recorded error and check the saved transfer before offering another submission. | After a reload, load the checkpoint and call `bridge.recover({ checkpoint })` before enabling another transfer. Wallet transaction history does not replace your application's bridge checkpoint. For a frontend built around REST, keep the same review and confirmation boundaries while following [the order flow](../trading/fund-and-bridge#4-submit-the-reviewed-transfer). The connected wallet signs the returned transactions. A wallet that broadcasts must report the original transaction to the API rather than submit it again. ## Use Embedded Wallets for bridging **Prerequisites:** A Node.js 22 or later service or Python 3.11 or later service, the bridge packages and Aleo client from [Connect the accounts](#1-connect-the-accounts), and mainnet RPC endpoints for your source networks. Reuse the Aleo initialization from that setup: TypeScript's `nativeAleo` or Python's `aleo`. Your chosen embedded wallet supplies the source signer below. Dynamic and Privy embedded wallets can authorize bridge transfers for your service or agent without an interactive browser prompt. The source wallet signs the Ethereum or Solana transfer; your Aleo account handles shielding, withdrawals, and USDCx destination claims. Choose one provider below. Each example connects Ethereum and Solana; keep only the connections your application uses. Supply credentials through your service's configuration or secret manager. These examples use environment variables. For a frontend, pass the connected wallet provider from Dynamic or Privy to the [browser bridge](#integrate-bridging-with-a-frontend). Server API tokens, app secrets, and signing passwords stay in your service. ### Dynamic Dynamic's [server wallet setup](https://www.dynamic.xyz/docs/node/wallets/server-wallets/overview) covers creating wallets and API credentials. Reconnect the existing wallets when your service restarts so their addresses stay the same. These examples use encrypted key shares backed up to Dynamic. The wallet password unlocks those shares for signing. For TypeScript, retain the complete metadata objects returned by wallet creation as `evmWalletMetadata` and `solanaWalletMetadata`; an address alone does not include the required backup information. Dynamic's [storage guide](https://www.dynamic.xyz/docs/node/wallets/server-wallets/storage-best-practices) describes what to retain. The Python signers resolve the existing wallets by address. Install the Dynamic clients alongside the bridge packages. The server clients need native addon support; run them in your Node.js service. Use `module: "ESNext"` and `moduleResolution: "Bundler"` in TypeScript. ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} npm install --legacy-peer-deps @dynamic-labs-wallet/node-evm@1.1.24 @dynamic-labs-wallet/node-svm@1.1.24 ``` ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import { DynamicEvmWalletClient } from "@dynamic-labs-wallet/node-evm" import { DynamicSvmWalletClient } from "@dynamic-labs-wallet/node-svm" import { createAleoClient, createBridgeClient, evmHttp, solanaHttp } from "@provablehq/aleo-bridge-sdk" import { createDynamicEvmClient, createDynamicSolanaClient } from "@provablehq/aleo-bridge-sdk/dynamic" const dynamicEvm = new DynamicEvmWalletClient({ environmentId: process.env.DYNAMIC_ENVIRONMENT_ID! }) const dynamicSolana = new DynamicSvmWalletClient({ environmentId: process.env.DYNAMIC_ENVIRONMENT_ID! }) await dynamicEvm.authenticateApiToken(process.env.DYNAMIC_API_TOKEN!) await dynamicSolana.authenticateApiToken(process.env.DYNAMIC_API_TOKEN!) const ethereum = await createDynamicEvmClient({ client: dynamicEvm, walletMetadata: evmWalletMetadata, password: process.env.DYNAMIC_EVM_WALLET_PASSWORD!, transport: evmHttp(process.env.ETHEREUM_RPC_URL!), }) const solana = await createDynamicSolanaClient({ client: dynamicSolana, walletMetadata: solanaWalletMetadata, password: process.env.DYNAMIC_SOLANA_WALLET_PASSWORD!, chainId: "101", transport: solanaHttp(process.env.SOLANA_RPC_URL!), }) const bridge = createBridgeClient({ environment: "mainnet", clients: { ethereum, solana, aleo: createAleoClient({ publicClient: nativeAleo.publicClient, account: nativeAleo.walletClient }), }, }) ``` The `bridge` now uses the selected Dynamic wallets for source authorization. `"101"` is Dynamic's Solana mainnet identifier and must match your Solana RPC endpoint. The bridge package includes Dynamic's Python SDK. Resolve each signer before quoting to check its credentials and wallet identity: ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import os from aleo_bridge import Bridge, Ethereum, FileCheckpointStore, Solana from aleo_bridge.dynamic import DynamicEvmSigner, DynamicSolanaSigner from dynamic_wallet_sdk import DynamicEvmWalletClient, DynamicSvmWalletClient evm_signer = DynamicEvmSigner( DynamicEvmWalletClient(os.environ["DYNAMIC_ENVIRONMENT_ID"]), address=os.environ["DYNAMIC_EVM_ADDRESS"], api_token=os.environ["DYNAMIC_API_TOKEN"], password=os.environ["DYNAMIC_EVM_WALLET_PASSWORD"], ) solana_signer = DynamicSolanaSigner( DynamicSvmWalletClient(os.environ["DYNAMIC_ENVIRONMENT_ID"]), address=os.environ["DYNAMIC_SOLANA_ADDRESS"], api_token=os.environ["DYNAMIC_API_TOKEN"], password=os.environ["DYNAMIC_SOLANA_WALLET_PASSWORD"], ) evm_signer.resolve() solana_signer.resolve() ethereum = Ethereum(os.environ["ETHEREUM_RPC_URL"], signer=evm_signer) solana = Solana(os.environ["SOLANA_RPC_URL"], signer=solana_signer) bridge = Bridge( aleo, ethereum=ethereum, solana=solana, checkpoints=FileCheckpointStore(".bridge/checkpoints"), ) ``` Both connections now authorize transfers through Dynamic. Call `evm_signer.close()` and `solana_signer.close()` when your service shuts down. ### Privy Privy's [wallet creation guide](https://docs.privy.io/wallets/wallets/create/create-a-wallet) covers provisioning a wallet and choosing who controls it. Retain each wallet's ID and address; they must identify the same wallet when you reconnect it. Your app credentials identify the service. Permission to sign depends on the wallet's owner and signer configuration. If that configuration requires an authorization key, supply it with the request. This key authorizes requests to Privy and is separate from a chain private key. Privy's [request-signing guide](https://docs.privy.io/controls/authorization-keys/using-owners/sign/overview) explains those permissions. Install Privy's server client alongside the bridge packages: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} npm install --legacy-peer-deps @privy-io/node@0.35.0 viem@2.54.6 ``` ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import { PrivyClient } from "@privy-io/node" import { getAddress } from "viem" import { createAleoClient, createBridgeClient, evmHttp, solanaHttp } from "@provablehq/aleo-bridge-sdk" import { createPrivyEvmClient, createPrivySolanaClient } from "@provablehq/aleo-bridge-sdk/privy" const privy = new PrivyClient({ appId: process.env.PRIVY_APP_ID!, appSecret: process.env.PRIVY_APP_SECRET!, }) const authorizationKey = process.env.PRIVY_AUTHORIZATION_PRIVATE_KEY const authorizationContext = authorizationKey ? { authorization_private_keys: [authorizationKey] } : undefined const ethereum = await createPrivyEvmClient({ client: privy, walletId: process.env.PRIVY_EVM_WALLET_ID!, address: getAddress(process.env.PRIVY_EVM_ADDRESS!), authorizationContext, transport: evmHttp(process.env.ETHEREUM_RPC_URL!), }) const solana = await createPrivySolanaClient({ client: privy, walletId: process.env.PRIVY_SOLANA_WALLET_ID!, address: process.env.PRIVY_SOLANA_ADDRESS!, authorizationContext, transport: solanaHttp(process.env.SOLANA_RPC_URL!), }) const bridge = createBridgeClient({ environment: "mainnet", clients: { ethereum, solana, aleo: createAleoClient({ publicClient: nativeAleo.publicClient, account: nativeAleo.walletClient }), }, }) ``` The `bridge` uses the saved wallet IDs to request source signatures from Privy. The bridge package includes Privy's Python SDK. Resolve the signers to verify that the wallet IDs, chains, and addresses match: ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import os from privy import PrivyClient from aleo_bridge import Bridge, Ethereum, FileCheckpointStore, Solana from aleo_bridge.privy import PrivyEvmSigner, PrivySolanaSigner privy = PrivyClient( app_id=os.environ["PRIVY_APP_ID"], app_secret=os.environ["PRIVY_APP_SECRET"], ) authorization_key = os.environ.get("PRIVY_AUTHORIZATION_PRIVATE_KEY") authorization_keys = [authorization_key] if authorization_key else None evm_signer = PrivyEvmSigner( privy, wallet_id=os.environ["PRIVY_EVM_WALLET_ID"], address=os.environ["PRIVY_EVM_ADDRESS"], authorization_private_keys=authorization_keys, ) solana_signer = PrivySolanaSigner( privy, wallet_id=os.environ["PRIVY_SOLANA_WALLET_ID"], address=os.environ["PRIVY_SOLANA_ADDRESS"], authorization_private_keys=authorization_keys, ) evm_signer.resolve() solana_signer.resolve() ethereum = Ethereum(os.environ["ETHEREUM_RPC_URL"], signer=evm_signer) solana = Solana(os.environ["SOLANA_RPC_URL"], signer=solana_signer) bridge = Bridge( aleo, ethereum=ethereum, solana=solana, checkpoints=FileCheckpointStore(".bridge/checkpoints"), ) ``` Both connections can now request authorized signatures from Privy. The optional authorization key is passed through only when supplied. ### Continue with the bridge flow Use the provider's `bridge` in the [SOL quote and submission steps](#2-review-a-sol-quote), replacing `solanaBridge` in TypeScript or `solana_bridge` in Python. For Ethereum assets, use the [ETH example](../trading/fund-and-bridge#3-request-and-review-a-quote) or the [USDCx flow](#receive-usdcx-confidentially). Use your configured Aleo account as the USDCx recipient so it can authorize destination completion. For REST, use the embedded wallet's Ethereum or Solana address as `SOURCE_ADDRESS` and your Aleo recipient as `DESTINATION_ADDRESS`. The [order flow](../trading/fund-and-bridge#4-submit-the-reviewed-transfer) returns transactions for your service to sign through Dynamic or Privy. Submit signed bytes only if the wallet has not broadcast them; otherwise report the original transaction ID. Provider credentials stay with the signing client. ## Next Steps ### Run packaged examples The [TypeScript bridge examples](https://github.com/ProvableHQ/veil/tree/main/packages/bridge/examples) cover Solana transfers, USDCx delivery modes, and USDC routes through Arc. From that example package, after its documented setup, these commands preview a quote: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} npx tsx sol-to-aleo.ts USDCX_MINT_MODE=private USDCX_SECRET_NONCE="${BRIDGE_MINT_SECRET_NONCE:?Set the saved mint nonce}" npx tsx usdc-to-usdcx.ts ``` The private USDCx example needs your saved nonzero nonce as `USDCX_SECRET_NONCE` and the matching Aleo recipient. Previewing a quote does not authorize a deposit. Follow the example's execution acknowledgement only after review. The [Python bridge examples](https://github.com/ProvableHQ/python-sdk/tree/master/bridge-sdk/examples) include `bridge_sol`, `bridge_usdc_private_balance`, `bridge_usdc_private_recipient`, and `l2_arc_aleo_roundtrip`. Their help lists the required account and route inputs: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} python -m aleo_bridge.examples.bridge_sol --help python -m aleo_bridge.examples.bridge_usdc_private_recipient --help python -m aleo_bridge.examples.dynamic_wallets --help ``` The [TypeScript Dynamic and Privy examples](https://github.com/ProvableHQ/veil/tree/main/packages/bridge/examples/remote-wallets) show how to reconnect existing embedded wallets. Python's `dynamic_wallets` and `privy_wallets` examples include quoting and optional execution. Run without execution flags first to review the transfer. ### Prepare funds for trading After Hyperlane delivery, [shield your public balance](../trading/fund-and-bridge#shield-public-balances). After USDCx record delivery, continue to [Quote](../trading/quote). ### Recover a transfer Use the saved checkpoint or REST order ID to [recover an interrupted transfer](../trading/fund-and-bridge#6-recover-an-interrupted-transfer) without starting another one. # Choose an integration path Source: https://shield.fi/docs/developers/integration-path Choose the TypeScript or Python SDK, agent tools, indexed API data, or direct program calls. Choose the tools you need to add trading or market data to your application. The SDKs handle transaction preparation and the swap lifecycle. The API supplies indexed market data. If you want an agent to operate an account, AgentKit provides trading guidance and an MCP server with tools the agent can call. ## Compare the paths | Path | Use it for | You configure | | - | - | - | | [TypeScript SDK](#typescript-sdk) | Browser apps, services, and trading agents. | An account or wallet connection, API authentication, and recovery storage. | | [Python SDK](#python-sdk) | Python services and trading strategies. | An account, API authentication, and a journal for recovery. | | [Agents](#agents) | Account setup, market queries, and trading through AgentKit's MCP tools. | A local account profile, execution permissions, and an MCP client. | | [REST API](#rest-api) | Market data, routes, positions, and unclaimed outputs. | Authentication for protected reads, caching, and retries. | | [Direct program calls](#direct-program-calls) | An integration that builds and submits its own transactions. | Chain access, amount calculations, record management, and recovery. | ## TypeScript SDK The [TypeScript SDK](https://www.npmjs.com/package/@provablehq/shield-swap-sdk) is Viem-like and supports trading and market making, with the [bridge SDK](./bridging) providing transfers to other chains. Like Viem, it runs in web browsers and locally. Use it to integrate Shield Swap into web wallets, trading terminals, and DEX frontends, or into JS/TS agents and backend services. Continue with [TypeScript SDK setup](../trading/configure#typescript), [swaps](../trading/swap#typescript), and [liquidity](../sdk/liquidity). ## Python SDK Use the [Python SDK](https://github.com/ProvableHQ/python-sdk/tree/master/shield-swap-sdk) when your service, market making or trading strategy runs in Python. Continue with [Python SDK Setup](../trading/configure#python), [swaps](../trading/swap#python), and [liquidity](../sdk/liquidity). ## Agents The [Shield Swap AgentKit skill](https://github.com/ProvableHQ/shield-swap-agent-kit) links your agent to a [guide for account setup and trading](https://github.com/ProvableHQ/shield-swap-agent-kit/blob/master/context/getting-started.md). Install it in your agent project: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} npx skills add ProvableHQ/shield-swap-agent-kit --skill shield-swap ``` ### Connect the MCP server With Node.js 22.13 or later and pnpm 10.33.2, build the [AgentKit MCP server](https://github.com/ProvableHQ/shield-swap-agent-kit/tree/master/mcp) and set up a **mainnet** account: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} git clone https://github.com/ProvableHQ/shield-swap-agent-kit.git cd shield-swap-agent-kit/mcp pnpm install --frozen-lockfile pnpm run build node dist/cli.js setup --guided --network mainnet ``` Follow the [MCP connection guide](https://github.com/ProvableHQ/shield-swap-agent-kit/blob/master/mcp/README.md#connect-an-mcp-client) to connect your agent, then [configure trading permissions](https://github.com/ProvableHQ/shield-swap-agent-kit/blob/master/mcp/README.md#configure-an-existing-wallet). ## REST API Use the API when you need market data for a dashboard, service, or trading strategy. It provides indexed reads; you still need a signer and transaction client to submit trades. Start with: 1. [List pools](../rest/quickstart). 2. [Create an API token](../rest/authentication) for routes and authenticated data. 3. [Handle pagination and retries](../rest/pagination-and-errors). 4. Use the generated API reference for exact parameters and responses. Public reads include pool analytics, Explore data, protocol state, and compliance data. Routes, positions, and unclaimed outputs require authentication and acceptance of the current Terms of Use. Indexed data can lag the chain. Check the state that affects your trade before signing a transaction. ## Direct program calls Use direct program calls when you need to construct transactions below the SDK surface. You take responsibility for record selection, contract-compatible calculations, and recovery when a transaction's outcome is uncertain. Record the network, program ID, edition, checksum, dependency revisions, and storage schema. Do not treat two deployments with the same source name as interchangeable. The current Q128.128 fee-growth layout is incompatible with scalar fee-growth deployments and requires a fresh deployment. Mirror nested Leo records and structs, including `u256::U256`, `MerkleProof`, the immutable `PositionNFT.withdrawal`, and `MintComplianceRecord` where investigator records are handled. Parse nested structs structurally. Do not split Leo values on commas inside braces. Represent `u128` and `i128` with arbitrary-precision integers. Load pool identity, slot state, initialized ticks, positions, pending swap outputs, token settings, and control state required by the task. Version parsers by deployment checksum or release, not by a repository branch name. Use native token base units in contract math. Apply display decimals only at the interface boundary. Match fixed-point operations, rounding direction, tick limits, price-limit rules, and iteration caps. Single-hop execution uses five swap iterations. Multi-hop execution uses three iterations per hop. Reaching an initialized tick exactly at the price limit completes the crossing. Discover records for the signer, verify token and amount, prevent double selection, preserve change, and mark a record spent only after acceptance is known. Every position change consumes a `PositionNFT`. Store the replacement returned by increase, decrease, collect, or rebalance. Rebalance creates a new token ID. Replace the old position identity only after the transaction is accepted. Derive the public confidential address from the exact program address, domain constant, signer, and factor. Persist the factor until claim succeeds and never reuse the resulting address. A deterministic factor derived by a test client is a client convention, not a contract rule. After swap acceptance, read `swap_outputs[swap_id]` and use its exact stored fields to construct the claim. Persist pending claims across restarts and network failures. Read global, token, pair, pool, and position controls before construction. Refresh them before retrying because state can change before finalization. A visible success requires both the expected public mapping change and possession of the expected output records. Test routed calls where `caller != signer`. ## Privacy boundary In the intended swap flow, your address stays confidential in ordinary public mappings. Trade amounts, routes, pools, output and refund amounts, timing, and market state remain public. Read [Protocol boundaries](../start/protocol-boundaries) before writing product or privacy claims. ## Next steps * [Setup](../trading/configure#developers) configures a mainnet trading account and client. * [Quickstart](../trading/quickstart) walks through a swap with test tokens. * [AgentKit's onboarding guide](https://github.com/ProvableHQ/shield-swap-agent-kit/blob/master/context/getting-started.md) helps you choose a trading or integration task. # Choosing a liquidity range Source: https://shield.fi/docs/liquidity/choosing-a-range Range construction, inventory behavior, tick boundaries, and operational tradeoffs. A position supplies liquidity over a half-open tick interval: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} tick_lower <= current_tick < tick_upper ``` The lower tick is included. The upper tick is excluded. That distinction affects inventory, active liquidity, and fee entitlement at the boundary. Range selection determines inventory composition, capital efficiency, execution exposure, and rebalancing frequency. A narrower range concentrates more liquidity near the current price, but it leaves the active set sooner and requires more frequent management. A wider range uses capital less intensively but tolerates larger price moves before becoming inactive. ## Decision outcome Before minting, record: * the exact pool and canonical token order * lower and upper ticks aligned to tick spacing * the current price region relative to the range * expected token0 and token1 inventory at entry * a rebalance trigger and the cost assumptions behind it The protocol does not choose a range or rebalance policy for the LP. Those are application and operator decisions. Use [Rebalance a position](./rebalance-position) to replace a position with a different range. ## Start with the correct pool Pool identity includes: * Canonically sorted token0 * Canonically sorted token1 * Fee tier The fee tier is stored in fee pips, or parts per million. Common values include: | Fee pips | Trading fee | | -: | -: | | 100 | 0.01% | | 500 | 0.05% | | 3000 | 0.30% | | 10000 | 1.00% | Fee tier and tick spacing are bound by administrator configuration. Both range boundaries must be multiples of the pool's tick spacing. The protocol fee setting is separate from the trading fee tier. A `fee_protocol` value of 4 assigns 4/16 of each integer step fee to the protocol, not 4 percent of trade value. Market makers should read the current setting from the slot because it changes the share left for LPs. Read [Trading fees and protocol share](../concepts/fees-and-protocol-share). ## Inventory by price region Inventory composition follows the current price region: * Below the range, the position is funded with token0. * Inside the range, the position uses both tokens. * Above the range, the position is funded with token1. These statements follow the contract's token ordering. A user interface that displays the inverse market quote must still construct amounts using canonical token0 and token1. The actual amounts are calculated from the current square-root price, the square-root prices at both ticks, and the requested liquidity. Amount0 and amount1 deltas use different rounding directions depending on whether the contract is taking tokens from an LP or crediting principal back. Do not select a range from a displayed decimal price without first applying token order and display-decimal adjustment. The public `sqrt_price` is a base-unit Q128.128 value, not a ready-made market quote. Read [Price, ticks, and square-root price](../concepts/price-ticks-and-sqrt-price) and [Native token amounts](../concepts/token-amount-normalization). ## Active liquidity and fee production Only active liquidity earns a swap step's LP fee. If the pool is below a position's lower tick or at or above its upper tick, that position does not participate in the active-liquidity denominator for the step. Fees accrue in the input token: * A zero-for-one swap adds token0 fee growth. * A one-for-zero swap adds token1 fee growth. A position that remains active through two-way flow can build entitlements in both tokens. A position near one boundary may see mostly one direction before it leaves the active range. Fee entitlement is not calculated from elapsed time or gross pool volume. It depends on: * Whether the position was active during each step * Its liquidity relative to total active liquidity * The protocol share in effect for that step * Integer rounding in fee growth and settlement ## Boundary behavior When price reaches an initialized tick, the contract: 1. Records the current step's LP fee using pre-crossing active liquidity. 2. Flips both token fee-growth-outside values. 3. Applies the tick's signed `liquidity_net`. 4. Updates the nearest initialized-tick pointers. A tick crossing completes when a step reaches the tick price exactly, including when that price equals the user's square-root-price limit. A quote engine must therefore predict the post-crossing liquidity state even if the swap stops at that boundary. For a downward crossing, the stored current tick becomes one less than the crossed tick. For an upward crossing, it becomes the crossed tick. This preserves the half-open range convention. The interaction matters for range managers. A position can become inactive at the end of an exact-limit swap even though no price movement occurs beyond its boundary. ## Tick initialization and hints The contract stores initialized ticks in a per-pool doubly linked list. A new boundary requires the tick immediately below it as an insertion hint. The contract verifies that: * The hint belongs to the same pool. * The hint is initialized or is the minimum sentinel. * `hint.tick < new_tick`. * `hint.next > new_tick`. Hints are state-sensitive. Another accepted liquidity transaction can make a previously correct hint stale. Fetch them immediately before proving or submitting the transaction. If both boundaries already exist, their current list links are reused. If gross liquidity at a boundary fell to zero, the mapping entry was removed and a later mint or increase must reinsert it. Read [Initialized tick index](../concepts/initialized-tick-index). ## Practical range tradeoffs ### Narrow range Potential benefits: * Higher active liquidity per unit of capital near the current price * Larger proportional fee share while active * Tighter quoted depth around the selected market Operational costs: * More frequent range exits * More rebalancing transactions and proof generation * More frequent fee settlement and sub-unit rounding * Greater inventory change for a given price move * Higher exposure to informed or one-sided flow ### Wide range Potential benefits: * Longer active duration across volatile markets * Fewer reactivation transactions * Lower sensitivity to stale operational signals Operational costs: * Less depth per unit of supplied capital near the current price * Smaller proportional fee share against concentrated competitors * More capital committed away from the current trading region ## Range-selection procedure For each range decision, record: 1. The reference price and its data source. 2. Canonical token order and display decimals. 3. Expected volatility and rebalancing interval. 4. Expected flow direction and fee tier. 5. Current protocol fee share. 6. Tick spacing and rounded lower and upper boundaries. 7. Capital limits and minimum output tolerances. 8. Custody and proving latency for position updates. 9. A response plan for pool pause, token pause, or position freeze. Institutional and regulated operators should preserve these inputs as execution-policy evidence. The contract proves the chosen ticks and amounts were applied. It does not establish that the range was suitable for a mandate or jurisdiction. ## Public observability The position's pool, range, liquidity, fee checkpoints, and owed amounts are public mapping data under its `token_id`. The `PositionNFT` record keeps the owner out of that mapping, but it does not hide the market-making strategy expressed by the range. Position ownership is confidential. The range, liquidity, fee accounting, and resulting performance remain public. Continue with [Minting a position](./minting-a-position) or [Market maker operations](./market-maker-operations). # Collecting and burning Source: https://shield.fi/docs/liquidity/collecting-and-burning Fee and principal collection, confidential payout records, and final position removal. Collect and burn perform different jobs. * `collect` transfers requested owed amounts into token records and advances fee checkpoints. * `burn` consumes an empty ownership record and removes the public position row. Neither operation removes active liquidity. Liquidity must be reduced first. ## Outcome Collection is complete when the transaction is accepted, requested owed amounts are transferred into token records, the replacement `PositionNFT` is stored, and the wallet has discovered both payout records. Burning is complete only after liquidity and owed amounts are zero and the public position row is removed. ## Before you start Read the public position and current inside fee growth, verify the position is not frozen, reserve the current `PositionNFT`, choose base-unit collection amounts within the settled and pending entitlement, and obtain non-inclusion proofs for the NFT owner and immutable withdrawal address. ## What can be collected A position can have value from: * Pending LP fees not yet reflected in `tokens_owed` * Previously settled LP fees * Unused desired amounts from mint or increase * Principal credited by decrease or freeze The public `tokens_owed0` and `tokens_owed1` fields include only amounts already settled. A collect transaction also calculates pending fees from current inside growth before checking the requested amounts. ## Collect inputs and outputs `collect` accepts: * Current `PositionNFT` record * Public base-unit token0 amount requested * Public base-unit token1 amount requested * Public token0 and token1 program IDs * Two owner and two withdrawal `MerkleProof` values It returns: * Replacement `PositionNFT` record * Token0 payout record * Token1 payout record The token IDs must match the pool. Payout always goes to the immutable `withdrawal` address in the NFT. That address cannot be the AMM, router, or zero address. Collection amounts and position accounting are public. The withdrawal address and payout token records are confidential under Aleo's record model, but the investigator-owned mint record contains the withdrawal address. Read [Public data](../confidentiality/public-data) for the broader observability boundary. ## Amount validation Collection requests use native token base units. The contract checks each request directly against total owed after pending fee settlement. ## Pending-fee settlement For each token: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} pending = floor( wrapping_sub(inside_now, inside_last) * position_liquidity / 2^128 ) total_owed = stored_tokens_owed + pending remaining_owed = total_owed - amount_requested ``` The contract stores current inside growth as the new checkpoint even if part of `total_owed` remains uncollected. The uncollected integer amount is preserved in `tokens_owed`. The fractional remainder below one native base unit is discarded when the checkpoint advances. Collect changes fee checkpoints and owed balances. Read [Fee entitlements](./fee-entitlements) and [Q128.128 fee growth](../math/q128-fee-growth). ## Choosing requested amounts A caller may request less than the available amount. This is useful when: * Keeping part of the balance in the AMM's owed accounting * Matching a custody or treasury instruction * Avoiding an overestimate caused by stale off-chain state * Collecting one token without collecting the other To request the full amount, calculate pending fees from a consistent slot, tick, and position snapshot. The integer result is already in native base units. A swap accepted before collect can only increase eligible growth for an active position, but tick movement can change the inside-growth derivation. Use modular arithmetic and current boundaries. A conservative request below the calculated total can succeed while leaving the remainder owed. An overestimate is rejected. ## Settlement dust Each step's LP fee is recorded with 128 fractional bits and no cross-epoch residual. This prevents a later LP set from receiving an earlier set's fractional fee. Two floors still matter: 1. Step growth is `floor(lp_fee * 2^128 / active_liquidity)`. 2. Position settlement is `floor(delta_growth * position_liquidity / 2^128)`. The first leaves less than one native token base unit unallocated across the active set for that step. The second leaves less than one base unit for the position at settlement. Because collect advances the checkpoint, repeated small collections can leave more settlement dust than a less frequent schedule. The contract has no generic sweep for this surplus. Protocol collection is limited to recorded `protocol_fees0` and `protocol_fees1`. See [Rounding, overflow, and invariants](../math/rounding-overflow-and-invariants). ## Collect under controls Collect does not apply the pool entry-pause hierarchy. Disabling a pool or pausing trading does not by itself trap an LP's settled value. A position freeze is different. Frozen positions cannot collect. If a live position is frozen, the freeze path first removes its liquidity and settles principal and fees into `tokens_owed`. Unfreeze makes that value collectable but does not restore liquidity. ## Burn prerequisites `burn` succeeds only when: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} position exists position is not frozen position.liquidity == 0 position.tokens_owed0 == 0 position.tokens_owed1 == 0 ``` Burn consumes the `PositionNFT` and removes the public `positions[token_id]` entry. It returns no replacement ownership record. The transition does not inspect pending fee growth. With zero position liquidity, later fee growth creates no additional position entitlement. Any fees earned before liquidity reached zero were settled by the decrease or freeze operation that removed the liquidity. ## Complete closeout Use this sequence: 1. Decrease all remaining liquidity. 2. Wait for acceptance and save the replacement `PositionNFT`. 3. Read public `tokens_owed` balances. 4. Include any pending fee estimate if liquidity was not actually zero. 5. Collect token0 and token1 in native base units. 6. Save the replacement ownership record from collect. 7. Verify liquidity and both owed balances are zero. 8. Burn using the latest record. 9. Mark the token ID closed in the custody and accounting systems. Do not submit burn with the pre-collect record. It has been spent. ## Closeout records An institutional closeout record should retain: * Deployment ID and program checksum * Position token ID and pool key * Decrease transaction and credited principal * Fee calculation inputs and rounding policy * Public requested collection amounts * Payout record custody references * Final zero position state * Burn transaction removing the mapping row The chain demonstrates the state transitions. Legal ownership, valuation policy, and accounting classification remain operator responsibilities. # Decreasing liquidity Source: https://shield.fi/docs/liquidity/decreasing-liquidity Principal withdrawal accounting, fee settlement, tick cleanup, and exit behavior. `decrease_liquidity` removes some or all liquidity from a position. It does not transfer token value to the holder. The contract converts withdrawn principal and pending fees into public `tokens_owed` balances, which the holder later withdraws through `collect`. Decrease and collect are separate operations: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} decrease = remove market exposure and create an owed balance collect = transfer an owed balance into token records ``` ## Outcome The decrease is complete when the transaction is accepted, the public position shows the remaining liquidity, withdrawn principal and fees are reflected in `tokens_owed`, and the replacement `PositionNFT` is stored. No payout record is created until a later collect. The Shield Swap app normally follows Decrease with collection. A 100 percent action also closes the empty position after collection. If a later app step does not finish, the principal and fees remain under **Pending Claims** in **Portfolio**. The user can collect them without another decrease. ## Before you start Read the current position, slot, fee growth, and freeze state. Reserve the current `PositionNFT`, choose the exact liquidity amount to remove, and calculate raw token minimums using the current price and the position's fixed range. ## Inputs and outputs The transition accepts: * Current `PositionNFT` record * Public liquidity to remove * Public token0 minimum * Public token1 minimum It returns the public token ID and a replacement `PositionNFT` record. The pool, token programs, and range come from the `PositionNFT`. The public `Position` mapping must match those values. ## Exit controls The position must not be frozen, and the requested amount cannot exceed its public liquidity. The decrease finalize path does not check pool entry controls. An LP can reduce exposure while pool, global, token, or pair controls stop new entry and trading. A position-specific freeze still blocks the decrease. A frozen live position is handled separately by the administrator's freeze path. Freeze removes all active liquidity and settles value into `tokens_owed`, then blocks decrease and collect until unfreeze. ## Principal calculation The contract calculates principal at the current square-root price using the position's fixed lower and upper ticks. Depending on price: * Below range, withdrawn principal is token0. * In range, withdrawn principal can include both tokens. * Above range, withdrawn principal is token1. Principal amounts and public minimums use native base units. The contract checks: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} amount0 >= amount0_min amount1 >= amount1_min ``` A failed minimum check rejects the finalize and its state changes. ## Fee settlement order The position's pending fee entitlement is settled using its full liquidity before the requested reduction: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} fee0 = floor(delta_inside0 * old_position_liquidity / 2^128) fee1 = floor(delta_inside1 * old_position_liquidity / 2^128) ``` The new owed balances are: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} tokens_owed0 = previous_owed0 + fee0 + withdrawn_principal0 tokens_owed1 = previous_owed1 + fee1 + withdrawn_principal1 ``` The position then stores: * Reduced liquidity * Current inside-growth checkpoints * Updated owed balances For a partial decrease, all pending fees through the checkpoint settle, including the share earned by liquidity that remains. Later growth applies only to the reduced liquidity. This ordering closes the old liquidity epoch before opening the reduced one. ## Tick updates At the lower boundary: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} liquidity_net -= removed_liquidity liquidity_gross -= removed_liquidity ``` At the upper boundary: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} liquidity_net += removed_liquidity liquidity_gross -= removed_liquidity ``` Outside fee-growth values are preserved while the boundary remains initialized. If `liquidity_gross` reaches zero, the contract: 1. Reconnects the boundary's previous and next neighbors. 2. Updates cached nearest-tick pointers if necessary. 3. Removes the boundary from the `ticks` mapping. Removing the mapping row prevents a later LP epoch at the same numeric tick from inheriting stale outside-growth state. If lower and upper boundaries interact with the same neighboring ticks, the contract rereads the upper boundary after unlinking the lower one so it does not write stale list links. Read [Initialized tick index](../concepts/initialized-tick-index). ## Active-liquidity update The slot's active liquidity decreases only if the current tick is inside the range: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} tick_lower <= slot.tick < tick_upper ``` An out-of-range decrease changes position and boundary liquidity but does not change current active liquidity. Use the accepted slot state, not a pre-transaction quote, when reconciling this condition. Reaching an initialized tick at an exact user price limit completes the crossing. A swap that stops exactly at a boundary can therefore change whether the position is active before the decrease executes. ## Partial and full decrease ### Partial decrease * Position remains in the mapping. * Replacement `PositionNFT` remains necessary. * Remaining liquidity may continue earning fees if active. * Settled principal and fees stay in `tokens_owed`. ### Full decrease * Position liquidity becomes zero. * Boundary ticks may be removed if no other position uses them. * All principal and settled fees stay in `tokens_owed`. * The position cannot be burned until both owed balances are collected. A full decrease leaves principal and settled fees in `tokens_owed`. The LP must collect those amounts before burning the position. ## Rounding and settlement cadence Principal is rounded down under the amount-for-liquidity formulas. Fee entitlement is also rounded down to an integer native base unit when the checkpoint advances. Each partial decrease can discard a sub-unit fee fraction. A strategy that repeatedly removes small amounts can settle more often than a single large decrease and may leave more dust in the contract. There is no per-position residual field, pool-level residual carry, or generic surplus sweep. Discarding the sub-Q128 remainder prevents fee fractions from moving across liquidity epochs. ## Operational sequence For a managed exit: 1. Read the latest slot, position, and boundary state. 2. Compute principal in native base units using the current Q128.128 price. 3. Set raw minimum amounts for the intended slippage tolerance. 4. Submit decrease with the latest ownership record. 5. Save the replacement record. 6. Verify new public liquidity and owed balances. 7. Collect the desired native base-unit amounts. 8. Burn only after liquidity and both owed balances are zero. See [Collecting and burning](./collecting-and-burning) and [LP workflow](./lp-workflow). # LP fee entitlements Source: https://shield.fi/docs/liquidity/fee-entitlements How swap fees become range-specific, position-specific amounts owed. An LP earns a share of the integer fee charged during a swap step only when its liquidity is active for that step. The entitlement is recorded through cumulative fee growth, not by iterating over positions during a swap. The accounting path is: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} step fee -> protocol share and LP share -> global growth for the input token -> tick outside-growth checkpoints -> growth inside a position's range -> position settlement -> tokens owed -> collect ``` ## Fee-bearing token Fees accrue in the input token: * `zero_for_one = true` advances token0 fee growth. * `zero_for_one = false` advances token1 fee growth. The output token's global accumulator does not advance merely because the pool paid output in that token. For multi-hop swaps, the rule applies separately to every hop. A route can therefore generate fees in different token programs at different pools during one transaction. ## Trading fee and protocol share The pool fee is expressed in fee pips, or parts per million. A step first calculates its integer total fee under the swap math. The configured protocol fraction then splits it: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} protocol_fee = floor(total_fee * fee_protocol / 16) lp_fee = total_fee - protocol_fee ``` `fee_protocol` may be zero or 4 through 10. The floor remainder belongs to `lp_fee`. Protocol fees accumulate in separate slot balances. They do not appear in LP fee growth and cannot be claimed by positions. ## Global growth For active liquidity `L` and the step's LP fee `F`: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} growth_delta = floor(F * 2^128 / L) ``` The input token's global growth becomes: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} global_next = global_previous + growth_delta mod 2^256 ``` The implementation stores the value as `u256::U256`: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} U256 { hi: high 128 bits, lo: low 128 bits } ``` The packed value is: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} (hi << 128) | lo ``` The accumulator wraps by design. A decrease in the packed raw value does not mean fees were removed. Read [Q128.128 fee growth](../math/q128-fee-growth). ## Liquidity epochs A liquidity epoch is a period during which one active-liquidity set earns fee growth. Epoch boundaries include: * Minting active liquidity * Increasing or decreasing active liquidity * Freezing a live position * Crossing an initialized tick * Reinitializing a removed boundary The implementation stores no pool-level residual from fee-growth division. Each step records the floor at 128-bit fractional precision using the liquidity active in that step. The sub-Q128 remainder is discarded, so it cannot cross into a later epoch and be credited to a different active-liquidity set. Discarding the sub-Q128 remainder prevents fee fractions from crossing liquidity epochs. It can also leave rounding surplus in the contract. ## Tick outside growth Each initialized tick stores outside growth for token0 and token1. At a crossing: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} outside0_next = global0 - outside0_previous mod 2^256 outside1_next = global1 - outside1_previous mod 2^256 ``` Both tokens flip, even though only the input token generated a fee in the current step. Crossing reverses which side of the tick is considered outside, so both reference frames must change. The current step's fee is added before the flip. Its LP fee therefore belongs to the pre-crossing active-liquidity set. A crossing also completes when the tick price equals the user's price limit. The slot finishes with post-crossing liquidity even if the swap stops at that exact price. ## Growth inside a range For either token, define: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} below = current_tick >= lower_tick ? lower.outside : global - lower.outside above = current_tick < upper_tick ? upper.outside : global - upper.outside inside = global - below - above ``` Every subtraction wraps modulo `2^256`. The branch conditions implement: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} lower_tick <= current_tick < upper_tick ``` At the lower tick, the range is active. At the upper tick, it is not. ## Position checkpoint A public `Position` stores: * Current liquidity * `fee_growth_inside0_last_x_128` * `fee_growth_inside1_last_x_128` * Integer `tokens_owed0` * Integer `tokens_owed1` For each token: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} delta_inside = inside_now - inside_last mod 2^256 pending_fee = floor(delta_inside * position_liquidity / 2^128) ``` Pending fees are not continuously written to `tokens_owed`. They become settled integer amounts during increase, rebalance, decrease, collect, or freeze. ## Settlement events | Operation | Liquidity used for pending fees | State after settlement | | - | - | - | | Mint | None | New position snapshots current inside growth | | Increase | Old position liquidity | Checkpoint advances, then liquidity increases | | Rebalance | Full old position liquidity | Old fees settle, then the replacement starts with a new checkpoint | | Decrease | Full pre-decrease liquidity | Checkpoint advances, then liquidity decreases | | Collect | Current liquidity | Checkpoint advances, requested amount leaves | | Freeze | Full live liquidity | Fees and principal become owed, liquidity becomes zero | This ordering prevents new liquidity from sharing old growth and prevents removed liquidity from losing growth already earned. Burn performs no fee settlement. It removes a position only after liquidity and both owed balances are zero. ## Worked example Suppose one step has: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} total fee = 160 internal token0 units fee_protocol = 4 active liquidity = 60 position liquidity = 15 ``` The split is: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} protocol fee = floor(160 * 4 / 16) = 40 LP fee = 160 - 40 = 120 ``` Global token0 growth advances by: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} floor(120 * 2^128 / 60) = 2 * 2^128 ``` If the position was active for the step and no boundary adjustment removes the growth from its range: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} pending token0 fee = floor((2 * 2^128) * 15 / 2^128) = 30 ``` The protocol records 40, the position earns 30, and the other active liquidity earns the remaining 90 in proportion to its liquidity, subject to settlement floors. ## Rounding and dust Global growth floors once per fee-bearing step. The aggregate shortfall is less than one native token base unit because active liquidity is a `u128` and the fee-growth scale is `2^128`. Position settlement floors again. Its shortfall is less than one native base unit for that position and token at that settlement. Advancing the checkpoint discards that position fraction. It is not stored in a residual field and is not reassigned to another LP. The contract has no generic sweep for the resulting surplus. `collect_protocol` can withdraw only recorded protocol fees. Institutional reconciliation should use: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} gross fees = recorded protocol fees + claimable or collected LP fees + rounding surplus retained by the contract ``` Read [Rounding, overflow, and invariants](../math/rounding-overflow-and-invariants). ## Public observability Global growth, tick outside growth, position checkpoints, position liquidity, and settled owed amounts are public mapping state. Collection requests are also public. The `PositionNFT` record authorizes the holder without storing the owner's address in `Position`. The position's range and fee economics remain public. An observer with a token ID and a consistent state snapshot can estimate pending fees. The estimate does not identify the private immutable withdrawal address. ## Frozen positions Freezing a live position removes its entire liquidity and settles fees plus principal into `tokens_owed`. The freeze then blocks collect. Unfreeze restores access to the owed value but does not restore active liquidity. Fees stop accruing after liquidity becomes zero. The holder must increase or mint again after unfreeze to return capital to the active set. ## Off-chain calculation rules An indexer or wallet should: 1. Read slot, lower tick, upper tick, and position at one state height. 2. Pack each `u256::U256` from its two limbs. 3. Calculate below, above, and inside separately for token0 and token1. 4. Use modulo `2^256` subtraction. 5. Multiply by position liquidity using arbitrary precision. 6. Shift right by 128 bits. 7. Reject a result above `u128` maximum, matching the contract. 8. Add stored `tokens_owed`. 9. Keep the result in the token's native base units and apply decimals only for display. Do not use floating-point arithmetic or ordinary signed subtraction. # Increasing liquidity Source: https://shield.fi/docs/liquidity/increasing-liquidity How a position adds capital without inheriting or diluting an earlier fee epoch. `increase_liquidity` adds capital to an existing range. It does not create a second position. The operation consumes the current `PositionNFT` record, transfers additional token value into the AMM, updates the public position, and returns a replacement `PositionNFT` record. The contract settles fees earned by the old liquidity amount before adding new liquidity. That ordering keeps fee ownership aligned with the liquidity that was active when each fee was earned. ## Outcome The increase is complete when the transaction is accepted, the public position reflects the added liquidity, the old ownership record is spent, the replacement `PositionNFT` is stored, and both token change records are accounted for. ## Before you start Read the current public position and fee growth, verify entry controls, reserve the current `PositionNFT`, select token0 and token1 funding records, and quote desired and minimum raw amounts for the position's existing range. ## Inputs and outputs The transition accepts: * Current `PositionNFT` record * Token0 and token1 records * Public desired token amounts * Public minimum token amounts * Public token0 and token1 program IDs * Public lower and upper insertion hints The tick range is taken from the ownership record. An increase cannot change the range. Use Rebalance to replace the complete position and select a different range. A successful transaction returns: * Public token ID * Replacement `PositionNFT` record * Token0 and token1 change records The operation does not pay pending LP fees to the holder. Settled fees and any `desired - used` funding excess remain in public `tokens_owed` accounting until `collect`. Returned change records contain only input-record value above the desired transfer amounts. ## Preconditions The public position must match the pool and ticks carried by the ownership record. The position must not be frozen. Entry controls apply: * Global pause must be off. * The pool must be enabled. * Neither pool token may be paused. * The token pair may not be paused. Token IDs must match canonical pool token0 and token1. The contract derives a positive liquidity amount from current Q128.128 square-root price, fixed range, and native base-unit desired amounts. It rejects an increase that produces zero liquidity or pushes either boundary above `max_liquidity_per_tick`. ## Amount calculation The increase follows the same inventory rules as mint: * Below the range, token0 supports the added liquidity. * In range, both tokens may be required. * Above the range, token1 supports the added liquidity. Desired and minimum amounts are native base units. The token calls transfer the full desired amounts into AMM custody. The AMM calculates liquidity and actual used amounts directly in those units, then adds each `desired - used` excess to `tokens_owed`. Dynamic token transfers return change records only for input-record value above the desired amounts. ## Settlement before addition Before the position's liquidity is increased, the contract derives fee growth inside the range and settles: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} accrued_fee = floor( wrapping_sub(inside_now, inside_last) * old_position_liquidity / 2^128 ) ``` It then: 1. Adds the integer accrued fee to `tokens_owed`. 2. Advances the position's inside-growth checkpoint. 3. Adds the new liquidity to the position. 4. Adds each `desired - used` funding excess to `tokens_owed`. 5. Adds that liquidity to the slot if the range is active. This creates two distinct liquidity epochs: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} before increase: old liquidity earns growth through the checkpoint after increase: old plus new liquidity earns later growth ``` The new liquidity does not share in growth that occurred before the increase. The old liquidity does not lose fees because the checkpoint is advanced only after its entitlement is calculated. Read [Fee entitlements](./fee-entitlements) for the global, outside, and inside calculation. ## Existing and removed boundaries If both range boundaries are still initialized, the contract increases their gross liquidity and updates signed net liquidity without changing their fee-growth-outside checkpoints. A position can have zero liquidity while its old boundary entries have been removed from the tick mapping. Increasing such a position reinitializes missing boundaries: * A new tick at or below the current tick starts with outside growth equal to current global growth. * A new tick above the current tick starts with zero outside growth. The caller must supply current predecessor hints for each missing boundary. The contract verifies the linked-list interval before insertion. This reinitialization starts a new tick epoch. Stale outside growth from the former boundary is not reused. ## Active-liquidity update If: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} tick_lower <= slot.tick < tick_upper ``` the slot's active liquidity increases by the calculated amount. Otherwise, slot liquidity is unchanged. The boundary changes are enough for the liquidity to become active when price later enters the range. The nearest initialized-tick pointers may tighten if a reinitialized boundary lies closer to the current tick. ## Rounding effects An increase has three separate rounding surfaces: 1. Liquidity and required token amounts are integer calculations. 2. Pending fees settle down to whole native base units. 3. Any fractional fee below one base unit is not carried after the checkpoint advances. Increasing frequently can therefore discard more settlement dust than adding capital less often. This does not transfer the fraction to another position. It leaves an accounting surplus in the contract. Use token metadata decimals to express one base unit in display terms when deciding whether rounding is operationally material. Read [Rounding, overflow, and invariants](../math/rounding-overflow-and-invariants). ## Record and custody handling The input `PositionNFT` and token records are spent by an accepted transaction. Persist the replacement `PositionNFT` record and both change records before marking prior records unavailable in an external custody ledger. If the transaction is rejected, the submitted records are not replaced by successful outputs. Refresh record spent status rather than assuming a replacement exists. For routed or institutional execution, associate the accepted transaction with: * Program ID and checksum * Position token ID * Old and new public liquidity * Raw desired and internal used amounts * New `tokens_owed` balances * Input and replacement record identifiers ## Common rejection causes * Position is frozen. * Pool or an entry control is paused. * Token IDs do not match the pool. * Current price and amounts calculate zero liquidity. * A boundary exceeds the per-tick liquidity cap. * A removed boundary is supplied with a stale or invalid hint. * Minimum amount exceeds actual raw amount used. * Input ownership or token records are spent or insufficient. After increasing, monitor [Fee entitlements](./fee-entitlements) and use [Collecting and burning](./collecting-and-burning) when value should leave the AMM. # Liquidity provider workflow Source: https://shield.fi/docs/liquidity/lp-workflow Build the full position lifecycle from pool selection through final burn. A Shield Swap position has two connected forms: * A public `Position` mapping entry holds the pool, tick range, liquidity, fee checkpoints, and amounts owed. * A `PositionNFT` record authorizes the holder to operate that position. The economic state is observable. Ownership remains confidential through the Aleo record model. Anyone who knows a `token_id` can follow its public range, liquidity, fee checkpoints, and owed balances, but the `positions` mapping does not contain the owner's address. Every position binds two private addresses at mint: * The owner recipient receives the `PositionNFT` and authorizes position operations. * The immutable withdrawal address receives each collect payout and nonzero rebalance refund. These addresses may differ from each other and from the mint signer. Collect cannot override the withdrawal address. A custody system must therefore confirm both destinations before minting and preserve the withdrawal field in every replacement ownership record. The lifecycle has six stages: 1. Select a pool and range. 2. Mint and preserve the returned ownership record. 3. Monitor price, inventory, fees, and pool controls. 4. Increase, decrease, or collect as needed. 5. Rebalance into a new range when the range must change. 6. Remove all liquidity, collect all value, and burn the empty position. ## Finish line A position is fully closed only when: * public liquidity is zero * `tokens_owed0` and `tokens_owed1` are zero * the final collect records are stored * `burn` is accepted * the public position row is removed * the final `PositionNFT` has been consumed ## Client state you must keep Store the deployment ID, pool key, token ID, latest `PositionNFT`, transaction state, and returned token records together. Do not update record inventory until acceptance is known. If you only need to display public positions, use the [REST API](../rest/overview). The workflow below is for applications that construct position transactions. ## Before supplying liquidity Confirm the deployment and pool before constructing a transaction. A pool key identifies a sorted token pair and fee tier, but mappings are scoped to a program deployment. The same logical pool key in an old and a new program does not make their state interchangeable. Check: * The intended program ID and deployment checksum * Canonical token0 and token1 order * Pool fee in fee pips, where 3000 means 0.30 percent * Tick spacing bound to that fee tier * Current tick and square-root price * Token display decimals and wrapper relationships from the deployment registry * Pool, token, pair, and global pause state * The predecessor hints required for any new boundary tick The current pool, tick, and position schemas use `u256::U256` Q128.128 fee-growth values. A deployment using scalar fee-growth fields cannot be upgraded in place to this layout. A production cutover requires a fresh deployment. LP tools should bind records, mapping queries, and pool configuration to one program ID rather than assuming all `shield_swap` deployments share state. Read [Pools and keys](../concepts/pools-and-keys), [Native token amounts](../concepts/token-amount-normalization), and [Choosing a range](./choosing-a-range) before building a mint. ## Mint `mint` consumes token records and creates: * A public `token_id` * A `PositionNFT` record * Change records for unused input-record value * An investigator-owned mint compliance record * A public `Position` mapping entry The request includes the pool, lower and upper ticks, desired and minimum token amounts, and insertion hints. All amounts use native token base units. Mint also binds a private position owner, immutable withdrawal address, and freezelist non-inclusion proofs. The contract snapshots fee growth inside the selected range when the position is created. That checkpoint is the start of the position's first liquidity epoch. It prevents the new position from claiming fees generated before it joined. The token calls move the full desired amounts into AMM custody. If the calculated liquidity uses less, the new public position starts with the `desired - used` excess in `tokens_owed`. Change records contain only input-record value above the desired transfers. Persist the returned `PositionNFT` ciphertext and its decrypted representation. The input records are spent. Reconstructing the public fields does not recreate a valid Aleo ownership record. See [Minting a position](./minting-a-position). ## Monitor an open position An open position can be: * In range and contributing active liquidity * Below range and represented economically by token0 * Above range and represented economically by token1 * Frozen and removed from active liquidity * At zero liquidity but still holding amounts owed Fee growth advances only for active liquidity. A zero-for-one swap pays fees in token0. A one-for-zero swap pays fees in token1. The public `tokens_owed0` and `tokens_owed1` fields are not live fee quotes. Pending fees remain encoded in the difference between current inside growth and the position's last checkpoint until a settlement operation runs. For an independent fee estimate, read the slot, both boundary ticks, and the position at a consistent chain height. Apply wrapping 256-bit subtraction. A normal signed subtraction is wrong after accumulator wrap. See [Fee entitlements](./fee-entitlements) and [Q128.128 fee growth](../math/q128-fee-growth). ## Increase `increase_liquidity` consumes the current `PositionNFT` and token records. It settles fees earned by the old liquidity amount before adding new liquidity. This creates a clean epoch boundary: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} old fee entitlement settles at old liquidity checkpoint advances new liquidity becomes active ``` If a boundary was removed after gross liquidity reached zero, the caller must provide valid insertion hints so it can be initialized again. Save the replacement `PositionNFT` and change records returned by the transaction. The increase credits both settled fees and any `desired - used` funding excess to `tokens_owed`; it does not return that AMM-held excess in the change records. See [Increasing liquidity](./increasing-liquidity). ## Rebalance `rebalance_position` consumes the complete current position and creates a replacement in one atomic transaction. It does not swap the recovered assets. The request can combine recovered principal, settled fees, stored owed amounts, and additional funding. Each asset must satisfy `recovered + funded = desired + refund`. The replacement keeps the same pool and immutable withdrawal address. The signer owns the new `PositionNFT`, and the new position has a new token ID. The transaction emits a new `MintComplianceRecord` for the replacement. A nonzero refund goes to the immutable withdrawal address. The quote is exact and short-lived. Refresh all state and review the new values when a pool change invalidates the quote. See [Rebalance a position](./rebalance-position) for the app flow. ## Decrease `decrease_liquidity` reduces liquidity but does not transfer tokens to the LP. It settles all pending fees for the position's full pre-decrease liquidity and credits both those fees and the withdrawn principal to `tokens_owed`. A partial decrease leaves the position active with less liquidity. A full decrease leaves a zero-liquidity position that may still hold amounts owed. Boundary ticks whose `liquidity_gross` reaches zero are removed from the initialized-tick index. Decrease remains an accounting operation. The LP receives tokens only through `collect`. See [Decreasing liquidity](./decreasing-liquidity). ## Collect `collect` requests explicit raw amounts for token0 and token1. It: 1. Derives current fee growth inside the range. 2. Settles pending fees using current position liquidity. 3. Adds settled fees to previously stored amounts owed. 4. Checks that each requested amount is available. 5. Returns token payout records. 6. Stores the uncollected remainder and a new fee checkpoint. The requested amounts are public. Payout goes to the private immutable withdrawal address in the `PositionNFT`. The records keep payout ownership confidential from public observers, but they do not hide fee growth, position state, or collection amounts. Each settlement floors fractional entitlement to an integer native base unit. Calling collect frequently can discard more sub-unit dust than settling less often. Dust is not reassigned to another LP, and the contract has no generic sweep for unaccounted surplus. See [Collecting and burning](./collecting-and-burning). ## Burn `burn` removes an already empty position record. It succeeds only when: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} liquidity == 0 tokens_owed0 == 0 tokens_owed1 == 0 position is not frozen ``` The operation consumes the `PositionNFT` and removes the public `positions` row. A correct closeout sequence is: 1. Decrease all remaining liquidity. 2. Collect all token0 and token1 owed. 3. Confirm the public position has zero balances. 4. Burn the position. ## Record-handling rule Every position operation consumes an ownership record. Increase, decrease, and collect return a record for the same token ID. Burn returns no replacement. Rebalance removes the old position and returns a `PositionNFT` for a new token ID. Store the new record before you retire the old position. Wallets and custody systems should update record inventory only after acceptance. They should retain the prior ciphertext until acceptance is confirmed. Do not rely on filenames, local timestamps, or the current `token_id` to determine which record is unspent. Use transaction state and record spent status. ## Operating checklist For each position: * Track program ID, pool key, token ID, and latest ownership record together. * Confirm the private owner recipient and immutable withdrawal address before mint; changing the withdrawal destination requires closing and reminting the position. * Read pause and freeze state before submitting a change. * Quote and reconcile in exact native token base units. Apply decimals only for display. * Treat `tokens_owed` as settled amounts, not total pending fees. * Recompute insertion hints immediately before mint or reactivation. * Account for exact-limit tick crossing when predicting post-swap active liquidity. * Decide a fee-settlement cadence that balances custody operations with sub-unit rounding. * Preserve separate decoders and history for scalar-fee-growth and Q128.128 deployments. # Manage positions Source: https://shield.fi/docs/liquidity/manage-positions Create, increase, decrease, collect, rebalance, and close a Shield Swap position. Open **Portfolio** and select **Liquidity Positions** to see your positions across pools. Select the pool in a position row to open its liquidity page. Open a pool to create or manage liquidity. Under **Your Positions**, select **Manage** in a position row. Select **Increase**, **Rebalance**, or **Decrease** from the menu to load that position into the action panel. ## Position actions | Action | Result | | - | - | | **New position** | Creates a position with a selected price range | | **Increase** | Adds funds without a range change | | **Rebalance** | Replaces the full position with a new range and position ID | | **Decrease** | Removes 25, 50, 75, or 100 percent of the liquidity | | **Collect** | Sends owed position funds to the immutable withdrawal address | ## Create a position 1. Select a pool. 2. Select **New position**. 3. Keep **Full range**, or select **Custom** to use a range preset or enter minimum and maximum prices. 4. Enter the required token amounts. 5. Open **Advanced addresses** to check the recipient and withdrawal addresses. Review these addresses, the range, and the amounts before confirmation. 6. Confirm the Shield Wallet request. An out-of-range position uses only one asset. The app still asks for both desired amounts and makes the unused side claimable. An in-range position usually uses both assets. Integer math or the selected ratio can also make part of a desired amount claimable. ## Owner and withdrawal address Open **Advanced addresses** in **New position** to find **Recipient address** and **Withdrawal address**. Both fields initially contain the connected wallet address. **Recipient address** sets the position owner. The position owner controls the `PositionNFT` and can change the position. The immutable withdrawal address receives collected principal, fees, and position refunds. These addresses can differ. Check both addresses before you create the position because an existing position cannot change its withdrawal address. ## Increase a position Use **Increase** when the range stays the same. The app calculates the required ratio from the position range and current pool price. An increase settles pending fees before it adds liquidity. Any desired amount that the position does not use becomes claimable. ## Decrease or close a position Use **Decrease** to remove part of the liquidity. The review includes the selected principal and any amount that is already claimable. Select 100 percent to collect claimable funds and close the position. If collection does not finish, open **Portfolio**, then select **Pending Claims** to recover the funds. Before you confirm a close, check the position ID, price range, and withdrawal address in the review. The withdrawal address receives the collected funds. Continue with [Rebalance a position](./rebalance-position) or [Collecting and burning](./collecting-and-burning). # Market maker operations Source: https://shield.fi/docs/liquidity/market-maker-operations A control framework for quoting, range management, fee reconciliation, and deployment cutovers. Market makers need current public pool state and controlled custody of confidential ownership records. The contract provides deterministic position accounting. Range selection, inventory limits, rebalancing policy, custody, and reconciliation remain operator responsibilities. ## Operating state Maintain a deployment-scoped state record for each market: * Program ID and checksum * Pool key * Canonical token0 and token1 * Token display decimals and wrapper relationships * Fee tier in fee pips * Current `fee_protocol` numerator * Tick spacing * Current tick and square-root price * Active liquidity * Nearest initialized ticks above and below * Pause and enable controls * Open position token IDs * Latest unspent `PositionNFT` record for each position Do not key the state store only by pool key. Pool keys are mapping keys inside a program. A fresh deployment can use the same logical pair and fee values while holding entirely separate liquidity and records. ## Quote construction Before quoting: 1. Read the slot and pool state at a known height. 2. Confirm token order, decimals, and wrapper relationships. 3. Walk the initialized-tick index in the proposed direction. 4. Apply the fixed iteration limit used by the selected swap path. 5. Apply fee and protocol-share rounding per step. 6. Keep final output and any refund in native token base units. 7. Enforce a nonzero minimum output appropriate to the instruction. The pool fee is in parts per million. For example, 3000 fee pips is 0.30 percent. The protocol share is a fraction of that integer fee: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} protocol_fee = floor(step_fee * fee_protocol / 16) lp_fee = step_fee - protocol_fee ``` The current setting is public and administrator-controlled. A fee forecast that assumes a static protocol share should state the observation height and update policy. ## Tiny inputs After fee adjustment, a small input can have zero amount available for price movement. The bounded partial path returns the full input without moving price or charging a fee. Routers and market makers should still reject quote sizes whose expected output is zero. ## Range and inventory management Each position is active over: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} tick_lower <= current_tick < tick_upper ``` The position moves toward token1 as price rises through the range and toward token0 as price falls. A range manager should track: * Distance to each boundary * Inventory by token * Active liquidity share * Realized and pending fees by input token * Expected proving and confirmation time for a rebalance * Available token records for the next mint or increase * Current predecessor hints if a boundary must be inserted Position ranges and liquidity are public, so an LP's strategy remains observable even when ownership is confidential. Read [Choosing a range](./choosing-a-range). ## Exact-limit tick behavior Reaching an initialized tick completes the crossing even when the tick price equals the user's square-root-price limit. At the boundary, the contract: 1. Charges the step fee. 2. Adds LP growth using pre-crossing active liquidity. 3. Flips both token outside-growth counters. 4. Applies `liquidity_net`. 5. Stops if no further price movement is allowed. The post-swap slot can therefore hold post-crossing liquidity while its price equals the limit. A simulator that leaves liquidity unchanged at an exact limit will misquote the next transaction and misclassify which ranges are active. Tests and monitors should include exact lower and upper boundary cases in both swap directions. ## Rebalancing workflow A range cannot be changed in place. Use one atomic Rebalance transaction: 1. Quote the complete recovery from the current position. 2. Select the replacement range and exact desired amounts. 3. Add optional funding for one or both assets. 4. Review each recovered, funded, desired, and refund amount. 5. Submit the Rebalance transaction. 6. Store the new token ID and `PositionNFT` after acceptance. The replacement keeps the pool and immutable withdrawal address. The signer becomes the owner of the replacement `PositionNFT`. The transaction fails atomically if a quote, proof, control, or amount is invalid. It does not leave an intermediate closed position. An increase is appropriate when the range stays the same. It settles fees at old liquidity before adding new liquidity. A partial decrease similarly settles at full old liquidity before reducing it. These settlement points define liquidity epochs. They prevent new capital from sharing in old fee growth, but each settlement also floors the position's fractional entitlement to an integer native base unit. ## Settlement cadence Pending fees do not need to be collected for the position to keep earning. They remain represented by Q128.128 growth until increase, decrease, collect, or freeze settles them. More frequent settlement can: * Improve treasury availability * Simplify period-end realization * Increase proof and custody operations * Discard more sub-unit fee dust The floor is less than one native base unit per token and position settlement. Use token decimals only to express that threshold in display units. Read [Fee entitlements](./fee-entitlements) and [Rounding, overflow, and invariants](../math/rounding-overflow-and-invariants). ## Fee reconciliation Track at least four fee categories: | Category | State source | | - | - | | Gross step fee | Swap execution model | | Protocol fee | `Slot.protocol_fees0` and `protocol_fees1` | | Settled LP amount | Position `tokens_owed` changes and collect | | Pending LP amount | Global, tick outside, and position checkpoint calculation | There is also a rounding surplus: * Fee growth floors once per step. * Position entitlement floors at settlement. * The contract does not carry a pool-level residual across liquidity epochs. * The contract has no generic sweep for the resulting surplus. Do not force reconciliation by assigning unexplained public token balance to protocol revenue. Protocol collection is limited to recorded protocol-fee fields. ## Public and confidential records Publicly observable data includes: * Pool fee and protocol share * Slot price, tick, liquidity, and fee growth * Initialized ticks and outside growth * Position range, liquidity, checkpoints, and owed amounts * Public amounts requested by collect Confidential data includes the ownership and payout information held in Aleo records, subject to investigator-owned compliance records and ordinary transaction metadata. Market surveillance can calculate pool and position economics without knowing the public owner address because no owner address is stored in `Position`. Amount and timing correlation remain possible. ## Controls and incident response Monitor: * Global, token, pair, and pool pause state * Position freeze mappings * Program checksum changes * Protocol fee setting changes * Boundary-list consistency * Active liquidity versus boundary net changes * Unexpected fee-growth wraps * Settlement amounts approaching `u128` limits * Persistent contract balance not explained by principal, protocol fees, or LP claims Pool disable and broad pause controls block entry and trading paths, but decrease and collect remain available. Position freeze is stricter. It removes live liquidity, settles value into owed accounting, and blocks the holder until unfreeze. An incident runbook should distinguish stopping new risk from blocking a specific position. ## Fresh-deployment cutover The current schema uses two-limb Q128.128 fee-growth values and no residual state. It is incompatible with deployments that use scalar growth fields in the same mapping structures. Aleo program upgrades freeze existing mapping and struct interfaces, so adopting the current schema requires a fresh deployment. For a cutover: 1. Stop routing new flow to the old deployment. 2. Disable old pools while preserving LP decrease and collect access. 3. Reconcile and collect recorded protocol fees. 4. Deploy and bootstrap the new program. 5. Establish new pools and approvals. 6. Update simulators, decoders, routers, and custody systems together. 7. Exit and remint liquidity rather than copying old fee checkpoints. 8. Keep old and new histories under separate schema versions. A residual from an earlier deployment cannot be fairly attached to a new active-liquidity set. Transplanting that state would violate the current rule that prevents fee fractions from crossing liquidity epochs. ## Daily control checklist * Confirm deployment checksum and configuration. * Reconcile token metadata, wrapper relationships, and canonical order. * Refresh slots and initialized ticks. * Review exact-limit boundary exposure. * Compare pending and settled fees. * Confirm the current protocol share. * Check unspent ownership and token records. * Review pause and freeze state. * Investigate unexplained surplus without assuming it is withdrawable. * Preserve evidence for each range, quote, settlement, and rebalance decision. # Minting a position Source: https://shield.fi/docs/liquidity/minting-a-position Inputs, state changes, fee checkpoints, records, and validation for position creation. `mint` creates a concentrated-liquidity position from token records. It writes public economic state and returns a `PositionNFT` record. Minting does not import earlier pool fees. The contract initializes the position's fee checkpoints at the current fee growth inside its chosen range. ## Outcome Minting is complete when the transaction is accepted, the public position row contains the expected range and liquidity, the returned `PositionNFT` is stored, and both token change records are accounted for. ## Prerequisites Before minting: 1. Confirm the program deployment and checksum. 2. Confirm the pool exists and is enabled. 3. Confirm global, token, and pair pause controls permit entry. 4. Verify the supplied token IDs match canonical pool token0 and token1. 5. Read the current slot and tick spacing. 6. Select aligned lower and upper ticks. 7. Find current predecessor hints for boundaries that are not initialized. 8. Select token records with enough unspent value. 9. Obtain freezelist non-inclusion proofs for signer, position owner, and withdrawal address. The Q128.128 fee-growth schema is incompatible with the earlier scalar mapping layout, so adopting it requires a fresh deployment. A wallet must not submit records or pool keys to a newly deployed program merely because they came from a contract with the same source name. Program addresses, records, and mapping state are deployment-specific. ## Inputs The transition accepts: * A private nonce * A token0 record * A token1 record * A private recipient address * A private immutable withdrawal address * A public `MintPositionRequest` * Public token0 and token1 program IDs * Three pairs of `MerkleProof` values for signer, recipient, and withdrawal The request contains: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} pool tick_lower tick_upper amount0_desired amount1_desired amount0_min amount1_min tick_lower_hint tick_upper_hint ``` The range, desired amounts, minimum amounts, pool, and hints are public. The recipient is a private input. The resulting `token_id` is public. The investigator-owned `MintComplianceRecord` supports authorized review of the mint. It contains the token ID, token programs, nonce, request, caller, signer, position owner, and immutable withdrawal address. The request's public fields remain visible. ## Range validation The contract requires: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} tick_lower % tick_spacing == 0 tick_upper % tick_spacing == 0 tick_lower < tick_upper MIN_TICK <= tick_lower tick_upper <= MAX_TICK ``` The active-range convention is: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} tick_lower <= current_tick < tick_upper ``` Read [Choosing a range](./choosing-a-range) for the inventory and boundary implications. ## Native base-unit amounts Desired and minimum amounts use each token's native base units. The contract calculates the maximum liquidity supported by the desired amounts at the current Q128.128 square-root price. It then calculates actual base-unit amounts with explicit rounding and checks that: * Actual amount does not exceed desired amount. * Actual amount meets the corresponding minimum. * Any token required by the current price region has a nonzero amount. The private-to-public token calls move the full desired amounts into AMM custody. Dynamic change records hold only input-record value above those desired transfers. After the contract calculates the actual amounts used for liquidity, it credits each `desired - used` excess to the new position's public `tokens_owed` balance. A newly minted position can therefore begin with a nonzero owed balance. ## Boundary initialization Each boundary has: * `liquidity_gross` * Signed `liquidity_net` * Outside fee growth for both tokens * Previous and next initialized-tick pointers For a new tick at or below the current tick, outside fee growth is initialized to current global growth. For a new tick above the current tick, outside growth starts at zero. This makes prior fee history outside the new position's entitlement. At the lower tick: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} liquidity_net += minted_liquidity liquidity_gross += minted_liquidity ``` At the upper tick: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} liquidity_net -= minted_liquidity liquidity_gross += minted_liquidity ``` Both gross values must remain within `max_liquidity_per_tick`. New boundaries are inserted into the pool's linked tick index using caller-supplied predecessor hints. Existing boundaries keep their current list links. ## Fee checkpoint After both ticks are updated, the contract derives current fee growth inside the range: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} inside = global - growth_below_lower - growth_above_upper ``` The result is stored as: * `fee_growth_inside0_last_x_128` * `fee_growth_inside1_last_x_128` This snapshot establishes the position's first liquidity epoch. The position can earn only growth that occurs after this checkpoint while its liquidity is active. The values are two-limb wrapping Q128.128 accumulators. An indexer must preserve `hi` and `lo` as arbitrary-precision integers. Read [Q128.128 fee growth](../math/q128-fee-growth). ## Pool active liquidity If the current tick is inside the new range, the slot's active liquidity increases immediately. If the position is out of range, only its boundary state changes. It will enter the active set when price later crosses the appropriate tick. The slot also tightens its cached nearest initialized-tick pointers if a new boundary is closer to the current tick. ## Outputs A successful mint returns: * Public `token_id` * `PositionNFT` record * Token0 change record * Token1 change record * Investigator-owned `MintComplianceRecord` The `PositionNFT` contains owner, immutable withdrawal address, token ID, token program IDs, pool, and tick range. It does not contain live liquidity or fee balances. Those remain in the public `positions` mapping. Persist: * Original transaction ID * Program ID and checksum * Pool key and token ID * Returned `PositionNFT` ciphertext * Decrypted record metadata * Returned change records * Public position state after finalization Reconcile the new position's `tokens_owed0` and `tokens_owed1` against the desired and actual used amounts. Do not treat the token change records as a refund of AMM-held excess. ## Common rejection causes * Wrong token order * Pool, token, pair, or global pause * Invalid signer, recipient, or withdrawal freezelist proof * Lower or upper tick not aligned to spacing * Invalid or stale insertion hint * Zero calculated liquidity * Per-tick liquidity cap exceeded * Minimum amounts above actual consumed amounts * Reused nonce and request producing an existing token ID * Spent or insufficient token record Do not automatically retry a failed mint with the same stale state. Refresh the slot, boundary mappings, hints, record inventory, and deployment identity first. After minting, continue with [LP workflow](./lp-workflow), [Increasing liquidity](./increasing-liquidity), or [Fee entitlements](./fee-entitlements). # Rebalance a position Source: https://shield.fi/docs/liquidity/rebalance-position Replace a complete position with a new range in one Shield transaction. **Rebalance** removes the complete current position and creates its replacement in one atomic Shield transaction. Use **Increase** when you want to keep the current range. Use **Decrease** when you only want to reduce liquidity. The app shows **Rebalance** only when the selected network has a compatible live rebalance router. ## Rebalance flow 1. Open a pool and find a position that has liquidity under **Your Positions**. 2. Select **Manage** in that position row, then select **Rebalance** from the menu. 3. Keep **Current range**, or select **Custom**. 4. For a custom range, use a preset or enter new minimum and maximum prices. 5. Review the prefilled position funds. 6. Increase a token amount if you want to add funds from Shield Wallet. 7. Select **Review Rebalance** and confirm the transaction. The custom range chart shows the current range and the proposed range. The entered amounts cannot be less than the funds recovered from the current position. The app shows **Nothing to rebalance** when the range stays the same and the position does not grow. Change the range, or select **Go to Increase** to add funds at the current range. ## Review values | Item | Meaning | | - | - | | Position funds | Principal, accrued fees, and stored owed amounts recovered from the current position | | New position | Amounts that the replacement position will use | | From your wallet | Extra Shield Wallet funds required for the replacement | | Stays in your wallet | Entered wallet funds that the transaction will not spend | | Returns to your wallet | Recovered position funds that the replacement will not use | Open **Details** to inspect position funds, new position amounts, and unused amounts. The review shows wallet amounts when they apply. **Stays in your wallet** funds remain in the connected wallet. **Returns to your wallet** funds go to the current position's immutable withdrawal address. That address can differ from the connected wallet address. ## Result Rebalance keeps the same pool and immutable withdrawal address. It removes the old position ID and creates a new position ID. The signer owns the new `PositionNFT`. The replacement position remains confidential under the same record model as a new position. The operation does not swap assets. A failed transaction leaves the old position unchanged. If pool state changes before confirmation, the app requires another review. Confirm the updated range and amounts before you continue. # Exact-limit tick crossing Source: https://shield.fi/docs/math/exact-limit-tick-crossing Why reaching an initialized tick at the price limit still changes active-liquidity state. When a swap reaches an initialized tick exactly at its price limit, the contract completes the tick transition without moving beyond the limit. This keeps square-root price, current tick, active liquidity, initialized-tick pointers, and fee accounting consistent. ## Crossing condition The contract defines a crossing as: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} crossed = previous_step_crossed && new_sqrt_price == target_tick_sqrt_price ``` There is no additional requirement that the target tick price differ from `sqrt_price_limit`. The target is already clamped to the price limit: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} zero_for_one target = max(next_tick_price, limit) one_for_zero target = min(next_tick_price, limit) ``` The equality case requires this state transition: * A limit before the next initialized tick stops inside the range and does not cross. * A limit exactly at the next initialized tick crosses if the step reaches it. * A limit beyond the next initialized tick allows the same crossing and continued traversal, subject to remaining input and iteration count. ## State inconsistency without equality crossing Suppose the crossing predicate instead required: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} target_tick_sqrt_price != limit ``` Consider one position with range `[lower, T)` and an upward `one_for_zero` swap that reaches `T` exactly at its price limit. The resulting state could be: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} sqrt_price = sqrt(T) current_tick = T slot.liquidity = prior active liquidity next_init_above = T ``` The current tick says the position is inactive because its upper boundary is exclusive. The liquidity field still includes it because `liquidity_net` was not applied. This is ghost active liquidity. If the LP then removed the full position, the normal range check would see `current_tick == tick_upper` and remove zero from pool liquidity. Once the boundary ticks were unlinked, the phantom amount could remain without a position backing it. ## Half-open position ranges The active-range rule is: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} tick_lower <= current_tick < tick_upper ``` This convention means: * A position is active at its lower tick. * A position is inactive at its upper tick. * Moving upward across an upper tick removes that range's liquidity. * Moving downward across a lower tick removes that range's liquidity in the opposite direction. Tick crossing must update current tick and active liquidity according to the same convention used by mint, increase, decrease, and fee settlement. ## State transition by direction Let `T` be the crossed initialized tick and `net` be its signed `liquidity_net`. ### Upward, `one_for_zero` ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} new_liquidity = old_liquidity + net new_current_tick = T new_next_init_below = T new_next_init_above = tick.next ``` An upper boundary normally has negative net liquidity, so crossing upward removes that range from active liquidity. ### Downward, `zero_for_one` ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} new_liquidity = old_liquidity - net new_current_tick = T - 1 new_next_init_below = tick.prev new_next_init_above = T ``` The `T - 1` convention represents the state immediately below the crossed tick. ## Fee growth at the boundary The crossing flips both token fee-growth-outside accumulators: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} outside0_new = global0_current - outside0_old outside1_new = global1_current - outside1_old ``` These are wrapping Q128 values represented by two `u128` limbs. The step's LP fee is added to the applicable global accumulator before the flip. This ordering assigns the boundary step to the liquidity that was active while the price moved to the tick. The next step begins with the post-cross active set. The transition combines exact-limit crossing with the Q128 fee-growth layout. Clients must use the two-limb Q128 fields when applying the crossing update. ## Price-limit preservation The crossing updates tick, liquidity, pointers, and fee-growth state while preserving the caller's price cap. After exact-limit arrival, the next iteration sees a next-tick target that is clamped back to the same current price. Its price interval is zero, so it cannot consume additional swap input or produce output beyond the limit. Completing the crossing does not change these boundary-step values: * End square-root price * Step input consumed * Step output * Step fee * Unspent input at the limit It does change the public state from which the next quote and future LP accounting begin. ## Quoter state update An off-chain simulator should persist all post-cross fields even when the user limit equals the tick price: * Current tick * Active liquidity * Next initialized tick below * Next initialized tick above * Both fee-growth-outside values on the crossed tick * Applicable global Q128 fee growth * Protocol-fee accumulator Returning only the current order output can hide a state divergence. A market maker may quote the next order with ghost depth even though the previous output was numerically correct. ## Edge cases ### Insufficient input If the step stops before the initialized tick, `new_sqrt_price != target_tick_sqrt_price`, so there is no crossing. ### Limit between ticks The clamped target is the limit, not the initialized tick price. There is no crossing. ### Zero liquidity The tick walk can advance across a zero-liquidity gap without exchanging tokens. If it arrives at an initialized tick exactly at the limit, crossing can activate or deactivate liquidity according to the tick net. ### Final available iteration A crossing on the final unrolled iteration updates state, but any still-unspent input is stored for refund. The contract does not add another step beyond the fixed count. ### Invalid current-price limit The public finalize requires the limit to be strictly on the correct side of current square-root price. A caller cannot submit a limit equal to the slot's starting price to force a zero-distance current-tick crossing. For order controls, return to [Price limits, slippage, and deadlines](../trading/price-limits-slippage-deadlines). # Fixed-point conventions Source: https://shield.fi/docs/math/fixed-point-conventions Q128.128 price and fee-growth units in the current contract. ## Square-root price The current contract stores square-root price as `u256::U256` in Q128.128 form. ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} Q128 = 2^128 sqrt_ratio = sqrt_price_x128 / Q128 base-unit price(token1/token0) = sqrt_ratio^2 ``` At tick zero, `sqrt_price_x128 = 2^128`. The value is `{ hi: 1, lo: 0 }` in `u256::U256` limbs. Display price must also account for canonical token order and token display decimals. The contract itself uses native base units and stores no decimal scale. ## Fee growth Global, outside, and inside fee growth also use Q128.128 `u256::U256` values: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} fee_owed = floor((fee_growth_now - fee_growth_last) * liquidity / 2^128) ``` Growth subtraction wraps modulo `2^256`. Whole token amounts must still fit `u128`. ## Wide arithmetic Price and liquidity formulas use full-width multiplication and division. The contract selects floor or ceiling rounding for each operation. A quote engine must copy that order. Floating-point algebra is not transaction-safe. # Liquidity formulas Source: https://shield.fi/docs/math/liquidity-formulas Current Q128.128 amount and liquidity relationships. Let `P` be current Q128.128 square-root price. Let `A < B` be the lower and upper Q128.128 boundaries. Let `L` be liquidity. Amounts are native token base units. ## Amounts from liquidity Below the range: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} amount0 = L * 2^128 * (B - A) / (A * B) amount1 = 0 ``` Inside the range: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} amount0 = L * 2^128 * (B - P) / (P * B) amount1 = L * (P - A) / 2^128 ``` Above the range: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} amount0 = 0 amount1 = L * (B - A) / 2^128 ``` The contract applies explicit floor or ceiling rounding and saturation checks through wide helpers. ## Liquidity cap Pool creation calculates a net-liquidity-sum cap from the number of usable ticks and then selects the smaller value against `LIQ_AMOUNT_SAFE_CAP = 702075911466779181339691826086`. Read `Slot.max_liquidity_per_tick` from chain state. Do not reconstruct it from a legacy Q64 constant. ## Mint and increase The contract transfers the full desired amounts into public AMM custody, calculates the maximum supported liquidity, and then calculates the actual native base-unit amounts used. It credits `desired - used` to the position's public `tokens_owed` balances. The returned token change records contain only input-record value above the desired transfer amounts. # Q128.128 fee growth Source: https://shield.fi/docs/math/q128-fee-growth Wide fee-growth storage and position settlement in the current contract. Global, outside, and position checkpoint fee growth use `u256::U256` in Q128.128 form. ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} U256 { hi: u128, lo: u128 } packed = hi * 2^128 + lo ``` ## Growth per unit of liquidity ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} growth_delta = floor(lp_fee * 2^128 / active_liquidity) ``` The input-token global accumulator increases by this value. ## Growth inside a range The contract derives growth below and above the position from global growth, each boundary's outside growth, and the current tick. It subtracts both from global growth to get inside growth. Crossing an initialized tick flips its outside accumulator relative to current global growth. This makes the same boundary work in either crossing direction. ## Owed amount ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} owed = floor((inside_now - inside_last) * liquidity / 2^128) ``` The subtraction wraps modulo `2^256`. The wide product is shifted by 128 bits. The result must fit `u128`. The contract stores `u256::U256` directly. It does not declare a separate `FeeGrowth` struct. Client code can use a semantic wrapper type, but its wire parser must match `{ hi, lo }`. # Rounding, overflow, and invariants Source: https://shield.fi/docs/math/rounding-overflow-and-invariants Integer safety rules that a current Shield Swap quote engine must preserve. ## Numeric domains * Native token amounts, liquidity, owed balances, and protocol fees use `u128`. * Square-root price and fee growth use `u256::U256`. * Liquidity net uses `i128`. * Fee pips use a one-million denominator. * Q128.128 values use a `2^128` denominator. ## Rounding direction The contract chooses floor or ceiling per operation. Funding calculations commonly round required input up. Output and fee entitlement calculations commonly round down. Copy the implementation, including operation order, instead of simplifying formulas algebraically. ## Saturating amount helpers `amt0_x128_sat` and `amt1_x128_sat` return an amount and overflow flag. A caller must use the flag. It must not treat `u128::MAX` alone as evidence of overflow. ## Liquidity safety `LIQ_AMOUNT_SAFE_CAP` bounds positions so amount deltas fit through the complete tick domain. Pool creation also applies a net-liquidity-sum cap based on tick spacing. Mint and increase enforce the stored per-tick cap at both boundaries. ## Swap invariants * Price never moves past the user limit. * A bounded partial fill never jumps past the next initialized tick. * Input consumed, fee charged, refund, and output use the same capped arithmetic path. * Zero fee-adjusted input is refunded. * Stored tick, price, active liquidity, and outside fee-growth crossing parity remain consistent. * Multi-hop continuity is checked by concrete token ID. ## Position invariants * Lower tick is less than upper tick. * Both ticks align with spacing and lie in the user domain. * The NFT fields bind token ID, pool, range, owner, and immutable withdrawal address. * Burn requires zero liquidity and zero owed balances. Property tests, source vectors, and accepted-network tests should cover all branch boundaries. Floating-point agreement at ordinary values does not prove transaction equivalence. # Swap step Source: https://shield.fi/docs/math/swap-step Fee, boundary, output, and remainder behavior in the Q128.128 swap engine. A swap step starts with square-root price, liquidity, remaining raw input, fee tier, direction, and a target boundary. ## Fee-adjusted input ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} input_less_fee = floor(remaining * (1_000_000 - fee_pips) / 1_000_000) ``` If this value is zero, the bounded partial path returns the input. It does not charge the whole input as a zero-output fee. ## Boundary selection The engine chooses the nearer value in the swap direction: * next initialized-tick price; or * user price limit. The fixed crossing loop can process five initialized ticks for a single-hop swap and three per multi-hop leg. A bounded partial fill then moves only up to the selected boundary. It never jumps past an uncrossed initialized tick. ## Amount relationships For token0 input, price moves down. Input follows the amount0 delta and output follows the amount1 delta. For token1 input, price moves up and the roles reverse. All price formulas use Q128.128 and full-width division. ## Fees The contract calculates gross input consumed, swap fee, protocol share, LP fee growth, output, and remainder with one consistent capped amount. Protocol fee is `fee_charged * fee_protocol / 16`. The LP share increases global Q128.128 fee growth for the input token. ## Stored tick An upward fill that would stop exactly on an initialized tick is held one Q128.128 unit below that boundary when the tick cannot be crossed. This preserves the half-open liquidity convention. `resolve_stored_tick` then makes stored tick agree with stored price and crossing state. Quotes must reproduce these branch and rounding rules. A continuous constant-product formula is not sufficient. # Tick and price conversion Source: https://shield.fi/docs/math/tick-price-conversion Convert between ticks and square-root prices within the contract domain. Ticks discretize the price curve. The main program contains the canonical conversion functions used by pool creation, swaps, liquidity operations, and tests. ## Tick to square-root price `get_sqrt_price_at_tick` starts from the fixed-point unit and conditionally multiplies precomputed constants according to the absolute tick's bits. Positive and negative ticks use reciprocal directions. The function supports the configured domain from `-400000` through `400000`. Sentinel ticks lie outside the position domain and are list anchors, not price boundaries for user positions. ## Square-root price to tick `get_tick_at_sqrt_price`: 1. Finds the most significant bit. 2. Normalizes the value into the expected fixed-point interval. 3. Computes fractional logarithm bits. 4. Divides by the logarithm of the tick base. 5. Checks the next tick boundary. 6. Corrects a possible one-tick high estimate. The returned value is intended to satisfy: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} sqrt_price_at_tick(tick) <= sqrt_price sqrt_price < sqrt_price_at_tick(tick + 1) ``` ## Pool creation interval Pool creation does not accept an arbitrary tick and price pair. For `initial_tick = t`, it requires: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} sqrt_price_at_tick(t) <= initial_sqrt_price initial_sqrt_price < sqrt_price_at_tick(t + 1) ``` It also requires tick alignment and an exclusive upper bound below `MAX_TICK`. ## Directional limits For a token0-to-token1 swap, price moves downward and the limit must be below the current price. For token1-to-token0, price moves upward and the limit must be above. A limit can fall before the next initialized tick or exactly at it. Exact equality completes the crossing bookkeeping. The next iteration cannot trade beyond the limit. ## Client conversion For acceptance-critical inputs, port the integer algorithm and constants. Floating-point logarithms can produce an adjacent tick near boundaries. If a UI uses a decimal price, convert it to a conservative integer square-root price, verify direction, and simulate the contract interval before submission. Always state the quote convention, canonical token order, and display decimals alongside a displayed price. # Zero-effective-input guard Source: https://shield.fi/docs/math/zero-input-guard Why a tiny swap can return all input without moving price. The bounded partial path calculates: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} input_less_fee = floor(input * (1_000_000 - fee_pips) / 1_000_000) ``` For a small input and a nonzero fee, this value can be zero. The current contract treats that as no fill: * output remains zero; * square-root price remains unchanged; * fee growth remains unchanged; * protocol fees remain unchanged; and * all input remains in `SwapOutput.amount_remaining` for later claim. This rule prevents a trade from consuming input entirely as fee while producing no output. An application should calculate the same integer threshold before submission. It should warn the user or reject the quote when fee-adjusted input is zero. # Constants and limits Source: https://shield.fi/docs/reference/constants-and-limits Current numeric domains, loop bounds, fee settings, and proof limits. ## Price and tick domain | Constant | Value | | - | - | | `MIN_TICK` | `-400000` | | `MAX_TICK` | `400000` | | `MIN_TICK_SENTINEL` | `-400001` | | `MAX_TICK_SENTINEL` | `400001` | | `MIN_SQRT_RATIO_X128` | `{ hi: 0, lo: 702075911466779181339691826087 }` | | `MAX_SQRT_RATIO_X128` | `{ hi: 484680305, lo: 8756686347225649145659787327114459760 }` | | Q128.128 unit | `2^128` | | `LIQ_AMOUNT_SAFE_CAP` | `702075911466779181339691826086` | Pool creation accepts `initial_tick >= MIN_TICK` and `initial_tick < MAX_TICK`. A position upper tick can equal `MAX_TICK`. ## Swap execution | Path | Hop count | Initialized-tick iterations | | - | -: | -: | | Single-hop | 1 | 5 | | Multi-hop | 2 or 3 | 3 per hop | After the fixed tick-walk loop, `view_bounded_partial` can fill only as far as the nearer next initialized tick or price limit. It returns unconsumed input in `amount_remaining`. ## Fees The pool fee is a `u16` count of parts per million. Bootstrap registers 100, 500, 3000, and 10000 and binds them to tick spacings 1, 10, 60, and 200. `fee_protocol` is zero or an integer from 4 through 10. The protocol share is `fee_protocol / 16` of the swap fee. New pools use `DEFAULT_FEE_PROTOCOL = 5`. ## Proof limits `MerkleProof.siblings` has 16 fields. `MAX_MERKLE_TREE_DEPTH` is 15. Mint validates signer, owner recipient, and withdrawal non-inclusion. Collect validates NFT owner and withdrawal. Claim validates the signer. ## Integer types Token amounts, liquidity, owed balances, and protocol fees use `u128`. Square-root prices and fee growth use `u256::U256`. Signed liquidity net uses `i128`. Swap deadlines use Aleo block heights in `u32`. Swap nonces use `u64`. Mint nonces use `field`. The AMM uses native token base units directly. It has no on-chain decimal scale or normalization registry. # Entry-point signatures Source: https://shield.fi/docs/reference/entrypoint-signatures Selected core and router functions grouped by role. This page summarizes selected integration functions at contract source commit `b26091f`. The Leo source remains authoritative for tuple order and visibility. ## Core administration | Function | Public inputs | Output | | - | - | - | | `transfer_admin` | `new_admin: address` | `Final` | | `accept_admin` | none | `Final` | | `add_tick_spacing` | `tick_spacing: u32` | `Final` | | `add_fee_tier` | `fee: u16` | `Final` | | `bind_fee_to_tick_spacing` | `fee: u16`, `tick_spacing: u32` | `Final` | | `set_pool_enabled` | `pool_key: field`, `enabled: bool` | `Final` | | `set_pool_creation_is_open` | `is_open: bool` | `Final` | | `set_global_paused` | `paused: bool` | `Final` | | `allow_token` | `token_id: field`, `underlying_token_id: field` | `Final` | | `set_token_paused` | `token_id: field`, `paused: bool` | `Final` | | `set_pair_paused` | `token0_id: field`, `token1_id: field`, `paused: bool` | `Final` | | `freeze_position` | `token_id: field` | `Final` | | `unfreeze_position` | `token_id: field` | `Final` | | `set_fee_protocol` | `pool_key: field`, `fee_protocol: u8` | `Final` | | `collect_protocol` | pool, requested amounts, token IDs, recipient | `Final` | `allow_token` is a one-time operation. Pass the same ID twice for a plain ARC-20. Pass distinct wrapper and underlying IDs for a wrapped asset. ## Core pool and position lifecycle ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} create_pool( public token0_program_id: field, public token1_program_id: field, public fee: u16, public initial_sqrt_price: u256::U256, public tick_spacing: u32, public initial_tick: i32 ) -> (public field, public address, Final) ``` ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} mint( private nonce: field, token0_record: dyn record, token1_record: dyn record, private recipient: address, private withdrawal: address, public request: MintPositionRequest, public token0_id: field, public token1_id: field, signer_merkle_proofs: [MerkleProof; 2], recipient_merkle_proofs: [MerkleProof; 2], withdrawal_merkle_proofs: [MerkleProof; 2] ) -> (public field, PositionNFT, dyn record, dyn record, MintComplianceRecord, Final) ``` `decrease_liquidity(PositionNFT, public liquidity_to_remove, public amount0_min, public amount1_min)` returns a public token ID, a replacement `PositionNFT`, and `Final`. `increase_liquidity(PositionNFT, two token records, public desired and minimum amounts, public token IDs, public tick hints)` returns a public token ID, a replacement `PositionNFT`, two change records, and `Final`. `rebalance_position(PositionNFT, private nonce, public CoreRebalanceRequest, public RebalanceAssets, owner proofs, withdrawal proofs)` returns a new public token ID and `PositionNFT`. It also returns two refund records, a `MintComplianceRecord`, and `Final`. `collect(PositionNFT, public requested amounts, public token IDs, owner proofs, withdrawal proofs)` returns a replacement `PositionNFT`, two token records, and `Final`. It always pays the immutable `withdrawal` address in the NFT. `burn(PositionNFT)` returns the public token ID and `Final`. ## Core trading `swap(token record, private factor, public confidential address, pool, direction, amount, minimum output, Q128.128 price limit, nonce, block-height deadline, token IDs)` returns a public swap ID, a change record, a `SwapComplianceRecord`, and `Final`. `swap_multi_hop(private factor, public confidential address, token record, endpoint tokens, amount, minimum output, three hop slots, hop count, nonce, block-height deadline)` returns a public swap ID, a change record, a `MultiHopSwapComplianceRecord`, and `Final`. The finalizer accepts hop counts of two or three. `claim_swap_output(private factor, public confidential address, swap ID, input token, output token, output amount, remaining input amount, freezelist proofs)` returns output and refund records plus `Final`. The same claim function settles single-hop and multi-hop swaps. ## Core views | Function | Purpose | | - | - | | `view_sqrt_price_at_tick_x128` | Convert a tick to Q128.128 square-root price | | `view_swap_iteration` | Run one initialized-tick walk step | | `view_swap_iteration_v2` | Run one tick step and return the protocol fee separately | | `view_mul_div` | Run full-width multiply-divide with selected rounding | | `resolve_stored_tick` | Resolve the half-open stored tick after a bounded fill | | `view_bounded_partial` | Fill up to the next tick or price limit and return a remainder | | `view_bounded_partial_v2` | Fill to a boundary and return the protocol fee separately | ## Swap router `shield_swap_router.aleo` exposes: * `swap_from_wrapped` * `swap_mh_from_wrapped` * `claim_to_wrapped_refund_arc20` * `claim_to_arc20_refund_wrapped` * `claim_to_wrapped_refund_wrapped` ## LP router `shield_swap_lp_router.aleo` exposes: * `mint_from_wrapped_arc20`, `mint_from_arc20_wrapped`, `mint_from_wrapped_wrapped` * `increase_from_wrapped_arc20`, `increase_from_arc20_wrapped`, `increase_from_wrapped_wrapped` * `collect_to_wrapped_arc20`, `collect_to_arc20_wrapped`, `collect_to_wrapped_wrapped` * `collect_protocol_wrapped`, `collect_protocol_both_wrapped` Router functions unwrap or wrap the asset side that requires an adapter. They call the same core state machine and are not separate liquidity venues. ## Rebalance router `shield_swap_rebalance_router.aleo` exposes: * `rebalance_plain_plain_none`, `rebalance_plain_plain_one`, `rebalance_plain_plain_both` * `rebalance_wrapped_plain_none`, `rebalance_wrapped_plain_fund0`, `rebalance_wrapped_plain_fund1`, `rebalance_wrapped_plain_both` * `rebalance_plain_wrapped_none`, `rebalance_plain_wrapped_fund0`, `rebalance_plain_wrapped_fund1`, `rebalance_plain_wrapped_both` * `rebalance_wrapped_wrapped_none`, `rebalance_wrapped_wrapped_one`, `rebalance_wrapped_wrapped_both` The router selects the asset shape and optional funding mode. Every route calls the same atomic core Rebalance operation. # Error and rejection catalog Source: https://shield.fi/docs/reference/error-and-rejection-catalog Common assertion failures and the state to inspect. Leo assertions do not carry the application error codes a production SDK might prefer. Client and support tooling should classify rejections from request context and current state. ## Authorization * Caller is not the current admin. * `accept_admin` caller does not match `pending_admin`. * Position record is not owned by the signer. * Confidential address does not match program, signer, domain, and factor. * Claim caller or recipient does not match the stored confidential address. ## Pool creation * Pool creation is closed and caller is not admin. * Token is not allowed or is paused. * Pair or global state is paused. * Tokens are identical. * Pool already exists. * Fee or spacing is unregistered. * Fee-to-spacing binding does not match. * Initial tick is unaligned or outside the allowed domain. * Initial square-root price does not lie in the tick interval. ## Mint and increase * Pool is disabled or paused. * Token IDs do not match canonical pool tokens. * Signer, recipient, or withdrawal freezelist proof is invalid. * Range is invalid or unaligned. * Calculated liquidity is zero. * Used amounts do not meet minimums. * New boundary exceeds per-tick liquidity cap. * Insertion hint is not the immediate initialized predecessor. * Position token ID already exists. * Position is frozen. ## Decrease, collect, and burn * Position fields do not match the public mapping. * Requested removal exceeds liquidity. * Principal does not meet raw minimums. * Requested collection exceeds total owed. * Owner or withdrawal freezelist proof is invalid. * Position is frozen. * Burn sees nonzero liquidity or owed balances. ## Rebalance * Caller is not the configured rebalance router. * Signer does not own the current `PositionNFT`. * Old position, pool, token, or range data does not match public state. * Old or target liquidity is zero. * Recovered, funded, desired, and refund amounts do not balance. * A funded asset also has a nonzero refund. * Replacement minimum amounts do not equal desired amounts. * Owner or withdrawal freezelist proof is invalid. * Deadline, entry control, range, hint, or liquidity validation fails. ## Swap * Deadline has passed. * Global, token, pair, or pool control blocks trading. * Token IDs do not match the pool. * Price limit lies outside the domain or on the wrong side of current price. * Amount is zero. * Actual raw output is below `amount_out_min`. * Swap ID already exists. * Confidential address has already been used. * Wide arithmetic cannot represent the result. ## Multi-hop * Hop count is not two or three. * Token chain between hops is discontinuous. * Final route token does not match requested output. * Any hop has invalid pause, limit, pool, or arithmetic state. ## Claim * Pending output is absent, already claimed, or uses different fields. * Any token or amount differs from stored `SwapOutput`. * Signer freezelist proof is invalid. * Token program rejects a public-to-private transfer. ## Operator response Do not blindly retry a rejection. Refresh records, mappings, deadline, price, hints, and control state. Escalate repeated arithmetic, linked-list, or reconciliation failures as possible invariant incidents. # External references Source: https://shield.fi/docs/reference/external-references Primary sources for Shield Swap, Aleo, Leo, and concentrated-liquidity concepts. ## Shield Swap source The protocol and documentation repositories are private. No public source URL is published, so this page does not link to an address that returns a public 404. ## App and contract source baseline The app and contract guides were reconciled on September 4, 2026 against: * `ProvableHQ/amm-v3` development commit `b26091fa8e8d80a2b69aafeacaba0b44abace0f1` * `ProvableHQ/amm-v3-monorepo` `amm-app` development commit `b40bdbaedf2fb25c868ee21b081308d5c4d47dbf` The development and production deployment registries pin their own contract references and artifact hashes. A deployed program can lag the latest contract source. For a transaction, the selected environment's live program and matching registry take precedence over this source baseline. The API reference uses the versioned OpenAPI document in this repository. ## Aleo and Leo * [Aleo VM specification](https://developer.aleo.org/specs/aleovm.pdf) * [Encryption toolkit](https://developer.aleo.org/sdk/wasm/encryption_toolkit/) * [Transition SDK reference](https://developer.aleo.org/sdk/wasm/transition/) * [Record plaintext SDK reference](https://developer.aleo.org/sdk/wasm/record_plaintext) * [Intro to zkSNARKs and records](https://developer.aleo.org/concepts/advanced/intro_to_zksnark) Use the Leo and SDK versions selected for your deployment. Current online documentation may describe a newer release. ## Concentrated-liquidity context * [Uniswap concentrated liquidity](https://developers.uniswap.org/docs/get-started/concepts/liquidity-providers/concentrated-liquidity) * [Uniswap fee concepts](https://developers.uniswap.org/docs/get-started/concepts/fees) * [Uniswap V3 core repository](https://github.com/Uniswap/v3-core) These sources explain the broader model and terminology. Shield Swap has different execution, storage, confidentiality, routing, iteration, and arithmetic behavior. Use Shield Swap documentation and the reviewed deployment source for protocol-specific details. # Glossary Source: https://shield.fi/docs/reference/glossary Protocol, trading, and control terminology. **Active liquidity**\ Liquidity from positions whose half-open range contains the current tick. **Confidential address** A public swap address derived from program address, domain, signer, and a private factor. Its relationship to the signer is not disclosed in ordinary public state. **Caller**\ The account or program that directly invokes the current AMM function. **Claim**\ The transaction that converts a pending public `SwapOutput` into token records. **Compliance record**\ An encrypted record owned by the hardcoded investigator recipient and created by mint, rebalance, or swap. **Confidential**\ Record ownership or attribution omitted from the ordinary public ledger view. Public metadata and correlation may still reveal related activity. **Current tick**\ The discrete price interval associated with the stored square-root price. **Fee growth**\ Cumulative input-token fees per unit of active liquidity. **Fee pips**\ Pool fee parts per million. A value of 3000 is 0.30 percent. **Finalize**\ The on-chain stage that runs futures, checks public state, and updates mappings. **Freezelist** An operator restriction tree used by Shield Swap non-inclusion proofs. Mint, rebalance, collect, and claim check the addresses required by each action. **Frozen position**\ A position whose liquidity has been removed and whose position operations are blocked until admin unfreeze. **Gross liquidity**\ Total position liquidity referencing a tick boundary. **Inside fee growth**\ Fee growth attributed to the interval between a position's lower and upper ticks. **Investigator key**\ The address hardcoded as owner of compliance records. The corresponding view key can decrypt them. **Liquidity epoch**\ A period with a particular active-liquidity set. Mint, increase, rebalance, decrease, freeze, or crossing can change the set. **Liquidity net**\ Signed active-liquidity change associated with crossing an initialized tick upward. **Mapping**\ Public consensus storage keyed by a Leo value. **Position NFT**\ The Leo record that carries authority over a public position token ID. It is not an ARC-721 token. **Price limit**\ A directional square-root-price bound for a swap or hop. **Private**\ An Aleo input or record visibility class that omits the value from ordinary public arguments. **Protocol fee**\ Configured fraction of the swap fee accumulated separately for admin collection. **Q128.128**\ A fixed-point interpretation with 128 whole and 128 fractional bits. Square-root prices and fee growth use `u256::U256` in this form. **Raw amount**\ Token amount in the token program's native base units. **Record**\ An owner-bound encrypted Aleo state object that can be consumed and replaced. **Rebalance** A transaction that replaces a complete position with a new range and token ID without a swap. **Signer**\ The top-level account that authorized and signed the transaction. **Shield transaction** A transaction constructed by a Shield Swap integration to trade, claim output, or manage liquidity. **Slot**\ The mutable per-pool state containing price, current tick, active liquidity, fee values, and nearest initialized ticks. **Square-root price**\ The pool's Q128.128 square root of the native base-unit token ratio. **Tick**\ A discrete price boundary and, when initialized, a stored liquidity and fee-accounting node. **Token ID**\ A field value used to identify a dynamically dispatched token program. **Tokens owed**\ Public position accounting for settled principal, fees, and unused deposit amounts available to collect. # Mapping reference Source: https://shield.fi/docs/reference/mappings Every mapping in the current Shield Swap AMM program. | Mapping | Key | Value | Lifecycle and use | | - | - | - | - | | `pools` | `field` | `PoolState` | Created once; updated when the pool is enabled or disabled | | `slots` | `field` | `Slot` | Mutable price, liquidity, fee, protocol-fee, and tick-walk state | | `ticks` | `field` | `Tick` | Keyed by a hash of `TickKey`; non-sentinel entries are removed when gross liquidity reaches zero | | `initialized_pools` | `field` | `bool` | Prevents duplicate pool creation | | `tick_spacings` | `u32` | `bool` | Additive registry of approved spacings | | `fee_tiers` | `u16` | `bool` | Additive registry of approved fee pips | | `fee_to_tick_spacing` | `u16` | `u32` | Canonical spacing for new pools at a fee tier | | `positions` | `field` | `Position` | Keyed by position token ID; removed by `burn` or atomic rebalance | | `swap_outputs` | `field` | `SwapOutput` | Pending single-hop or multi-hop claim; removed by a successful claim | | `swap_execution_headers` | `field` | `SwapExecutionHeader` | Permanent execution height and hop count for an accepted swap | | `swap_execution_hops` | `SwapExecutionKey` | `HopExecution` | Permanent execution values for each accepted swap hop | | `admin` | `bool` | `address` | Uses key `true` for the current admin | | `pending_admin` | `bool` | `address` | Uses key `true`; removed by `accept_admin` | | `used_blinded_addresses` | `address` | `bool` | Rejects reuse of a confidential address | | `pool_creation_is_open` | `bool` | `bool` | Uses key `true`; false means admin-only creation | | `global_paused` | `bool` | `bool` | Uses key `true`; pauses entry and trading across the AMM | | `token_allowed` | `field` | `bool` | One-way pool-creation eligibility | | `token_paused` | `field` | `bool` | Token-wide entry and trading control | | `pair_paused` | `PairKey` | `bool` | Pair-wide control across fee tiers | | `frozen_position` | `field` | `u32` | Stores the freeze block height; removed on unfreeze | | `from_wrapper_token_id` | `field` | `field` | Maps an AMM wrapper token ID to its underlying token ID | | `to_wrapper_token_id` | `field` | `field` | Maps an underlying token ID to its AMM wrapper token ID | | `pool_creators` | `field` | `address` | Records the caller that created each pool | The current contract has no `token_decimals` mapping. Token metadata and display decimals live in the off-chain registry and API. ## Current state and history Mappings hold current consensus state. They are not logs. An indexer must preserve accepted transitions and removed values when historical reconstruction matters. The swap execution receipt mappings remain after claim. They cover accepted swap execution but do not record rejected transactions. ## Query behavior Some finalizers use `get_or_use`, while others require a value through `get`. A client must match the exact entry-point behavior. It must not apply one default rule to every mapping. ## Key construction * Pool keys hash sorted token IDs and the fee. * Tick keys hash the pool key and tick. * Pair-pause keys use sorted token IDs in a `PairKey`. * Singleton controls use boolean key `true`. See [Pools and identifiers](../concepts/pools-and-keys). # Record reference Source: https://shield.fi/docs/reference/records Position, compliance, and dynamic token records in the current protocol. Records are encrypted state objects. Their ciphertexts and commitments appear on chain. An authorized holder uses view material to decrypt their contents. ## `PositionNFT` | Field | Type | Purpose | | - | - | - | | `owner` | `address` | Account authorized to consume the record | | `withdrawal` | `address` | Immutable recipient for every later collect and nonzero rebalance refund | | `token_id` | `field` | Public position identifier | | `token0_id` | `field` | Canonical pool token0 ID | | `token1_id` | `field` | Canonical pool token1 ID | | `pool` | `field` | Pool key | | `tick_lower` | `i32` | Inclusive lower boundary | | `tick_upper` | `i32` | Exclusive upper boundary | Mint creates the record. Increase, decrease, and collect return a replacement for the same token ID. Rebalance consumes the old record and returns a new record with a new token ID and range. Burn consumes a record without replacement. `PositionNFT` is a Leo record, not an ARC-721 token. ## `SwapComplianceRecord` Single-hop swap creates this investigator-owned record. It contains `owner`, `swap_id`, `token_in`, `token_out`, the full `SwapRequest`, `caller`, `signer`, and `blinded_address`. ## `MultiHopSwapComplianceRecord` Multi-hop swap creates this investigator-owned record. It contains `owner`, `swap_id`, the full `SwapMultiHopRequest`, `caller`, `signer`, and `blinded_address`. ## `MintComplianceRecord` | Field | Type | Purpose | | - | - | - | | `owner` | `address` | Investigator recipient | | `token_id` | `field` | Public position identifier | | `token0_id` | `field` | Pool token0 ID | | `token1_id` | `field` | Pool token1 ID | | `nonce` | `field` | Private mint nonce disclosed to the record owner | | `request` | `MintPositionRequest` | Range, amounts, minimums, and hints | | `caller` | `address` | Immediate caller | | `signer` | `address` | Top-level signer | | `recipient` | `address` | `PositionNFT` owner | | `withdrawal` | `address` | Immutable payout address | Mint and rebalance create this investigator-owned record. A rebalance record describes the replacement position. ## Dynamic token records The AMM accepts and returns `dyn record` values. The selected token program defines the concrete record schema. Plain ARC-20, wrapped credits, and wrapped ARC-22 assets can therefore use different source programs and amount types at the router boundary. Use the deployment contract registry to resolve the AMM token program, underlying record program, token field ID, decimals, and record amount type for each asset. # Struct reference Source: https://shield.fi/docs/reference/structs Public and helper structures declared by the current Shield Swap AMM program. This page matches `shield_swap.aleo` at source commit `b26091f`. ## Market state ### `PoolState` `token0: field`, `token1: field`, `fee: u16`, `enabled: bool`. Token identifiers are sorted. The AMM stores and calculates all token amounts in each token program's native base units. The current struct has no decimal scale fields. ### `Slot` `tick: i32`, `tick_spacing: u32`, `sqrt_price: u256::U256`, `fee_protocol: u8`, `liquidity: u128`, `fee_growth_global0_x_128: u256::U256`, `fee_growth_global1_x_128: u256::U256`, `max_liquidity_per_tick: u128`, `protocol_fees0: u128`, `protocol_fees1: u128`, `next_init_below: i32`, `next_init_above: i32`. ### `Tick` `pool: field`, `liquidity_net: i128`, `liquidity_gross: u128`, `tick: i32`, `fee_growth_outside0_x_128: u256::U256`, `fee_growth_outside1_x_128: u256::U256`, `prev: i32`, `next: i32`. ### `Position` `token_id: field`, `pool: field`, `tick_lower: i32`, `tick_upper: i32`, `liquidity: u128`, `fee_growth_inside0_last_x_128: u256::U256`, `fee_growth_inside1_last_x_128: u256::U256`, `tokens_owed0: u128`, `tokens_owed1: u128`. ## Keys and identifiers ### `PoolKey` `token0: field`, `token1: field`, `fee: u16`. ### `PairKey` `token0: field`, `token1: field`. It omits the fee so one pair pause spans all fee tiers. ### `TickKey` `pool: field`, `tick: i32`. ### `SwapKey` `pool: field`, `zero_for_one: bool`, `amount_in: u128`, `sqrt_price_limit: u256::U256`, `recipient: address`, `nonce: u64`, `caller: address`. ### `TokenIDPreimage` `request: MintPositionRequest`, `recipient: address`, `nonce: field`. ### `RebalanceTokenIDPreimage` `request: MintPositionRequest`, `liquidity_target: u128`, `recipient: address`, `nonce: field`. ### `ChecksumEdition` `checksum: [u8; 32]`, `edition: u16`. The upgrade constructor hashes this value into a multisig operation ID. ### `MerkleProof` `siblings: [field; 16]`, `leaf_index: u32`. The AMM accepts two proofs for each non-inclusion check and permits a maximum effective tree depth of 15. ## Requests and outputs ### `MintPositionRequest` `pool: field`, `tick_lower: i32`, `tick_upper: i32`, `amount0_desired: u128`, `amount1_desired: u128`, `amount0_min: u128`, `amount1_min: u128`, `tick_lower_hint: i32`, `tick_upper_hint: i32`. ### `RebalanceAsset` `token_id: field`, `underlying_id: field`. ### `RebalanceAssets` `token0: RebalanceAsset`, `token1: RebalanceAsset`. ### `CoreRebalanceRequest` `old_liquidity: u128`, `recovered0: u128`, `recovered1: u128`, `funded0: u128`, `funded1: u128`, `refund0: u128`, `refund1: u128`, `liquidity_target: u128`, `mint: MintPositionRequest`, `deadline: u32`. ### `SwapRequest` `pool: field`, `zero_for_one: bool`, `amount_in: u128`, `amount_out_min: u128`, `sqrt_price_limit: u256::U256`, `recipient: address`, `nonce: u64`, `deadline: u32`. The deadline is an Aleo block height. ### `SwapHop` `pool: field`, `zero_for_one: bool`, `sqrt_price_limit: u256::U256`. ### `SwapMultiHopRequest` `token_in: field`, `token_out: field`, `amount_in: u128`, `amount_out_min: u128`, `recipient: address`, `hop0: SwapHop`, `hop1: SwapHop`, `hop2: SwapHop`, `hop_count: u8`, `nonce: u64`, `deadline: u32`, `caller: address`. The current finalizer accepts only two or three active hops. ### `SwapOutput` `recipient: address`, `caller: address`, `token_in: field`, `token_out: field`, `amount_out: u128`, `amount_remaining: u128`. Single-hop and multi-hop swaps use this same shape. A multi-hop output refunds only unspent original route input. It does not store one refund slot per hop. ### `SwapExecutionHeader` `executed_height: u32`, `hop_count: u8`. ### `SwapExecutionKey` `swap_id: field`, `hop_index: u8`. ### `HopExecution` `pool: field`, `zero_for_one: bool`, `amount_in: u128`, `amount_out: u128`, `fee_paid: u128`, `protocol_fee: u128`, `sqrt_price_after: u256::U256`, `liquidity_after: u128`, `tick_after: i32`. ## Swap-loop helpers ### `SwapIterCfg` `z: bool`, `lim: u256::U256`, `fee_pips: u32`, `fee_protocol: u8`, `slot_fg0: u256::U256`, `slot_fg1: u256::U256`. ### `SwapIterState` `sp: u256::U256`, `rem: u128`, `out: u128`, `fg: u256::U256`, `liq: u128`, `tk: i32`, `crossed: bool`, `pf: u128`, `nb: i32`, `na: i32`. `u256::U256` contains `hi: u128` and `lo: u128`. Fee growth and square-root prices both use this wide type, but they have different semantic units. # Authentication Source: https://shield.fi/docs/rest/authentication Create a wallet session for browser requests or mint an API token for a backend, bot, or command-line client. Pool metadata, pool analytics, Explore data, protocol state, and compliance reads are public. Routes, positions, and other protected endpoints require credentials. Choose the credential that matches your client: | Client | Credential | Storage rule | | - | - | - | | Browser application | Wallet session in httpOnly cookies | Let the browser manage cookies; keep the CSRF token in memory | | Backend, bot, or CLI | Long-lived `ss_...` API token | Store it in a secret manager or protected environment variable | Both flows begin with a wallet signature. The sign-in message includes the current Terms of Use and related disclosures. Successful verification records acceptance of that terms version. ## Create a browser wallet session Send the wallet address to `POST /auth/challenge`. ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} curl --fail-with-body \ "https://api.swap.shield.fi/auth/challenge" \ --header "Content-Type: application/json" \ --data '{"address":"aleo1..."}' ``` The response contains `data.challenge_id`, `data.nonce`, and `data.message`. Keep the challenge ID for verification. Ask the wallet to sign the exact UTF-8 bytes in `data.message`. Do not reconstruct, trim, or normalize the message. Signing proves control of the address. It does not submit a transaction or spend a token record. Send the address, signature, and returned challenge ID to `POST /auth/verify`. The example stores the returned cookies in `session.cookies`. ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} curl --fail-with-body \ "https://api.swap.shield.fi/auth/verify" \ --header "Content-Type: application/json" \ --cookie-jar session.cookies \ --data '{"address":"aleo1...","signature":"sign1...","challenge_id":"YOUR_CHALLENGE_ID"}' ``` A successful response sets access and refresh tokens as httpOnly cookies. The JSON body returns the wallet address, `expires_at`, `server_time`, session details, and a CSRF token. Use `expires_at - server_time` to calculate the remaining lifetime. Include the cookie jar on subsequent requests. Add the CSRF token in `X-CSRF-Token` for state-changing session requests. ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} curl --fail-with-body \ "https://api.swap.shield.fi/api-tokens" \ --cookie session.cookies ``` Access cookies expire after 15 minutes. `POST /auth/refresh` rotates the refresh token and sets fresh cookies. If two refreshes race, one can return `409`; retry the original API request after the successful refresh completes. ## Create an API token for a backend API-token creation requires an authenticated browser session and acceptance of the current Terms of Use. If a protected request returns `403` with code `terms_required`, repeat the wallet sign-in flow with a new challenge. ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} curl --fail-with-body \ "https://api.swap.shield.fi/api-tokens" \ --request POST \ --cookie session.cookies \ --header "Content-Type: application/json" \ --header "X-CSRF-Token: YOUR_CSRF_TOKEN" \ --data '{"name":"market-data-worker","expires_in_days":30}' ``` The response shows the full `ss_...` token once. Store it before closing the response. Later list responses expose only token metadata and a prefix. Use the token as a bearer credential: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} curl --fail-with-body \ "https://api.swap.shield.fi/route?token_in=TOKEN_A&token_out=TOKEN_B&amount_in=1000000" \ --header "Authorization: Bearer ss_YOUR_API_TOKEN" ``` ## Credential boundaries * A valid wallet signature establishes wallet control, not administrator authority. * API tokens cover protected data and route endpoints. API-token management and administrative endpoints require a wallet session. * WebSocket clients can request a short-lived ticket with a browser session or API token. They must renew authentication before the 60-second ticket expires. * Never place session cookies, CSRF tokens, or API tokens in URLs or logs. * Revoke a backend token with `DELETE /api-tokens/{id}` when it is no longer needed. * Use `GET /auth/sessions` and `POST /auth/sessions/{session_id}/revoke` to manage wallet sessions. * Use `POST /auth/logout` for the current session or `POST /auth/logout-all` for every wallet session. # REST API overview Source: https://shield.fi/docs/rest/overview Read Shield Swap markets, routes, indexed activity, positions, and protocol state over HTTPS. Use the REST API when your application needs indexed market data or protocol state without reading every protocol mapping itself. ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} https://api.swap.shield.fi ``` Mainnet is the documentation default. The network selector in the upper-right corner changes REST, WebSocket, OpenAPI, and example URLs across the site. A selection is stored in the browser and persists across pages and reloads. The API does not sign, prove, submit, or reconcile Shield transactions for your application. ## What the API provides * Pool and token discovery, including Explore market lists and transaction feeds * Pool state, statistics, trades, candles, and oracle snapshots * Indexed positions and unclaimed outputs * Routes with up to three hops, route topology, fee tiers, protocol state, and compliance controls * Live update notifications over WebSocket ## Access model The API has three access levels. | Access | Use it for | Credential | | - | - | - | | Public | Pool metadata and analytics, token lists, Explore data, protocol state, and compliance reads | None | | Application | Routes, positions, unclaimed outputs, initialized ticks, rebalance state, route topology, and fee tiers | Wallet session or `ss_...` API token, with current terms accepted | | Session-only | API-token management, referral code redemption and management, and administrative operations | Browser wallet session; CSRF token for state-changing requests | An API token does not grant administrative privileges. The testnet faucet and diagnostic helpers require an enabled deployment policy. The current mainnet deployment disables them. ## Pool oracle Use `GET /pools/{key}/oracle?window_seconds=1800` for a public indexed oracle snapshot. Supported windows are 300, 1800, 3600, and 86400 seconds. The default is 1800 seconds. The response uses stored snapshots and quotes. Check `observed_block`, `evaluated_at`, `start_at`, and `end_at` before using the result. Window metrics include mean tick, tick variance, harmonic liquidity, and zero-liquidity duration. The `totals` object contains cumulative counters, not totals for the selected window. A `409` response can indicate `oracle_uninitialized` or `oracle_history_unavailable`. A `503` response can indicate `twap_not_deployed` or `oracle_source_unavailable`. Do not substitute zero values for unavailable oracle data. See [Pagination and errors](./pagination-and-errors) for retry guidance. ## Choose the next page | Goal | Start here | | - | - | | Confirm the API is reachable | [List pools](./quickstart) | | Authenticate a browser or backend | [Authentication](./authentication) | | Build a market-data service | [Build a trading bot](./trading-bot) | | Subscribe to indexed changes | [WebSocket feed](./websocket) | | Handle pagination and retries | [Pagination and errors](./pagination-and-errors) | ## Service endpoints | Service | URL | | - | - | | REST | `https://api.swap.shield.fi` | | WebSocket | `wss://ws.swap.shield.fi/ws` | | OpenAPI | `https://api.swap.shield.fi/openapi.json` | | Standalone reference | `https://api.swap.shield.fi/docs/` | ## Exact values Financial values use JSON strings. Preserve token amounts, liquidity, fees, revisions, and field identifiers exactly. Do not pass them through JavaScript `number` or localized number formatting. Routing and indexed state can change before a Shield transaction finalizes. Apply your own price limit, minimum output, deadline, record checks, and final-state reconciliation. # Pagination and errors Source: https://shield.fi/docs/rest/pagination-and-errors Paginate Shield Swap API collections, interpret HTTP errors, and implement safe retries for long-running clients. Most collection endpoints use offset pagination. The Explore transaction feed uses a cursor. Error responses contain an `error` string and can include a machine-readable `code`. ## Offset pagination Pass `limit` and `offset` as query parameters: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} curl "https://api.swap.shield.fi/pools?limit=50&offset=0" ``` Pool listing and Explore token and pool lists accept a page size from 1 through 100. Begin with offset `0`, advance the offset by the number of returned items, and stop when the response metadata or an empty page indicates completion. Do not assume that an offset identifies a permanent snapshot. New indexed activity can change collection ordering while a bot paginates. For reconciliation jobs, record stable resource identifiers and tolerate duplicates between pages. ## Explore transaction cursor Request `GET /explore/transactions?limit=50` for the first page. Pass the returned `nextCursor` as the `cursor` query parameter for the next page. Stop when `nextCursor` is `null`. Do not calculate a cursor or use an offset for this feed. Keep the `window`, `type`, `token`, and `pool` filters unchanged between pages. Supported windows are `1h`, `1d`, `1w`, and `1m`. Start a new request without a cursor when you change a filter. ## Error shape Errors use this general structure: ```json theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} { "error": "error description" } ``` | Status | Meaning | Client action | | - | - | - | | `400` | Invalid parameters or request body | Correct the request before retrying | | `401` | Missing, invalid, or expired authentication | Refresh the wallet session or replace the API token | | `403` | Current terms are not accepted, or permission is missing | For `terms_required`, sign in with a new challenge; otherwise check permissions | | `404` | Resource or route was not found | Verify identifiers or treat the route as unavailable | | `409` | State conflicts with the request | Read the error code; oracle history can be unavailable, or a session refresh can already be in progress | | `429` | Too many requests | Wait and retry with backoff | | `500` or `503` | Server, dependency, deployment, or configuration failure | Retry only transient failures with a bounded policy | ## Retry policy Retry network failures, `429`, and transient `5xx` responses with exponential backoff and jitter. Honor `Retry-After` when present. An oracle `twap_not_deployed` error requires deployment support before the request can succeed. Set both a request timeout and a maximum number of attempts. Do not automatically retry non-idempotent write requests unless the endpoint provides an idempotency mechanism or the client can prove the previous attempt did not take effect. ## Large integers Preserve token amounts, liquidity values, and other on-chain integers exactly. When a response represents an integer as a JSON string, parse it with an arbitrary-precision integer type such as JavaScript `BigInt` rather than `number`. # REST API quickstart Source: https://shield.fi/docs/rest/quickstart List Shield Swap pools, inspect the response, and use a pool key in a second public request. This quickstart uses `GET /pools`, a public endpoint. You do not need an account, wallet signature, or API token. Send a request to the selected network. Mainnet is the default; use the network selector in the upper-right corner to switch every example on this site to testnet. ```bash cURL theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} curl --fail-with-body \ "https://api.swap.shield.fi/pools?limit=10&offset=0" ``` ```javascript JavaScript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const url = new URL("https://api.swap.shield.fi/pools"); url.search = new URLSearchParams({ limit: "10", offset: "0" }); const response = await fetch(url); if (!response.ok) { throw new Error(`GET /pools returned ${response.status}`); } const pools = await response.json(); console.log(pools.data); ``` ```python Python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import json from urllib.request import urlopen url = "https://api.swap.shield.fi/pools?limit=10&offset=0" with urlopen(url, timeout=10) as response: pools = json.load(response) print(pools["data"]) ``` A successful response has a `data` array and a `pagination` object. Each pool includes its identifiers, enabled state, creation fee, and current fee. ```json theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} { "data": [ { "key": "3748...8179field", "token0": "2118...3732field", "token1": "4096...6596field", "enabled": true, "fee_percent": "30", "current_fee_percent": "30" } ], "pagination": { "total": 4, "limit": 10, "offset": 0 } } ``` The example shows selected fields and shortened identifiers. Treat pool keys, token IDs, amounts, and fees as exact strings in application code. Both fee fields use basis points: `"30"` means 0.30%. The creation value, `fee_percent`, remains part of the pool identity. The live value, `current_fee_percent`, is the current fee before Pricing Terms reductions. It is `null` when its source is unavailable. Copy the exact `key` from a result and request that pool: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} curl --fail-with-body \ "https://api.swap.shield.fi/pools/POOL_KEY" ``` Replace `POOL_KEY` with the complete field value returned by `GET /pools`. Do not derive a pool key from token symbols. ## Success check You are done when: * both requests return HTTP `200` * `data` contains the expected pool * the returned pool key is unchanged between requests * your client preserves integer-like values as strings ## Before you call other endpoints Pool metadata, pool analytics, token lists, Explore data, protocol state, and compliance reads are public. Routes, positions, unclaimed outputs, initialized ticks, and rebalance state require a wallet session or an `ss_...` API token with current terms accepted. Read [Authentication](./authentication) before adding those endpoints to a backend or bot. ## Continue from here * [Choose an integration path](../developers/integration-path) if you are deciding between API data, the TypeScript SDK, and direct program calls. * [Build a trading bot](./trading-bot) if you need routes, history, and live updates. * [Browse the API reference](./overview) for endpoint access requirements and exact schemas. # Build a market-data bot Source: https://shield.fi/docs/rest/trading-bot Discover Shield Swap markets, load indexed state, request routes, and recover after data-source failures. This guide covers data collection and route lookup. It does not cover record selection, proving, transaction submission, or claims. ## Before you start You need: * Node.js with built-in `fetch` * a wallet that has accepted the current Terms of Use * an `ss_...` API token stored outside source control * exact-integer handling for token amounts and field identifiers Pool and token discovery, pool analytics, and Explore market reads are public. Route requests need the API token. ## 1. Create an API client ```javascript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const baseUrl = "https://api.swap.shield.fi"; const apiToken = process.env.SHIELD_SWAP_API_TOKEN; if (!apiToken) { throw new Error("Set SHIELD_SWAP_API_TOKEN before starting the bot"); } async function getJson(path, { publicEndpoint = false } = {}) { const response = await fetch(`${baseUrl}${path}`, { headers: publicEndpoint ? {} : { Authorization: `Bearer ${apiToken}` }, }); if (!response.ok) { throw new Error(`${path} returned ${response.status}`); } return response.json(); } ``` Create the token through the wallet-session flow in [Authentication](./authentication). ## 2. Discover tokens and pools ```javascript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const [{ data: tokens }, { data: pools }] = await Promise.all([ getJson("/tokens", { publicEndpoint: true }), getJson("/pools?limit=100&offset=0", { publicEndpoint: true }), ]); ``` Continue requesting pool pages until `data` is empty or `pagination` shows that all rows have been returned. Keep the exact pool key, token IDs, fee fields, and scale values. The `fee_percent` field records the creation fee. Use `current_fee_percent` for the live fee before Pricing Terms reductions, when available. Both fields use basis points. ## 3. Load the data your strategy uses Do not fetch every endpoint for every pool. Request only the resources needed by the strategy: * `GET /pools/{key}` for public pool metadata * `GET /pools/{key}/stats` for current indexed statistics * `GET /pools/{key}/trades` for trade history * `GET /pools/{key}/ohlcv` for candles * `GET /explore/transactions` for the public market transaction feed These endpoints are public. Call `getJson` with `{ publicEndpoint: true }` for each one. Use `GET /pools/{key}/oracle` for an indexed oracle snapshot. See [Pool oracle](./overview#pool-oracle) for supported windows and unavailable-data states. Use offset pagination for Explore token and pool lists. Use the returned `nextCursor` for Explore transactions. See [Pagination and errors](./pagination-and-errors). When the API encodes an integer amount as a JSON string, parse it with an arbitrary-precision integer type. Never pass it through JavaScript `number`. ## 4. Request a route ```javascript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const query = new URLSearchParams({ token_in: inputToken, token_out: outputToken, amount_in: "1000000", }); const route = await getJson(`/route?${query}`); ``` The router returns at most three hops. A route is current indexed guidance, not an execution guarantee. Before submission, apply the transaction's price limit, minimum output, deadline, record checks, and final-state reconciliation. ## 5. Refresh state safely Use request timeouts, bounded exponential backoff, jitter, and a maximum retry count. | Response | Bot action | | - | - | | `401` | Replace or reissue the API token | | `403` | For `terms_required`, repeat wallet sign-in; otherwise check permissions | | `404` from `/route` | Treat the route as unavailable | | `429` | Wait before retrying | | transient `5xx` | Retry within the bounded policy | The bot can request a short-lived WebSocket ticket with its API token or a browser wallet session. Follow the [WebSocket feed](./websocket). After any disconnect, reload REST state before acting on new events. ## Transaction boundary The API supplies indexed data and routes. Authenticated clients can read `GET /pools/{key}/initialized-ticks` when constructing transactions. Each tick includes its exact `mapping_key`; preserve that value for mapping reads and transaction inputs. Do not use the tick number as its mapping key. Your wallet or proving client still owns: * token-record selection and reservation * transaction construction and signing * proof generation and submission * swap-output claim handling * accepted, rejected, and unknown transaction states * final public-state and record reconciliation Continue with [Swap](../trading/swap#developers) when the bot must submit trades. # WebSocket feed Source: https://shield.fi/docs/rest/websocket Authenticate, renew, subscribe, synchronize, and recover current live feeds. Use WebSocket events as invalidation hints. REST remains the source of truth. ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} wss://ws.swap.shield.fi/ws ``` ## Authentication and renewal 1. Request a ticket from `GET /auth/ws-ticket` with a browser session or `ss_...` API token. 2. Open the socket and send `authenticate` within five seconds. 3. Subscribe only after authentication succeeds. 4. Request and send a new ticket before the 60-second ticket expires. Use the ticket response fields `expires_at` and `server_time` to calculate its remaining lifetime. Renew before that duration ends, rather than relying only on the local clock. The socket accepts only WebSocket tickets. It rejects session JWTs and API tokens in the frame. ```json theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} {"action":"authenticate","token":""} ``` Reauthentication must use the same principal. An expired ticket ends the connection if no valid renewal extends it. ## Subscribe and synchronize ```json theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} {"action":"subscribe","room":"trades:"} {"action":"unsubscribe","room":"trades:"} {"action":"synchronize"} ``` After reconnecting, restore subscriptions and send `synchronize`. Wait for `{"control":"synchronized"}`. Then refetch every REST resource that depends on the stream. ## Rooms | Room | Purpose | | - | - | | `pool_launches` | Newly indexed pools | | `pool_stats:` | Price and liquidity changes | | `ohlcv:` | Candle updates | | `trades:` | Swap and LP activity | | `balances:
` | Balance-change hints for the authenticated ticket subject | | `protocol_config` | Revisioned protocol-configuration invalidations | One connection can subscribe to at most 32 rooms. It accepts at most 240 client frames per minute and 2 KB per frame. ## Events and controls Event types are `PoolLaunch`, `PoolStats`, `Ohlcv`, `Trade`, `BalanceChange`, and `ProtocolConfigChanged`. Treat `ProtocolConfigChanged` as invalidation data. Fetch: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} GET /protocol/state?minimum_revision= ``` If the API returns `503 protocol_revision_pending`, wait for `Retry-After`. Discard or requote any route whose `protocol_revision` is older than the event revision. Known controls are `synchronized` and `resync_required`. On `resync_required`, refetch affected REST state. Ignore unknown future control values. ## Delivery and reconnect Delivery is at most once. The server does not replay missed events. During deployment it can close with code 1012. Reconnect with backoff and jitter, obtain a fresh ticket, restore rooms, synchronize, and reload REST snapshots. # Manage liquidity with the TypeScript SDK Source: https://shield.fi/docs/sdk/liquidity Preview, mint, manage, collect, and close a Shield Swap liquidity position. A Shield Swap position has public economic state and a `PositionNFT` record. The record authorizes position changes. Its token ID remains stable while replacement records are issued through the position lifecycle. ## Preview a position Choose a pool and read balances for both tokens. `previewMint` aligns the range to tick spacing, calculates the supported liquidity, and reports the token amounts the position would consume. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const { data: pools } = await client.api.getPools({ limit: 20 }) const market = pools.find((pool) => pool.token0_info && pool.token1_info) if (!market) throw new Error("no pool with token metadata") const balances = await client.getBalances({ tokens: [market.token0, market.token1], }) const preview = await client.previewMint({ poolKey: market.key, amount0Desired: (balances[market.token0]?.private ?? 0n) / 10n, amount1Desired: (balances[market.token1]?.private ?? 0n) / 10n, rangePercent: 5, }) if (preview.liquidity === 0n) { throw new Error("deposit is too small for this range") } ``` You can pass `tickLower` and `tickUpper` together instead of `rangePercent`. `preview.inRange` tells you whether the position will earn fees at the current tick. State can move between preview and finalization, so set minimum amounts when the application needs tighter deposit protection. ## Mint Resolve token programs, then use the aligned range and consumed amounts returned by the preview. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const token0 = await client.tokenData(market.token0) const token1 = await client.tokenData(market.token1) if (!token0.ammTokenProgram || !token1.ammTokenProgram) { throw new Error("pool token program is missing") } const imports = await client.resolveDexImports({ tokenPrograms: [token0.ammTokenProgram, token1.ammTokenProgram], }) const minted = await client.mint({ poolKey: market.key, tickLower: preview.tickLower, tickUpper: preview.tickUpper, amount0Desired: preview.amount0, amount1Desired: preview.amount1, recipient: account.address, withdrawal: account.address, imports, }) if (!minted.positionTokenId) { throw new Error("position token ID is not available yet") } const positionTokenId = minted.positionTokenId ``` `recipient` receives the `PositionNFT` and controls the position. `withdrawal` receives every later `collect` payout. The withdrawal address is fixed at mint and cannot be changed. Confirm both addresses before submission. They may be different from each other and from the transaction signer. Mint creates the first `PositionNFT`. Store its token ID and wait for the new record before offering another position action. ## Find owned positions ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const positions = await client.getOwnedPositions() const position = await client.getOwnedPosition({ positionTokenId, }) ``` Mapping state may appear before the replacement record reaches the scanner. Poll for the expected record after each accepted action. ## Increase liquidity An increase spends the current position record and both token records. It keeps the same position token ID and issues an updated `PositionNFT`. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} await client.increaseLiquidity({ poolKey: market.key, positionTokenId, amount0Desired: 100_000n, amount1Desired: 200_000n, imports, }) ``` The tick range and withdrawal address do not change. A local-key client can select records through its scanner. A wallet client must supply its position and token record requests. ## Decrease and collect `decreaseLiquidity` moves principal and accrued fees into the position's owed balances. It does not transfer token records to the LP. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} await client.decreaseLiquidity({ poolKey: market.key, positionTokenId, liquidityToRemove: 500_000n, }) await client.collect({ poolKey: market.key, positionTokenId, amount0Requested: 100_000n, amount1Requested: 200_000n, imports, }) ``` Collect sends token records to the immutable withdrawal address stored in the `PositionNFT`. It has no recipient parameter. Increase, decrease, and collect each consume the current position record and issue a replacement, so wait for that replacement before the next action. ## Close the position Burn succeeds after liquidity and both owed balances reach zero. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} await client.burn({ poolKey: market.key, positionTokenId, }) ``` The closing order is: 1. Decrease all remaining liquidity. 2. Collect all owed token amounts. 3. Confirm the latest replacement `PositionNFT` is available. 4. Burn the empty position. Burn consumes the final record and removes the public position. It does not issue a replacement. See [Liquidity provider workflow](../liquidity/lp-workflow), [minting a position](../liquidity/minting-a-position), and [collecting and burning](../liquidity/collecting-and-burning) for the contract-level behavior. # Swap with the TypeScript SDK Source: https://shield.fi/docs/sdk/swaps Plan, submit, claim, and recover private swaps with the TypeScript SDK. A Shield Swap trade has two transactions. The request spends the input record and writes a pending output. The claim delivers the output and any unspent input as token records. Handle both transactions in your integration. The trade settles once the claim is accepted and the returned records are available. ## Plan the route In v0.12.1, you can request a quote with `client.quote({ from, to, amountIn: "1", slippageBps: 50 })` and submit it with `client.swap({ quote })`. See the [Quickstart](../trading/quickstart) for a complete example. Use the manual steps below if you need to supply execution parameters yourself. `planSwap` resolves token metadata, requests a route and quote, checks each pool against current chain controls, applies a slippage floor, and fetches the required program imports. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import { parseUnits } from "@provablehq/shield-swap-sdk" const from = await client.tokenData("USDCx") const plan = await client.planSwap({ from: from.id, to: "ETH", amountIn: parseUnits("1.5", from.decimals), slippageBps: 50, }) ``` Amounts passed to transaction actions are raw base units. `50` basis points means 0.5 percent slippage. ## Submit the request Use `swap` for one pool and `swapMultiHop` for a route with two or more pools. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const handle = plan.multiHop ? await client.swapMultiHop({ poolKeys: plan.poolKeys, tokenInId: plan.from.id, amountIn: plan.amountIn, expectedOut: plan.expectedOut, slippageBps: plan.slippageBps, imports: plan.imports, }) : await client.swap({ poolKey: plan.poolKeys[0]!, tokenInId: plan.from.id, amountIn: plan.amountIn, expectedOut: plan.expectedOut, slippageBps: plan.slippageBps, imports: plan.imports, }) ``` The returned handle contains the data needed to claim the output. A local client with `fileBlindedIdentityStore` records it before the action returns. If you do not use that store, save the handle in your own database before ending the request flow. Do not submit another request because a confirmation wait timed out. Check the transaction and mapping state first. A second submission can spend another record and create another trade. ## Claim the output The pending output becomes readable after the request finalizes. Both route types use `claimSwapOutput`. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import { SwapOutputNotFinalizedError } from "@provablehq/shield-swap-sdk" async function claimWhenReady() { for (let attempt = 0; attempt < 20; attempt++) { try { return await client.claimSwapOutput({ handle, imports: plan.imports, }) } catch (error) { if (!(error instanceof SwapOutputNotFinalizedError)) throw error await new Promise((resolve) => setTimeout(resolve, 15_000)) } } throw new Error("swap output is still pending") } const claim = await claimWhenReady() console.log(claim.amountOut, claim.amountRemaining) ``` `amountOut` is the received output. `amountRemaining` is the unused portion of the original input. Wrapped assets are paid out as records of the underlying asset. For a multi-hop route, each later hop must consume its intermediate input. A partial fill in a later hop rejects the route. Only unused original input can appear as the final refund. ## Verify settlement Use `publicClient.getConfirmedTransaction({ id })` for both the request and claim transaction IDs. Both must report `accepted`. Then check that `client.getSwapOutput({ swapId: handle.swapId })` returns `null` and that the account has the claim's returned token records. An absent pending output or an empty `getUnclaimedSwaps()` result does not establish settlement on its own. Match the accepted claim to the swap ID and its output commitments to the decrypted records. See [Verify swap settlement](../trading/verify-settlement) for the complete quickstart verifier. ## Recover pending claims For long-running local clients, check the configured identity store at startup. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const pending = await client.getUnclaimedSwaps() for (const swap of pending.swaps) { if (!swap.claimable || !swap.handle) continue await client.claimSwapOutput({ handle: swap.handle, }) } ``` `getUnclaimedSwaps` reads `swap_outputs` from chain. It returns per-token totals and handles that can be claimed from the store alone. In v0.12.1, `claimSwapOutput` resolves program imports from the output tokens automatically. Older records without a complete handle may require `reconcileSwapHistory()`. ## Run concurrent swaps safely The composed client reserves a distinct confidential address for each local swap. Use one store and one writer for each local account. A file store protects reservations across restarts, but it does not coordinate multiple processes or machines. Two concurrent swaps cannot spend the same input token record. Using different input tokens avoids that collision. For the same token, serialize record selection and submission or reserve distinct records in your own transaction queue. A connected wallet manages its own confidential addresses and record requests. Follow the wallet's pending-transaction model and wait for a terminal state before reusing any record. See [Trader workflow](../trading/trader-workflow), [output claims and refunds](../trading/output-claims-and-refunds), and [failures and recovery](../trading/failures-and-recovery) for the protocol rules behind these actions. # Set up the TypeScript SDK Source: https://shield.fi/docs/sdk/typescript-client Set up the Shield Swap TypeScript SDK for chain reads, API access, swaps, and liquidity. `@provablehq/shield-swap-sdk` adds typed Shield Swap actions to a Veil client. Use the extended wallet `client` for Shield Swap reads, writes, and records; `publicClient` for general ledger queries such as transaction acceptance; and `client.api` for indexed REST methods. The SDK handles record selection, program imports, swap planning, confidential addresses, and position math for applications that sign Shield transactions. This guide targets SDK version 0.12.1. For a complete testnet trade, start with the [Quickstart](../trading/quickstart). ## Install ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} pnpm add @provablehq/shield-swap-sdk@0.12.1 @provablehq/veil-core@0.12.1 ``` Local-key clients also need the network package, which installs the underlying Provable SDK. ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} pnpm add @provablehq/veil-aleo-sdk@0.12.1 ``` ## Create a local client This example uses mainnet. Change `loadNetwork("mainnet")` to `loadNetwork("testnet")` for testnet. `shieldSwapActions({ api: {} })` selects the Shield API host for the client's network; you do not need to supply its URL. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import { loadNetwork } from "@provablehq/veil-aleo-sdk" import { shieldSwapActions } from "@provablehq/shield-swap-sdk" import { swapFileStore } from "@provablehq/shield-swap-sdk/node" const aleo = await loadNetwork("mainnet") const privateKey = process.env.SHIELD_SWAP_PRIVATE_KEY if (!privateKey) throw new Error("Set SHIELD_SWAP_PRIVATE_KEY before continuing") const { walletClient, publicClient, account } = aleo.createAleoClient({ privateKey }) const client = walletClient.extend( shieldSwapActions({ api: {}, blindedIdentities: swapFileStore( `.shield-swap/mainnet/${account.address}/swaps.json`, ), }), ) ``` `createAleoClient` configures the hosted gateway, record scanner, delegated proving, and FeeMaster payment by default. The default gateway needs no Provable API credentials. Pass `useFeeMaster: false` when the account should pay its own transaction fees. The default confidential-address store protects concurrent swaps through one client, but it lives in memory and is lost when the process exits. For bots and services, use `swapFileStore`, an alias of `fileBlindedIdentityStore`, to retain pending claims across restarts. Use a separate store for each network and account. Change the path when switching this example to testnet, and keep `.shield-swap/` out of source control. Keep private keys, Provable credentials, API tokens, and confidential-address files out of source control. ## Connect a wallet The wallet holds keys and records and proves transactions. Pass Shield Swap's algorithm grants when connecting, then extend the wallet client. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import { createWalletClient } from "@provablehq/veil-core" import { fromWalletAdapter } from "@provablehq/veil-aleo-wallet-adapter" import { SHIELD_SWAP_ALGORITHM_GRANTS, shieldSwapActions, } from "@provablehq/shield-swap-sdk" await adapter.connect(network, decryptPermission, { algorithmsAllowed: SHIELD_SWAP_ALGORITHM_GRANTS, }) const { account, transport } = fromWalletAdapter(adapter) const client = createWalletClient({ account, transport }).extend( shieldSwapActions({ api: {} }), ) ``` A connected wallet supplies record requests to transaction actions. The local-key client can find its own records through the configured scanner. ## Authenticate the API Pool and token discovery and public market data do not require credentials. Routes, positions, and unclaimed outputs require authentication and acceptance of the current Terms of Use. `getBalances` reads records and chain state without a DEX API credential. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} await client.authenticateShieldSwap() ``` `authenticateShieldSwap()` signs a challenge and stores the session on the API client. Long-running services can create an API token once and pass it as `shieldSwapActions({ api: { apiToken } })` on later runs. See [Authentication](../rest/authentication). ## Read state Use the API to discover markets and metadata. Before signing, read the pool state needed for execution from chain. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const pools = await client.api.getPools({ limit: 50 }) const balances = await client.getBalances() const market = pools.data[0] if (!market) throw new Error("no pools are available") const poolKey = market.key const pool = await client.getPool({ poolKey }) const slot = await client.getSlot({ poolKey }) const controls = await client.getTradeControls({ poolKey }) if (!pool || !slot || !controls.tradeable) { throw new Error("pool is unavailable") } ``` Token amounts use raw base units and `bigint`. Apply token decimals only when parsing user input or formatting output. Pool price and fee-growth values use Q128.128 fixed-point representation. For transaction acceptance, call `await publicClient.getConfirmedTransaction({ id: transactionId })` and inspect `status`. This method is not on the extended wallet `client` by default. See [Verify swap settlement](../trading/verify-settlement) to check the swap, claim, and returned records together. ## Resolve program imports Shield Swap calls token programs dynamically. Write actions need the source for each token program plus the AMM's declared imports. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const token0 = await client.tokenData(pool.token0) const token1 = await client.tokenData(pool.token1) if (!token0.ammTokenProgram || !token1.ammTokenProgram) { throw new Error("pool token program is missing") } const imports = await client.resolveDexImports({ tokenPrograms: [token0.ammTokenProgram, token1.ammTokenProgram], }) ``` Resolve imports once for a route or pool and reuse the map. Continue with [SDK swaps](./swaps) or [SDK liquidity](./liquidity). The [package source and examples](https://github.com/ProvableHQ/veil/tree/main/packages/shield-swap) document the full SDK. # Administrative control risks Source: https://shield.fi/docs/security/admin-control-risks Capabilities, concentration, misuse cases, and mitigating procedures. The admin controls market availability, fee settings, asset admission, protocol-fee collection, and position freezes. ## Capabilities The admin can: * Add fee tiers and tick spacings * Bind or rebind a fee to a spacing * Register token decimals once * Allow tokens for pool creation * Open or close public pool creation * Enable or disable a pool * Pause globally, by token, or by pair * Set the protocol fee share for a pool * Collect recorded protocol fees to a chosen recipient * Freeze and unfreeze positions * Nominate a new admin ## Admin and investigator concentration `DEPLOYER` and `SWAP_INVESTIGATOR_KEY` are separate hardcoded addresses. The deployer initializes administration. The investigator address receives compliance records. Admin transfer does not rotate the investigator recipient. The old investigator remains able to decrypt new records. ## Misuse and compromise cases * Venue or market denial through pause controls * Incorrect token decimal registration, which is write-once * Risky asset admission through the token allowlist * Unexpected fee and spacing rebinding for future pools * Excessive protocol fee share within the allowed range * Protocol fee collection to an unauthorized recipient * Position immobilization without adequate authority or review * Loss of investigator confidentiality through key compromise The contract exposes no user-fund seizure function for frozen positions, but a freeze blocks that position's exit until unfreeze. ## Multisig limit The imported multisig check applies only to upgrades above edition zero. Routine admin calls require the caller to match the `admin` mapping. ## Mitigations * Use a dedicated operational control account with threshold approval. * Separate investigator custody from admin signing. * Require a signed manifest for decimals, tokens, fees, and pools. * Apply dual approval for protocol fee collection and position freeze. * Monitor every admin mapping change and transfer. * Reconcile control transactions against a case or change record. * Test emergency pause and recovery procedures. * Maintain an investigator-key upgrade or redeployment plan. ## User disclosure Publish the admin address, governance design, control scope, freeze effect, pause matrix, fee limits, and change history. # Confidential data limitations Source: https://shield.fi/docs/security/confidential-data-limitations Information exposure, correlation surfaces, lifecycle gaps, and recovery risks in the confidential execution model. Aleo private inputs, encrypted records, and confidential addresses omit direct participant attribution from ordinary public AMM state. Amounts, routes, prices, ranges, token IDs, and timing remain observable. ## Amounts are public Swap input amount, minimum output, exact output, and refund values are public. Mint desired and minimum amounts are public. Increase, decrease, collect, and claim amounts are public. Token records keep their owner and stored token amount confidential from ordinary public state, but the amount transferred into or out of the AMM is part of the public execution interface. Distinctive sizes can support correlation with off-chain orders, custody movements, or other chain activity. ## Routes and market effects are public Single-hop swaps publish pool and direction. Multi-hop swaps publish every pool, direction, price limit, and hop count. Pool, tick, price, liquidity, fee-growth, and protocol-fee changes are public. A market participant can reconstruct the economic effect of an execution even when the wallet address is absent from the AMM's public fields. Order size, route, price impact, and execution timing remain public. ## Confidential addresses are public pseudonyms A confidential address is a public, one-use pseudonym. The private factor and its relation to the signer remain confidential, subject to correlation and investigator disclosure. The contract binds it to the signer and private factor. `used_blinded_addresses` prevents a second successful swap with the same pseudonym. Public data can still correlate transactions by: * Amount * Route * Price movement * Submission and claim timing * Network metadata * Client behavior * Off-chain order information * Reuse of surrounding accounts or infrastructure The TypeScript client derives factors from a view key and counter. If production adopts that scheme, a party with the view key and enough counter context may be able to reconstruct confidential addresses. A separate derivation secret may provide a better boundary, but it requires its own backup and recovery design. ## Mint does not use confidential addresses Mint computes the public token ID from the public request, private position owner, and private nonce. The `PositionNFT` is encrypted to that owner and also binds a private immutable withdrawal address. The public token ID remains stable through increase, decrease, collect, and burn. Observers can link those actions to the same position. Rebalance removes the old token ID and creates a new token ID. The public transaction links the old position to its replacement. The investigator can connect that token ID to the initial signer, caller, owner recipient, and withdrawal address through `MintComplianceRecord`. ## Position activity is linkable by token ID Public position state includes range, liquidity, fee checkpoints, and owed amounts. Increase, decrease, collect, and burn use the same token ID. Rebalance uses the old token ID and returns a new token ID. This public transaction links both identifiers. `burn` does not return the direct signer as a public output, but the public `token_id` still links the burn to earlier position activity. ## Investigator access The investigator-key holder can decrypt the compliance records. The investigator receives: * Top-level signer * Immediate caller * Mint recipient or swap confidential address * Public transaction identifiers * Full request data The deployer and investigator use separate addresses. The administrator cannot decrypt investigator-owned records unless it also controls the investigator key. There is no in-contract key rotation. View-key compromise can expose records accessible to that investigator account, and a program change can redirect only future records. ## Lifecycle attribution is incomplete The contract emits compliance records for mint, rebalance, and swaps. It does not emit them for: * Increase * Decrease * Collect * Burn * Claim * Freeze or unfreeze `collect` pays the immutable private `withdrawal` address embedded in the `PositionNFT`. The mint compliance record discloses that withdrawal address to its investigator owner. Swap claims always direct records to the signer, but no new claim compliance record is emitted. ## Signer is not verified identity Compliance records preserve top-level account attribution across intermediary programs through the `signer` field. The field is still an Aleo address. It does not prove: * Natural-person identity * Beneficial ownership * Institutional authority * Jurisdiction * KYC or sanctions status Those assertions require external evidence. ## Mapping removal affects current visibility Current mappings are not complete event history. * Claim removes `swap_outputs`. * Burn removes `positions`. * Accepting admin removes `pending_admin`. * Unfreeze removes `frozen_position`. The freeze block height exists only while the freeze is active. Durable history requires transaction and state-diff indexing. ## Claim values are public and exact Claimants supply output and refund amounts as public inputs. Finalization checks exact equality with the stored `SwapOutput`. Exact equality rejects amount substitution. Rejected claim inputs remain public, so clients must read canonical values from public state. ## Recovery can fail A claim requires the signer and private blinding factor that reproduce the stored public address. The contract cannot recover lost blinding material. Wallet backup must cover: * Account keys * Factor or deterministic derivation state * Domain and program version * Pending swap identifiers * Record discovery state Counter reuse can cause one of two prepared swaps to reject because the address is already used. Counter loss can also prevent deterministic factor recovery. ## Token-program confidentiality varies The AMM calls a dynamic `IARC20` implementation. Record schema, authorization, wrapper behavior, and issuer controls belong to that token program. Token programs can implement different record, authorization, wrapper, and issuer-control behavior. Verify each token before enabling it in a market. ## Integration requirements Production integrations should cover: * Routed compliance records with `caller != signer` * Field-level assertion of the new `signer` member * Swap compliance-record decryption * Multi-hop compliance-record decryption * Key rotation * View-key compromise containment * End-to-end collection attribution Provide a typed `MintComplianceRecord` parser in any integration that ingests mint attribution records. ## Confidentiality terminology `Confidential` applies to participant attribution and record ownership, not transaction economics. Amounts, routes, prices, timing, and lifecycle identifiers remain public. Compliance records permit investigator access but do not establish legal compliance. ## Related pages * [Confidentiality model](../confidentiality/model) * [Public data](../confidentiality/public-data) * [Confidential addresses](../confidentiality/confidential-addresses) * [Trust and key boundaries](./trust-and-key-boundaries) # Security model Source: https://shield.fi/docs/security/security-model The assets, invariants, trust boundaries, and failure classes a Shield Swap deployment must protect. Use this page to identify what the program protects and where security depends on token programs, client software, administrators, or key custody. ## Threat boundaries | Boundary | Primary failure | | - | - | | AMM accounting | Incorrect price, liquidity, tick, fee, claim, or position state | | Token program | Transfer behavior differs from the interface assumptions | | Wallet or client | Key, record, factor, order, or transaction-state leakage or loss | | Investigator custody | Unauthorized access to participant-attribution records | | Administration | Misconfiguration, pause abuse, position freeze, or unsafe deployment handoff | | Indexer and API | Stale, unavailable, or incorrectly interpreted off-chain data | The program can reject an invalid state transition. It cannot make a compromised client, token program, administrator, or custody process trustworthy. ## Protected assets * Token balances held by the AMM and user records * Position authority carried by `PositionNFT` * Pool price, active liquidity, ticks, fees, and owed-token accounting * Pending swap output and refund entitlements * Private blinding factors and record decryption material * Investigator view-key material and decrypted compliance data * Admin and upgrade authority ## Core accounting invariants * Pool tokens are sorted, distinct, and bound to the expected token IDs. * The current square-root price lies in the current tick interval. * Active liquidity matches the ranges that contain the current tick. * An initialized tick's gross and net liquidity reconcile with positions. * Linked-tick pointers are sorted and reciprocal. * Swap fees are applied to the liquidity active when earned. * Position settlement uses modular fee-growth deltas and the correct liquidity epoch. * A pending swap output can be claimed once and only with exact stored fields. * A rebalance must account for all recovered, funded, desired, and refunded value atomically. * A rebalance must keep the old pool and withdrawal address and must create a different token ID. * A position can be burned only after liquidity and owed balances reach zero. The protocol completes initialized-tick crossing at an exact price limit, protects the zero-effective-input arithmetic path, and isolates fee ownership across liquidity epochs. Burn omits the signer from its public output, while encrypted compliance records preserve signer and caller attribution for the investigator. ## Confidentiality assumptions Records and private inputs rely on proof and encryption for confidentiality. Public protocol state still exposes amounts, pools, routes, ranges, token IDs, timing, and control actions. Confidentiality does not prevent statistical correlation. Control of the investigator key grants access to participant-attribution records and must be treated as disclosure authority. ## External trust The AMM delegates token transfers to dynamic token programs. Security depends on their code, state, upgrade authority, and transfer semantics. Clients are also part of the trust boundary. A client can leak private keys, view keys, blinding factors, decrypted records, or intended trades before proof submission. ## Administrative trust The operational admin can change market availability, configuration, protocol fee share, and position freeze state. Operational actions are not multisig-gated by the AMM itself. Program upgrade authorization is separate. The current Q128 storage layout requires a fresh deployment when migrating from a Q64 deployment, which creates a new configuration and migration trust boundary. ## Compiler warnings Leo 4.3.4 emits checks-effects-interactions warnings where some finalizers call token futures before later reads, assertions, or writes. Compilation and tests continue despite the warnings. Auditors should analyze atomic rollback and external token behavior at every warning site. ## Failure posture Arithmetic and assertion failures reject execution. Repeated rejections can reduce availability, so operators should distinguish malformed requests, stale state, unsupported assets, and invariant failures. # Trust and key boundaries Source: https://shield.fi/docs/security/trust-and-key-boundaries Authority, custody, record ownership, token-program dependencies, and failure domains across the protocol. Shield Swap distributes authority across the transaction signer, record owners, the protocol admin, the investigator account, token programs, and the upgrade path. These roles have different powers and should be held, monitored, and documented separately. ## Boundary map | Boundary | Authority | Does not provide | | - | - | - | | Transaction signer | Authorizes top-level proof execution and receives swap claim records | Legal identity, admin authority, or investigator access | | Immediate caller | Identifies the account or program invoking the current function | Guaranteed end-user identity | | PositionNFT owner | Controls the PositionNFT record used for lifecycle operations | Control over public protocol settings | | Token-record owner | Controls token records under the token program's rules | Authority over AMM mappings | | Protocol admin | Configures pools, pauses, token eligibility, fees, and position freezes | Automatic access to encrypted participant records | | Investigator key | Decrypts compliance records addressed to it | Admin authority or legal permission to disclose | | Token program | Enforces token transfers, records, balances, and issuer restrictions | AMM price, liquidity, or position accounting | | Upgrade authorization | Approves program editions greater than zero through the imported multisig check | Routine admin approval for current-edition operations | ## Deployer and investigator separation The program assigns separate addresses to `DEPLOYER` and `SWAP_INVESTIGATOR_KEY`. The addresses have separate responsibilities: * The deployer initializes the administrative state. * The investigator address owns the compliance records. The addresses provide cryptographic role separation. Key custody must also keep the two keys separate. ## Admin boundary The constructor writes the hardcoded deployer address into the `admin` mapping for edition zero. A code comment says the program owner must be that deployer. The constructor body does not assert this relationship. Routine admin functions capture the caller and compare it with the mapping. Admin powers include: * Fee-tier and tick-spacing registration * Fee-to-spacing binding * Write-once token-decimal registration * Pool enable and disable * Pool-creation gate * Token eligibility * Global, token, and pair pauses * Position freeze and unfreeze * Protocol-fee configuration and collection * Two-step admin transfer The admin cannot use the core program to transfer an LP's position value to itself. Position freeze can immobilize value and remove active liquidity until unfreeze. ## Investigator boundary The investigator address is a source constant, not a mapping. No admin function rotates it. The investigator can decrypt records for: * Mint * Single-hop swap * Multi-hop swap The investigator does not receive AMM records for later position operations or claims. Its account address is known from source, but record contents remain encrypted. Admin transfer and investigator custody are independent. A new admin does not become investigator, and the original investigator does not lose record access. ## Signer and caller boundary The signer is the top-level account. The caller is the immediate invoker and can be a program. Compliance records retain both values. Integrations must preserve them as separate fields rather than collapsing them into a single actor. The signer address is account-level evidence. External systems must establish customer identity, beneficial ownership, institutional authority, and policy status. ## Record-owner boundary Position and token records are controlled through Aleo record ownership. * Mint recipient owns the PositionNFT. * Position operations consume and reissue that record. * Token change records return to the token-record owner under the token program's implementation. * Collect token records go to the immutable private withdrawal address in the consumed `PositionNFT`. * Claim token records go to the signer. * Compliance records go to `SWAP_INVESTIGATOR_KEY`. One transaction can involve different owners. For example, the signer can fund a mint whose PositionNFT recipient is another address. ## Blinding-secret boundary The private blinding factor is required to reproduce the signer-bound public address for claim. The AMM does not store or recover it. The TypeScript client derives the factor from the account view key and a counter. This couples wallet record-view capability with swap-pseudonym derivation. Production wallets should decide whether that coupling is acceptable and document backup, concurrency, and compromise behavior. The public used-address mapping prevents a second successful swap with the same confidential address. It does not restore lost secrets or prevent two prepared transactions from racing. ## Token-program boundary The AMM dynamically invokes token programs through `IARC20`. The AMM trusts the selected program to implement compatible semantics for: * Private-to-public transfer * Public-to-private transfer * Public transfer * Record ownership * Amount accounting * Failure and freeze behavior The token allowlist is an admin admission decision. It does not inspect program code or prove issuer-policy compatibility. The included `wrapped_usdcx` targets a test stablecoin dependency. Its bridge calls the underlying program, while its internal ARC-20 transfers do not consult the underlying freezelist on every movement. Verify production USDCx behavior end to end before enabling a market. ## Upgrade boundary For program editions greater than zero, the constructor checks a completed signing operation in `test_shield_swap_multisig_core.aleo`. The multisig constructor check applies only to upgrades. Routine pool, pause, fee, freeze, and admin-transfer calls are authorized by the deployed admin address. The dependency name includes `test_`, and `program.json` points to a local multisig program. Production documentation needs the actual deployed governance dependency and its operating policy. ## Mutable-state boundary Public mappings expose current state, not a complete immutable control log. * Admin acceptance removes the pending entry. * Claim removes the pending swap output. * Burn removes the position. * Unfreeze removes the freeze entry and stored height. An independent indexer is required for durable audit history. The indexer becomes another trust boundary and needs source-version tracking, reorganization handling, completeness monitoring, and retention controls. ## Failure scenarios ### Admin-key compromise An attacker can change operational settings, pause markets, freeze positions, redirect protocol-fee collection within permitted balances, and initiate admin transfer. The attacker does not automatically decrypt compliance records unless the same underlying key controls both roles. ### Investigator view-key compromise An attacker can attempt to discover and decrypt records owned by the investigator account. There is no ordinary in-contract rotation for future records. ### Investigator private-key compromise Private-key compromise includes record decryption and broader account-control risk. ### Blinding-state loss A trader may be unable to reproduce the relation required to claim a pending swap output. ### Token-program failure The AMM can fail to fund, settle, collect, or claim if the token implementation rejects calls or does not match the expected interface. Issuer and wrapper restrictions can change the practical exit path. ### Indexer failure Historical freeze, claim, burn, and admin evidence may be missing even though live mappings appear correct. ## Production control requirements Before launch, document and test: * Separate admin and investigator custody, or an explicit acceptance of concentration * Investigator loss and compromise response * Admin recovery and transfer * Upgrade authorization * Blinding-state backup and concurrency * Record discovery and spent-state tracking * Historical state indexing * Token admission and wrapper review * Identity binding * Disclosure authorization and logging * Pause and freeze runbooks ## Related pages * [Caller and signer](../confidentiality/caller-and-signer) * [Compliance records](../confidentiality/compliance-records) * [Compliance controls](../confidentiality/regulator-and-compliance-review) * [Confidential data limitations](./confidential-data-limitations) # Reading paths Source: https://shield.fi/docs/start/audiences Start with the pages that match your Shield Swap integration or review role. These docs serve two readers. If you trade in the Shield Swap app, the Guides tab's Start and Trade pages each open with an app section, and you can stop reading when the programmatic sections begin. If you are building against Shield Swap, use the shortest path that matches the state and risk your system must own. | You are trading, building, or reviewing | Start here | Do not miss | | - | - | - | | Trading in the Shield Swap app | [Start with Shield Swap](./getting-started) | [Fund your wallet](./fund-wallet), the two-step settlement in [Swap](../trading/swap#how-a-swap-settles), and [Pending Claims](../trading/history-and-recovery#in-the-app) | | Backend, bot, dashboard, or explorer | [REST API quickstart](../rest/quickstart) | [Authentication](../rest/authentication) and exact integer strings | | Wallet or trading application | [Choose an integration path](../developers/integration-path) | Swap plus claim, record replacement, and unknown transaction state | | Liquidity interface or market-maker system | [Liquidity provider workflow](../liquidity/lp-workflow) | Deployment identity, position records, fee settlement, and tick hints | | Security reviewer | [Security model](../security/security-model) | Dynamic token trust, admin authority, and compiler warning sites | | Compliance or risk reviewer | [Regulator and compliance review](../confidentiality/regulator-and-compliance-review) | Protocol controls versus off-chain policy responsibilities | ## Trading in the app Start with [Start with Shield Swap](./getting-started), then [Fund your wallet](./fund-wallet). [Quote](../trading/quote), [Swap](../trading/swap), and [Swap history and recovery](../trading/history-and-recovery) each begin with an **In the app** section. [Manage positions](../liquidity/manage-positions) covers liquidity in the app. ## Application integrators Begin with indexed REST data. Use the TypeScript SDK or direct program calls if the application must submit Shield transactions. Keep these facts in view: * A swap and its claim are separate transactions. * Wallets must reserve input records, preserve change, and store returned records. * Q128.128 values use `u256::U256` with `hi` and `lo` limbs. * Pool parsing must not expect legacy `scale0` or `scale1` fields. * Public position accounting and `PositionNFT` ownership are different state. * Public market data does not require credentials. Routes and wallet-specific data require an authenticated session or API token. ## Traders and wallet teams The transaction's token IDs, amount, route, output, refund, price movement, and timing remain public. The intended confidentiality property keeps the participant address out of ordinary public market mappings. Read [Trader workflow](../trading/trader-workflow), then [Failures and recovery](../trading/failures-and-recovery). ## Liquidity teams Bind every position to one deployment, pool key, token ID, and current `PositionNFT`. The same logical pool in a different deployment is different state. Fee fractions are attributed when earned using active liquidity. Later liquidity cannot receive prior residual fee dust. ## Reviewers The protocol exposes controls and encrypted investigator records. It does not supply key custody, case management, retention rules, screening, jurisdictional policy, or deployment evidence. Treat a deployment as identified only when its network, program ID, edition, checksum, dependencies, admin state, approved tokens, active pools, and pause state have been independently verified. # Fund your wallet Source: https://shield.fi/docs/start/fund-wallet Move assets into Shield Wallet with Fund Wallet, bridge them back out, and track a transfer. This page helps you move assets between another network and your confidential balance in Shield Wallet, using **Fund Wallet** in the Shield Swap app. For funding with the SDKs or CLI, see [Fund a trading account](../trading/fund-and-bridge). ## How funding works Shield Swap trades from the confidential balance in Shield Wallet. **Fund Wallet** moves a supported asset from Ethereum or Solana into that balance, and moves it back out to a wallet on the other network. Each asset uses one bridge. USDC travels through Circle xReserve and arrives as USDCx. ETH, wBTC, USDT, and SOL travel through Hyperlane. The bridge decides the fee, the number of wallet signatures, and the time a transfer takes, and the review screen shows all three before you confirm. A transfer into Shield Wallet is paid in the asset you send. A Hyperlane transfer out of Shield Wallet is different: its fee is taken from your public ALEO balance, not from your confidential balance and not from the asset you are sending. If your public ALEO balance is empty, the transfer cannot start. The source transaction, the destination transaction, and the amount are public on both networks. Your Shield Wallet address is shown as the recipient on the review screen and in the public transfer. ## Move assets into Shield Wallet **Before you start** You'll need: * Shield Wallet connected to Shield Swap. See [Connect to Shield Swap](./getting-started#connect-to-shield-swap). * A browser wallet for the source network, such as Rabby or MetaMask for Ethereum, holding the asset you want to move. 1. Select **Fund Wallet** at the top right. The **Bridge to Shield Swap** panel opens. 2. In **You send**, select **Connect Wallet**, then select your Ethereum or Solana wallet from the list and approve the connection in that wallet. 3. In **You send**, select the asset and enter the amount. **You receive** shows the asset that will arrive in Shield Wallet and its current balance. 4. Check the fee, the steps, and the estimated time shown under the amounts, then select the bridge button. Its label names the amount and asset, such as **Bridge 5 USDC**. 5. In **Review bridge**, check the amounts, the fee, the steps, the estimated time, and that **Recipient** is your Shield Wallet address. Select **Confirm & Bridge**. 6. Approve each request in your source wallet. A USDC transfer asks for two signatures, an approval and then the deposit. The **Confirm in your wallet** screen shows which step is waiting. The screen changes to **Bridge in progress** and tracks each step. You can close the window; the transfer continues. When it finishes, **Bridge complete** confirms that the asset is in your confidential balance in Shield Wallet. Select **View Portfolio** to see it, or **Done** to stay on the page. ## Move assets out of Shield Wallet **Before you start** You'll need: * Shield Wallet connected to Shield Swap, with a confidential balance of the asset you want to move. * A browser wallet on the destination network to receive the asset. * For ETH, wBTC, USDT, or SOL: a public ALEO balance in Shield Wallet to pay the Hyperlane fee. To move ALEO from your confidential balance to your public balance, use **Unshield** in Shield Wallet. See [Shielding and unshielding tokens in Shield](https://support.shield.app/en/articles/13226405-shielding-and-unshielding-tokens-in-shield). 1. Select **Fund Wallet** at the top right. 2. Select the arrow button between **You send** and **You receive**. The panel changes to **Bridge from Shield Swap**. 3. In **You send**, select the asset and enter the amount. 4. In **You receive**, select **Connect Wallet**, then select the destination wallet and approve the connection in that wallet. 5. Check the fee and the estimated time, then select the bridge button. 6. In **Review bridge**, check the amounts, the fee, and the destination address. Select **Confirm & Bridge**. **Check the destination address before you confirm.** The transfer sends the asset to the connected wallet on the other network, and it cannot be reversed. 7. Approve the request in Shield Wallet. The network fee for this request shows **Covered by Shield**. The screen tracks the transfer until it reaches the destination wallet. **Bridge complete** names the address the asset arrived at, with **Aleo Explorer** and **Etherscan** links to the two transactions. Select **Done** to close it. ## Track a transfer **Your Bridging History** at the bottom of the **Fund Wallet** page lists recent transfers. It needs both Shield Wallet and your Ethereum or Solana wallet connected. A transfer appears as soon as it is submitted, with its direction, amount, bridge, time, and transaction ID. A transfer continues after you close the progress window, change pages, or restart the browser. The progress window links to the source transaction on Etherscan while the transfer runs. **Do not start a second transfer because the first looks slow.** Check the source transaction, the destination transaction, and the progress window first. The links at the bottom of the page open the bridges' own sites: [Hyperlane Bridge](https://nexus.hyperlane.xyz), [USDCx Bridge](https://usdcx.aleo.org), and [Funding Routes](https://docs.aleo.org/participate/funding/crosschain/), which lists other current routes. ## Troubleshooting | What you see | Why it happens | What to do | | - | - | - | | **Not enough public credits**, with a message that the bridge fee is taken from your public ALEO balance | A Hyperlane transfer out of Shield Wallet pays its fee from public ALEO, and your public balance is empty or too small. | Unshield enough ALEO to cover the fee shown in the message, then try again. | | **Connect a wallet on Ethereum** or **Connect a Solana wallet** stays on the main button | The asset you selected lives on that network, and no wallet for it is connected yet. | Select **Connect Wallet** in the matching panel and choose a wallet. The list shows only wallets installed in your browser. | | **Bridge in progress** has stayed on one step for longer than the estimated time | The bridge is waiting for the source network or the bridge provider. The transfer has not failed. | Keep the transfer open or come back later. Check the source transaction through the Etherscan link before doing anything else. | | **Your Bridging History** shows no transfers | One of the two wallets is not connected, or the transfers are older than 30 days. | Connect Shield Wallet and the external wallet you used for the transfer. | ## Next steps * [Quote](../trading/quote) to check a price before you trade. * [Swap](../trading/swap) to trade the assets you moved in. * [Fund a trading account](../trading/fund-and-bridge) to fund with the SDKs or CLI. # Start with Shield Swap Source: https://shield.fi/docs/start/getting-started Install and connect the Shield Wallet extension, then find your way to your first trade in the Shield Swap app. This page helps you set up Shield Wallet, connect it to Shield Swap, and find the pages for your first trade. ## How Shield Swap works with your wallet Shield Swap is a web app for trading assets and providing liquidity. You can browse markets, pools, prices, liquidity, and registered assets without a wallet. Any action that moves funds, such as a swap or a new position, needs Shield Wallet. Shield Wallet holds the confidential balance that Shield Swap trades from, and it approves every transaction before it is sent. The app labels this balance **Private Balance**. Your wallet address stays out of the public market data. The assets traded, the amount, the route, the output, any refund, the price movement, and the timing of each swap are public. See [Public data](../confidentiality/public-data) for the full list. You do not need ALEO to trade. Shield Swap covers the network fee for trading activity, and Shield Wallet shows **Covered by Shield** in each transaction request. ## Install Shield Wallet **Before you start** You'll need: Chrome or a Chromium-based desktop browser, such as Brave. 1. Open the [Shield extension in the Chrome Web Store](https://chromewebstore.google.com/detail/shield/hhddpjpacfjaakjioinajgmhlbhfchao). Check that the publisher is **Provable Inc**. 2. Install the extension and complete the browser prompts. 3. Open **Shield** from your browser's extensions menu. 4. Follow the setup prompts to create a new wallet or import an existing one. For a new wallet, Shield Wallet walks you through backing up the seed phrase, naming the account, and setting a password. The [Shield extension setup guide](https://support.shield.app/en/articles/13123296-shield-browser-extension-setup-guide) covers each screen. **Never enter your seed phrase in Shield Swap.** Shield Swap never asks for it. Enter it only in Shield Wallet's own import or recovery flow, and store the written copy offline. Shield Wallet is installed and unlocked, with your account selected. ## Connect to Shield Swap **Before you start** You'll need: Shield Wallet installed and unlocked, with the account you want to use selected. 1. Open [Shield Swap](https://swap.shield.fi/) in the same browser. From [shield.fi](https://shield.fi/), **Launch App** opens it in a new tab. 2. Select **Connect Wallet** at the top right. 3. In the **Connect Shield Wallet** dialog, read the Terms of Use and Privacy Policy notice, then select **Agree and Connect**. 4. In the **Connect to this site?** prompt in Shield Wallet, check that the site is swap.shield.fi. If the prompt says your wallet is on a different network, selecting **Connect** switches it to the network Shield Swap uses. 5. Select **Connect**. 6. In the **Sign message** panel in Shield Wallet, select **Sign**. The message records your acceptance of the current Terms of Use and related disclosures. Signing it is free and creates no transaction. 7. If a referral prompt appears, enter a code or select **Skip for now**. The prompt appears once per wallet. You can add a code later from the wallet menu under **Referral**. Your wallet address replaces **Connect Wallet** at the top right, and the trade panel's button changes to **Enter Amount**. To disconnect, select your address at the top right, then **Disconnect**. To use a different account, switch accounts in Shield Wallet and connect again. ## Move a public balance into your confidential balance Shield Swap trades only from the confidential balance in Shield Wallet. If an asset arrived as a public balance, move it into your confidential balance first. Shield Wallet calls this shielding. **Before you start** You'll need: a public balance of the asset in Shield Wallet. 1. Open Shield Wallet and select **Shield**. 2. Select the asset. The **Public** filter shows only assets with a public balance. 3. With **Shield** selected at the top, enter the amount, or select **25%**, **50%**, **75%**, or **Max**. 4. Select **Next**. 5. On the summary screen, check the amount, the network, and the estimated fee, then select **Shield**. 6. Select **Confirm**. The asset moves to the **Private** filter in Shield Wallet, and Shield Swap can trade it. The summary screen shows the current fee for this action. For the reverse direction and more detail, see [Shielding and unshielding tokens in Shield](https://support.shield.app/en/articles/13226405-shielding-and-unshielding-tokens-in-shield) in the Shield Wallet support hub. ## Your first trade With Shield Wallet connected, a first trade runs through four pages. Open **Fund Wallet** and move a supported asset into Shield Wallet. See [Fund your wallet](./fund-wallet). Shield Swap opens on **Trade**. Use **Buy** or **Sell** to trade the selected market, or open **Swap** to exchange any two assets. The panel shows the price, the swap fee, the price impact, and the least you will receive. See [Quote](../trading/quote). Review the details, select **Confirm & Swap**, then approve the request in Shield Wallet. See [Swap](../trading/swap). Open **Portfolio**, then select **History**. If a swap went through but the funds have not arrived, open **Pending Claims**. See [Swap history and recovery](../trading/history-and-recovery). To provide liquidity instead of trading, open **Pools**. See [Manage positions](../liquidity/manage-positions). ## Troubleshooting | What you see | Why it happens | What to do | | - | - | - | | Shield Swap asks you to install the extension, but it is already installed | Shield Swap could not reach the extension. | Unlock Shield Wallet, reload the Shield Swap page, then select **Connect Wallet** again. | | Shield Swap asks for an updated program permission | Shield Swap's programs have changed since you last connected. | Reconnect the wallet and approve the updated permission in Shield Wallet. | | Shield Wallet shows a balance, but Shield Swap shows nothing to trade | The balance is public. Shield Swap trades only from the confidential balance. | Shield the asset in Shield Wallet. See [Move a public balance into your confidential balance](#move-a-public-balance-into-your-confidential-balance). | ## Next steps * [Fund your wallet](./fund-wallet) to move assets into Shield Wallet. * [Manage positions](../liquidity/manage-positions) to provide liquidity. * For CLI and SDK trading, start with the [Quickstart](../trading/quickstart). # Protocol overview Source: https://shield.fi/docs/start/overview The program, public state, records, API, and client responsibilities that make up Shield Swap. Shield Swap is a concentrated-liquidity AMM. An integration touches more than the core program: token programs move value, encrypted records carry ownership, public mappings hold market state, and off-chain clients build and track Shield transactions. Latest source and deployed bytecode are separate version boundaries. Select testnet or mainnet first, then verify the matching registry, program edition, and artifact hash before constructing a transaction. ## System at a glance | Layer | What it does | What an integrator uses | | - | - | - | | `shield_swap.aleo` | Validates swaps, positions, fee accounting, claims, rebalances, and controls | Entrypoints, mappings, structs, and exact math | | `shield_swap_router.aleo` | Wraps swap inputs and unwraps outputs or refunds | Wrapped-asset swap and claim entry points | | `shield_swap_lp_router.aleo` | Wraps LP inputs and unwraps collected assets | Wrapped-asset mint, increase, collect, and protocol-fee entry points | | `shield_swap_rebalance_router.aleo` | Funds an atomic position replacement and returns unused recovered assets | Plain-asset and wrapped-asset rebalance entry points | | Token programs | Transfer token value into and out of the AMM | Token records and the dynamic `IARC20` interface | | Public mappings | Store pools, prices, ticks, positions, fees, pending outputs, and controls | Chain reads, indexers, quotes, and reconciliation | | REST API and indexer | Expose indexed data, routes, public market history, and settlement status | HTTPS and WebSocket clients | | Wallet or proving client | Owns records, signs, proves, submits, recovers, and reconciles | Wallet SDK or equivalent client code | The current frontend implements these boundaries behind its internal SDK layer. That internal API is application code, not a stable public package contract. ## A trade is two transactions 1. `swap` consumes an input token record and updates public AMM state. Finalization writes the output and any unspent input to `swap_outputs`. 2. `claim` reads that pending output and returns output and refund token records to the signer. A wallet should report the trade as complete only after the claim is accepted and the returned records are stored. A finalized swap with no finalized claim is still unsettled for the user. Read [Trader workflow](../trading/trader-workflow) for the complete sequence. ## A position has public economics and confidential ownership The public `positions` mapping stores the range, liquidity, fee checkpoints, and owed tokens. A `PositionNFT` record proves authority over that position. Increase, decrease, and collect consume the current `PositionNFT` and return a replacement. Burn consumes it permanently. A wallet must reconcile the latest ownership record with the latest public mapping state. Read [Liquidity provider workflow](../liquidity/lp-workflow) before building position management. ## Rebalance a position Use **Rebalance** to replace a complete position in one Shield transaction. Use **Increase** to keep the current range. Use **Decrease** to reduce liquidity. Rebalance keeps the same pool and withdrawal address. It removes the old position ID and creates a new position ID that the signer owns. The transaction can use recovered position funds and additional wallet funds. Unused wallet funds remain in the wallet. Recovered refunds go to the immutable withdrawal address. The operation is atomic. A failed transaction leaves the old position unchanged. The app requires another review when pool state changes. Read [Rebalance a position](../liquidity/rebalance-position) for the complete app flow. ## What remains public Public state exposes: * pool and token identifiers * prices, ticks, liquidity, and fees * trade amounts, routes, outputs, refunds, and timing * position ranges and public accounting * configuration and control actions Token and position records keep ownership confidential in the intended flow. Investigator-owned records can support authorized participant attribution. They do not hide market activity. Read [Protocol boundaries](./protocol-boundaries) for the complete responsibility and visibility model. ## Controls and deployment identity The administrator can configure fee tiers, tick spacings, token eligibility, pool creation, protocol fee shares, pause layers, and position freeze state. An operational action can delay access or market activity even when accounting remains intact. Every integration must bind itself to a network, program ID, edition, checksum, dependencies, and storage schema. The current two-limb Q128.128 fee-growth layout cannot replace a scalar fee-growth deployment in place; it requires a fresh deployment. ## Choose the next page | If you need to | Read | | - | - | | Query data or routes | [REST API overview](../rest/overview) | | Decide how to integrate | [Choose an integration path](../developers/integration-path) | | Build a wallet trade | [Trader workflow](../trading/trader-workflow) | | Build position management | [Liquidity provider workflow](../liquidity/lp-workflow) | | Review trust and failure boundaries | [Security model](../security/security-model) | # Protocol boundaries Source: https://shield.fi/docs/start/protocol-boundaries What the Shield Swap program enforces, what the API exposes, and what clients and operators must supply. `shield_swap.aleo` is the accounting and control core. It is not a wallet, indexer, custody system, or operating policy. The swap, LP, and rebalance routers adapt wrapped assets and funded operations to the core program. ## Responsibility matrix | System | It is responsible for | It is not responsible for | | - | - | - | | Shield Swap program | Pool state, tick accounting, swaps, positions, rebalances, fees, pending claims, and protocol controls | Route discovery, record storage, key custody, finality monitoring, or policy decisions | | Token program | The concrete transfer functions and token-record behavior it implements | AMM pricing, position accounting, or Shield Swap controls | | REST API and indexer | Indexed data, routes, public market history, events, and settlement status | Signing, proving, submission, finality, or record reconciliation | | Wallet or application | Records, signing, proving, submission, claims, retries, and user-visible transaction state | Changing contract invariants or bypassing program controls | | Operator | Deployment identity, administration, monitoring, key custody, asset policy, and incident response | Rewriting finalized program behavior through documentation or policy | ## Visibility matrix | Data | Visibility | | - | - | | Pools, prices, ticks, liquidity, fee growth, and controls | Public | | Trade tokens, amounts, routes, output, refund, and timing | Public | | Position range, liquidity, fees, and owed-token accounting | Public | | Token-record and `PositionNFT` ownership | Confidential in the intended execution flow | | Participant attribution in mint, rebalance, and swap investigator records | Encrypted to the configured investigator recipient | | Keys, decrypted records, intended orders before submission, and custody procedures | Off-chain client or operator responsibility | Shield Swap keeps participant ownership out of ordinary public mappings. It does not make market activity anonymous. ## Program boundary The program enforces: * canonical pool and token ordering * concentrated-liquidity and initialized-tick accounting * single-hop and multi-hop execution limits * LP and protocol fee accounting * position creation, replacement, freeze, unfreeze, and burn rules * pending swap output and refund accounting * confidential-address verification for swaps and claims * encrypted investigator records for mint, rebalance, and swap creation * admin gates, token eligibility, pause controls, and fee settings ## Token boundary The AMM calls the dynamic `IARC20` interface. A production integration must verify the exact token program's transfer behavior, decimals, record shape, authorization rules, issuer controls, upgrade authority, and wrapper redemption path. Included wrappers and test tokens do not prove that every production asset behaves the same way. ## Client boundary A production wallet or application must supply: * record discovery, reservation, and spent-state tracking * change and replacement record storage * confidential-address factor creation, retention, and recovery * nonce uniqueness * transaction construction, proving, submission, and finality monitoring * mapping reads and nested Leo parsing * quote simulation and conservative transaction limits * exact rebalance quotes and replacement record storage * claim submission after swap acceptance * reconciliation between public mappings and wallet-held records The test harness demonstrates parts of this work. It does not remove the responsibility from a production client. ## Routing boundary The contract executes the route supplied by the caller. It does not compare pools, choose a fee tier, split an order, or estimate price impact across alternatives. The router must construct a valid token chain and a directionally valid price limit for every hop. ## Compliance boundary The program creates encrypted investigator records for mint, rebalance, and swap creation. It also exposes administrative controls. It does not decide participant eligibility, disclosure authority, data retention, or jurisdiction. Those decisions require deployment-specific policy, authorization, custody, audit, and reporting systems. ## History boundary Most public mappings hold current consensus state, not a durable event archive. Entries can change or disappear after claim, burn, or unfreeze. Accepted swap execution receipts remain after claim. Historical reports still require an independent transaction and mapping-change archive. Continue with [Security model](../security/security-model) for threats and assumptions, or [Choose an integration path](../developers/integration-path) for implementation ownership. # Shield Swap Setup Source: https://shield.fi/docs/trading/configure Configure your trading setup to trade assets privately on Shield Swap.
Shield Swap is a decentralized AMM exchange that enables private swaps between asset pairs. This guide introduces the SDKs, CLIs, and APIs available for Shield Swap and provides installation and configuration steps for each option. The table below lists the available trading options and when to use each. | Option | When to use it | | - | - | | [Web app](https://swap.shield.fi/) | Trade via a UI in a browser with a connected wallet. | | [TypeScript SDK](https://www.npmjs.com/package/@provablehq/shield-swap-sdk) | Add private trading to web/browser apps, trading terminals, Node.js services, or JS/TS based trading agents. | | [Python SDK](https://pypi.org/project/shield-swap-sdk/) | Add private trading to Python based trading strategies/agents. | | [Shield Swap CLI](https://www.npmjs.com/package/@provablehq/shield-swap-cli) | Agents or humans that need to run tool calls via CLI or shell scripts. | The sections below show how to set up your chosen trading tools for **mainnet**. To begin trading on testnet, use the [Quickstart](./quickstart). ## Install #### Services, scripts, and agents Install Node.js 22 or later and the SDK packages in an application that uses ES modules: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} npm install @provablehq/shield-swap-sdk@0.12.1 @provablehq/veil-aleo-sdk@0.12.1 ``` #### Browser For browser-based apps, install the trading SDK and wallet adapter packages: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} npm install @provablehq/shield-swap-sdk@0.12.1 @provablehq/veil-core@0.12.1 @provablehq/veil-aleo-wallet-adapter@0.12.1 npm install @provablehq/aleo-wallet-adapter-shield @provablehq/aleo-types @provablehq/aleo-wallet-standard ``` Install Python 3.11 or later, then install the SDK in the application's Python environment: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} python -m pip install "shield-swap-sdk==0.6.1" ``` Install Node.js 22 or later, then install the CLI: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} npm install -g @provablehq/shield-swap-cli@0.12.1 shield-swap --help ``` The `--help` option lists all available Shield Swap commands. ## Configure ### 1. Setup an account Shield Swap runs on the Aleo network to enable private trades. The guide below shows how to setup an Aleo account for private trading. Your private key controls your account and its funds. Never share it or include it in source control or logs. Keep a secure backup of your private key or saved account files so you can restore the account later. [Aleo's account keys guide](https://docs.aleo.org/build/sdk/guides/create_account/index.html#account-keys) explains the private key, view key, compute key, and address. #### Services, scripts, and agents Load an existing mnemonic or private key to set up an account, or generate a new account if neither is available. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import { loadNetwork } from "@provablehq/veil-aleo-sdk" const aleo = await loadNetwork("mainnet") const mnemonic = process.env.SHIELD_SWAP_MNEMONIC const privateKey = process.env.SHIELD_SWAP_PRIVATE_KEY const account = mnemonic ? aleo.mnemonicToAccount(mnemonic) : privateKey ? aleo.privateKeyToAccount(privateKey) : aleo.generateAccount() ``` A newly generated account exists only in memory. Save `account.privateKey` in your application's secret manager before ending the session so you can load the same account next time. #### Browser apps Browser apps use a connected wallet to manage the account and its private key. Select an existing account in the wallet, or create one there, then connect it from the app: ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import { ShieldWalletAdapter } from "@provablehq/aleo-wallet-adapter-shield" import { Network } from "@provablehq/aleo-types" import { WalletDecryptPermission } from "@provablehq/aleo-wallet-standard" import { SHIELD_SWAP_ALGORITHM_GRANTS } from "@provablehq/shield-swap-sdk" const adapter = new ShieldWalletAdapter() await adapter.connect( Network.MAINNET, WalletDecryptPermission.UponRequest, undefined, { algorithmsAllowed: SHIELD_SWAP_ALGORITHM_GRANTS }, ) ``` Load an existing private key, or use a `Profile` to restore a saved account or create one if none exists. A profile is an account file managed by the SDK that stores the private key, address, and network locally for later sessions. ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import os from aleo import mainnet from aleo_shield_swap import Profile key = os.environ.get("SHIELD_SWAP_PRIVATE_KEY") if not key: profile = Profile.load_or_create(".shield-swap/python/mainnet", network="mainnet") if profile.network != "mainnet": raise SystemExit("This setup requires a mainnet profile") key = profile.private_key private_key = mainnet.PrivateKey.from_string(key) address = str(private_key.address) ``` Use a separate profile directory for each account and network. The CLI reuses the account saved in `.shield-swap/mainnet/state.json`. If none exists, it imports `SHIELD_SWAP_PRIVATE_KEY` or `SHIELD_SWAP_PRIVATE_KEY_FILE` when supplied; otherwise, `--new` creates and saves an account. Run later commands from the same directory to reuse it. ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} shield-swap setup --new --network mainnet ``` Setup saves the account and credentials before checking funds. If it reports that the account holds no tokens on mainnet, [fund the printed address](./fund-and-bridge), then rerun the command. The CLI does not load `.env` files automatically. ### 2. Setup a trading client #### Client setup for services, scripts, and agents The typescript SDK uses viem-like `Client` and `Action` semantics and allows clients to be extended with arbitrary actions. The step below uses the account created above to create a Wallet Client and extends it with Shield-Swap trading actions. In detail, `createAleoClient` creates a `WalletClient` configured with Aleo RPC and gas payment services. The example then extends it with `shieldSwapActions`, which provides actions for quoting, swapping, inventory management, and accessors to the Shield Swap API. Continue with `aleo` and `account` from step 1: ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import { shieldSwapActions } from "@provablehq/shield-swap-sdk" const { walletClient, publicClient } = aleo.createAleoClient({ privateKey: account.privateKey, }) let client = walletClient.extend(shieldSwapActions({ api: {} })) ``` #### Browser client setup When building a web or browser application, a wallet manages the account, all token inventory records, and transaction signing. In this case, an `RPC` account must be created to request these services from a browser or embedded wallet. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import { createPublicClient, createWalletClient, fallback, http } from "@provablehq/veil-core" import { fromWalletAdapter } from "@provablehq/veil-aleo-wallet-adapter" import { shieldSwapActions } from "@provablehq/shield-swap-sdk" const { account, transport } = fromWalletAdapter(adapter) const rpc = http("https://edge.provable.com/api/v2", { network: "mainnet" }) const publicClient = createPublicClient({ transport: rpc }) const walletClient = createWalletClient({ account, transport: fallback([transport, rpc]), }) const client = walletClient.extend(shieldSwapActions({ api: {} })) ``` `publicClient` reads chain state, including the transaction checks in [Swap](./swap#check-the-result-2). The connected wallet authorizes trades. The `python SDK` provides `web3.py` style semantics to communicate with Shield Swap and the Aleo Network. An `Aleo` object must be created and configured with an RPC provider, and should be configured with the account created in the previous step. From there, register with the Aleo Record Scanning service (that scans for token records) and use the configured **`Aleo`** object to create a **`ShieldSwap`** client. ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} from aleo import Aleo, HTTPProvider from aleo_shield_swap import ShieldSwap aleo = Aleo(HTTPProvider("https://edge.provable.com/api", network="mainnet")) account = aleo.account.from_private_key(private_key) aleo.default_account = account registration = aleo.records.register(account) if not registration["ok"]: raise RuntimeError(f"Record scanner registration failed: {registration['error']}") dex = ShieldSwap(aleo) ``` The Shield-Swap CLI auto-configures a client upon account registration. This configuration can be verified by checking the account’s balances. ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} shield-swap balances --network mainnet ``` ### 3. Configure optional storage Persistent storage retains swap history and the secret data needed to claim an output after a restart. Configure it before submitting swaps if the application needs this recovery path. #### Client setup for services, scripts, and agents For faster trading, it’s recommended to persist recovery data. To enable this, configure a swap file store before authenticating or submitting swaps. If this step is skipped, recovery data and swap history are lost when the process exits. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import { swapFileStore } from "@provablehq/shield-swap-sdk/node" const identities = swapFileStore(`.shield-swap/mainnet/${account.address}/swaps.json`) client = walletClient.extend( shieldSwapActions({ api: {}, blindedIdentities: identities }), ) ``` #### Browser client setup All browser configurations persist history and recovery data within the configured wallet. No storage configuration is required for browser applications. For faster trading, it’s recommended to persist recovery data. To enable this, attach a `Journal` to the `ShieldSwap` client before submitting swaps. If this step is skipped, swap history is not saved automatically, and the application will need to either manually record swap history or reconstruct it on every restart. ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} from aleo_shield_swap import Journal dex.journal = Journal(f".shield-swap/python/mainnet/{address}/journal.jsonl") ``` The CLI manages storage automatically. It retains the private key, API credentials, and claim data under `.shield-swap/mainnet/`. Run later commands from the same directory and keep it between sessions; no additional storage setup is needed. ### 4. Authenticate with the API The Shield Swap API requires authentication via a signature over a challenge containing the current Terms of Use. The step below performs this signature and grants API access. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} await client.authenticateShieldSwap() ``` ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} dex.api.authenticate( address, lambda message: str(private_key.sign(message.encode())), ) ``` `shield-swap setup` authenticates and saves an API token for later commands. Confirm the configured account can read its balances: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} shield-swap balances --network mainnet ``` #### Get or redeem a referral code (optional) Optional referral code redemption can be redeemed via traders participating in Shield Swap incentive program. **Get the account’s referral code** `getMyReferralCode()` calls [GET /referral/my-code](../api-reference/referrals/get-my-referral-code) to retrieve your shareable code or create one if permitted: ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const { code: myReferralCode } = await client.api.getMyReferralCode() ``` **Redeem another trader’s code** Replace `REFERRAL_CODE` with the code another trader shared with you: ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const referralCode = "REFERRAL_CODE" await client.api.redeemReferralCode(referralCode) ``` **Get the account’s referral code** `my_referral_code()` calls [GET /referral/my-code](../api-reference/referrals/get-my-referral-code) to retrieve the account’s shareable code or create one if permitted: ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} my_referral_code = dex.api.my_referral_code() ``` **Redeem another trader’s code** ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import os referral_code = os.environ.get("SHIELD_SWAP_REFERRAL_CODE") if referral_code: dex.api.redeem_code(referral_code) ``` **Get the account’s referral code** Use the account saved by `setup` to retrieve its shareable code or create one if permitted: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} shield-swap redeem --generate --network mainnet --execute ``` **Redeem another trader’s code** Replace `REFERRAL_CODE` with the code another trader shared with you: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} shield-swap redeem --code REFERRAL_CODE --network mainnet --execute ``` Omit `--execute` to preview either action. Run generation and redemption separately. ## Next Steps **Fund the trading account** Follow the [Fund guide](./fund-and-bridge) to add assets to the configured Shield Swap trading account. **Run full examples** The [TypeScript examples in the Shield Swap JS/TS SDK](https://github.com/ProvableHQ/veil/tree/main/packages/shield-swap/examples/first-swap) and [Python examples in the Shield Swap SDK](https://github.com/ProvableHQ/python-sdk/tree/master/shield-swap-sdk/examples/first-swap) cover account setup, funding, quoting, submission, and claiming. Follow the [run instructions](./quickstart#run-packaged-examples) to try them with test tokens on testnet.
Shield Wallet holds the account you use with Shield Swap and asks you to approve connections and transactions. You can create a new account or import one you already use. ## Install Shield Wallet **Before you start** You'll need Chrome or a Chromium-based desktop browser, such as Brave. 1. Open the [Shield Wallet extension in the Chrome Web Store](https://chromewebstore.google.com/detail/shield/hhddpjpacfjaakjioinajgmhlbhfchao). 2. Install the extension through your browser's prompts. 3. Open Shield Wallet from the extensions menu. ## Create or import an account Choose the path that matches whether you already have an account. ### Create a new account 1. Select **Create Wallet** in Shield Wallet. 2. Back up the seed phrase offline. It lets you restore the account if you lose access to this browser. 3. Complete the wallet's prompts to name the account and set a password. The [extension setup guide](https://support.shield.app/en/articles/13123296-shield-browser-extension-setup-guide) covers each screen. ### Import an existing account **Enter your seed phrase or private key only in Shield Wallet.** Shield Swap never needs either to connect your account. 1. Choose the import option during wallet setup. 2. Follow the prompts to import with your existing seed phrase or private key. 3. Check that the imported account shows the address you expect to use. If Shield Wallet is already set up, open the account selector and choose **Import**. The [wallet import guide](https://support.shield.app/en/articles/13162125-how-to-import-a-non-legacy-wallet-into-shield) covers importing an account created in Shield Wallet. Your account is ready in Shield Wallet. Continue to [Fund](./fund-and-bridge#web-app) to connect it to Shield Swap and add assets for trading.
# Failures and recovery Source: https://shield.fi/docs/trading/failures-and-recovery How to diagnose rejected swaps, ambiguous record state, and failed output claims. Recovery starts by identifying the order's actual on-chain phase. Do not retry from an error string alone. ## Classify the phase | Phase | Evidence | Next action | | - | - | - | | Not submitted | No transaction identifier | Correct locally and rebuild | | Submitted, status unknown | Transaction identifier, no terminal status | Query transaction and record state | | Rejected swap | Terminal rejection, no pending output | Requote and use a confirmed unspent record | | Accepted swap | Accepted transaction and `swap_id` | Read pending output and claim, do not reswap | | Submitted claim, status unknown | Claim transaction identifier | Query claim and mapping state | | Rejected claim | Pending output still exists | Correct claim inputs and retry | | Accepted claim | Pending output removed | Discover and store token records | If submission status is unknown, keep the input record reserved until its commitment state and transaction outcome are confirmed. Use the [public transaction lookup](./verify-settlement#check-both-transactions) or `publicClient.getConfirmedTransaction({ id })` to check the request and claim independently. For a completed quickstart trade, the [settlement verifier](./verify-settlement#verify-the-output-record) also matches the returned ETH record to the accepted claim. ## Pre-submit failures These should be caught before proving or broadcasting: ### No sufficient token record The AMM consumes one input record. Acquire a larger record through the token program or select a different amount. Do not attempt to pass several records. ### Wrong token record Compare the record's concrete token program with canonical pool token order and direction. Symbols and API metadata are not sufficient. ### Base-unit amount is invalid Parse the user's decimal input into an exact integer using the token metadata decimals. Reject a fractional base unit or a value outside `u128`. ### Invalid price limit Refresh the slot and require a limit strictly below current price for `zero_for_one` or strictly above it for `one_for_zero`. A limit equal to current price is invalid. ### Economically empty dust trade If fee flooring reduces effective input to zero, the trade produces no output and returns the input. Raise the amount or decline the order. ## Swap rejection Common finalize rejection causes include: | Cause | Diagnostic | Recovery | | - | - | - | | Deadline expired | Current height exceeds request deadline | Read a fresh height, requote, and sign a new request | | Pause or disabled pool | Public control mapping or pool flag changed | Wait, choose another active route, or follow operator guidance | | Token mismatch | Supplied token IDs differ from pool state | Rebuild from canonical pool data | | Price limit out of range | Limit outside global constants | Clamp only according to user policy and requote | | Price direction wrong | Limit is not strict relative to current slot | Refresh state and recompute | | Amount zero | Public input is zero | Use a positive economically meaningful amount | | Minimum output not met | Final raw output is below floor | Refresh quote, change amount or tolerance, or cancel | | Confidential address reused | Address already marked used | Derive fresh blinding material | | Token-chain mismatch | Multi-hop input or output IDs do not connect | Rebuild the route by concrete token ID | | Arithmetic boundary | Helper precondition or integer fit fails | Reduce scope and investigate before retrying | A rejected order should not create a usable pending output. Before reusing its input record, confirm rejection and record spend state with chain data. Client timeouts are not proof of rejection. ## Accepted partial execution An accepted swap can leave input unspent while still meeting `amount_out_min`. The claim returns that remainder in a token record. Possible causes include: * Price limit reached * Five-iteration single-hop cap reached * Three-iteration per-hop multi-hop cap reached * Zero effective input after fee flooring Read `swap_outputs` and show the original-input remainder. Do not automatically submit another swap for it. The user should receive the refund record, review current state, and authorize any follow-up order. ## Accepted swap with no claim If the swap is accepted, recovery is a claim, not a repeated swap. 1. Extract or reconstruct `swap_id` from the accepted transition output. 2. Read `swap_outputs[swap_id]`. 3. Recover the original signer and private blinding factor. 4. Submit `claim_swap_output` with exact mapping values and current signer freezelist proofs. 5. Confirm mapping removal. 6. Decrypt and store returned token records. Trading pauses do not directly block claim finalizers. Diagnose a failed claim from the pending mapping and token-transfer result. ## Claim rejection ### Confidential address mismatch Use the same signer and factor that created the confidential address. A new factor cannot be substituted after the swap. ### Mapping value mismatch Read the mapping again and copy every token and amount exactly. Do not use quoted output or a rounded display amount. ### Wrong router claim Both core swap paths use `claim_swap_output`. If an output or refund is wrapped, select the matching `shield_swap_router.aleo` claim adapter from the deployment asset registry. ### Output already claimed If the mapping is absent, inspect the prior claim transaction and local record scanner. Repeating the claim cannot recreate the removed entry. ### Token transfer failure Confirm the concrete token programs, amounts, deployment availability, and record output handling. The AMM mapping may remain present after a rejected claim, allowing a corrected retry. ## Lost blinding recovery material The normal claim path requires the original signer and private factor. The protocol does not provide an administrator recovery or redirect function for pending swap outputs. Wallets should persist encrypted recovery material before swap submission and include it in backup procedures. Investigator-owned compliance records support authorized attribution but are not spend credentials. ## Missing output records If the claim is accepted but the wallet shows no funds: 1. Confirm the pending mapping was removed. 2. Retrieve the accepted claim transaction outputs. 3. Enumerate record, dynamic-record, and dynamic-ID record outputs. 4. Decrypt with the signer's view key. 5. Preserve `_version` and `_nonce` when parsing. 6. Match each record to its concrete token program. 7. Rebuild the local record index without rebroadcasting the claim. Verify confidential settlement from accepted claim outputs and record decryption. Public balance changes do not show record delivery. ## Multi-hop-specific recovery * A route rejection is atomic. Do not assume earlier hops produced claimable intermediate assets. * An accepted route returns final output and can return remaining original input. * If a hop hit its iteration cap, a follow-up route begins from new public pool state and needs a new input record after the refund claim. * A two-hop request still carries a padded third hop. Validate `hop_count` before interpreting route telemetry. * Final minimum output applies to the route output, not to the original-input remainder. ## Operational metadata failures An off-chain token API outage can prevent a frontend from discovering symbols or constructing operator onboarding steps. It does not change an existing pool's public token identifiers. The API seed script is separate from on-chain pool-creation allowlisting. After a metadata failure, confirm the environment, run a dry run, review each token program, and authorize on-chain actions separately. ## Incident record For every material failure, retain: * Network, program ID, and edition * Pool and route identifiers * Transaction identifiers and terminal status * Block heights used for quote and deadline * Public request parameters * Slot and relevant tick snapshots * Local record handles, never plaintext in general logs * Pending-output snapshot * Client and SDK version * Recovery action and result When identity disclosure is required, use the approved compliance-record process. Do not mix decrypted investigator material into ordinary support logs. Return to [Trader workflow](./trader-workflow) after the order reaches a known terminal state. # Fund a trading account Source: https://shield.fi/docs/trading/fund-and-bridge Fund your trading account, prepare assets for swaps, and withdraw to another chain.
## How funding works Shield Swap runs on the Aleo network and uses token records for private trading. Each record holds an amount your account can spend in a swap. To fund your trading account from a supported EVM chain or Solana, you bridge assets to Aleo, then convert the received public balance into token records. This conversion is called shielding. USDC can be bridged directly into USDCx records, without a separate shielding step. Once trading is complete, you can bridge your funds out to another supported chain. If your account already holds spendable token records, you can go straight to [getting a quote](./quote). ## Check spendable funds **Prerequisites:** Your account and trading clients from [Setup](./configure): `client` for TypeScript, `dex` for Python, or a configured CLI profile. For practice with test assets, use the separate [testnet funding flow](#request-testnet-tokens). Check your USDCx balance to decide whether you need funding or record preparation. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import { formatUnits } from "@provablehq/shield-swap-sdk" const token = await client.tokenData("USDCx") const balances = await client.getBalances({ tokens: [token.id] }) const privateBalance = formatUnits(balances[token.id]?.private ?? 0n, token.decimals) ``` `privateBalance` is a decimal string in USDCx units. For example, `"1"` means 1 USDCx, not one base unit. ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} balances = dex.get_balances() token = dex.api.get_token("USDCx") can_swap = dex.has_swap_balance(token.address, "1") ``` `balances` reports your holdings. `can_swap` is `True` when one record covers 1 USDCx; it is `False` when record preparation or funding is needed. ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} shield-swap balances --network mainnet --all --json ``` The JSON response reports your balances, including the `private` amount held in records. Your total record balance can be sufficient even when no single record covers the swap. For example, two records of 0.5 USDCx total 1 USDCx, but neither covers a 1 USDCx input. [Prepare a covering record](./preparing-token-records) before swapping. If you have a public balance, configure the bridge client in [step 1](#1-configure-access), then continue to [shielding](#shield-public-balances). If your funds are on another chain, follow the bridge steps below. ## Bridge assets Use these **mainnet** examples to transfer ETH between Ethereum and Aleo through Hyperlane. Choose TypeScript, Python, or REST, and use the same interface through delivery so you can track one transfer throughout. For the supported pairs and examples covering Solana, USDCx delivery modes, frontend integration, and embedded wallets, see the [Bridging integration guide](../developers/bridging). Use the Shield Swap CLI to check balances before and after funding. ### 1. Configure access You need a source account that can sign the transfer and a destination address to receive it. Keep funds for network fees in the source account in addition to the transfer amount. When funding, use your mainnet Aleo account from [Setup](./configure) as the recipient. The examples below use private keys managed by your application. For a Dynamic or Privy signer, follow [Use Embedded Wallets for bridging](../developers/bridging#use-embedded-wallets-for-bridging), then continue at [route discovery](#2-discover-assets-and-routes). Your application must supply the keys, RPC URL, and wallet addresses from an external source, such as its configuration or a secret manager. The examples below use environment variables: | Input | Where to get it | Used by | | - | - | - | | `ALEO_PRIVATE_KEY` | The private key for your mainnet trading account from Setup. | TypeScript and Python. | | `EVM_PRIVATE_KEY` | The private key for your Ethereum source account. TypeScript expects 32 bytes as `0x`-prefixed hex. | TypeScript and Python. | | `ETHEREUM_RPC_URL` | An Ethereum mainnet RPC URL from your RPC provider. | TypeScript and Python. | | `SOURCE_ADDRESS` | The address of the wallet sending the transfer. | REST. | | `DESTINATION_ADDRESS` | The address shown by the wallet receiving the transfer. | REST. | Install the packages in an ES module application using Node.js 22 or later: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} npm install --legacy-peer-deps @provablehq/aleo-bridge-sdk@0.12.1 @provablehq/veil-aleo-sdk@0.12.1 @provablehq/veil-aleo-devnode@0.12.1 ``` `--legacy-peer-deps` avoids a conflict between the bridge package's optional Solana and server-wallet peers in npm. Connect your Aleo trading account and Ethereum signer to the bridge. Your Ethereum RPC connection handles chain reads and transaction submission: ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import { loadNetwork } from "@provablehq/veil-aleo-sdk" import { createAleoClient, createBridgeClient, createEvmClient, evmHttp, evmPrivateKey, } from "@provablehq/aleo-bridge-sdk" const aleoKey = process.env.ALEO_PRIVATE_KEY const evmKey = process.env.EVM_PRIVATE_KEY const rpc = process.env.ETHEREUM_RPC_URL if (!aleoKey || !evmKey || !rpc) throw new Error("Set both keys and ETHEREUM_RPC_URL") if (!/^0x[0-9a-fA-F]{64}$/.test(evmKey)) throw new Error("EVM_PRIVATE_KEY must be a 32-byte hex key") const aleo = await loadNetwork("mainnet") const { publicClient, walletClient, account } = aleo.createAleoClient({ privateKey: aleoKey }) const bridge = createBridgeClient({ environment: "mainnet", clients: { ethereum: createEvmClient({ transport: evmHttp(rpc), account: evmPrivateKey(evmKey as `0x${string}`) }), aleo: createAleoClient({ publicClient, account: walletClient }), }, }) ``` You now have a mainnet `bridge` client and Aleo `account`. Keep both in the same session for the TypeScript steps that follow. Install the SDK in a Python 3.11 or later environment: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} python -m pip install "aleo-bridge-sdk==0.6.1" ``` Connect your accounts and register the Aleo record scanner so it can find records for withdrawals. Registration shares your account's view key with the hosted scanner, which can read every record belonging to that account. `FileCheckpointStore` saves transfer progress locally so you can recover after a restart. Keep its directory between sessions. ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import os from aleo import Aleo, HTTPProvider from aleo_bridge import Bridge, Ethereum, FileCheckpointStore aleo = Aleo(HTTPProvider("https://edge.provable.com/api", network="mainnet")) aleo.default_account = aleo.account.from_private_key(os.environ["ALEO_PRIVATE_KEY"]) registration = aleo.records.register(aleo.default_account) if not registration.get("ok"): raise RuntimeError(registration.get("error", "Record scanner registration failed")) ethereum = Ethereum( os.environ["ETHEREUM_RPC_URL"], private_key=os.environ["EVM_PRIVATE_KEY"], ) store = FileCheckpointStore(".bridge/checkpoints") bridge = Bridge(aleo, ethereum=ethereum, checkpoints=store) ``` On success, `registration["ok"]` is `True`. You now have the mainnet `bridge`, `ethereum`, and `store` objects used by the Python steps. Use `curl` and `jq` to request quotes and transfer instructions from the wallet services API. Your source wallet signs transactions; you do not send its private key to this API. ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} export WALLET_API=https://wallet.api.provable.com : "${SOURCE_ADDRESS:?Set the source wallet address}" : "${DESTINATION_ADDRESS:?Set the destination wallet address}" ``` Your shell now has the wallet services URL and both addresses. This host handles bridging; the Shield Swap API in Setup handles trading data. Send `X-Client-Type: programmatic` for this workflow. Wallet integrations use their supported `X-Wallet-*` capability headers instead; do not combine the two header sets. ### 2. Discover assets and routes **Prerequisites:** The `bridge` client or REST shell configuration from [Configure access](#1-configure-access). A route identifies the asset and the provider that can move it between two chains. Discover routes to check where you can send your funds. Use the returned chain and asset identifiers when quoting; a symbol alone does not identify an asset across chains. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const assets = bridge.registry.getAssets() const inboundRoutes = bridge.registry.getRoutes({ environment: "mainnet", sourceChainId: "ethereum", destinationChainId: "aleo", }) const outboundRoutes = bridge.registry.getRoutes({ environment: "mainnet", sourceChainId: "aleo", destinationChainId: "ethereum", }) ``` `assets` lists the registered assets; `inboundRoutes` and `outboundRoutes` list routes in each direction. Check availability before quoting: `metadata-required` means deployment metadata is still needed. ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} inbound_routes = bridge.routes(source_chain="ethereum", destination_chain="aleo") outbound_routes = bridge.routes(source_chain="aleo", destination_chain="ethereum") ``` `inbound_routes` and `outbound_routes` contain the matching routes. Check the returned route's availability before quoting. ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} curl --fail-with-body --silent --show-error "$WALLET_API/common/assets" | jq '.data[] | {code, chain, symbol, decimals, supportedDestinations}' ``` Each output object contains `code`, `chain`, `symbol`, `decimals`, and `supportedDestinations`. Use `code` and `chain` in your quote; `supportedDestinations` shows where you can send the asset. For the ETH examples, the identifiers are: | Interface | Ethereum ETH | Aleo ETH | | - | - | - | | REST chain and asset | `EVM:1`, `ETH_MAINNET` | `ALEO`, `ETH_ALEO` | | SDK chain and asset | `ethereum`, `eth` | `aleo`, `eth` | For an Aleo-to-Ethereum withdrawal, Hyperlane spends your public balance. If your ETH is held in records, [unshield the withdrawal amount](#shield-public-balances) and wait for acceptance before quoting. ### 3. Request and review a quote **Prerequisites:** The client or REST shell configuration from [Configure access](#1-configure-access), a supported route from [Discover assets and routes](#2-discover-assets-and-routes), and your recipient address. Request a quote to see how much your recipient can expect and the route fees before you move funds. Choose one direction below. Each example requests **0.01 ETH in display units**, not base units. For an Ethereum destination, replace `ETHEREUM_RECIPIENT` with the receiving wallet's Ethereum address. For an Aleo destination, the SDKs use your configured trading account. In REST, set `SOURCE_ADDRESS` and `DESTINATION_ADDRESS` to the wallets for your chosen direction. | Parameter | Type and unit | Required or default | | - | - | - | | `source`, `destination` | Objects with `chain` and `asset` identifier strings. | Required; no default. | | `bridgeProtocol` | Protocol identifier string. | Optional when the route is unambiguous; this example selects `"hyperlane"`. | | `amount` | Decimal string in source-asset display units. | Required; no default. | | `recipient` | Destination address string. | Required; no default. | Ethereum to Aleo: ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const quote = await bridge.quote({ source: { chain: "ethereum", asset: "eth" }, destination: { chain: "aleo", asset: "eth" }, bridgeProtocol: "hyperlane", amount: "0.01", recipient: account.address, }) ``` Aleo to Ethereum, with your destination address in place of `ETHEREUM_RECIPIENT`: ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const quote = await bridge.quote({ source: { chain: "aleo", asset: "eth" }, destination: { chain: "ethereum", asset: "eth" }, bridgeProtocol: "hyperlane", amount: "0.01", recipient: "ETHEREUM_RECIPIENT", }) ``` `quote.plan` contains the selected route, amount, and recipient. Review the returned fees and transfer details before using that plan to submit. | Parameter | Type and unit | Required or default | | - | - | - | | `source_chain`, `source_asset` | Source chain and asset identifier strings. | Optional if you supply a route instead; supplied here. | | `destination_chain`, `destination_asset` | Destination identifier strings. | Optional when route selection is unambiguous; supplied here. | | `amount` | Decimal string in source-asset display units. | Required unless you supply `amount_atomic`; no default amount. | | `sender` | Source address string. | Defaults to `None`; required for this Ethereum quote, so supply it explicitly. | | `recipient` | Destination address string. | Required; no default. | Ethereum to Aleo: ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} quote = bridge.quote( source_chain="ethereum", source_asset="eth", destination_chain="aleo", destination_asset="eth", amount="0.01", sender=ethereum.require_address(), recipient=bridge.aleo_address(), ) ``` Aleo to Ethereum, with your destination address in place of `ETHEREUM_RECIPIENT`: ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} quote = bridge.quote( source_chain="aleo", source_asset="eth", destination_chain="ethereum", destination_asset="eth", amount="0.01", sender=bridge.aleo_address(), recipient="ETHEREUM_RECIPIENT", ) ``` Review `quote.amount_out` and `quote.fees`. Keep `quote.plan`, which contains the transfer details you will submit. For Ethereum to Aleo, use the chain and asset identifiers below. These request parameters describe the quote: | Parameter | Type and unit | Required or default | | - | - | - | | `srcChain`, `destChain` | Chain identifier strings from route discovery. | Required; no default. | | `srcAsset`, `destAsset` | Asset code strings from route discovery. | Required; no default. | | `amountIn` | Decimal string in source-asset display units. | Required; no default. | | `slippageBps` | Numeric string in basis points. | Optional; omitted unless supplied. This example requests `50`, or 0.5%. | | `fromAddress`, `refundAddress` | Source wallet address strings. | Optional in the API; supply both for this workflow. | | `recipientAddress` | Destination wallet address string. | Optional in the API; supply it for this workflow. | ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} export SRC_CHAIN=EVM:1 SRC_ASSET=ETH_MAINNET export DEST_CHAIN=ALEO DEST_ASSET=ETH_ALEO export AMOUNT=0.01 QUOTES=$(curl --fail-with-body --silent --show-error --get "$WALLET_API/bridge/quotes" \ -H 'X-Client-Type: programmatic' \ --data-urlencode "srcChain=$SRC_CHAIN" \ --data-urlencode "srcAsset=$SRC_ASSET" \ --data-urlencode "destChain=$DEST_CHAIN" \ --data-urlencode "destAsset=$DEST_ASSET" \ --data-urlencode "amountIn=$AMOUNT" \ --data-urlencode 'slippageBps=50' \ --data-urlencode "fromAddress=$SOURCE_ADDRESS" \ --data-urlencode "refundAddress=$SOURCE_ADDRESS" \ --data-urlencode "recipientAddress=$DESTINATION_ADDRESS") printf '%s\n' "$QUOTES" | jq '.data | to_entries[] | {index: .key, quote: .value}' ``` For Aleo to Ethereum, set `SRC_CHAIN=ALEO`, `SRC_ASSET=ETH_ALEO`, `DEST_CHAIN=EVM:1`, and `DEST_ASSET=ETH_MAINNET`. Change the source and destination addresses, then repeat the request. You receive indexed quote objects with the provider, `amountOut`, any `minAmountOut`, fees, and estimated time. Review those values and the recipient before selecting an index. An empty list means no quote is available. ### 4. Submit the reviewed transfer **Prerequisites:** Your client from [Configure access](#1-configure-access) and the `quote` or `QUOTES` response you reviewed in [Request and review a quote](#3-request-and-review-a-quote). Submission moves funds into the bridge. Save a reference so you can check delivery after a restart: the SDKs use checkpoints, while REST uses an order ID. **Recover an uncertain submission before sending again.** A timeout does not prove that funds stayed in your source account; another submission could send them twice. Use [Recover an interrupted transfer](#6-recover-an-interrupted-transfer) to check the original transfer. Supply `saveCheckpoint`, your storage callback, before running this example. It receives a `BridgeCheckpoint` and must persist it before returning; it can return `void` or `Promise`. The SDK has no default persistent store for this callback. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const execution = await bridge.execute({ plan: quote.plan, onCheckpoint: saveCheckpoint, }) ``` You receive `execution.receipt`, which identifies the submitted work for the delivery check. Some token routes require an approval before the transfer. Submit your reviewed plan with the `FileCheckpointStore` configured in [Configure access](#1-configure-access). The SDK saves checkpoints there as submission advances: ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} progress = bridge.execute(quote.plan) checkpoint_id = progress.plan.journal_id ``` Keep `checkpoint_id` to reload this transfer. If the call is interrupted before it returns, use `store.list()` to find the saved checkpoint by its route and transaction details. Set `QUOTE_INDEX` to the integer index you reviewed in the quote response; `0` selects the first result. Creating an order returns funding instructions without signing or submitting a transaction. The order copies its provider, integration type, chain and asset identifiers, amount, and quote ID from that quote. `walletAddress` is your destination address; `refundAddress` is your source address. The example supplies the same 50-basis-point tolerance as the quote. ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} export QUOTE_INDEX=0 ORDER_REQUEST=$(printf '%s\n' "$QUOTES" | jq -e --argjson i "$QUOTE_INDEX" \ --arg recipient "$DESTINATION_ADDRESS" --arg refund "$SOURCE_ADDRESS" \ '.data[$i] | select(.quoteId != null) | { providerId: .provider.id, integrationType, srcChain, destChain, srcAsset, destAsset, amountIn, quoteId, slippageBps: "50", walletAddress: $recipient, refundAddress: $refund }') ORDER=$(curl --fail-with-body --silent --show-error "$WALLET_API/bridge/orders" \ -H 'X-Client-Type: programmatic' -H 'Content-Type: application/json' \ --data-binary "$ORDER_REQUEST") ORDER_ID=$(printf '%s\n' "$ORDER" | jq -er '.data.orderId') printf '%s\n' "$ORDER" | jq '.data' ``` You receive `data.orderId` and `data.instructions`. Save `orderId` before sending funds so you can find the same transfer after a restart. Follow the action for your returned `data.instructions.type`: | Type | Action | | - | - | | `ONCHAIN_DEPOSIT` | Send the exact returned asset and amount to the returned address, including its memo when present, before expiry. | | `SIGN_TRANSACTIONS` | Fetch unsigned transactions, review them, and sign with the source account. | | `OFFCHAIN_WIDGET` | Continue through the provider's returned widget instructions. | For `SIGN_TRANSACTIONS`, fetch the unsigned transactions immediately before signing so you can check their expiry: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} curl --fail-with-body --silent --show-error \ "$WALLET_API/bridge/orders/$ORDER_ID/transactions" \ -H 'X-Client-Type: programmatic' | jq '.data | {unsignedTxs, quoteExpiresAt}' ``` You receive `unsignedTxs` and `quoteExpiresAt`. Each entry's `kind` identifies its chain format. Process the entries in order, checking the chain, sender, recipient, amount, and call. For Solana, preserve any existing signatures when adding your wallet's signature. Fetch fresh transaction data if a Solana blockhash expires while the order is still valid. An expired order needs a new quote and order; fetching its transactions again will fail. Before replacing an order, check that its source transaction was never broadcast. If your wallet returns signed bytes without broadcasting, supply `SIGNED_TRANSACTIONS` as a JSON string containing a `signedTxs` array. Fill that array with the wallet's signed transactions in execution order: `0x`-prefixed hex for EVM or base64 for Solana. **Submit only transactions your wallet has not broadcast.** If it already broadcast them, use the confirmation request below to report the original transaction. ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} : "${SIGNED_TRANSACTIONS:?Set the wallet-signed transaction JSON}" curl --fail-with-body --silent --show-error \ "$WALLET_API/bridge/orders/$ORDER_ID/transactions" \ -H 'X-Client-Type: programmatic' -H 'Content-Type: application/json' \ --data-binary "$SIGNED_TRANSACTIONS" ``` After submission, check the saved order ID in [Check delivery](#5-check-delivery). Submission alone does not confirm that the destination received funds. If your wallet broadcasts, including for an Aleo source, report its original bridge transaction ID. Hyperlane also needs a source-account signature to confirm ownership. Replace `ORDER_ID` with the saved order ID and `TX_ID` with the wallet's broadcast transaction ID. Have the source wallet sign this UTF-8 message: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} hyperlane-confirm-v1 ORDER_ID TX_ID ``` Join the lines with newline characters and no trailing newline. The signer must match your refund address. Use EIP-191 `personal_sign` for EVM, a base58 Ed25519 signature for Solana, or an Aleo signature. Supply `TX_ID` from your wallet's broadcast result and `OWNER_SIGNATURE` from that signing operation, then confirm: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} jq -n --arg txId "$TX_ID" --arg signature "$OWNER_SIGNATURE" \ '{txId: $txId, phase: "SWAP", ownerSignature: $signature}' | curl --fail-with-body --silent --show-error \ "$WALLET_API/bridge/orders/$ORDER_ID/transactions/confirm" \ -H 'X-Client-Type: programmatic' -H 'Content-Type: application/json' \ --data-binary @- ``` This example sets the optional `phase` string to `"SWAP"`, the source phase for a single-transaction bridge order. Report the bridge deposit or withdrawal transaction ID, not a preceding ERC-20 approval. Check the order status to follow delivery. ### 5. Check delivery **Prerequisites:** Your configured client and the submission result from [Submit the reviewed transfer](#4-submit-the-reviewed-transfer): `quote.plan` and `execution.receipt` for TypeScript, `progress` for Python, or `ORDER_ID` for REST. Your source transaction can be confirmed while delivery is still pending. Check the existing transfer to see whether the destination has received the funds. These calls do not sign another transaction. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const progress = await bridge.wait({ progress: { next: "wait", plan: quote.plan, receipt: execution.receipt }, }) ``` ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} progress = bridge.wait(progress) ``` ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} curl --fail-with-body --silent --show-error \ "$WALLET_API/bridge/orders/$ORDER_ID" | jq '.data | {orderId, status, currentStepKey, finalStatus: .finalStatus.key, providerStatus}' ``` The API's `status` uses lowercase values such as `pending` and `completed`. The optional `finalStatus.key`, flattened to `finalStatus` above, uses uppercase values such as `COMPLETED`. Repeat the request while the order is in progress; review `FAILED`, `REFUNDED`, or `EXPIRED` before starting another transfer. When present, `providerStatus.txHash` identifies your source transaction and `providerStatus.destinationTxHash` identifies delivery. Providers may also return `depositTxHash` and `withdrawalTxHash`. Check the available receipts on their respective chains. For either SDK, use the returned `progress.next` to decide what to do: | Result | Meaning | | - | - | | `done` | Check the destination receipt and balance; delivery has completed. | | `wait` | Check the same transfer again; delivery is pending. | | `failed` | Review the returned error before taking further action. | | `resume` | Continue unfinished source work using the route's packaged example. | | `complete` | Complete the destination transaction using the route's packaged example, such as a Circle USDCx mint. | For Hyperlane withdrawals from Aleo, both SDKs can use the recipient's balance increase to detect delivery. Spending from that destination account before the check can leave a delivered transfer marked as pending; unrelated incoming funds can also make the balance check misleading. Confirm the original transfer in the [Hyperlane explorer](https://explorer.hyperlane.xyz/) and check its destination transaction before resubmitting or treating the balance change as proof. After inbound Hyperlane delivery, [shield your public balance](#shield-public-balances) to prepare it for trading. After a withdrawal, check the destination receipt and recipient balance. ### 6. Recover an interrupted transfer **Prerequisites:** Recreate the client from [Configure access](#1-configure-access) with the same account, then load the checkpoint or order ID saved during [submission](#4-submit-the-reviewed-transfer). You can inspect an interrupted transfer without sending funds again. Recovery uses its saved reference to find how far the original transfer progressed. **Recover the original transfer before calling `execute` again.** Another execution can submit a second transfer instead of finishing the first. Load `checkpoint`, the `BridgeCheckpoint` object saved by your `saveCheckpoint` callback. Pass that object to your recreated client: ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} let recovered = await bridge.recover({ checkpoint }) if (recovered.next === "wait") recovered = await bridge.wait({ progress: recovered }) ``` Replace `CHECKPOINT_ID` with the journal ID saved during submission, or find it with `store.list()`. Use the same `FileCheckpointStore` directory to reload it: ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} checkpoint_id = "CHECKPOINT_ID" recovered = bridge.recover(store.load(checkpoint_id)) if recovered.next == "wait": recovered = bridge.wait(recovered) ``` Restore `ORDER_ID` from your saved submission result and repeat the [delivery check](#5-check-delivery). You can also inspect the order's audit events: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} curl --fail-with-body --silent --show-error \ "$WALLET_API/bridge/orders/$ORDER_ID/audit" | jq '.data' ``` The response contains your order details, workflow steps, and `providerEvents`. If your wallet broadcast the transaction but reporting failed, repeat the confirmation request with the same transaction ID and owner signature. Read `recovered.next` using the [delivery result table](#5-check-delivery). If it requests `resume` or `complete`, follow the route's [packaged example](#run-packaged-examples). A Circle mint using a secret nonce also needs the original nonce; retain it separately because checkpoints and public transaction history cannot reconstruct it. ## Shield public balances **Prerequisites:** Your `bridge` client from [Configure access](#1-configure-access), plus a public ETH balance for shielding or a sufficient ETH record for unshielding. Shield the amount you want to trade after Hyperlane delivers it to your public balance. To withdraw through Hyperlane, unshield that amount instead. Unshielding exposes the recipient and amount in public balance state. Each example converts **0.01 ETH in display units** and submits a separate transaction with a fee. The CLI and wallet services order endpoints do not provide this conversion. Use your configured mainnet client to shield 0.01 ETH: ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const conversion = await bridge.shield({ asset: { chain: "aleo", asset: "eth" }, amount: "0.01", }) ``` To unshield, supply `record` as an encoded plaintext string from your account's record scanner. It must be an unspent ETH record covering at least 0.01 ETH: ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const conversion = await bridge.unshield({ asset: { chain: "aleo", asset: "eth" }, amount: "0.01", record, }) ``` You receive `conversion.transactionId`. Wait for acceptance before spending the converted amount. A connected wallet that supports record requests can select the record if you omit it; the local-key example supplies it explicitly. After inbound delivery, shield 0.01 ETH and wait for acceptance: ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} receipt = bridge.shield("aleo/eth", amount="0.01").delegate(wait=True) ``` Before an outbound transfer, unshield 0.01 ETH and wait for acceptance: ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} receipt = bridge.unshield("aleo/eth", amount="0.01").delegate(wait=True) ``` You receive `receipt.transaction_id` after acceptance. Your configured scanner can select a sufficient record; optionally supply a specific record with `record=`. After shielding is accepted, repeat the [balance check](#check-spendable-funds) with ETH in place of USDCx. Your received ETH is ready for a swap when a record covers the input amount. Circle's supported record-based USDCx withdrawal can burn records directly and does not need this Hyperlane conversion. ## Request testnet tokens **Testnet prerequisites:** The separate account and clients from the [Quickstart](./quickstart#2-get-test-tokens): `client` and `account` for TypeScript, `shield_swap_client` for Python, or the CLI testnet profile. For the Python examples below, set `dex = shield_swap_client`. Use the faucet to practice without transferring mainnet assets. The SDK calls wait for the faucet transfer and its token records. With the Quickstart's delegated proving setup, you do not need a public ALEO balance. A faucet job can deliver USDCx even if another asset transfer fails. Check the same account's USDCx records before requesting more tokens; an available record covering your input is enough to continue the testnet swap. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const funding = await client.api.confirmAirdrop(account.address) ``` On success, check your testnet balance to confirm the received funds. If the call times out, set `jobId` to the job ID reported by the timeout, then inspect that request: ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const job = await client.api.getAirdropStatus(jobId) ``` ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} funding = dex.confirm_airdrop() ``` On success, check your testnet balance to confirm the received funds. If the call times out, set `job_id` to the job ID reported by the timeout, then inspect that request: ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} job = dex.api.get_airdrop_job(job_id) ``` Run setup for your testnet profile. If the account has no holdings, the CLI requests faucet tokens and saves the job ID so it can resume after an interruption: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} shield-swap setup --network testnet ``` Setup can skip the faucet when you hold another asset, even if you lack the asset you want to trade. In that case, use an SDK funding example for the same account. A completed faucet job can still be waiting for record indexing. Check your balance before requesting more funds; you can trade when a record covers your intended input. ## Next steps ### Get a trading quote Use the [Quote guide](./quote) to find a pool and review a swap once your input is available in a record. ### Run packaged examples Run a complete **mainnet** example for your chosen route. Each repository documents the accounts and connections you need. Follow the [Veil bridge examples](https://github.com/ProvableHQ/veil/tree/main/packages/bridge/examples) README to configure your accounts, then run the ETH example for your direction from its example directory: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} npx tsx eth-to-aleo.ts npx tsx eth-to-ethereum.ts ``` You receive a quote without submitting funds. To execute, supply the acknowledgement documented in the example. The same directory covers USDC/USDCx routes and checkpoint recovery. The [Python bridge examples](https://github.com/ProvableHQ/python-sdk/tree/master/bridge-sdk/examples) ship with the installed package. Read the help for the route or recovery operation you need: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} python -m aleo_bridge.examples.quote_transfer --help python -m aleo_bridge.examples.bridge_wbtc_to_ethereum --help python -m aleo_bridge.examples.recover_from_journal --help ``` The help lists the required accounts, amount units, and recovery inputs. Quote commands do not submit transactions; `--execute`, `--action resume`, or `--action complete` authorize the corresponding transaction.
The [Fund page](https://swap.shield.fi/fund) moves assets from an Ethereum or Solana wallet into Shield Wallet so you can trade them. You can reverse the direction on the same page to bridge assets out. Shield Swap trades from your confidential balance. USDC arrives as USDCx without a separate shielding step. For other assets, funding includes shielding the received balance. The source-chain transfer remains public; see the [risk disclosures](https://shield.fi/risk). ## Connect your wallets **Before you start** You'll need: * Shield Wallet installed with your account selected. [Setup](./configure#web-app) covers creating or importing an account. * A compatible Ethereum or Solana wallet, such as MetaMask, Brave Wallet, or Phantom, holding the asset you want to bridge and funds for network fees. The external wallet sends your funds; Shield Wallet receives them. The asset you select determines which network and wallet options appear. 1. Open [Fund Wallet](https://swap.shield.fi/fund). 2. If you're sending SOL, select **SOL** in the asset selector to use the Solana wallet options. 3. Under **You send**, select **Connect Wallet** and choose your external wallet. 4. Approve the connection in that wallet. 5. Under **You receive**, select **Connect Wallet** to connect Shield Wallet. 6. Read the terms in **Connect Shield Wallet** before selecting **Agree and Connect**. 7. Approve the connection request in Shield Wallet after checking that the site is swap.shield.fi. 8. Sign the challenge in Shield Wallet. It records acceptance of the terms shown in the app; signing this message creates no transaction. Both wallets are connected, and Shield Wallet is the destination for your transfer. ## Bridge assets into Shield Wallet 1. Select an available asset, such as **USDC**, **ETH**, or **WBTC**, under **You send**. **You receive** shows the corresponding destination asset. 2. Enter the amount to send. 3. Review the fees and estimated time shown by the app. 4. Select the bridge button to open **Review bridge**. **Check the recipient address before confirming.** It must match the account you intend to fund in Shield Wallet. 5. Select **Confirm & Bridge** when the amount and recipient are correct. 6. Approve the requests in your source wallet. An asset approval may be required before the transfer. 7. Wait for the bridge and any shielding step to finish. **Your Bridging History** at the bottom of the page lets you check the transfer with both wallets connected. If a transfer is still pending, check its status before starting another one. ## Check the funds available for trading Open [Portfolio](https://swap.shield.fi/portfolio) to see the assets available to trade. If a non-USDC transfer arrived but its shielding step failed, the asset can remain in your public balance. You can finish shielding it in Shield Wallet: 1. Open Shield Wallet and select **Shield**. 2. Select the received asset. 3. Enter the amount with **Shield** selected. 4. Select **Next**. 5. Review the fee and balance changes, then select **Continue**. 6. Select **Confirm**. The wallet's **Private** balance shows the shielded amount. Return to **Portfolio** to check the asset is available for trading. The [Shield Wallet shielding guide](https://support.shield.app/en/articles/13226405-shielding-and-unshielding-tokens-in-shield) covers these screens. ## Bridge assets out The direction control on [Fund Wallet](https://swap.shield.fi/fund) reverses the transfer: Shield Wallet sends the asset, and your Ethereum or Solana wallet receives it. Keep enough public ALEO in Shield Wallet for the bridge fee on Hyperlane routes. The app shows the required fee before submission. 1. Select the arrow between **You send** and **You receive**. The panel changes to **Bridge from Shield Swap**. 2. Select the asset and enter the amount to withdraw. 3. Connect the receiving wallet under **You receive**. 4. Review the destination address and fee before confirming the bridge. 5. Approve the request in Shield Wallet. Follow the transfer in **Your Bridging History** until it reaches the receiving wallet. ## Continue to a trade Once your funds appear in **Portfolio**, [Quote](./quote#web-app) shows how to review a trade before submitting it.
# Swap history and recovery Source: https://shield.fi/docs/trading/history-and-recovery Check an existing swap and finish an interrupted claim in the Shield Swap app or with the SDKs or CLI.
If your swap went through but the output has not arrived, the output is waiting for its claim and you can collect it. A timeout, a closed window, or a restart does not undo the trade. This page helps you check what happened to a swap and finish it, in the Shield Swap app or with the SDKs or CLI. ## How recovery works Every swap finishes in two steps, a swap and then a claim. [Swap](./swap#how-a-swap-settles) explains why. Recovery starts with the original trade: you check its history and status before deciding whether a claim is still needed. In the app, Shield Wallet keeps the data that connects a swap to your wallet, so your history and any unfinished claims appear whenever that wallet is connected. With the SDKs or CLI, your trading tools retain that data. Reopening it allows you to find unfinished trades after a restart. A transaction ID helps you check what happened, but it does not replace the secret data needed to claim.
## In the app **Before you start** You'll need: Shield Wallet connected to Shield Swap, using the same account that made the trade. ### Check a swap 1. Open **Portfolio**. 2. Select **History**. Each row shows the direction, the amount in, the amount out, the price, the **Status**, the **Route**, and when the trade happened. A trade that finished shows **Claimed**. \[VERIFY: the other values Status can show, such as a swap whose claim has not finished or a swap that failed] Use **All**, **Direct**, or **Multi-hop** to filter by route, and **Export CSV** to download the table. On the **Trade** page, **Your Trades** under the chart shows the same trades for the current market, with a **Remaining** column for any input the trade did not spend. On the **Swap** page, **Swap history** shows your most recent swaps, and **View All** opens **Portfolio**. ### Finish a pending claim 1. Open **Portfolio**. 2. Select **Pending Claims**. When nothing is waiting, the tab reads **No funds to recover**. The **Position funds** tile shows funds available from liquidity positions; it does not include pending swap outputs. 3. Select **Claim** in the row for the swap. 4. In **Review collection**, check the transaction details, then select **Confirm**. 5. Approve the request in Shield Wallet. The received asset appears in your Shield Wallet balance, and the row leaves **Pending Claims**. **Do not submit the swap again.** A second swap opens a new trade instead of finishing the first one. The output of the first swap is still waiting in **Pending Claims**. Position funds also arrive here. If you decrease, close, or rebalance a liquidity position and its funds do not reach your wallet, they wait in the same tab. See [Manage positions](../liquidity/manage-positions). ### Troubleshooting | What you see | Why it happens | What to do | | - | - | - | | **History** is empty, but you made trades | Shield Swap shows the history for the connected account only. | Check which account is selected in Shield Wallet. Switch to the account that made the trade and connect again. | | A trade shows in **History**, but the received asset is not in Shield Wallet | The claim has not finished, or Shield Wallet has not refreshed its balance yet. | Open **Pending Claims**. If nothing is waiting, select the refresh control in Shield Wallet and check again. \[VERIFY: how long a balance can take to update after a claim] | | **Pending Claims** reads **No funds to recover**, but a swap seems stuck | The swap may still be pending, its claim may have finished, or the history check may be incomplete. | Check the original transaction in Shield Wallet and its explorer link. If its status is pending or unavailable, wait and check again. Do not submit another swap because the trade is missing from **History**. |
## Read account history **Prerequisites:** Use the same account and network that submitted the swap. These examples use the mainnet clients from [Setup](./configure), with the [original recovery store or journal](./configure#3-configure-optional-storage) reopened. For a testnet trade with saved recovery data, reopen its testnet store and set `--network testnet` in CLI commands. The Quickstart SDK snippets keep recovery data in memory. If that session is still open, continue with its original `client` or `shield_swap_client` and `handle` in [the claim step](./quickstart#4-wait-for-the-output-and-claim). Creating a new file store does not copy that session's recovery data. An empty SDK store does not restore saved claim data. The TypeScript examples use `client` and its `identities` store; Python uses `dex` with its original journal attached. Run CLI commands from the account directory used for the trade. Reconciliation matches your saved recovery data against transaction history. It can fill in missing swap IDs and claim results so you can distinguish completed trades from outputs still waiting to be claimed. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const result = await client.reconcileSwapHistory({ maxPages: 40, pageSize: 50 }) const records = await identities.load() const pending = await client.getUnclaimedSwaps() ``` `records` contains your stored history, including `swapId`, `status`, and any saved `handle` or `claim`. `pending.swaps` lists outputs still present on chain; each has a `swapId` and a `claimable` flag indicating whether the store contains a usable handle. Both pagination options are optional integers. `maxPages` limits this search to 40 pages; omitting it allows the full history walk. `pageSize` sets calls per page, from 1 to 50, and defaults to 50. If `result.complete` is `false`, raise or omit `maxPages` to search farther back. `pending.unresolvable` lists stored identities whose swap IDs remain unknown. Even a complete history search cannot establish the outcome of a transaction still awaiting confirmation. Your journal records local swap submissions and confirmed claims. A submission can appear before its swap ID is known, then appear again after confirmation. Keeping the latest event for each transaction gives you one entry per submission: ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} swaps = {} claims = {} for event in dex.journal.events(): if event["type"] == "swap": swaps[event["transaction_id"]] = event elif event["type"] == "claim": claims[event["swap_id"]] = event ``` `swaps` maps transaction IDs to their latest swap events. Each event's `swap_id` lets you find a matching entry in `claims`, which contains the claim's `transaction_id` and `amount_out` in base units. A swap with no `swap_id` still needs investigation; it is omitted from `dex.journal.pending_claims()`. A journal entry alone is not an independent chain confirmation, so check transaction status before submitting a claim. The journal saves the received amount but does not retain the refund amount. For refund accounting, retain `claim.amount_remaining` when a claim completes. The history command combines saved recovery data with chain state. It can update local history without submitting a claim: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} shield-swap history --network mainnet --json ``` The JSON response includes `history` entries and an `owed` list of unclaimed outputs. Missing identifiers or claim amounts may need a transaction-history search: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} shield-swap history --network mainnet --reconcile --pages 40 --json ``` `--reconcile` forces a search even if local history looks complete. The optional integer `--pages` bounds the search; raise or omit it if the command reports that it stopped early. By default, the search has no page limit. The CLI also looks for account identities beyond those already saved. Its optional integer `--window` defaults to 16 counters beyond the last known identity. A bounded search can miss older activity separated by larger gaps. Stored claim amounts use base units. Resolve the token's decimals before displaying received output or refunded input. Account history serves a different purpose from the [pool trade feed](../api-reference/pools/list-pool-trades), which reports public market activity rather than your account's recovery state. ## Establish the original outcome A transaction can succeed even when your application stops waiting for it. Use the original swap and claim transaction IDs with the [transaction lookup](./verify-settlement#check-both-transactions) before submitting anything else. | What you find | What to do next | | - | - | | The swap was accepted and its output is claimable. | Submit one claim for that swap ID, after checking that no claim is already pending. | | The claim was accepted, but your balance has not updated. | Wait for record scanning and check the returned records. Do not claim again. | | The transaction is pending, or its status cannot be retrieved. | Keep the original IDs and check again. Do not start a replacement trade. | | The transaction was rejected. | Inspect the rejection and your original records before preparing another operation. | | Your local entry has no swap ID. | Inspect its transaction or proving job and recover the ID before attempting a claim. | A missing pending-output entry on chain can mean the swap has not finalized or its output has already been claimed. The accepted transactions and returned records let you distinguish these cases. [Failures and recovery](./failures-and-recovery) covers unresolved transactions and scanner delays. ## Claim one pending output **Prerequisites:** Your original mainnet account and recovery store from [Read account history](#read-account-history), and an accepted swap whose output remains unclaimed. Replace `SWAP_ID` with that swap's ID from history, including its `field` suffix. **Check that no earlier claim for this swap is still pending.** Run one claim worker for the swap so separate processes do not submit competing claims. Find the original swap's handle in the store. The check below stops if its output is absent or the data needed to claim is missing: ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const swapId = "SWAP_ID" const available = await client.getUnclaimedSwaps() const swap = available.swaps.find(item => item.swapId === swapId) if (!swap?.handle || !swap.claimable) { throw new Error("No claimable handle; inspect the original transaction") } const claim = await client.claimSwapOutput({ handle: swap.handle }) ``` `claim.transactionId` identifies the claim transaction. `claim.amountOut` is the received output and `claim.amountRemaining` is refunded input, both as base-unit `bigint` values. Your journal supplies the saved handle. The claim call checks for the output on chain before preparing a transaction: ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} swap_id = "SWAP_ID" handle = next((item for item in dex.journal.pending_claims() if item.swap_id == swap_id), None) if handle is None: raise ValueError("No matching handle; inspect the original transaction") claim = dex.claim_swap_output(handle, timeout=60).delegate(wait=True) ``` The optional numeric `timeout` waits up to 60 seconds for the output; its default of `0` checks once. `.delegate(wait=True)` submits the claim and waits for confirmation. If an error interrupts either stage, check transaction status before another attempt. `claim.transaction_id` identifies the claim transaction. `claim.amount_out` is received output and `claim.amount_remaining` is refunded input, both as base-unit integers. Select the original swap explicitly so the command claims only that output: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} shield-swap history --claim --swap-id SWAP_ID --network mainnet --execute ``` `--claim` requests a claim and `--execute` authorizes submission; both flags are off by default. `--swap-id` accepts the original ID as a string. Omitting it can claim other pending swaps. `--network mainnet` overrides the CLI's testnet default. The result includes the claim transaction ID and received amount for the selected swap. After the claim is accepted, [check your spendable funds](./fund-and-bridge#check-spendable-funds). Use [Verify swap settlement](./verify-settlement) when you need to match the claim to its exact output record. ## Retain recovery data Your recovery store contains secret claim data as well as transaction history. Keep a protected backup with your account, and preserve it while investigating an unfinished swap. Resetting identity counters or deleting the journal can remove data needed to recover the trade. Raw handles and journal events do not belong in application logs. If the store is damaged or a transaction remains unresolved, keep a protected copy. The public transaction ID, network, SDK version, and stage where the operation stopped can help with investigation without exposing claim secrets. ## Next Steps ### Verify the completed trade [Verify swap settlement](./verify-settlement) shows how to confirm both transactions and match the claim to the funds received. ### Continue from packaged examples The [TypeScript recovery example](https://github.com/ProvableHQ/veil/tree/main/packages/shield-swap/examples/first-swap#recover-an-interrupted-swap) explains how to reuse its account and saved handles. The [Python recovery example](https://github.com/ProvableHQ/python-sdk/tree/master/shield-swap-sdk/examples/first-swap#recovery) covers its journal and recovery requirements. Follow their recovery instructions for an existing trade; rerunning a first-swap program starts a new one.
# Multi-hop swaps Source: https://shield.fi/docs/trading/multi-hop-swaps Build and settle current two-hop and three-hop routes. `swap_multi_hop` executes two or three connected pools in one transaction. The route, amounts, directions, limits, nonce, and block-height deadline are public. Funding and delivered token records remain private. ## Request The request contains endpoint token IDs, native base-unit input and minimum output, a confidential recipient, three `SwapHop` slots, hop count, nonce, block-height deadline, and confidential caller. Each hop contains pool key, direction, and a Q128.128 square-root-price limit. `hop_count` must be two or three. For two hops, supply a valid deterministic placeholder in `hop2`; the finalizer does not execute it. ## Continuity For each hop, derive input and output from the pool's canonical token order and `zero_for_one` flag. The contract requires: 1. Hop 0 input equals request input. 2. Hop 0 output equals hop 1 input. 3. For three hops, hop 1 output equals hop 2 input. 4. The final executed hop output equals request output. Symbols are not identifiers. Compare concrete token fields. ## Execution bound Each active hop walks at most three initialized ticks, followed by a partial fill bounded by the nearer next tick or price limit. Hop 0 can leave unspent original input. Hop 1 must consume all hop 0 output. In a three-hop route, hop 2 must consume all hop 1 output. A nonzero later-hop remainder rejects the whole route because the claim ABI cannot return an intermediate token. The final aggregate output must also meet `amount_out_min`. Intermediate output feeds the next hop inside the finalizer. The current `SwapOutput` stores only final output and remaining original input. It does not create public per-hop refund slots. ## Settlement After acceptance, read `swap_outputs[swap_id]` and call `claim_swap_output`. The function returns one final-output record and one original-input refund record. It validates the same confidential-address relation and signer freezelist proof used by single-hop settlement. Use `swap_mh_from_wrapped` when route input begins as an underlying wrapped-asset record. Use a matching router claim function when final output or original-input refund must be unwrapped. ## Quote checklist * Route has exactly two or three active hops. * Every pool and token ID comes from the selected deployment. * Route topology revision is current. * Each pool state snapshot is current and enabled. * Per-hop Q128.128 limits point in the correct direction. * Quote reproduces three crossing iterations plus bounded partial behavior. * Quote proves that every later hop consumes its full intermediate input. * Final minimum uses output-token base units. * Deadline is an Aleo block height. * Original input record is sufficient. * Factor and confidential-address recovery material are durable. # Output claims and refunds Source: https://shield.fi/docs/trading/output-claims-and-refunds Settle current single-hop and multi-hop pending outputs into encrypted token records. Both swap paths write the same public `SwapOutput`. Both settle through `claim_swap_output`. ## Outcome Settlement is complete when the claim is accepted, `swap_outputs[swap_id]` is removed, and the wallet has stored the output and optional refund records. ## Pending output shape ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} SwapOutput { recipient: address, caller: address, token_in: field, token_out: field, amount_out: u128, amount_remaining: u128 } ``` All amounts use native token base units. For a multi-hop swap, `token_in` and `amount_remaining` refer to the original route input. The current contract does not expose one refund pair per intermediate hop. ## Claim sequence Resolve transaction finality before reading the mapping or retrying the swap. Load `swap_outputs[swap_id]`. Copy its token IDs and amounts exactly. Build the two `MerkleProof` values required to prove non-inclusion for the claiming signer. Use the original private factor and public confidential address. Pass the mapping values and proofs. Confirm acceptance, mapping removal, and wallet discovery of both dynamic record outputs. ## Identity and compliance checks The claim reconstructs the confidential address from the program address, signer, factor, and fixed domain. It also validates signer non-inclusion against the AMM freezelist root. Knowing the public mapping values does not authorize a claim. The investigator compliance record is not a claim credential. The admin has no redirect or recovery function for lost factor material. ## Router claims When output or refund must cross a wrapper boundary, use the matching function in `shield_swap_router.aleo`: * `claim_to_wrapped_refund_arc20` * `claim_to_arc20_refund_wrapped` * `claim_to_wrapped_refund_wrapped` Resolve both asset kinds from the selected environment's contract registry. ## Retry rules On an ambiguous result: 1. Query the claim transaction. 2. Read `swap_outputs[swap_id]` again. 3. If the mapping remains, verify the prior claim is rejected before resubmitting. 4. If the mapping is absent, scan and decrypt the accepted transaction outputs. Do not infer success from a client timeout. Do not reconstruct claim amounts from an old quote. # Preparing token records Source: https://shield.fi/docs/trading/preparing-token-records How wallets select, preserve, and spend token records for confidential swaps. Shield Swap does not debit a public wallet balance at the start of a user swap. The `swap` and `swap_multi_hop` functions consume an input token record and ask that token program to transfer the requested amount into the AMM's public custody. Order construction must include record selection, spent-state checks, and change-record tracking. ## Outcome You are ready to build a swap when: * one record from the exact input token program is reserved for the attempt * the record is owned by the signer, unspent, and large enough for `amount_in` * the public amount satisfies the pool's scale requirement * the client can serialize the complete record without dropping private fields * the client is prepared to discover and store any change record returned by the input transfer ## Before you start Confirm the concrete input record program, AMM token program, signer, target base-unit amount, asset kind, and current record commitments. A symbol or display name is not sufficient proof that a record belongs to the required program. ## Record requirements The selected record must satisfy all of the following: * It was created by the concrete input token program used by the pool. * It is owned by the account signing the swap. * It has not already been spent. * Its private `amount` is at least `amount_in`. * Its `_nonce` and `_version` match the on-chain commitment. * The wallet can serialize it in the format expected by the SDK or CLI. The selected record must originate from the exact input token program. Wrapper records with matching symbols or decimals remain distinct assets. ## Amounts are raw token units `amount_in` uses the AMM token program's native base units. The core contract performs no decimal normalization. For tokens with at most nine decimals, the scale is `1`. For a token with more than nine decimals: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} scale = 10^(decimals - 9) internal_amount = raw_amount / scale ``` The raw input must be an exact multiple of `scale`. A record may contain a larger amount, but the public `amount_in` itself must satisfy this no-dust requirement. Review [Native token amounts](../concepts/token-amount-normalization) before building amount controls for mixed-decimal markets. ## Acquiring a record A wallet can use a record already in its local record store or call the input token program's public-to-private transfer path to create one: 1. Determine the concrete token program. 2. Call `transfer_public_to_private` for the signer. 3. Wait for acceptance. 4. Locate the returned record output. 5. Decrypt it with the signer's key when needed. 6. Preserve the complete record plaintext for later spending. The token program performs this conversion. It neither deposits funds into the AMM nor establishes a pool position. Production wallets should prefer an existing spendable record when possible. Repeated public-to-private conversion adds latency, fees, and a public funding event before the swap. ## Preserve record version and nonce A token record includes private owner and amount fields plus public commitment metadata. The current helper preserves `_version` when it is present and defaults to version `1` only when parsing an older form. Dropping `_version` can make the wallet derive a different commitment and cause a `Commitment does not exist` failure. Do not rebuild a record from owner and amount alone. The record `_nonce` is unrelated to: * The public swap nonce used in `swap_id` * The counter used to derive a blinding factor * The private blinding factor itself Keep these values in separate typed fields in wallet storage. ## Selecting among records The contract consumes one dynamic input record. It does not combine several small records inside `swap`. A practical selector should: 1. Filter by concrete token program or dynamic record identifier. 2. Exclude records already marked spent or pending. 3. Exclude records owned by a different signer. 4. Choose a record whose amount covers `amount_in`. 5. Prefer the smallest sufficient record when that reduces change-management cost. 6. Reserve the record locally before transaction construction. If no one record is large enough, acquire or construct a larger record through the token program before submitting the swap. Do not pass multiple records or assume the AMM will consolidate them. ## Change record behavior The AMM requests an exact private-to-public transfer of `amount_in`. If the input record contains more, the token program returns a change record as a transition output. The change record is not the same as an unfilled swap refund: | Value | Created when | Source | | - | - | - | | Input change | The consumed record exceeds `amount_in` | Token transfer during the swap transition | | Swap refund | The AMM does not consume all native base-unit `amount_in` | `swap_outputs`, claimed later | A user can receive both. The wallet must discover and store the input change immediately, then claim the AMM refund after finalize. ## Record state machine Use at least four local states: * `available`: confirmed unspent and eligible for selection * `reserved`: assigned to a transaction that has not reached a terminal status * `spent`: consumed by an accepted transaction * `unknown`: transaction outcome or record spend status cannot yet be confirmed Do not return an `unknown` record to the available pool. Query transaction status and record commitment state first. When a swap is accepted, atomically update the local store with the consumed record, any change record, the `swap_id`, and the pending claim. Local crashes between those writes are a common cause of apparently missing funds. ## Token metadata and record authority The off-chain API supplies display metadata such as symbol, decimals, and wrapper program. Record spent state comes from chain data, token eligibility from contract mappings, and market approval from operator policy. Keep these layers separate: 1. Off-chain token metadata 2. Concrete token-program record identity 3. On-chain decimal registration 4. On-chain pool-creation allowlisting 5. Pool, token, pair, and global pause state 6. Operator compliance policy and asset review The seed script is operator tooling. It defaults to a production API endpoint and writes unless dry-run mode is selected. Wallet record handling and compliance controls are separate. ## Confidentiality considerations Token records keep their plaintext confidential from ordinary public observers, but the swap exposes `amount_in`, pool, direction, price limit, minimum output, token identifiers, and timing as public data. Wallets should still protect: * Decrypted record plaintext * Private keys and view keys * Blinding factors and derivation counters * Local links between records, swap identifiers, and user profiles * Compliance record exports received through an authorized disclosure process Avoid logging full record plaintext. Error messages should identify a local record by a non-sensitive internal handle or commitment, not by owner and amount. ## Pre-submit checklist * The concrete record token matches the pool input side. * The signer owns the record. * The record is confirmed unspent. * The record amount covers `amount_in`. * `amount_in` is an exact native base-unit integer within `u128`. * `_nonce` and `_version` are preserved. * The record is locally reserved. * Change-record discovery is enabled. * Blinding recovery material is persisted. * The quote and deadline were generated from recent state. Continue with [Single-hop swaps](./single-hop-swaps) or [Multi-hop swaps](./multi-hop-swaps). # Price limits, slippage, and deadlines Source: https://shield.fi/docs/trading/price-limits-slippage-deadlines Build the three acceptance controls for current swap entry points. | Control | Unit | Purpose | | - | - | - | | `sqrt_price_limit` | Q128.128 `u256::U256` | Bounds movement inside one pool | | `amount_out_min` | Native output-token base units | Bounds final output | | `deadline` | Aleo block height in `u32` | Rejects stale execution | ## Square-root-price limits The limit must remain inside the Q128.128 domain: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} minimum = { hi: 0, lo: 702075911466779181339691826087 } maximum = { hi: 484680305, lo: 8756686347225649145659787327114459760 } ``` A `zero_for_one` limit must be below current price. A `one_for_zero` limit must be above current price. Equality with current price rejects. To derive a limit from display price, apply canonical token order and decimal adjustment, take the square root at sufficient precision, multiply by `2^128`, and round conservatively. Use integer or decimal arbitrary precision. Never build the signed value with binary floating point. ## Tick boundaries The crossing loop can cross an initialized tick whose price equals the limit. It does not trade past that limit. The bounded partial path cannot perform an uncrossed upward rest exactly on an initialized tick; it stops one Q128.128 unit below when necessary to preserve active-liquidity state. ## Minimum output `amount_out_min` applies to final output after all fees, rounding, crossing bounds, and partial-fill behavior. Multi-hop has one final minimum, not one minimum per hop. A quote must include: * current revisioned protocol state; * current pool slots and initialized ticks; * all fee tiers and protocol shares; * five crossing iterations for single-hop or three per active multi-hop leg; * bounded partial behavior; and * native base-unit rounding at each step. ## Remainder Execution can leave original input when it reaches a price limit, crossing bound, or zero-effective-input condition. If final output meets the minimum, the swap can succeed and store that amount in `SwapOutput.amount_remaining`. For multi-hop, hop 0 can leave original input, but every later hop must consume its full intermediate input or the route rejects. The current mapping stores only remaining original route input. It has no intermediate refund slots. ## Deadline Finalize accepts when `block.height <= deadline`. Read a recent height and add a policy-defined block offset that accounts for proof generation and submission delay. Encode the deadline as an Aleo block height. Do not encode a Unix deadline. # Quickstart Source: https://shield.fi/docs/trading/quickstart Complete a testnet swap with the TypeScript SDK, Python SDK, or CLI. Swap **1.5 USDCx for ETH on testnet** with an SDK or the CLI. A swap takes two transactions: the request and the claim. The SDK steps follow the [Veil](https://github.com/ProvableHQ/veil/tree/main/packages/shield-swap/examples/first-swap) and [Python](https://github.com/ProvableHQ/python-sdk/tree/master/shield-swap-sdk/examples/first-swap) examples. Run them in order in one session; they use an existing testnet key and keep recovery data in memory. The [complete programs](#run-packaged-examples) also cover account creation and storage. For a first trade from a terminal, we recommend the CLI. ## 1. Install and configure an account For the SDKs, supply your existing testnet private key through `SHIELD_SWAP_PRIVATE_KEY` in the process environment. The CLI can create and save an account for you. Use Node.js 22 or later in an ES module project: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} npm install @provablehq/shield-swap-sdk@0.12.1 @provablehq/veil-aleo-sdk@0.12.1 ``` ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import { loadNetwork } from '@provablehq/veil-aleo-sdk' import { parseUnits, shieldSwapActions } from '@provablehq/shield-swap-sdk' const privateKey = process.env.SHIELD_SWAP_PRIVATE_KEY if (!privateKey) throw new Error('Supply a testnet private key') const aleo = await loadNetwork('testnet') const { walletClient, publicClient, account } = aleo.createAleoClient({ privateKey }) const client = walletClient.extend(shieldSwapActions({ api: {} })) ``` The client includes a record scanner to find spendable funds, plus delegated proving and fee payment. Use Python 3.11 or later: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} python -m pip install "shield-swap-sdk==0.6.1" ``` ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import os from aleo import Aleo, HTTPProvider, testnet from aleo_shield_swap import ShieldSwap aleo = Aleo(HTTPProvider("https://edge.provable.com/api", network="testnet")) private_key = testnet.PrivateKey.from_string(os.environ["SHIELD_SWAP_PRIVATE_KEY"]) account = aleo.account.from_private_key(private_key) address = str(account.address) registration = aleo.records.register(account) if not registration["ok"]: raise RuntimeError(f"Record scanner registration failed: {registration['error']}") shield_swap_client = ShieldSwap(aleo) ``` Registration lets the scanner find your account's records. The SDK decrypts the returned records locally. Use Node.js 22 or later. Run these commands from the directory you will keep for the account: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} npm install -g @provablehq/shield-swap-cli@0.12.1 shield-swap setup --new --network testnet ``` Setup saves the account, authenticates, and requests test tokens when needed. Keep `.shield-swap/testnet/` between sessions and out of source control. ## 2. Get test tokens Your account needs spendable USDCx before you can trade. These examples authenticate and request test tokens if your existing balance is insufficient. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} await client.authenticateShieldSwap() const amountIn = '1.5' const from = await client.tokenData('USDCx') const balances = await client.getBalances({ tokens: [from.id] }) if ((balances[from.id]?.private ?? 0n) < parseUnits(amountIn, from.decimals)) { await client.api.confirmAirdrop(account.address) } ``` `confirmAirdrop` waits for the faucet transfer and its token records. ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} shield_swap_client.api.authenticate(address, lambda message: str(private_key.sign(message.encode()))) source = shield_swap_client.api.get_token("USDCx") amount_in = "1.5" if not shield_swap_client.has_swap_balance(source.address, amount_in): funding = shield_swap_client.confirm_airdrop() if not funding.success: raise RuntimeError(funding.error) ``` `has_swap_balance` checks for one spendable record covering 1.5 USDCx. ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} shield-swap balances --network testnet ``` Check for at least 1.5 USDCx in the `private` balance. If setup reports `AIRDROP_PENDING`, resume it with `shield-swap setup --network testnet`. A swap spends one token record. If your balance is split across smaller records, [prepare a covering record](./preparing-token-records). ## 3. Quote and submit the swap Quote **1.5 USDCx for ETH** with 50 basis points (0.5%) slippage. The SDKs choose the route and preserve the quote's minimum output when submitting. Each run submits a new swap. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const quote = await client.quote({ from: 'USDCx', to: 'ETH', amountIn, slippageBps: 50 }) const handle = await client.swap({ quote }) ``` ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} quote = shield_swap_client.quote( token_in="USDCx", token_out="ETH", amount_in=amount_in, slippage_bps=50, ) handle = shield_swap_client.swap(quote).delegate(wait=True) ``` Preview the quote, then submit with `--no-claim` to collect the output in step 4: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} shield-swap swap --from USDCx --to ETH --amount 1.5 --slippage 50 --network testnet shield-swap swap --from USDCx --to ETH --amount 1.5 --slippage 50 --network testnet --no-claim --execute ``` `--execute` obtains a fresh quote and submits without another prompt. Keep the returned swap ID. Keep `handle` available until you claim the output. ## 4. Wait for the output and claim The claim delivers the output and any unused input to your account. Continue with the handle or swap ID from step 3. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} await client.waitForSwapOutput({ handle }) const claim = await client.claimSwapOutput({ handle }) if (claim.amountOut <= 0n) throw new Error('The claim returned no ETH') ``` ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} claim = shield_swap_client.claim_swap_output( handle, timeout=5, ).delegate(wait=True) if claim.amount_out <= 0: raise RuntimeError("The claim returned no ETH") ``` `.delegate(wait=True)` waits for claim confirmation. Replace `SWAP_ID` with the ID from step 3, including its `field` suffix: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} shield-swap history --claim --swap-id SWAP_ID --network testnet --execute ``` You're done when the claim confirms with a positive ETH amount. If a call times out, keep the session open and [recover the existing trade](./history-and-recovery) before retrying. ## Run packaged examples The complete programs create or reuse a testnet account and perform the same **1.5 USDCx swap**. Each run starts a new trade. With Node.js 22 or later and pnpm 10, run from a [Veil checkout](https://github.com/ProvableHQ/veil/tree/main/packages/shield-swap/examples/first-swap): ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} pnpm setup:first-swap cd packages/shield-swap/examples/first-swap npm start ``` Keep the saved private key and recovery JSON file. The example's README explains how to reuse them. Run the [installed Python example](https://github.com/ProvableHQ/python-sdk/tree/master/shield-swap-sdk/examples/first-swap): ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} python -m aleo_shield_swap.examples.first_swap.swap ``` The example saves a generated account in a profile. Enable its optional journal before trading if you need swap recovery after a restart. ## Next Steps [Setup](./configure) covers mainnet configuration. [Swap History & Recovery](./history-and-recovery) shows how to finish an existing trade, and [Verify swap settlement](./verify-settlement) checks its transactions and output records. # Quote Source: https://shield.fi/docs/trading/quote Find pools and review a quote before submitting a swap.
This page helps you review the expected output of a trade before you submit it, in the Shield Swap app or with the TypeScript SDK, Python SDK, or CLI. ## How quotes work A quote estimates your output for a chosen pair and input amount. Shield Swap finds a route through the available pools, which can include an intermediate asset when the trade needs more than one pool. You can review the estimate without spending funds. A quote does not reserve liquidity, so the output can change before you submit the swap. Every quote includes a slippage setting. Slippage is how much the price can move between your quote and your trade. In practice, it is the gap between the amount you expect to receive and the least you will accept. If slippage is set too low, the swap can fail when the price moves. If it is set too high, you can receive less than you expected. At 1% slippage, a quote for 100 USDCx can settle for as little as 99.
## In the app The app quotes a trade as soon as you enter an amount. Nothing is sent until you confirm it on the [Swap](./swap) page's steps. **Before you start** You'll need: Shield Wallet connected to Shield Swap, with a confidential balance of the asset you want to sell. See [Fund your wallet](../start/fund-wallet). Shield Swap has two places to get a quote. **Trade** quotes one market at a time, each paired with USDCx, with a price chart and recent market activity. **Swap** quotes any two assets and routes through USDCx when no direct pool exists. ### Quote on the Trade page 1. Open **Trade**. 2. Select the market name at the top left to open **Switch pair**, then select a market. Each entry shows the pool's fee. 3. Select **Buy** to receive the market's asset or **Sell** to sell it. **Private Balance** shows how much of the asset you pay with is available. 4. Enter an amount in **You pay** or **You are selling**, or select **25%**, **50%**, **75%**, or **Max**. The panel shows the amount you are buying or receiving, the current exchange rate, **Price impact**, **Swap Fee** with the pool's fee percentage, and **You Receive At Least**, which is the quote after your slippage setting. ### Quote on the Swap page 1. Open **Swap**. 2. In **From**, select the asset to sell. In **To**, select the asset to receive. The arrow button between them swaps the two. 3. Enter the amount to sell. The panel shows the amount you will receive. **Spot price** and **Market details** below it show the current market, with links to the **Trade** page and the **Pool** for that pair. ### Change the slippage setting 1. Select the gear next to **Slippage**. 2. Select **0.10%**, **0.50%**, or **1.00%**, or enter a value. The control shows the current default and the allowed range. **You Receive At Least** updates to match. At a low setting, the app warns that the swap can fail if the price moves beyond your limit before confirmation. The setting applies to both the **Trade** and **Swap** pages. ### Read the quote | Field | What it means | | - | - | | **Price impact** | How much your trade moves the pool's price. Larger trades in smaller pools move it more. | | **Swap Fee** | The pool's fee, taken from the asset you sell. A route through two pools shows the two fees combined. | | **You Receive At Least** | The least you will receive after slippage. If the market moves further than that before your swap is included, the swap is rejected before it spends your input. The asset stays in your wallet and nothing waits in **Pending Claims**. |
## Discover pools Pool discovery helps you find markets for the asset you want to trade. A listed pool may have no active liquidity or have trading disabled; requesting a quote checks whether a route is available for your amount. **Prerequisites:** Your authenticated mainnet clients from [Setup](./configure): `client` for TypeScript, `dex` for Python, or a configured CLI profile. You do not need a funded account to discover pools. These examples find pools containing USDCx: ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const token = await client.tokenData("USDCx") const page = await client.api.getPools({ limit: 50, offset: 0 }) const pools = page.data.filter( pool => pool.token0 === token.id || pool.token1 === token.id, ) ``` `pools` contains the matching entries, each with a pool `key` and the token IDs `token0` and `token1`. This request inspects up to 50 pools, starting at offset 0. Check `page.pagination` and increase `offset` to read subsequent pages if you need a complete list. #### Look up token details Token metadata lets you display a symbol and format amounts with the correct decimal precision. This example looks up both tokens in the first matching pool: ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const selectedPool = pools[0] if (!selectedPool) throw new Error("No matching pools in this page") const token0 = await client.tokenData(selectedPool.token0) const token1 = await client.tokenData(selectedPool.token1) ``` Each result includes `symbol` and `decimals`, such as `token0.symbol` and `token0.decimals`. #### Check pool availability A pool needs active liquidity and permission to trade. Continue with `selectedPool` from the token lookup to check both on chain: ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const poolKey = selectedPool.key const slot = await client.getSlot({ poolKey }) const controls = await client.getTradeControls({ poolKey }) const hasLiquidity = (slot?.liquidity ?? 0n) > 0n const canTrade = hasLiquidity && controls.tradeable ``` `hasLiquidity` checks liquidity at the current price. `controls.tradeable` checks whether the pool is enabled and whether global, token, or pair pauses block trading. `canTrade` is `true` only when both checks pass. ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} token = dex.api.get_token("USDCx") pools = [ pool for pool in dex.api.get_pools() if token.address in (pool.token0, pool.token1) ] ``` `pools` contains the matching entries. Each entry has a `key` and the token IDs `token0` and `token1`. `get_pools()` reads the API's default page. If you need a complete list, use the [paginated pool endpoint](../api-reference/pools/list-pools). #### Look up token details Token metadata lets you display a symbol and format amounts with the correct decimal precision. This example looks up both tokens in the first matching pool: ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} if not pools: raise ValueError("No matching pools in this page") selected_pool = pools[0] tokens_by_id = {item.address: item for item in dex.api.get_tokens()} token0 = tokens_by_id[selected_pool.token0] token1 = tokens_by_id[selected_pool.token1] ``` Each result includes `symbol` and `decimals`, such as `token0.symbol` and `token0.decimals`. `get_token()` accepts a symbol; the catalog above lets you look up the token IDs stored in a pool. #### Check pool availability Continue with `selected_pool` from the token lookup. The Python SDK reads pool configuration and active liquidity on chain; its API client provides the global, token, and pair pause states: ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} pool_key = selected_pool.key pool = dex.get_pool(pool_key) slot = dex.get_slot(pool_key) global_controls = dex.api.get_compliance() token0_controls = dex.api.get_token_compliance(pool.token0) token1_controls = dex.api.get_token_compliance(pool.token1) pair_controls = dex.api.get_pair_compliance(pool.token0, pool.token1) has_liquidity = slot.liquidity > 0 trading_enabled = pool.enabled and not ( global_controls.global_paused or token0_controls.paused or token1_controls.paused or pair_controls.paused ) can_trade = has_liquidity and trading_enabled ``` `has_liquidity` checks liquidity at the current price. `trading_enabled` checks the pool's enabled flag and pause states; `can_trade` is `True` only when both conditions pass. An unknown or uninitialized pool raises an error when read. ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} shield-swap pools --network mainnet --token USDCx --limit 50 --json ``` The JSON response lists matching pools with their token IDs, active liquidity, and `tradeable` status. A `false` status includes a `reason` explaining why the pool cannot trade. `--token` accepts a symbol or token ID. The command inspects up to `--limit` pools before filtering, so increase this limit if you do not find the pair you need. These checks help you exclude unavailable pools. API data can lag behind the chain, and liquidity or trading controls can change before your swap executes. ## Request a quote You choose the asset to sell, the asset to receive, and the input amount. The SDK finds the route for you, so you do not need to select a pool from the discovery results. **Prerequisites:** Your authenticated mainnet `client`, `dex`, or CLI profile from [Setup](./configure). The SDKs can quote an unfunded account. The CLI checks your holdings while preparing the trade, so [fund your account](./fund-and-bridge) before using its example. These examples quote **1 USDCx for ETH on mainnet**. The slippage setting is 50 basis points (0.5%), which determines the minimum output you'll accept if the price changes before execution. | Parameter | Type and units | Required or default | | - | - | - | | `from` | `string`: input token symbol or ID. | Required. | | `to` | `string`: output token symbol or ID. | Required. | | `amountIn` | Decimal `string` in token units, or `bigint` in base units. | Required. | | `slippageBps` | Whole `number` in basis points. | Optional; defaults to `50`. | ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import { formatUnits } from "@provablehq/shield-swap-sdk" const quote = await client.quote({ from: "USDCx", to: "ETH", amountIn: "1", slippageBps: 50, }) const expectedOutput = formatUnits(quote.expectedOut, quote.to.decimals) const minimumOutput = formatUnits(quote.minOut, quote.to.decimals) ``` `expectedOutput` and `minimumOutput` are decimal strings in ETH units. Use them to compare the estimate with the least you're willing to receive. The quote retains the amounts as base-unit `bigint` values and lists its route in `quote.hops`. `quote.expiresAt` gives the expiry time in Unix milliseconds. Request a new quote if it expires before you submit the swap. | Parameter | Type and units | Required or default | | - | - | - | | `token_in` | `str`: input token symbol or ID. | Required. | | `token_out` | `str`: output token symbol or ID. | Required. | | `amount_in` | Decimal `str` or `Decimal` in token units. | Required. | | `slippage_bps` | `int` in basis points. | Optional; defaults to `50`. | ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} quote = dex.quote( token_in="USDCx", token_out="ETH", amount_in="1", slippage_bps=50, ) ``` `quote.estimated_amount_out` and `quote.minimum_amount_out` are decimal strings in ETH units. Compare both amounts with what you're willing to receive; `quote.hops` lists the pools along the route. Python quotes do not expire automatically. If you wait before submitting, request a fresh quote to review the current estimate. | Option | Type and units | Required or default | | - | - | - | | `--from` | Input token symbol or ID. | Required. | | `--to` | Output token symbol or ID. | Required. | | `--amount` | Decimal amount in token units. | Required unless using `--amount-raw` in base units. | | `--slippage` | Integer in basis points. | Optional; defaults to `50`. | | `--network` | `mainnet` or `testnet`. | Optional; defaults to `testnet`. Set `mainnet` here. | | `--json` | Boolean flag for JSON output. | Optional; off by default. | ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} shield-swap swap --from USDCx --to ETH --amount 1 --slippage 50 --network mainnet --json ``` Without `--execute`, this command returns a preview with `submitted: false`. Its `quote` contains `expectedOut`, `minOut`, and `hops`. Amounts are integer strings in base units; `quote.to.decimals` gives the output token's decimal places. The CLI does not save the preview for execution. When you're ready to submit a swap, the next invocation requests a fresh quote. ## Next Steps ### Submit a swap Once you're ready to trade, follow [Swap](./swap) to submit the quote and claim the output. ### Run packaged examples The [TypeScript examples](https://github.com/ProvableHQ/veil/tree/main/packages/shield-swap/examples/first-swap) and [Python examples](https://github.com/ProvableHQ/python-sdk/tree/master/shield-swap-sdk/examples/first-swap) take you through account setup, funding, and a complete swap. Follow the [Quickstart commands](./quickstart#run-packaged-examples) to run them. Both examples submit and claim a **1.5 USDCx swap on testnet**.
# Single-hop swaps Source: https://shield.fi/docs/trading/single-hop-swaps Build, submit, and settle a current one-pool swap. `swap` consumes one dynamic input token record, moves value to public AMM custody, updates one pool, and writes a pending `SwapOutput`. A later `claim_swap_output` returns output and any original-input remainder as encrypted token records. ## Inputs * Dynamic input token record * Private blinding factor * Public confidential address * Pool key and direction * Native base-unit input and minimum output * Q128.128 square-root-price limit * `u64` nonce * `u32` block-height deadline * Canonical token0 and token1 IDs The factor is private. The confidential address, route, amounts, limit, nonce, deadline, and token IDs are public. ## Before submission 1. Resolve program IDs and asset type from the selected deployment registry. 2. Read a revisioned protocol snapshot and current pool state. 3. Confirm global, pool, token, and pair controls allow trading. 4. Derive direction from canonical token order. 5. Select a sufficient unspent input record. 6. Simulate five tick crossings plus the bounded partial path. 7. Set Q128.128 price limit, native base-unit minimum, and block-height deadline. 8. Persist factor, confidential address, nonce, and recovery state. 9. Obtain any wrapper records required by the selected asset path. Use `swap_from_wrapped` in `shield_swap_router.aleo` when the input begins as an underlying wrapped-asset record. ## Execution The finalizer verifies controls, token order, price direction, deadline, confidential-address uniqueness, and router caller identity when applicable. It walks at most five initialized ticks. It then fills only to the nearer next tick or user limit. If fee-adjusted input is zero, execution returns all input without moving price or charging a fee. If input remains after another bounded partial result, the mapping preserves it for claim. ## Pending output ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} SwapOutput { recipient, caller, token_in, token_out, amount_out, amount_remaining } ``` Read the mapping only after the swap is accepted. Copy its exact values into `claim_swap_output` together with the original factor, confidential address, and current signer freezelist proofs. ## Retry rules Do not resubmit after an ambiguous response until transaction status and mapping state are known. A fresh nonce does not make duplicate economic execution safe. # Swap Source: https://shield.fi/docs/trading/swap Submit a quoted trade and claim the output in the Shield Swap app or with the SDKs or CLI.
This page takes you from a quote to the funds received from a completed swap, in the Shield Swap app or with the TypeScript SDK, Python SDK, or CLI. ## How a swap settles Every swap finishes in two steps. The swap trades your input asset through the pool and leaves the output waiting to be collected. The claim then delivers the output to your wallet, along with any unused input. In the app, the claim runs on its own after the swap confirms, and the price is final as soon as the swap confirms. If anything interrupts the claim, the output waits for you. In the app, open **Portfolio**, then **Pending Claims**. With the SDKs or CLI, [recover the existing swap](./history-and-recovery) and finish its claim. You do not need to start another trade, and a second swap opens a new trade instead of finishing the first one. The settled amounts can differ from the quote. A trade can stop before it spends the full input when the price reaches your limit, and the claim returns the unused amount alongside the output. On a route through two pools, any unused input comes back in the asset you sold, never in the intermediate asset. The assets traded, the amount, the route, the output, any refund, the price movement, and the timing of each swap are public. Your wallet address stays out of the public market data. See [Public data](../confidentiality/public-data).
## In the app **Before you start** You'll need: * Shield Wallet connected to Shield Swap, with a confidential balance of the asset you want to sell. See [Fund your wallet](../start/fund-wallet). * A quote on the **Trade** or **Swap** page. See [Quote](./quote#in-the-app). **Trade** sells or buys one market's asset against USDCx. **Swap** exchanges any two assets and routes through USDCx when the pair has no direct pool. A routed swap shows both pools' fees combined as one **Swap Fee**, and **History** labels it **Multi-hop**. ### Submit the trade 1. With your quote showing, select the main button. On **Trade** it names the action and asset, such as **Buy ALEO** or **Sell ETH**. On **Swap** it reads **Swap**. 2. In **Review buy**, **Review sell**, or **Review Swap**, check the amounts, **Max Slippage**, **Price Impact**, **Swap Fee**, and **You Receive At Least**. On **Swap**, the **Quote refresh** bar counts down; if it runs out, select **Review Updated Quote** and check the new amounts. 3. Select **Confirm & Swap**. **Confirm once.** After you confirm in Shield Wallet, the swap is sent. If the window closes or looks stuck, do not submit the trade again. Open **Portfolio**, then **History** or **Pending Claims**, to see where it is. 4. In the **Transaction request** in Shield Wallet, check that the request is from swap.shield.fi and that the amounts match your review, then select **Confirm**. Your assets do not move until you confirm. The app shows **Executing buy**, **Executing sell**, or **Executing swap** with a progress circle, and Shield Wallet shows **1 pending transaction** while the swap is in flight. The app then shows **Buy Complete**, **Sell Complete**, or **Swap Complete**. The first completion screen says the price is final and the claim will finish in the background. When the claim finishes, the screen shows **Swap** and **Claim** both marked **Confirmed**, with an **Explorer** link to the public transactions. The received asset appears in your Shield Wallet balance. ### Check the result Open **Portfolio**, then **History**. The newest row shows the trade with its status, route, and amounts. On the **Trade** page, **Your Trades** under the chart lists the same trades for the current market. See [Swap history and recovery](./history-and-recovery#in-the-app) for the full table and what each status means. ### Troubleshooting | What you see | Why it happens | What to do | | - | - | - | | **The quote changed. Review the updated amounts before confirming.** | The market moved while the review window was open, or the quote refresh ran out. | Select **Review Updated Quote**, check the new amounts, then select **Confirm & Swap** again. | | The warning under **Slippage** says the swap may fail if the price moves beyond your limit | Your slippage setting is low for the market's current movement. | Raise the setting, or keep it and accept that the swap can fail. A failed swap is rejected before it spends your input, so the asset stays in your wallet. Request a new quote and try again. | | **Swap Complete** shows the price is final, but the claim has not finished | The claim is still running in the background. | Wait. If the received asset has not appeared in Shield Wallet after a few minutes, open **Portfolio**, then **Pending Claims**. \[VERIFY: how long a claim can take, and what Pending Claims shows for an unfinished claim] | | Shield Wallet shows a public balance of the asset, but the trade panel shows nothing to sell | Shield Swap trades only from the confidential balance. | Shield the asset in Shield Wallet. See [Move a public balance into your confidential balance](../start/getting-started#move-a-public-balance-into-your-confidential-balance). |
## Quote, submit, and claim **Prerequisites:** Your authenticated mainnet `client` for TypeScript, `dex` for Python, or CLI profile from [Setup](./configure). Your account needs one spendable token record covering **1 USDCx**, plus funds for transaction fees. [Fund your account](./fund-and-bridge) or [prepare a covering record](./preparing-token-records) if needed. For services, scripts, and agents, the [optional storage setup](./configure#3-configure-optional-storage) saves the data needed to resume after a restart. Without it, your application must retain the swap handle returned on submission. A handle contains the identifiers and claim data needed to finish that trade. These examples trade **1 USDCx for ETH on mainnet**. To practice with test assets, use the [Quickstart](./quickstart). ### 1. Review a quote Your quote gives you an estimated output and the minimum you'll accept for **1 USDCx on mainnet**. The examples allow 50 basis points (0.5%) below the estimate. Use your authenticated client from [Setup](./configure); [Quote](./quote#request-a-quote) explains the parameter types and defaults. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import { formatUnits } from "@provablehq/shield-swap-sdk" const quote = await client.quote({ from: "USDCx", to: "ETH", amountIn: "1", slippageBps: 50, }) const expectedOutput = formatUnits(quote.expectedOut, quote.to.decimals) const minimumOutput = formatUnits(quote.minOut, quote.to.decimals) ``` `expectedOutput` and `minimumOutput` are decimal strings in ETH units. Review both before submitting. If the time in `quote.expiresAt` has passed, request a fresh quote. ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} quote = dex.quote( token_in="USDCx", token_out="ETH", amount_in="1", slippage_bps=50, ) ``` `quote.estimated_amount_out` and `quote.minimum_amount_out` are decimal strings in ETH units. Review both before submitting. Python quotes do not expire automatically, so request a fresh quote if you wait before trading. You can review a trade without submitting it by leaving out `--execute`: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} shield-swap swap --from USDCx --to ETH --amount 1 --slippage 50 --network mainnet ``` The preview shows `sell`, `buy`, `floor`, and `route`. `--amount 1` is one USDCx, and `--slippage 50` allows 0.5% below the estimated output. ### 2. Submit the reviewed quote Once you're satisfied with the quote, you can submit the swap. The SDK examples use the mainnet `quote` from [Review a quote](#1-review-a-quote) and preserve its route and minimum output. The CLI requests a fresh quote when you execute the command. **Submit only when you're ready to trade on mainnet.** These calls spend the quoted input. Running them again starts another swap. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const handle = await client.swap({ quote }) ``` `handle.transactionId` identifies the submitted transaction, and `handle.swapId` identifies the swap to claim. Keep the handle until the claim finishes; a configured file store retains it for recovery. ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} handle = dex.swap(quote).delegate(wait=True) ``` `.delegate(wait=True)` submits through the configured proving service and waits for confirmation. `handle.transaction_id` identifies the transaction, and `handle.swap_id` identifies the swap to claim. An attached journal saves the handle for recovery after a restart. Use `--no-claim` to leave collection for the next step. Without this flag, the CLI attempts the claim in the same run. ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} shield-swap swap --from USDCx --to ETH --amount 1 --slippage 50 --network mainnet --no-claim --execute ``` `--execute` submits a fresh quote without another confirmation; the preview does not lock its price. The command reports the swap transaction ID and swap ID, and saves recovery data in the account directory. Keep both IDs for the remaining steps. If proving or confirmation times out, check the original transaction before retrying. A timeout leaves its outcome unknown. Your [history and recovery data](./history-and-recovery) let you continue investigating the existing trade. ### 3. Claim the output After the swap is accepted, its output becomes available to collect. The claim delivers the output and any unused input to your account. Continue with the mainnet account and `handle` from [Submit the reviewed quote](#2-submit-the-reviewed-quote), or the saved CLI account and swap ID. If a claim attempt was interrupted, [check its transaction status](./history-and-recovery#establish-the-original-outcome) before submitting another claim. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} await client.waitForSwapOutput({ handle, timeout: 60_000 }) const claim = await client.claimSwapOutput({ handle }) ``` The optional `timeout` is a polling limit in milliseconds; this example allows 60 seconds instead of the default 15 seconds. Waiting does not submit a transaction. Once the output is available, the claim call submits the transaction that delivers it. `claim.transactionId` identifies the claim. `claim.amountOut` and `claim.amountRemaining` are `bigint` amounts in output-token and input-token base units, respectively. The latter is zero when no input is refunded. ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} claim = dex.claim_swap_output(handle, timeout=60).delegate(wait=True) ``` The optional `timeout` is a polling limit in seconds. This example waits up to 60 seconds for the output; the default of `0` checks once. `.delegate(wait=True)` then submits the claim and waits for confirmation. `claim.transaction_id` identifies the claim. `claim.amount_out` and `claim.amount_remaining` are integer amounts in output-token and input-token base units, respectively. Save the latter for refund accounting; the journal records the received amount but does not retain the refund amount. Replace `SWAP_ID` with the ID reported by the submission command, including its `field` suffix. Run this from the same account directory: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} shield-swap history --claim --swap-id SWAP_ID --network mainnet --execute ``` `--swap-id` selects the original trade; omitting it can claim other pending swaps. `--execute` authorizes the claim transaction. The result includes its transaction ID and the received amount. ## Check the result Your swap and claim each have a transaction ID. Checking both confirms whether the ledger accepted the trade and its collection. Your account's balance then shows whether record scanning has found the received funds. The SDK examples use the `handle` and `claim` from the mainnet steps above. They also use `publicClient` for TypeScript or `aleo` for Python, alongside the trading client from [Setup](./configure#2-setup-a-trading-client). These checks read existing state and do not submit another transaction. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} const swapTransaction = await publicClient.getConfirmedTransaction({ id: handle.transactionId, }) const claimTransaction = await publicClient.getConfirmedTransaction({ id: claim.transactionId, }) if (swapTransaction.status !== "accepted" || claimTransaction.status !== "accepted") { throw new Error("Swap and claim must both be accepted") } const balances = await client.getBalances({ tokens: [quote.from.id, quote.to.id], }) ``` Both transaction results must have `status: "accepted"`. `balances[quote.to.id]?.private` reports your ETH record balance as a base-unit `bigint`. Use `formatUnits` from the quote example with `quote.to.decimals` to display it in ETH units. ```python theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} swap_transaction = aleo.network.get_confirmed_transaction(handle.transaction_id) claim_transaction = aleo.network.get_confirmed_transaction(claim.transaction_id) if swap_transaction["status"] != "accepted" or claim_transaction["status"] != "accepted": raise RuntimeError("Swap and claim must both be accepted") balances = dex.get_balances() ``` Both transaction dictionaries must contain `"status": "accepted"`. `balances` reports your account's holdings; the ETH entry includes the amount held in token records. Replace `SWAP_TRANSACTION_ID` and `CLAIM_TRANSACTION_ID` with the IDs from the submission and claim commands. The public lookup checks transaction acceptance; the CLI reads your account's balances: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} curl --fail-with-body --silent --show-error \ "https://api.explorer.provable.com/v1/mainnet/transaction/confirmed/SWAP_TRANSACTION_ID" curl --fail-with-body --silent --show-error \ "https://api.explorer.provable.com/v1/mainnet/transaction/confirmed/CLAIM_TRANSACTION_ID" shield-swap balances --network mainnet ``` Both lookup responses must report `"status": "accepted"`. The balance command shows received ETH in the `private` column. If a transaction lookup fails or remains unconfirmed, repeat the read before deciding whether to retry an operation. Record scanning can also lag behind an accepted claim; a delayed balance update does not mean you need to claim again. A total balance does not identify which trade produced it. [Verify swap settlement](./verify-settlement) shows how to match the claim to its exact output record and check that the pending output has been removed from the contract. ## Handle partial fills and refunds The settled amounts can differ from the quote. A price limit can stop a single-pool trade before it spends the full input; the claim returns the unused amount alongside your output. Record both received output and refunded input when accounting for the trade. For a route through several pools, later hops must spend their intermediate input or the route rejects. [Output claims and refunds](./output-claims-and-refunds) and [multi-hop swaps](./multi-hop-swaps) explain these settlement rules. ## Next Steps ### Recover an interrupted swap [Swap History & Recovery](./history-and-recovery) shows how to check an existing trade and finish its claim after a timeout or restart. ### Run packaged examples The [TypeScript examples](https://github.com/ProvableHQ/veil/tree/main/packages/shield-swap/examples/first-swap) and [Python examples](https://github.com/ProvableHQ/python-sdk/tree/master/shield-swap-sdk/examples/first-swap) include setup, funding, and a complete **1.5 USDCx swap on testnet**. Follow the [Quickstart commands](./quickstart#run-packaged-examples) to run them. Each run starts a new trade.
# Trader workflow Source: https://shield.fi/docs/trading/trader-workflow Build a wallet flow that quotes, submits, claims, and reconciles a Shield Swap trade. A Shield Swap trade is two transactions. `swap` consumes an input token record and writes a public pending output. `claim` returns the output and any unspent input as token records. A wallet should report success only after the claim is accepted and the returned records are stored. A finalized swap without a finalized claim is still unsettled for the user. ## Finish line The workflow is complete when all of these are true: * the swap transaction is accepted * `swap_outputs[swap_id]` contains the finalized output used for the claim * the claim transaction is accepted * output and refund records are stored in the wallet * the pending mapping entry is removed * local record state and public AMM state reconcile ## Flow 1. Confirm the deployment, market, and controls. 2. Quote with the contract's integer math and iteration caps. 3. Reserve the input token record and persist claim recovery material. 4. Submit `swap` once and track its terminal state. 5. Read the finalized `SwapOutput` from the mapping. 6. Submit `claim` with the exact stored fields. 7. Store returned records and reconcile final state. ## Before you start Your client needs deployment identity, chain reads, a record store, exact-value parsing, proof submission, finality monitoring, and durable pending-claim storage. If you only need market data or routes, start with the [REST API](../rest/overview) instead. ## 1. Confirm the market Before quoting, read the pool and slot from chain state. Confirm: * The pool contains the intended token programs in the expected token0 and token1 order. * The pool is enabled. * Global, token, and pair pause controls do not block trading. * The fee tier and tick spacing match the market the user selected. * The current square-root price, current tick, active liquidity, and initialized-tick pointers are available. Do not infer token ordering from symbols. The contract validates the token identifiers passed to `swap` against the public `PoolState`, and direction is defined in that canonical order. The token metadata API supplies display names and decimals. Use chain state for pool identity and token eligibility, and operator policy records for market approval. ## 2. Establish direction `zero_for_one` determines both the input token and the direction of price movement: | `zero_for_one` | Input | Output | Square-root price movement | | - | - | - | - | | `true` | token0 | token1 | Down | | `false` | token1 | token0 | Up | Use the same direction when computing the quote, price limit, record token, and token identifiers. A direction mismatch normally fails validation or attempts to spend the wrong input record. ## 3. Prepare a spendable record Select a token record from the input token program with an amount at least as large as `amount_in`. The record owner must be the transaction signer, the record must be unspent, and its concrete token program must match the selected pool side. Preserve all record fields, including `_nonce` and `_version`. Reconstructing a record without its version can produce a commitment that does not exist on chain. The AMM asks the token program to move exactly `amount_in` from private to public custody. If the record is larger, the token program returns a change record. The wallet should track the consumed record and the returned change as part of the same state update. See [Preparing token records](./preparing-token-records). ## 4. Quote with integer math Read current state close to submission time and simulate the contract's fixed-point arithmetic. The quote should include: * Expected output in raw token units * Expected unspent input * Expected end price and tick * Expected initialized ticks crossed * Expected active liquidity after each crossing * Fee and protocol-fee estimates * The maximum number of swap iterations available Single-hop execution performs five fixed swap iterations. If the requested amount needs more initialized-tick steps, execution stops after the fifth step and stores the remaining input for refund. A quote must model this cap. Use an exact transcription of the contract math. Floating-point calculations are suitable for display, not for acceptance-critical parameters. See [Swap-step math](../math/swap-step). ## 5. Set protections Set all three protections deliberately: * `sqrt_price_limit` caps adverse price movement. * `amount_out_min` sets the minimum acceptable final output in raw output-token units. * `deadline` sets the last acceptable block height. The price limit must lie inside the contract domain and strictly in the trade direction relative to the current price. It cannot equal the current square-root price. If the limit exactly equals the next initialized tick and the swap reaches that price, the contract completes the tick crossing. It updates active liquidity, tick pointers, and both Q128 fee-growth-outside accumulators, but it does not trade beyond the limit. See [Price limits, slippage, and deadlines](./price-limits-slippage-deadlines) and [Exact-limit tick crossing](../math/exact-limit-tick-crossing). ## 6. Create confidential addressing material Generate a fresh private blinding factor and derive the public confidential address from: * The AMM program address * The claim-or-swap domain separator * The signer address * The private blinding factor Persist the blinding factor, or the deterministic derivation material needed to recover it, before submitting the swap. The same signer and factor are required for the claim. Each accepted confidential swap marks its confidential address as used. Do not reuse a counter or factor combination that derives an address already accepted by the program. The public request exposes the pool, direction, amount, minimum output, price limit, nonce, deadline, and token identifiers. Token records keep funding and delivery confidential, while the confidential address keeps the signer's account out of the public recipient field. Amounts and routes remain public. ## 7. Submit the swap The single-hop `swap` input order is: 1. Input token record 2. Private blinding factor 3. Public confidential address 4. Public pool key 5. Public direction 6. Public input amount 7. Public minimum output 8. Public square-root-price limit 9. Public nonce 10. Public deadline 11. Public token0 identifier 12. Public token1 identifier Use a unique `u64` nonce. The nonce contributes to `swap_id`; it is distinct from the record nonce and the blinding-factor derivation counter. An accepted transaction returns a public `swap_id`, a token change record when applicable, and an investigator-owned compliance record. The investigator record contains request and attribution data. Deployment policy determines disclosure authorization and legal eligibility. ## 8. Observe finalized output After acceptance, read `swap_outputs[swap_id]`. Do not construct claim arguments from the pre-trade quote. The mapping is authoritative and records: * Output token * Raw output amount * Input token * Raw unspent input amount * Confidential recipient and caller addresses Public observers can read the pending output amounts and observe when the mapping is removed. ## 9. Claim output and refund Call `claim_swap_output` with the same signer, blinding factor, and confidential address. Pass the exact token identifiers and amounts stored in `swap_outputs`. On acceptance, the output and refund are returned as token records, and the pending mapping is removed. A second claim fails because the mapping no longer exists. Claims are separate from trading pause checks. A pause that prevents a new swap does not, by itself, remove the normal claim path. Token transfer behavior and exact mapping validation still apply. See [Output claims and refunds](./output-claims-and-refunds). ## 10. Reconcile wallet state A wallet should finish the workflow by recording: * The consumed input record as spent * Any input change record * The accepted swap transaction and `swap_id` * The pending-output mapping state * The claimed output record * Any refund record * The claim transaction and final mapping removal If transaction status is uncertain, query chain state before retrying. Never submit the same input record twice on the assumption that the first transaction failed. For recovery procedures, see [Failures and recovery](./failures-and-recovery). # Verify swap settlement Source: https://shield.fi/docs/trading/verify-settlement Check transaction acceptance, pending output, and the exact token record returned by a swap claim. Verify a submitted swap by checking its transactions and returned token records. The examples default to mainnet and read existing state. For a [Quickstart](./quickstart) trade, select the testnet lookup and use its original testnet account and recovery store. A completed trade has an accepted swap, an accepted claim for that swap, and returned token records available to the account. Check these separately: | Check | What it establishes | | - | - | | Both transactions report `accepted` | The ledger accepted the swap and claim. | | The swap and claim reference the same swap ID | The claim belongs to the trade being checked. | | `swap_outputs[swap_id]` is absent after acceptance | No pending output remains for that swap. Absence alone does not prove a swap ever existed. | | An unspent ETH record matches the claim's output commitment and amount | The account can find the exact proceeds from this claim. A total balance alone cannot identify their source. | Keep the swap transaction ID, claim transaction ID, swap ID, and actual received amount from the execution output. Use the swap ID including its `field` suffix. Do not substitute the quoted estimate or minimum for the received amount. ## Check both transactions Use the lookup for the network that received the trade. The response includes `status`, `transaction`, and `finalize`. Replace each placeholder with the original transaction ID: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} curl --fail-with-body --silent --show-error \ "https://api.explorer.provable.com/v1/mainnet/transaction/confirmed/SWAP_TRANSACTION_ID" curl --fail-with-body --silent --show-error \ "https://api.explorer.provable.com/v1/mainnet/transaction/confirmed/CLAIM_TRANSACTION_ID" ``` ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} curl --fail-with-body --silent --show-error \ "https://api.explorer.provable.com/v1/testnet/transaction/confirmed/SWAP_TRANSACTION_ID" curl --fail-with-body --silent --show-error \ "https://api.explorer.provable.com/v1/testnet/transaction/confirmed/CLAIM_TRANSACTION_ID" ``` Inspect `status` in each JSON response. HTTP success alone is not transaction acceptance. | Result | Next action | | - | - | | `accepted` for both | Verify the matching output record below. | | `rejected` for the swap | Inspect the rejection and input-record state before preparing a new trade. | | Swap accepted, claim rejected | Recover the existing swap after checking whether its output remains claimable. | | Not found, timeout, or another lookup error | The outcome is unknown. Check the network, ID, and connectivity, then repeat the lookup. Do not resubmit based on this result. | For TypeScript SDK 0.12.1, the equivalent lookup is `await publicClient.getConfirmedTransaction({ id })`. Keep `publicClient` from `aleo.createAleoClient({ privateKey })`; the wallet client extended with `shieldSwapActions` does not expose this method by default. See [Swap History & Recovery](./history-and-recovery#establish-the-original-outcome) to check an uncertain transaction before retrying. ## Verify the output record This TypeScript verifier checks a **USDCx to ETH trade** using the 0.12.1 SDK packages. It uses the network selected by the configured clients. It checks the ETH proceeds; a trade with an input refund also needs its USDCx refund record checked. Run verification before spending or combining the returned ETH record. A later spend can make this unspent-record check fail even though settlement succeeded. The verifier uses the configured ledger gateway and account scanner. It is independent of CLI history summaries, but is not a standalone cryptographic ledger proof. Record plaintext stays in memory and is never printed. ### 1. Load the existing account For mainnet, use the `client` and `publicClient` initialized in [Setup](./configure). For testnet, use the clients from the [Quickstart](./quickstart). Keep the original private key and recovery store; do not generate a new account. If the trade came from a packaged SDK example, load that example's account and store instead. The verification snippet also uses transaction types from the core package: ```bash theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} npm install @provablehq/veil-core@0.12.1 ``` For a CLI trade, load the saved session from the directory containing its `.shield-swap/` account: ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import { loadSession } from "@provablehq/shield-swap-cli/session" import { getConfirmedTransaction } from "@provablehq/veil-core" const { client } = await loadSession({ network: "mainnet" }) const publicClient = { getConfirmedTransaction: (params: Parameters[1]) => getConfirmedTransaction(client, params), } ``` The session helper requires `@provablehq/shield-swap-cli@0.12.1` in the application's dependencies. It reuses the saved account for the selected network. Pass `network: "testnet"` for a CLI Quickstart trade. ### 2. Match the claim to its output record The following snippet performs one verification pass with the existing clients. Replace the four input placeholders with the original transaction IDs, swap ID, and actual received ETH amount. `receivedEth` is a decimal string without the token symbol. If confirmation or scanning has not caught up, repeat the checks without submitting a transaction. ```typescript theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} import assert from "node:assert/strict" import type { Execution } from "@provablehq/veil-core" import { parseTokenRecordInfo, parseUnits, formatUnits } from "@provablehq/shield-swap-sdk" const swapTx: string = "SWAP_TRANSACTION_ID" const claimTx: string = "CLAIM_TRANSACTION_ID" const swapId = "SWAP_ID" const receivedEth = "RECEIVED_ETH" assert(swapTx && claimTx && swapId && receivedEth, "Pass swap transaction ID, claim transaction ID, swap ID, and received ETH") assert(/^\d+field$/.test(swapId), "Swap ID must include its field suffix") assert(swapTx !== claimTx, "Swap and claim must be different transactions") try { const swap = await publicClient.getConfirmedTransaction({ id: swapTx }) const claim = await publicClient.getConfirmedTransaction({ id: claimTx }) assert.equal(swap.status, "accepted", "Swap is not accepted") assert.equal(claim.status, "accepted", "Claim is not accepted") assert.equal(swap.transaction.id, swapTx, "Swap transaction ID mismatch") assert.equal(claim.transaction.id, claimTx, "Claim transaction ID mismatch") console.log("Swap: accepted; claim: accepted") const swapCalls = (swap.transaction.execution as Execution | undefined)?.transitions ?? [] const claimCalls = (claim.transaction.execution as Execution | undefined)?.transitions ?? [] const swapCall = swapCalls.find((t) => t.program === "shield_swap.aleo" && ["swap", "swap_multi_hop"].includes(t.function) && t.outputs?.some((o) => o.type === "public" && o.value === swapId)) const claimCall = claimCalls.find((t) => t.program === "shield_swap.aleo" && ["claim_swap_output", "claim_swap_output_no_refund"].includes(t.function) && t.inputs?.[2]?.type === "public" && t.inputs[2].value === swapId) assert(swapCall && claimCall, "Transactions do not reference the supplied swap ID") const eth = await client.tokenData("ETH") const expected = parseUnits(receivedEth, eth.decimals) assert(expected > 0n, "Received ETH must be positive") // In these claim entry points, inputs 4 and 5 are token_out and amount_out. assert.equal(claimCall.inputs?.[4]?.value, eth.id, "Claim output token is not ETH") assert.equal(claimCall.inputs?.[5]?.value, `${expected}u128`, "Received amount does not match the accepted claim") const pending = await client.getSwapOutput({ swapId }) assert.equal(pending, null, "Pending swap output still exists; check ledger state") console.log("Swap ID: matched; pending output: absent") const program = eth.underlyingProgram ?? eth.ammTokenProgram assert(program, "ETH token metadata has no output program") const commitments = new Set(claimCalls .filter((t) => t.program === program) .flatMap((t) => t.outputs ?? []) .filter((o) => ["record", "record_with_dynamic_id"].includes(o.type)) .map((o) => o.id)) assert(commitments.size > 0, "Claim has no ETH record output") const records = await client.requestRecords({ program, statusFilter: "unspent", includePlaintext: true, }) const matched = records.some((record) => { if (record.spent !== false || record.programName !== program || !record.commitment || !commitments.has(record.commitment) || !("recordPlaintext" in record) || typeof record.recordPlaintext !== "string" || !record.recordPlaintext) return false const info = parseTokenRecordInfo(record.recordPlaintext) return info !== null && !info.recipientBound && info.amount === expected && (info.tokenId === undefined || info.tokenId === eth.id) }) assert(matched, "No matching unspent ETH record. Check scanner progress, account, or later spends") console.log(`ETH record: unspent; commitment matched; amount ${formatUnits(expected, eth.decimals)}`) console.log("VERIFIED: swap accepted, claim accepted, pending output cleared, ETH record available") } catch (error) { console.error(error instanceof assert.AssertionError ? error.message : "Verification read failed. Check connectivity and inputs, then rerun verification.") process.exitCode = 1 } ``` The commitment comparison identifies an output created by this claim, even if the account already held ETH. Amounts use `bigint` base units throughout, avoiding floating-point rounding. ### 3. Inspect the result A successful run ends with: ```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}} VERIFIED: swap accepted, claim accepted, pending output cleared, ETH record available ``` If a check fails, inspect the last completed stage. A scanner delay, wrong account, wrong ID, or later record spend can prevent verification. An empty `getUnclaimedSwaps()` result or an absent mapping alone is insufficient evidence. Repeat read-only checks or follow [Failures and recovery](./failures-and-recovery); do not rerun the original swap to obtain a verification result. ## Packaged trading examples The [TypeScript example in Veil](https://github.com/ProvableHQ/veil/tree/main/packages/shield-swap/examples/first-swap) and [Python example in python-sdk](https://github.com/ProvableHQ/python-sdk/tree/master/shield-swap-sdk/examples/first-swap) include complete trading programs. Their [run commands](./quickstart#run-packaged-examples) submit new testnet swaps. Use the read-only checks above to verify an existing trade.