[Feat] Migrate shield-swap-sdk to the shield_swap.aleo stack - #110
Merged
Conversation
The new shield_swap stack passes [MerkleProof; 2] proof arrays on nearly every entrypoint. encodePlaintextValue encodes structs/arrays from plain JS values against ABI descriptors, encodeInputs and the record parsers handle composites bracket-aware, and codegen's plaintextFieldExpr emits element-wise decoders where it previously generated non-compiling output.
regen-abi.sh fetches transitive imports and stages them for leo abi; pins bytecode + ABI for shield_swap, shield_swap_router, and shield_swap_lp_router alongside the v3 pins. Generated bindings for all three land next to the v3 module (renamed shield_swap_v3.ts; existing imports rewired). Codegen learns external struct references (leo emits foreign record outputs as program-qualified struct refs and prunes their definitions) — they decode as opaque StructValue. New stack constants (program ids, hardcoded router addresses, wrapper table) are additive; DEFAULT_PROGRAM still points at v3 until the actions flip.
…tors utils/q128.ts mirrors the deployed contract bit-exactly: the 20-entry magic cascade with positive-tick inversion, tick estimation via the 14-round log2 fraction, mulDiv, amount0/amount1 deltas, and U256 hi/lo split/join/format helpers. Vectors are generated from amm-v3's scripts/q128 Python oracles; a pinning test recombines every cascade constant from the pinned bytecode's u64 halves.
resolveTokenRoute reads from_wrapper_token_id (the on-chain definition of wrapped) with an immutable-relationship cache and offline override; token ids decode to and from program names; freezelist proof assembly ships the canonical empty-tree witness with a provider hook for populated lists; detectTokenStandard prefers the wasm SDK's Program.isArc20()/.isArc22() and falls back to veil-core's pure checker; recipient-bound wrapper records parse their binding fields and are never selected as spendable inventory.
Swap, multi-hop, unified claim, mint, increase, collect, decrease, burn, create_pool, collect_protocol, and allow_token now dispatch internally between shield_swap.aleo and its swap/LP routers based on each token's on-chain wrapped-ness — callers still name only tokens, amounts, and pools. mint requires an explicit immutable withdrawal address; claim is unified across single and multi-hop; collect pays nft.withdrawal. Reads parse Q128.128 U256 state to bigint at the boundary; getTokenDecimals and claimMultiHopOutput are removed. DEFAULT_PROGRAM, derivations, tick alignment, agent tools, and decorators follow. Integration tests against the live stack are rewritten in the next commit.
Hard cutover to the shield_swap.aleo stack: delete the v3 generated bindings and pinned ABI/bytecode, drop the v3 codegen entry, and remove SHIELD_SWAP_V3 / createShieldSwapV3Contract from the public surface (new-stack constants exported in their place). The two v3-pinned unit tests move to the new bindings, and the blinding golden vectors are regenerated against shield_swap.aleo (both factor and address are program-scoped). session.ts, the READMEs, and SKILL.md drop v3 and the obsolete raw-unit dust helpers.
Regenerate the pinned OpenAPI from staging (amm-api-staging), which renamed TokenDoc's wrapper_program to amm_token_program + underlying_program + underlying_token_id. getBalances now scans private records in underlying_program (where users hold spendable inventory), and parseTokenRecordInfo reads credits.aleo's microcredits field as well as ARC-20 amount so wrapped ALEO's balance isn't silently zero. regen-openapi.sh takes a VEIL_DEX_API_URL override.
Default each suite's DEX API base to staging (still VEIL_DEX_API_URL- overridable), drop the removed tokenInProgram param, swap getSqrtPriceAtTick for the Q128 getSqrtPriceAtTickX128, and clear v3 program/scale/token_decimals assumptions. reads.integration drops the deleted getTokenDecimals (now a wrapper-registry resolution check), asserts PoolState has no scales, and updates the mapping list for from/to_wrapper_token_id. Verified green against staging's live pools (reads + auth: 25 passed).
Runs the unkeyed shield-swap read + auth integration subset against the migrated staging DEX API on every push/PR, catching drift between the client and the live API. Soft-fails (continue-on-error) so a staging outage doesn't red CI; keyed/write assertions self-skip without a funded key.
Drop the removed dust rule (raw atomic amounts now), the tokenInProgram argument (routing is internal), and floorToDust; collapse the claimMultiHopOutput branch into the unified claimSwapOutput; rename the holding/token program field from wrapper_program to underlying_program (getHoldings now scans the underlying, matching getBalances); refresh the startup wrapped-balance examples to the new asset framing.
…ap.aleo stack
Replace the vendored-v3-bytecode devnode fixture with a compile-from-source
harness that builds the amm-v3 Leo stack (multisig, freezelist, shield_swap,
the fake token wrappers, both routers) from the sibling checkout and deploys
it under the devnode genesis admin — which equals the DEPLOYER the AMM
constructor asserts against, so no address patching is needed. Bootstrap runs
the freezelist init/role, fee tier + tick spacing + binding, wrapper->underlying
allow_token registration, three pools (plain/plain, wrapped/plain,
wrapped/wrapped), and seeds the plain pool via the SDK mint. set_token_decimals
is gone.
The two lifecycle suites drive the migrated ABI:
- actions suite: full plain/plain matrix through the SDK write actions — mint,
increase, both swap directions with unified claimSwapOutput, decrease,
collect (including owner != withdrawal), and burn — asserting on-chain
positions/slots/swap_outputs after each step.
- generated suite: createShieldSwapContract with the create_pool sqrt price and
swap sqrt_price_limit as { hi, lo } U256 literals and the mint/claim
freezelist witnesses as [MerkleProof; 2] literals.
The wrapped-side router matrix is written against the SDK's internal dispatch
but gated (WRAPPED_SDK_BLOCKED): the SDK's routed mint/swap local paths read
the public id at a fixed output offset that assumes the router's leading
change record is present, but veil-core's devnode extractTransitions drops
router-forwarded external/dynamic records with no plaintext value, shifting the
id to index 0 (mint.ts:250, swap.ts:302). The stack itself deploys, registers,
and creates the wrapped pools; only the SDK local-path return parsing is
affected. Flip the gate once the SDK anchors on the public field output.
Verified green end to end on a fresh devnode (leo 4.3.4, aleo-devnode 0.2.0):
14 passed | 2 skipped.
The routed swap and liquidity actions read the public id (swap id, position token id, pool key) at a fixed output offset that assumed the router's forwarded underlying-change record occupied output slot 0. veil-core's extractTransitions drops router-forwarded records that carry no plaintext value, so the id lands at a lower index and the actions threw an "Unexpected output shape" error after the on-chain effect had already succeeded. Extract requireFieldOutput, which returns the transition's field-literal output regardless of record positions, and use it at all seven id-reading sites. Drop the now-unneeded tokenIdIndex from the liquidity dispatch. This un-gates the wrapped-side devnode matrix (wrapped/plain, wrapped/wrapped mint/increase/collect and a wrapped-input swap + routed claim), now green. Add the shield_swap.aleo migration changeset.
The devnode lifecycle suites now compile the AMM stack from amm-v3 Leo source, so the vendored shield_swap_v3.aleo bytecode, the old multisig, and the hand-vendored test tokens under test/fixtures/programs/ are unused, as is refresh-devnode-fixtures.sh that populated them. Nothing reads them.
Remove a stray paren that broke the snippet and the stale dust-flooring note; the new stack collects owed balances in raw base units directly.
Address code review on the field-output read: requireFieldOutput now throws unless exactly one field output is present, so a future transition that emitted a second public field fails loudly instead of silently returning the wrong id. The generated devnode suite's firstField delegates to the shared helper rather than duplicating the regex, and the harness comment that still described the fixed positional-offset bug is corrected.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
* [Feat] Add parsePlaintextValue for struct and literal plaintext parsing * [Feat] Emit struct decoders over StructValue instead of RecordValue * [Refactor] Decode struct mapping values as plaintext, not loose records * [Fix] Replace record parsers with parseRecord mirroring snarkVM grammar * [Docs] Update codegen README for StructValue decoders * [Refactor] Apply simplify-pass findings to the parser split * [Docs] Correct output-routing comments: future text parses as struct plaintext * [Feat] Parse future outputs into typed FutureValue * [Fix] Parse dynamic futures as their own DynamicFutureValue type * [Refactor] Rename RecordValue.ownerMode to ownerVisibility * [Refactor] Rename record/struct fields to entries/members mirroring snarkVM * Revert "[Refactor] Rename record/struct fields to entries/members mirroring snarkVM" This reverts commit 7b8be95. * [Refactor] Rename codegen emitter internals to verb-led decoder names
* [Feat] Decode mapping reads with ABI-derived codegen decoders
Mapping reads previously returned raw literals typed as string, and
shield-swap hand-rolled three partial parsers on top (a u8/u16/u32-only
regex, a raw === 'true' check, and a struct guard). Absent keys and
malformed requests were also conflated: the Provable API answers 200
null for an absent key (and for an unknown mapping or program), while
404 means the request itself was malformed — its body misleadingly
blames the program.
Core: readContract/readMapping return string | null, folding the
JSON-null and "null" answers into null and rethrowing the 404 with the
actionable cause; TransportError gains structured status/body fields so
layers above stop regexing messages.
Codegen: every mapping now emits a to<Name>MappingValue decoder —
struct values guard the shape and delegate to the struct decoder,
literal values decode through core's strict parseValue with a declared-
width check — and the generated factory's read methods encode typed
keys via core's encodeValue, null-guard absence, and resolve to
Promise<Value | null> instead of Promise<unknown>.
Shield-swap: readStructMapping/readBoolMapping/readUintMapping collapse
into a generic readDecodedMapping plus a readFlagMapping that owns the
absence-is-false rule for flag mappings; all sixteen read actions ride
the generated decoders.
Docs, the e2e demo, and the reference dApp are updated to the new
absent-key semantics.
* [Feat] Parse signature literals in parseValue
Signatures are prefix-recognizable (sign1...) exactly like addresses
(aleo1...), so parseValue now classifies them as { value, type:
'signature' } instead of throwing. Signature-valued mappings ride the
strict generated decoder path as a result. Identifier literals stay
outside the grammar deliberately — they are bare tokens with no
recognizable shape, so the lenient string passthrough is the only
honest treatment.
* [Fix] Report mapping-read 404s without diagnosing the cause
* [Fix] Trim the mapping-read 404 message to the bare fact
…drift (#113) Live-verified all four trading journeys (startup, swap+claim, concurrent swaps, liquidity mint/increase/decrease, collect) against the staging DEX API on testnet, and fixed what the runs surfaced: - setup.ts accepts --api-url to pin a DEX API deployment. The URL persists in the state file; switching deployments resets the deployment-scoped state (access grant, API token, pending airdrop job) while key material and Provable credentials carry over. Comparison treats an unset state as the SDK default so re-pinning the default is a no-op, and the SHIELD_SWAP_API_URL env var stays a purely ephemeral per-run override in loadSession — only the flag pins. - setup.ts's funded report read h.wrapperProgram, a field getHoldings has not returned since the stack migration; it now prints underlyingProgram. - liquidity.md's mint snippet passed the AMM-side token programs as token0Program/token1Program, which breaks wrapped sides — those parameters name the programs holding the caller's records, and the SDK resolves them on chain. The snippet also gained the now-required recipient/withdrawal parameters, and the increaseLiquidity section no longer claims the action lacks positionTokenId — it accepts one, and the snippet pins it.
* [Feat] Mirror the contract position-view math in q128 utils * [Feat] Decode full PositionNFTs and list them via listPositionNFTs * [Feat] Add getOwnedPositions and getOwnedPosition with derived amounts and fees * [Feat] Expose owned-position reads on the shield-swap client * [Feat] Add owned-position agent tools * [Test] Add keyed e2e coverage for owned-position views * [Docs] Document the owned-position views and add the changeset * [Refactor] Simplify the owned-position read path Delegate PositionNFT decoding to the generated toPositionNFT, push the token-id filter into listPositionNFTs, collapse the per-position reads into a single concurrent wave with the pool slot shared as a promise, dedupe the per-token fee settlement, export the new q128 mirrors, and default amountsForLiquidity to withdrawal-side rounding. * [Refactor] Rename enrichOwnedPosition to resolveOwnedPosition
iamalwaysuncomfortable
added a commit
that referenced
this pull request
Aug 4, 2026
…e swap and liquidity journeys (#121) * [Fix] Derive the DEX API host from the client's network DEFAULT_API_URL shipped as amm-api.dev.provable.com, which indexes the pre-migration shield_swap_v3.aleo. Since #110 moved this SDK to shield_swap.aleo, that host serves pools which do not exist on the program the SDK reads — so discovery returned keys whose every chain read came back null, surfacing as "pool does not exist" rather than as a misconfigured host. Verified: a pool served by that API exists under testnet shield_swap_v3.aleo and under neither mainnet program, while the pools on api.testnet.swap.shield.fi exist under shield_swap.aleo. The API is deployed per-network on separate domains, so one constant cannot be right for both. shieldSwapActions derives it from the client's network, resolved per request so switchChain re-targets the API rather than leaving it on the network the client started from — the same defect class as the prover URL. baseUrl accordingly accepts a resolver and ApiClient.baseUrl becomes a getter. SHIELD_SWAP_API_URLS and defaultApiUrl() are exported for direct construction, and DEFAULT_API_URL is deprecated. Three integration suites defaulted to amm-api-staging.dev.provable.com, which now 404s on every path. That, not an outage, is why the DEX suites have been red: with the correct host and no env override, 35 live tests pass — including the route-quote test recorded as known-red. * [Fix] Walk the tick list for insert hints, and export the list sentinels pickInsertHint consulted slot.next_init_below/above, which bracket the pool's current tick rather than the target. Any bound further out than one initialized tick got a hint above itself, which the contract rejects on finalize — mined, reverted, fee consumed. Measured on the live ETH/USDCx pool at tick -200996: a lower bound of -203230 returned -200996, where the true predecessor is -273894. The docblock already carried this as a known limitation with the exact walk as a follow-up; this is that follow-up. The walk visits one entry per initialized tick, of which live pools hold 3 to 18, so the extra reads are few and bounded rather than proportional to the tick range. Also exports MIN_TICK_SENTINEL / MAX_TICK_SENTINEL. The list is anchored one step outside the usable range — ∓400_001 against MIN_TICK/MAX_TICK of ∓400_000 — and with no constant for it callers hardcoded -400001, as devnodeLifecycle does; that only holds while a pool's tick list is empty. Getting this wrong is how the first version of this patch silently returned an uninitialized tick. Verified against the live deployment: 30 hints across all 5 testnet pools, each initialized, strictly below its target, and with its successor at or beyond it. * [Test] Add live swap and liquidity suites against testnet Two gated suites covering the journeys that had no live coverage: swaps (single-hop, forced multi-hop, concurrent) and the liquidity lifecycle (mint, increase, decrease, collect, burn, plus the owned-position reads). Everything is discovered — pools from the API, balances via getBalances, tick spacing from the fee tier on chain, ranges from the active tick, insert hints from pickInsertHint. Nothing is hardcoded, so they follow the deployment. No createPool: pool_creation_open reads false on this deployment, so a suite that created its own pool would only ever skip. Verified live: single-hop swap and claim, the two-hop swap and claim, pool discovery against both API and chain gates, hint predecessor checks, and the mint with its on-chain position. Deposit amounts come from amountsForLiquidity at the current price rather than fixed figures, which balance for one pool's price and revert elsewhere. Three timing behaviours needed handling, each confirmed by a real failure first: finalize writes lag the confirmed transaction, so swap_outputs reads and claims poll; the record scanner indexes a minted position NFT after the mint confirms, so dependent steps wait for it; and multi-hop needs more than the default 5-minute confirmation window. Also exposes confirmationTimeout on createAleoClient, which createProvingConfig accepted but the factory did not forward. Two findings worth carrying forward. Concurrent swaps from one account revert on finalize even with disjoint input tokens. And minting into the native-credits pool reverts where the wrapped-token pool succeeds, so the suite prefers the deepest pool; the LP path there likely needs an explicit token route. * [Chore] Point the DEX shape-parity job at the live deployment The job pinned VEIL_DEX_API_URL to amm-api-staging.dev.provable.com, which now returns 404 on every path. Because it is continue-on-error, it has been failing silently — and the pin is what let it keep checking a decommissioned deployment long after the migration moved on, which is the same misconfiguration this branch fixes in the client. Dropping the pin lets the suites derive the host from the network, so the job follows the deployment instead of a snapshot of it. Renamed staging-shapes to dex-shapes since it no longer targets staging; no ruleset requires a status check by name, so nothing depends on the old one. Verified with CI's exact command and no env override: 26 passed, where it had been failing every path with 404. continue-on-error is kept — a live-API outage should not red the whole run — but the comment now says plainly that this hides real drift failures, which is how the staging rot went unnoticed. * [Feat] Shorten the confirmation window and report what the polls saw `waitForConfirmation` defaulted to 300s. Measured against the live testnet deployment, healthy confirmations land far inside that — a mint took 49.7s and an increase 39s, both including proving — so a transaction still absent at the limit is more often one the node never included than one about to arrive. The default is now 60s. This is a behaviour change: a write that previously confirmed between one and five minutes now throws instead of returning. Multi-hop swaps are the known slow path, one measured at 322s, so a client submitting them sets `confirmationTimeout` explicitly — documented on the multi-hop section of the shield-swap README and in the `createProvingConfig` reference, and applied to the live swap suite. Every polling failure was previously swallowed, so a node that answered cleanly and consistently did not have the transaction was indistinguishable from one that could not be reached — while the message asserted the transaction "may still be pending", which is backwards for one that was dropped before inclusion. `TransactionTimeoutError` now carries `polls` and `absentPolls` and says which case it saw. It does not diagnose why: the confirmed-transaction endpoint cannot tell a pending transaction from a dropped one on its own. * [Feat] Add liquidityForAmounts, the deposit-side inverse of amountsForLiquidity The package could turn a liquidity figure into token amounts but not the reverse, which is the direction a depositor starts from: a caller holds two balances and wants to know what position they support. Without it every caller had to invent a liquidity number and work forwards, and a figure that balances at one pool's price falls short at another — one side runs out and the mint reverts. Mirrors the contract's derivation, with the same branch boundaries as `amountsForLiquidity`: token0 binds at or below the range, token1 at or above it, and inside the range the shorter side governs. Every step floors, so the result is a lower bound — feeding it back through `amountsForLiquidity` with deposit-side rounding returns amounts that fit inside the originals, which keeps a mint from reverting for want of a base unit. That property is asserted across a sweep of ticks, range widths and magnitudes rather than on one case. * [Fix] Stop the liquidity suite building writes on spent records The live liquidity lifecycle failed at `decrease`, then at `collect`, with a confirmation timeout against a transaction the chain had never heard of — 404 on both the confirmed and unconfirmed endpoints, permanently. Each write spends the position record and creates a new one, and the scanner indexes that asynchronously, so a write built moments after the previous one carries a serial number the chain has already consumed. The node drops it at verification, where it never becomes a rejected transaction and never reaches a block. The suite only waited for the scanner after `mint`, and that wait checked mere presence — which the spent record also satisfies. It is now a freshness wait keyed on the record tag, run after every write that respends the position. `burn` needed the other lag: the mapping delete propagates to reads asynchronously, so the position read back with liquidity 0 immediately after the burn confirmed and null shortly after. Both views are now polled. Also: spacing comes from `slot.tick_spacing`, which is what `mint` aligns against, with the fee registry asserted to agree rather than used as the source. Insert hints are left to `mint`, which applies a correction a caller cannot — passing an explicit `tickUpperHint` disables it and reverts whenever no initialized tick sits between the bounds. The deposit is a thousandth of what the account holds, so repeated runs cannot drain it, and the minted liquidity is checked against `liquidityForAmounts` with the chain as the authority. A failure aborts the remaining steps instead of paying a fee each to revert. Verified live on testnet: everything through `collect` passes, and the mint parity assertion holds. The `burn` poll is the one change still unverified. * [Fix] Poll every post-write position read in the liquidity suite The mint confirmed and its `positions` entry still read back null, failing the step against a position the chain had created — the same asynchronous propagation that made `burn` read back with liquidity 0 after the entry was already removed. Every step in the lifecycle reads exactly what its own write just changed, so all five had this race and were passing on timing luck. They now share one helper that polls the position read until the entry shows what the write did: present after mint, increased after increase, zero after decrease, cleared after collect, absent after burn. The scanner's separate lag on the burned record keeps its own poll, since it is a different view catching up rather than the same one. * [Fix] Tighten the liquidity suite's waits and stop gating on scanner latency The polling bounds were 200s, which is far past useful: mapping propagation is quick, and a read that has not caught up long after its write is a signal, not a slow network. Mapping reads now poll every second for ten seconds. The scanner-freshness waits keep a longer 30s bound, because record indexing is measurably slower — the mint's record took 8–15s to appear across runs — and that wait failing means the next write builds on a spent record and is dropped. The scanner polls also tolerate a failed read inside their window rather than failing the step on one error, and attach the last failure if none succeed. The SDK already force-refreshes the JWT and retries a 401 four times, which handled the intermittent scanner errors seen on these runs; this survives an outage longer than that. The burn test no longer asserts the scanner has dropped the record. It marks records spent on its own schedule — still serving one 30s after the burn confirmed — so gating on it makes the suite hostage to third-party indexing latency rather than to anything this SDK does. The chain-side assertion, that the `positions` entry is gone, is what proves the burn worked and passes inside 10s. Verified live: mint with its parity check, the owned-position reads, increase, decrease and collect all pass. Two runs were cut short by Provable API auth flakiness — a scanner 'iss' 401 and a prover 401 on /prove/testnet/pubkey — neither related to these changes. * [Docs] Say what a null state means on an owned position The docs described it as a mint that has not finalized, which is one of two causes. A live run measured the record scanner still serving a burned position more than four minutes after the burn confirmed, so the same `null` equally means the position no longer exists — and a caller rendering a portfolio that reads it as "still loading" shows a phantom position for minutes. Both directions of the record/mapping lag are now stated, in the action's docblock and the README, with the public mapping named as what settles which case it is. * [Fix] Keep the derived DEX API host when baseUrl is passed as undefined `buildApi` set the derived host and then spread the caller's `api` options over it, so `baseUrl: process.env.VEIL_DEX_API_URL` with the variable unset passed the key with an undefined value and won the spread. `ApiClient` then fell back to its deprecated testnet constant, pointing a mainnet client at testnet — reading pools that do not exist on the program it proves against, with nothing in the config to suggest it. The coalesce now runs after the spread, so only a set `baseUrl` wins. Also corrects the `pickInsertHint` docblock, which still described deriving hints from the slot's active-range neighbours after the implementation moved to walking the initialized-tick list. It now says what the code does and why the neighbours are unusable: they bracket the pool's current tick, not the target. Both reported by Copilot on #121. The regression test fails against the previous spread order. * [Fix] Keep pickInsertHint working without the optional WASM peer Walking the tick list reads `ticks`, which is keyed by a hash of pool and tick, so it derives keys through `@provablehq/sdk`. `mint` calls `pickInsertHint` whenever hints are omitted — and `mint` uses the soft loader while `increaseLiquidity` never loads WASM — so the walk made both require the peer and broke wallet-backed browser installs that previously minted fine. That contradicts the design in `utils/sdk.ts`, which reserves WASM for local-account derivations. An absent peer now degrades to the slot's neighbours: one mapping read keyed by the pool, deriving nothing, and exactly what this function returned before the walk existed. Callers with the peer keep the true predecessor for any target; callers without it are no worse off than before the change. Documented as best-effort, with explicit hints named as the remedy for a distant range. Reported by Copilot on #121. The fallback tests fail against the unguarded walk. * [Feat] Derive insert hints from the DEX API without the WASM peer `pickInsertHint` needs `@provablehq/sdk` to hash tick keys for the on-chain walk, and fell back to the slot's two neighbours without it — correct only for a target within one initialized tick of the current price, and rejected by finalize for anything further out. The API answers this exactly and was already in the vendored spec, unused: `GET /pools/{key}/initialized-ticks` returns the pool's full sorted tick list, and its description names this as the purpose — computing the hints the AMM's hint-walk asserts on, which is what the frontend mint flow does. Exposed as `client.api.getInitializedTicks`, and supplied by the decorator to pickInsertHint, mint, and increaseLiquidity, so a wallet-backed client without WASM gets the exact predecessor rather than a guess. Three tiers by authority: the contract's list when the peer is present, the API list when it is not, the slot's neighbours when neither is. The chain stays preferred because the API indexes positions rather than reading the contract, so it can lag a fresh mint, and a stale hint costs a fee. A failing or unauthenticated API drops to the slot rather than failing the write. Verified live against three testnet pools: the API-derived predecessor matched the chain walk on every one, including a 17-tick pool where the walk takes 17 round trips and this takes one. * [Test] Pin that the chain-walk path makes no API request for ticks The tick list is attached to hint-deriving actions as a supplier rather than a fetched array, so a client that can derive tick keys never pays for the fallback being wired up. Asserted rather than assumed: the fetch spy records no `initialized-ticks` request while `pickInsertHint` walks the chain. Also dedupes the request across an action's hints. `mint` derives two and passed the same supplier to both, which fetched the identical list twice on the fallback path. The promise is now shared, rejection included, so both hints fall back to the slot together rather than disagreeing about their source. * [Fix] Do not trust an empty tick list, and read the poll status null-safely Review of this branch's own changes turned up two defects. `predecessorOf([])` returns the sentinel, so an API answering `{data: []}` because the pool is not indexed yet produced a hint below every initialized tick, which finalize rejects. An empty list now falls through to the slot instead: a pool that genuinely has no initialized ticks anchors at the sentinel, which the slot reports as `next_init_below` anyway, so the safe case loses nothing and the stale case is covered. The 404 check read `.status` off the caught value directly, which throws on a null rejection — inside the loop whose purpose is to survive transient failures. Now optional-chained. Also keeps `tickLowerHint`/`tickUpperHint` adjacent in the increaseLiquidity docblock, which the new property split apart. * [Test] Cover the null-rejection path in waitForConfirmation The empty-tick-list fix shipped with a test; the null-safety fix did not. A rejection carrying no status now asserts that the timeout is still what surfaces and that `absentPolls` stays 0 — a rejection with no status is not evidence the node reported the transaction absent. Fails against the direct property read it replaced.
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.
Migrates
@provablehq/shield-swap-sdkfromshield_swap_v3.aleoto the deployedshield_swap.aleostack (core AMM + swap router + LP router + freezelist + multisig + token wrappers). Breaking, hard cutover — v3 support is removed.What changes for callers
from_wrapper_token_id), selects the underlying records, and dispatches toshield_swap.aleoor the correct router transition internally — no wrapper params anywhere in the public API.swap/swapMultiHopno longer taketokenInProgram.getSqrtPriceAtTickX128,getTickEstimateX128,U256sqrt-price literals).getSqrtPriceAtTick/MIN_SQRT_PRICEand the poolscale0/scale1fields are gone.collectsupports an owner distinct from the withdrawal address.claimSwapOutputserves both single- and multi-hop swaps and routes the payout (wrapped vs plain) internally;claimMultiHopOutputis removed.amm_token_program+underlying_program+underlying_token_id, replacingwrapper_program); private-balance scans key onunderlying_programand readcredits.aleomicrocredits.Under the hood
field-literal output rather than a positional offset. This is correct across direct and routed dispatch — a router prepends forwarded change records andextractTransitionsdrops the valueless ones, so the id's index shifts. (requireFieldOutput, kept in shield-swap; no core interface change.)Testing
pnpm vitest rungreen from the repo root (unit + example suites).staging-shapes, continue-on-error): the unkeyed read + auth subset runs against the live migrated API on every push/PR for drift detection.Notes for review
minor. The changesetfixedgroup versions the whole@provablehq/veil-*suite in lockstep and it's pre-1.0 (0.5.0); amajorentry would escalate the entire suite to 1.0.0.minorfolds into the planned 0.6.0 release (pre-1.0 convention for a breaking change). Say if you want the 1.0.0 jump instead.DEFAULT_API_URLintentionally stays the dev host as a forward placeholder; the migrated shapes are served by staging (amm-api-staging.dev.provable.com) until prod migrates. This PR does not claim prod validation.