Repository navigation
feat: resolve freeze-list compliance proofs automatically (bridge + Shield Swap) - #80
Merged
iamalwaysuncomfortable merged 12 commits intoOct 22, 2026
Merged
Conversation
…ge and Shield Swap SDKs Add aleo.compliance (tree math, MerkleProof/MerkleProofPair, witness verification, freeze_list_program, FreezeListProofs/AsyncFreezeListProofs with a per-client cache pinned to each list's root) and the freeze-list endpoint on both network clients and facade network modules. Bridge: unshield, xreserve.burn and execute take compliance_proofs=; the merkle_proof= keyword remains as an alias. Shield Swap: aleo_shield_swap.compliance plans the proof slots of every entrypoint (swap, claim, mint, increase, collect, all rebalance variants) per MerkleProofReference.md; the write methods take compliance_proofs= as a tuple in input order (typed per-call unions on the bundle count) and build every bundle themselves when it is unset; wrapper_proofs= remains as an alias. Live-verified on testnet (swap, claim, USDCx leg, liquidity lifecycle) and on mainnet (USDCx unshield, private xReserve burns to Ethereum and Arc).
FROZEN_ADDRESS_1 / FROZEN_ADDRESS_2 name the frozen accounts. While a list does not contain them the checks skip and say so; once it does they prove the resolver refuses a proof for the address and that a swap, mint, unshield, or private burn involving it is refused before anything is proved or paid.
MerkleProof -> MerkleProofInput, ProofSlot -> ComplianceProofContext, exclusion_proof_from_tree -> generate_freezelist_proof, one_bundle -> literal_compliance_proof, bundle_literal -> format_merkle_proof_pair, KNOWN_FREEZE_LISTS -> KNOWN_TOKEN_FREEZELISTS, freeze_list_program -> freezelist_program, get_program_freeze_list -> get_compliance_freeze_list. None of these shipped in a release; compliance_proofs=, merkle_proof=, wrapper_proofs=, FreezeListProofs and the released bridge helpers are unchanged.
…roofInput siblings take ints or "…field" strings and leaf_index an int or "Nu32"; values normalise to ints so equality and hashing are unchanged, and sibling_literals renders the literal form back out.
…l #193) The three warp programs changed token_owner on 2026-10-02; the stale shared owner made every Aleo-origin transfer fail the on-chain app_metadata check. Registry version is unchanged (data fix, as upstream). Owners verified against the live app_metadata mappings; fixture mirrors veil's reviewed metadata with a regression test over the five outbound routes.
One [MerkleProof; 2] argument as a caller supplies it (literal, MerkleProofPair, or a pair of MerkleProofInput); the name follows veil's ComplianceProofs.
… for the argument ComplianceProof is one [MerkleProof; 2] value (literal, MerkleProofPair, or a pair of MerkleProofInput). Each SDK package now defines ComplianceProofs as its calls' whole compliance_proofs argument, as veil does: the bridge's is one proof bare or in a one-tuple or None; Shield Swap's is the union of the per-method tuple aliases or None.
Explain how the private Merkle inputs establish freezelist compliance inside the transaction proof, list the functions that take proofs with their slots and subjects, then describe automatic generation, caching, errors, and the optional proof inputs. Wording follows veil #194.
aleo-sdk, aleo-contract-abi-generator, shield-swap-sdk and aleo-bridge-sdk move to 0.7.0 in lockstep (manifests, lockfiles, docs conf, __version__ and the version tests). The shield-swap and bridge aleo-sdk floors rise to >=0.7.0: both now import aleo.compliance, which first ships in this release.
…s missing aleo_bridge and aleo_shield_swap check for aleo.compliance on import and raise an ImportError that names the installed aleo-sdk version and the pip command, instead of a bare ModuleNotFoundError from the first private call.
…ve runners With maturin-action's sccache input, sccache starts on the first rustc call and a transient failure to reach the GitHub cache service (a DNS error on a macOS runner in run 39753109410) fails the whole build before compiling. SCCACHE_IGNORE_SERVER_IO_ERROR does not cover server startup. Native runners now start sccache themselves and build without a compiler cache when it does not come up; the manylinux containers keep the action input.
kpandl
approved these changes
Oct 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Every Shield Swap and bridge call that takes freeze-list exclusion proofs now resolves them itself, following the accounting in veil's
feat/automatic-freezelist-proofsbranch and the slot tables inamm-v3/MerkleProofReference.md(f6694b7). Callers can still pass fully formed proofs; the parameter iscompliance_proofseverywhere, and the old keywords keep working.Shared layer (
aleo)AleoNetworkClient.get_compliance_freeze_list(program)and the async / facade twins readGET /programs/{id}/compliance/freeze-list(the published tree, leaves first and root last;Nonewhen a program has no list — the endpoint answers 404 today, the docs saynull, both are handled).aleo.compliance: the tree math (moved from the bridge, same vectors),MerkleProofInput/MerkleProofPair/ComplianceProof(one proof) with a per-packageComplianceProofsalias for a call's whole argument, as in veil,generate_freezelist_proof(checks the tree against the on-chain root and recomputes every witness),freezelist_program(program name →*freezelist.aleoimport → known table;credits.aleois a placeholder slot), andFreezeListProofs/AsyncFreezeListProofs: one root read per list per call, the tree fetched only when the root changed, witnesses cached per client (16 lists × 256 addresses) and discarded when the root moves. An empty list yields the fixed empty-tree pair without fetching anything beyond the root.Bridge
unshield,xreserve.burn,executetakecompliance_proofs=(a literal, aMerkleProofPair, a(MerkleProofInput, MerkleProofInput)tuple, or a one-tuple of any).merkle_proof=is kept as an alias; passing both raises.aleo_bridge.freezelistre-exports the shared helpers andFreezeList.exclusion_proofdelegates to the shared resolver.Shield Swap
aleo_shield_swap.compliance: pure slot planners for swap / claim / mint / increase / collect / rebalance (every entrypoint in the reference, including all 14 rebalance variants) and tuple-union aliases on the bundle count:SwapProofs0–1,ClaimProofs1–3,MintProofs3–5,IncreaseProofs0–2,CollectProofs2–4,RebalanceProofs2–6.compliance_proofs=on the six sync write methods and on asyncswap/claim_swap_output. A wrong count raisesValueErrornaming the slots the route expects.wrapper_proofs=still fills every wrapped-side slot while the AMM slots are built by the SDK. Receiver slots screen the position's withdrawal address; sender slots the signer; AMM slots signer / recipient / withdrawal.Backward compatibility
FrozenAddressError/FreezeListErrorinstead of submitting a transaction the contract would reject.aleo_bridge.freezelistnow raisealeo.compliance.FreezeListError(anAleoError) instead ofConfigurationError;FreezeListmethods still raiseConfigurationError.bridge-sdk/pyrightconfig.jsonadds../sdk/pythontoextraPathsso the bridge type-checks against the editable sdk (CI already installs the sdk wheel from this repo). All four packages are bumped to 0.7.0 in lockstep, and the shield-swap and bridgealeo-sdkfloors rise to>=0.7.0because both now importaleo.compliance.Naming follows veil 0.13's compliance vocabulary (
MerkleProofInput,ComplianceProofContext,generate_freezelist_proof,literal_compliance_proof,format_merkle_proof_pair,KNOWN_TOKEN_FREEZELISTS,freezelist_program,get_compliance_freeze_list); none of these existed in a release. Kept deliberately:compliance_proofs=as the parameter,FreezeListProofsas the resolver (noProofProvidercallback), and "bundle" for one[MerkleProof; 2]argument, which is the amm-v3 reference's word.Tests
tests/integration/test_compliance_live.py(both lists, published tree == on-chain root);test_swap_lifecycle.py; a USDCx → ETH swap + claim (sender bundle againsttest_usdcx_freezelist.aleo, AMM bundle againstshield_swap_freezelist.aleo);test_liquidity_lifecycle_live.py(mint → increase → rebalance → decrease → collect → burn on a pool with a wrapped USDCx side).tests/live/test_compliance_live.py; a 0.1 USDCx unshield with an SDK-built proof (at1xc73qqqpugs0njsuq927skyq8leldn4wu6sf6tqn5dkcezwv3v8qg9yqhd, public USDCx 13.534309 → 13.634309); the 2.000001 USDCx private xReserve burn throughexecuteon both routes (test_lifecycle_live.py::test_aleo_xreserve): to Ethereumat1uupu8mt465sxsaf96yuftphf39mv5taj6xt5992y0tyljt4hnyfq6mceml→0x8be667e0…b1655(+0.996501 USDC), to Arcat1qhh39e8pfh4ynkrxpxhl44acczwyrpt0ujret590mj00vhrzqvfqg03yhp→0xf2828fff…2df0d(+1.983601 USDC).Every freeze list is empty on both networks as of 2026-10-20, so the live runs exercise the empty-root branch end to end; populated trees are covered by the unit tests against locally rebuilt trees whose roots are checked.
Testnet with a populated list (2026-10-21)
FROZEN_ADDRESS_1was added toshield_swap_freezelist.aleo(the USDCx list stayed empty). With real witnesses from that tree: the swap lifecycle, the USDCx swap + claim, and the full liquidity lifecycle (ETH/USDCx pool) passed; a fresh profile whose address sorts below the frozen leaf claimed two swaps (at1yynlq…,execute accepted), so both bracket shapes are proven on chain. New live modulestests/integration/test_frozen_live.py(shield-swap) andtests/live/test_frozen_live.py(bridge) check that the frozen account is listed, refused by the resolver, and refused before proving on swap / mint / unshield / private burn; they skip with a message for a list that does not contain it yet.The regular shield-swap live suite showed 8 failures unrelated to this branch (API drift: 401s no longer returned for bad credentials,
GET /positions404,POST /referral/activity410,codes_per_user_limitnull, and a test sizing bug in the fresh-profile mint). Those belong to a separate amm-api sync.Also on this branch
MerkleProofInputaccepts siblings andleaf_indexas ints or as"…field"/"Nu32"literals (normalised to ints;sibling_literalsrenders them back).token_ownervalues rotated on 2026-10-02 and the stale shared owner failed every Aleo-origin transfer'sapp_metadatacheck. Registry version unchanged, owners verified against the liveapp_metadatamappings, regression test over the five outbound routes. Not yet exercised with a live Aleo-origin BAT/USDG/ZEC transfer.