@steerprotocol/sdk 4.0.0-next.10 → 4.0.0-next.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -37,7 +37,11 @@ Consumers use these accessors (lending still needs an integration):
37
37
 
38
38
  ```ts
39
39
  const vault = await steer.getVault(ChainId.Base, vaultAddress);
40
- const amm = steer.getAmm({ chainId: ChainId.Base, protocol: AmmProtocol.Uniswap, version: UniswapVersion.V3 });
40
+ const amm = steer.getAmm({
41
+ chainId: ChainId.Base,
42
+ protocol: AmmProtocol.Uniswap,
43
+ version: UniswapVersion.V3,
44
+ });
41
45
  const lending = steer.getLending(aaveV3Base);
42
46
 
43
47
  steer.getSubgraphUrl(ChainId.Base);
@@ -50,6 +54,233 @@ Base V3/Integral and Polygon V3/Legacy/Integral vault reads are implemented belo
50
54
  implementations. The Steer catalog supplies network metadata and indexed vault discovery. Networks without resolver bindings report `NotConfigured` when loading a vault. Consumers don't assemble a chain or pass a `steer: steerBase` setting.
51
55
  SDK integration code owns Steer's per-network contracts and data adapters.
52
56
 
57
+ ## Development tooling
58
+
59
+ Run these commands from `next/`:
60
+
61
+ - `npm run fix`: Biome formatting, import organization, and safe lint fixes.
62
+ - `npm run check:code`: non-mutating Biome CI checks.
63
+ - `npm run check`: code checks, generated ABI/reader verification, types, tests, build and package guards.
64
+ - `npm run check:consumer`: packed ESM, CJS, TypeScript and Next.js consumer verification after the build.
65
+
66
+ Biome is pinned to `2.6.6`, with recommended lint rules, standard formatting defaults and
67
+ two-space indentation. Open `next/` as the VS Code folder to use its formatter, save actions,
68
+ and Biome extension recommendation. The repository's legacy package has separate tooling.
69
+
70
+ Dependencies, build output, generated reader/ABI artifacts, retained benchmark JSON and
71
+ lockfiles are excluded from routine code formatting. The existing generators format their own
72
+ output, and reproducibility checks verify it. No unsafe fixes are included in `npm run fix`.
73
+
74
+ Tests allow non-null assertions for fixed fixtures and decoded tuples; making those accesses
75
+ optional can weaken test assertions. Production non-null exceptions are limited to accesses
76
+ whose bounds were already checked or whose source guarantees a nonempty list. Local bigint
77
+ min/max exceptions preserve arithmetic that cannot use JavaScript's number-only Math functions.
78
+ Each production exception includes its invariant beside the expression.
79
+
80
+ ## Steer ABI sources
81
+
82
+ Complete Steer ABIs are generated using the development-only, exact dependency
83
+ `@steerprotocol/contracts@3.1.7` into `src/generated/vault/steer-abis.ts`. The generator selects explicit
84
+ chain/contract/function entries, preserves input/output types and names, and removes only Solidity
85
+ `internalType` metadata. Runtime consumers do not load the contracts package or its deployment JSON.
86
+
87
+ Run `npm run build:abis` to regenerate and `npm run check:abis` to verify. The normal
88
+ `npm run check` includes this verification. Updating the package version requires reviewing the
89
+ generator's source selections and all qualified ABI compatibility exceptions.
90
+
91
+ The npm package does not include the qualified standalone V4, Aerodrome staking, or Blackhole
92
+ farming implementations. Their missing methods are explicit compatibility entries in the ABI
93
+ generator, with references to each family's qualification evidence. Callers import complete ABIs.
94
+ Shared deposit encoding retains an explicit payable adjustment for qualified native V4 deposits; the package's older deposit ABI
95
+ is nonpayable. External AMM, gauge, NFT and wrapped-token interfaces remain independently maintained.
96
+ SDK reader ABIs continue to be generated from their Solidity reader sources.
97
+
98
+ ABI generation does not generate addresses, change chain support, or update deployment registrations.
99
+ The SDK catalog, contract maps, implementation identities, and manual deployment additions are
100
+ preserved independently of the package's deployment inventory.
101
+
102
+ ## App and data-connector loader dependencies
103
+
104
+ The SDK installs exact runtime dependencies `@steerprotocol/app-loader@3.2.6` and
105
+ `@steerprotocol/data-connector-loader@5.0.0-next.7`. Their APIs remain separate from `SteerClient`;
106
+ they are not re-exported from the SDK root.
107
+
108
+ Both loader packages support ESM and CommonJS. The connector loader owns the execution-context
109
+ types and no longer depends on the stable SDK. The SDK's portable root does not import either
110
+ loader at runtime.
111
+
112
+ ## IPFS content and vault configuration
113
+
114
+ `IpfsClient` reads JSON and bytes through `https://ipfs.io/ipfs/` by default. Bare CIDs,
115
+ `ipfs://` references and gateway URLs with an `/ipfs/` path are resolved through the configured
116
+ gateways. Construction performs no requests. JSON is `unknown` until validated by the caller.
117
+
118
+ ```ts
119
+ import { IpfsClient, SteerClient } from "@steerprotocol/sdk";
120
+
121
+ const ipfs = new IpfsClient();
122
+ const steer = new SteerClient({ ipfs });
123
+ const vault = await steer.getLiquidityVault(chainId, vaultAddress);
124
+ const config = await vault.getConfig();
125
+ const json = await ipfs.getJson(cid);
126
+ const bytes = await ipfs.getBytes(wasmCid); // Uint8Array with an ArrayBuffer backing store.
127
+
128
+ if (config.strategy) {
129
+ const wasm = await ipfs.getBytes(config.strategy.execBundle);
130
+ const { chainId, tokenId } = config.strategy; // Strategy registry identity.
131
+ }
132
+ ```
133
+
134
+ `vault.getConfig()` fetches `vault.metadata.payloadIpfs` and validates the stored manifest.
135
+ `VaultConfig` preserves strategy-specific `strategyConfigData` and `vaultPayload` objects,
136
+ including empty objects and nested configuration. `dataConnectorsData` can be empty; each entry
137
+ has a `bundleHash` and its own `configData` object. The optional `strategy` object contains
138
+ `chainId`, `execBundle` (the strategy WASM IPFS reference) and decimal-string `tokenId` (the
139
+ strategy registry token ID). Older manifests without `strategy` remain readable. Validation
140
+ checks this envelope, leaving individual configuration schemas to their strategy or connector.
141
+ The method does not add the loader's runtime `vaultAddress` or `epochTimestamp`.
142
+ Missing references and invalid manifests throw. Indexed
143
+ metadata selects the reference; this read does not revalidate that reference onchain.
144
+
145
+ Configure fallback gateways in order with `new IpfsClient({ gateways: [...] })`. Each attempt
146
+ uses the existing fetch transport with a 15-second default timeout, optional headers and fetch
147
+ override, and caller cancellation. A caller abort stops fallback. `timeoutMs: 0` disables the
148
+ timeout. `maxResponseBytes` defaults to 16 MiB and is enforced while streaming the decoded body.
149
+ These settings can also be passed per call, including to `vault.getConfig()`. IPFS settings are
150
+ separate from the client's indexer `http` settings. Gateway responses are not cryptographically
151
+ verified against the CID. Uploading, pinning and executing downloaded bundles are separate tasks.
152
+
153
+ ## Running vault strategies and data connectors
154
+
155
+ Liquidity vaults own their manifests. Callers pass one loader-native context through both
156
+ execution stages:
157
+
158
+ ```ts
159
+ const config = await vault.getConfig(); // Optional inspection; warms the instance cache.
160
+ const context = await vault.getExecutionContext({
161
+ blockNumber,
162
+ epochTimestamp,
163
+ epochLength,
164
+ });
165
+ const connectorData = await vault.executeDataConnectors(context);
166
+ const output = await vault.executeStrategy(context, connectorData);
167
+ ```
168
+
169
+ `getConfig()` caches the in-flight promise and resolved manifest per vault instance and payload
170
+ CID. Failed loads are removed so a later call can retry. Cached manifests are deeply frozen.
171
+ The first request's transport settings govern a shared download. Each execution stage verifies
172
+ the RPC chain and reads VaultRegistry and StrategyRegistry at `context.blockNumber`, selecting
173
+ the registered payload CID and execution bundle independently of indexed metadata. A new CID
174
+ gets a separate cache entry, including for historical reads. An embedded manifest `strategy`
175
+ object must agree with the registration.
176
+
177
+ The SDK validates and captures the supplied context and connector results before asynchronous
178
+ reads. It does not recompute caller-supplied financial fields; use `getExecutionContext()` for
179
+ verified same-block vault accounting. A stored `epochLength`, when present, must agree with the
180
+ context. Empty strategy settings use the caller's context epoch length.
181
+
182
+ `executeDataConnectors()` delegates v1 request loops and v2 execute/transform dispatch to the
183
+ data connector loader in manifest order, returning an
184
+ empty array when no connectors are configured. Each connector initializes with its own
185
+ `configData` and nested `executionContext`. Versionless connectors follow the loader's v1 path,
186
+ and chain reads use the SDK RPC client at the supplied block. `executeStrategy()` uses the
187
+ loader's `ExecutionApp.executeWithContext()` with the manifest, flattened
188
+ strategy settings and context fields taking precedence, then supplies one serialized argument
189
+ per connector. The result count must match the manifest. It returns decoded JSON as `unknown`
190
+ or the exact `"continue"` sentinel, without preparing or submitting a transaction. The loader owns
191
+ result decoding; other non-JSON output remains an error.
192
+
193
+ Supplied-context execution passes only the vault and chain identity plus optional host credentials.
194
+ It does not require pool-type placeholders or a discovery subgraph endpoint.
195
+
196
+ WASM loading is lazy and each call gets fresh module instances. Initialization, malformed JSON,
197
+ transport and execution failures reject the call. Manifests are never mutated to construct runtime
198
+ inputs. Request options apply to IPFS downloads; connector HTTP uses the loader's host runtime
199
+ and is not guaranteed to reflect the selected historical block. No hard CPU deadline or process
200
+ isolation is provided by these portable methods.
201
+
202
+ Offline regression tests exercise retained public Static Stable, Liquid Arc and Robinhood
203
+ connector binaries, including a complete connector-to-strategy run with a synthetic HTTP quote.
204
+ The sampled No-op registration's empty manifest is readable, but its binary rejects those empty
205
+ settings; execution reports that initialization failure rather than inventing defaults. This is
206
+ execution-path verification, not qualification of real-time data feeds or transaction outcomes.
207
+
208
+ ## Vault execution context
209
+
210
+ Liquidity vaults expose strategy inputs through the existing vault object:
211
+
212
+ ```ts
213
+ const blockNumber = 12345678n; // Caller-selected operation block.
214
+ const vault = await steer.getLiquidityVault(chainId, vaultAddress, {
215
+ blockNumber,
216
+ });
217
+ const context = await vault.getExecutionContext({
218
+ blockNumber,
219
+ epochTimestamp: 1793577600n, // Explicit Unix seconds, independent of the wall clock.
220
+ epochLength: 60, // Strategy epoch duration in seconds.
221
+ });
222
+ ```
223
+
224
+ The return type is `SmartPoolExecutionContext`, imported directly from the installed npm
225
+ package. The SDK does not define or re-export its own execution-context output type:
226
+
227
+ ```ts
228
+ import type {
229
+ ExecutionContext,
230
+ SmartPoolExecutionContext,
231
+ } from "@steerprotocol/data-connector-loader";
232
+
233
+ const smartPool: SmartPoolExecutionContext = context;
234
+ const execution: ExecutionContext = smartPool;
235
+ ```
236
+
237
+ The result uses the loader's native field names: `chainId`, numeric block/epoch values,
238
+ `executionTimestamp` in milliseconds, `epochLength` in seconds, `blockTime: 0` for the
239
+ explicitly pinned path, positions as `{ startTick, endTick, weight }`, decimal-string token
240
+ totals, and numeric `lastExecutionTime` in seconds. It contains no `provenance` or extra
241
+ vault/pool identity fields beyond `chainId` and `vaultAddress`. Those identities are verified
242
+ internally before returning data.
243
+ Configured ranges are independent of active NFT inventory.
244
+
245
+ Each call revalidates the native vault binding and dispatches registered vault/AMM overrides.
246
+ All onchain reads use the explicit block. `totalAmount0`/`totalAmount1` come from the original
247
+ vault's `getTotalAmounts()` without poke, matching the prior execution-context semantics;
248
+ they are not aliases for the fee-adjusted `getSnapshot().total` accounting fields.
249
+
250
+ The Steer data adapter queries the latest indexed `vaultSnapshot` at that block, bounded by
251
+ the earlier of the block timestamp and execution epoch. Its timestamp is the history source,
252
+ not proof that the indexer has captured every execution. An empty history maps to
253
+ `lastExecutionTime: 0`, matching the loader contract. Unsupported history adapters,
254
+ unavailable blocks, indexing errors, malformed responses and RPC/HTTP failures throw.
255
+ Block numbers must fit the indexer's nonnegative GraphQL Int range. The optional second
256
+ argument accepts the existing per-request HTTP overrides for the history query.
257
+
258
+ Custom Steer data adapters can implement `getLastVaultExecution(request, options)` to supply
259
+ history without a subgraph. The request contains `vaultAddress`, `blockNumber` and the bounded
260
+ `timestamp`; return a nonnegative bigint no later than that timestamp, or `null`.
261
+
262
+ Pass this result to the loader's `initializeStrategyWithContext` or
263
+ `initializeDataConnectorWithContext` methods. `ExecutionApp.executeWithContext` also accepts
264
+ `SmartPoolExecutionContext` directly; `DataConnectorExecution.executeWithContext` accepts its
265
+ base `ExecutionContext`. No SDK-specific wrapper or mapping is required. Full WASM strategy
266
+ execution requires separate qualification.
267
+
268
+ The underlying read path was verified live on November 2, 2026 using 13 fixtures across six chains:
269
+ Base Uniswap V3, Base QuickSwap Integral, all three Base Aerodrome generations, Polygon
270
+ QuickSwap Legacy, Arbitrum Camelot, Avalanche Blackhole, BSC Pancake V3, and ERC20/native
271
+ standalone V4 vaults on both BSC and Robinhood. The prior wrapper was compared with independent
272
+ same-block original contract totals, configured ranges, registry/beacon identity, pool
273
+ tick/spacing and identity, and a separate indexed history query. The updated native loader
274
+ shape also passed new live Base V3 and Robinhood native ETH checks, plus the offline suite.
275
+ These results qualify those fixtures and reads, not every deployment or full WASM strategy
276
+ execution. Blackhole execution pool reads retain the custom deployer from the verified vault layout.
277
+
278
+ Reproduce with the explicit RPC/indexer settings in `.env.integration`:
279
+
280
+ ```sh
281
+ npm run test:integration -- tests/integration/vault/execution-context.multichain.test.ts --reporter=verbose
282
+ ```
283
+
53
284
  ## Vault discovery
54
285
 
55
286
  List indexed vault metadata, then load a result through the identity-verified resolver:
@@ -68,7 +299,10 @@ for (const metadata of page.items) {
68
299
  }
69
300
 
70
301
  // Load a page or any explicit address list through the same resolver.
71
- const vaults = await steer.getVaults(ChainId.Base, page.items.map((metadata) => metadata.id));
302
+ const vaults = await steer.getVaults(
303
+ ChainId.Base,
304
+ page.items.map((metadata) => metadata.id),
305
+ );
72
306
 
73
307
  if (page.nextCursor) {
74
308
  const next = await steer.listVaults({
@@ -145,10 +379,14 @@ its onchain identity checks remain the loading path.
145
379
  When catalog records are already available, supply them through the optional `VaultLoadOptions.metadata` field:
146
380
 
147
381
  ```ts
148
- const vaults = await steer.getVaults(ChainId.Base, records.map((record) => record.id), {
149
- blockNumber: 100000n,
150
- metadata: records,
151
- });
382
+ const vaults = await steer.getVaults(
383
+ ChainId.Base,
384
+ records.map((record) => record.id),
385
+ {
386
+ blockNumber: 100000n,
387
+ metadata: records,
388
+ },
389
+ );
152
390
  ```
153
391
 
154
392
  This skips metadata subgraph requests while retaining all onchain registration, implementation,
@@ -190,9 +428,10 @@ already checked by that reader and adds no RPC calls. Custom bindings without a
190
428
  `implementationCodeHash` omit the metadata. `beaconName` reuses the indexed descriptive name from
191
429
  the vault metadata, not onchain identity evidence. It does not assert fee-event completeness.
192
430
 
193
-
194
431
  ```ts
195
- const supported: boolean = steer.getDeployment(chainId).supportsVaultSnapshots(beaconName);
432
+ const supported: boolean = steer
433
+ .getDeployment(chainId)
434
+ .supportsVaultSnapshots(beaconName);
196
435
  ```
197
436
 
198
437
  This synchronous check performs no RPC or HTTP requests. It matches an exact indexed beacon name
@@ -222,7 +461,8 @@ const results = await steer.getVaultSnapshots(ChainId.Base, vaultAddresses, {
222
461
  allowFailure: true,
223
462
  });
224
463
  for (const [index, result] of results.entries()) {
225
- if (result.status === "success") consume(vaultAddresses[index], result.snapshot);
464
+ if (result.status === "success")
465
+ consume(vaultAddresses[index], result.snapshot);
226
466
  else report(vaultAddresses[index], result.error);
227
467
  }
228
468
  ```
@@ -278,7 +518,9 @@ const amm = steer.getAmm({
278
518
  protocol: AmmProtocol.Uniswap,
279
519
  version: UniswapVersion.V3,
280
520
  });
281
- const pools = await amm.getPools([poolAddress, anotherPoolAddress], { blockNumber });
521
+ const pools = await amm.getPools([poolAddress, anotherPoolAddress], {
522
+ blockNumber,
523
+ });
282
524
 
283
525
  // Let the family discover each pool's registered AMM.
284
526
  const discovered = await steer.getPools({
@@ -328,13 +570,22 @@ without a wrapper; install it in the application and use its numeric debug level
328
570
 
329
571
  ```ts
330
572
  import { createConsola } from "consola";
331
- import { AmmProtocol, ChainId, SteerClient, UniswapVersion } from "@steerprotocol/sdk";
573
+ import {
574
+ AmmProtocol,
575
+ ChainId,
576
+ SteerClient,
577
+ UniswapVersion,
578
+ } from "@steerprotocol/sdk";
332
579
 
333
580
  const steer = new SteerClient({
334
581
  logger: createConsola({ level: 4 }),
335
582
  indexers: { theGraph: { proxyUrl: "/api/the-graph" } },
336
583
  });
337
- const amm = steer.getAmm({ chainId: ChainId.Base, protocol: AmmProtocol.Uniswap, version: UniswapVersion.V3 });
584
+ const amm = steer.getAmm({
585
+ chainId: ChainId.Base,
586
+ protocol: AmmProtocol.Uniswap,
587
+ version: UniswapVersion.V3,
588
+ });
338
589
 
339
590
  await amm.listPools({ token: { symbol: "USDC" } });
340
591
  ```
@@ -407,7 +658,13 @@ new catalog as additional chains.
407
658
  ## Uniswap V3 on Base
408
659
 
409
660
  ```ts
410
- import { AmmFamily, ChainId, SteerClient, AmmProtocol, UniswapVersion } from "@steerprotocol/sdk";
661
+ import {
662
+ AmmFamily,
663
+ ChainId,
664
+ SteerClient,
665
+ AmmProtocol,
666
+ UniswapVersion,
667
+ } from "@steerprotocol/sdk";
411
668
 
412
669
  const steer = new SteerClient({ transports: { [ChainId.Base]: rpcTransport } });
413
670
  const pool = await steer.getPool({
@@ -426,23 +683,32 @@ pool.token0Decimals;
426
683
  pool.token1Decimals;
427
684
  pool.price?.currency0InCurrency1; // Whole token1 per whole token0, as a decimal string.
428
685
  pool.price?.currency1InCurrency0; // Whole token0 per whole token1.
429
- pool.fees.swapFeePercent; // "0.05" for raw fee 500.
430
- pool.balances.token0.raw; // bigint from token0.balanceOf(pool.address).
686
+ pool.fees.swapFeePercent; // "0.05" for raw fee 500.
687
+ pool.balances.token0.raw; // bigint from token0.balanceOf(pool.address).
431
688
  pool.balances.token0.formatted; // Exact decimal string using token0Decimals.
432
689
  pool.balances.token1.raw;
433
690
  pool.balances.token1.formatted;
434
691
 
435
- const amm = steer.getAmm({ chainId: ChainId.Base, protocol: AmmProtocol.Uniswap, version: UniswapVersion.V3 });
692
+ const amm = steer.getAmm({
693
+ chainId: ChainId.Base,
694
+ protocol: AmmProtocol.Uniswap,
695
+ version: UniswapVersion.V3,
696
+ });
436
697
  amm.contracts.factory.address;
437
698
  amm.subgraphUrl;
438
- const state = await amm.getPool(pool.address, { blockNumber: pool.blockNumber });
699
+ const state = await amm.getPool(pool.address, {
700
+ blockNumber: pool.blockNumber,
701
+ });
439
702
  // Also exposes token0, token1, fees.swapFeePips, tickSpacing, factory, and sqrtPriceX96.
440
- const quote = await amm.quote({
441
- pool: pool.address,
442
- inputCurrency: state.token0,
443
- outputCurrency: state.token1,
444
- amountIn: 1_000_000_000_000_000n,
445
- }, { blockNumber: state.blockNumber });
703
+ const quote = await amm.quote(
704
+ {
705
+ pool: pool.address,
706
+ inputCurrency: state.token0,
707
+ outputCurrency: state.token1,
708
+ amountIn: 1_000_000_000_000_000n,
709
+ },
710
+ { blockNumber: state.blockNumber },
711
+ );
446
712
  ```
447
713
 
448
714
  `rpcTransport` implements `request(method, params): Promise<unknown>` and returns the JSON-RPC
@@ -490,13 +756,20 @@ indexed listing.
490
756
 
491
757
  ### List indexed pools
492
758
 
493
- Configure The Graph once on the client, using an API key in server code:
759
+ Configure Graph and Sentio keys once on the client in server code. They are reused by SDK indexer requests and vault data connectors. Connector execution keeps both keys in host-side loader settings, outside WASM configuration. The loader attaches Graph authorization only to recognized HTTPS gateway requests after resolving their actual destination, and scopes Sentio authentication to its HTTPS endpoints. It does not change the stored IPFS manifest. Graph proxy configuration continues to apply to SDK indexer requests, not WASM connector requests.
494
760
 
495
761
  ```ts
496
762
  const steer = new SteerClient({
497
- indexers: { theGraph: { apiKey: theGraphApiKey } },
763
+ indexers: {
764
+ theGraph: { apiKey: theGraphApiKey },
765
+ sentio: { apiKey: sentioApiKey },
766
+ },
767
+ });
768
+ const amm = steer.getAmm({
769
+ chainId: ChainId.Base,
770
+ protocol: AmmProtocol.Uniswap,
771
+ version: UniswapVersion.V3,
498
772
  });
499
- const amm = steer.getAmm({ chainId: ChainId.Base, protocol: AmmProtocol.Uniswap, version: UniswapVersion.V3 });
500
773
  const first = await amm.listPools({ limit: 100, token: { symbol: "USDC" } });
501
774
 
502
775
  first.items; // Pool addresses; load one with amm.getPool(address) for verified RPC state.
@@ -518,7 +791,11 @@ For browser applications, configure a server proxy instead of putting a private
518
791
  const steer = new SteerClient({
519
792
  indexers: { theGraph: { proxyUrl: "/api/the-graph" } },
520
793
  });
521
- const amm = steer.getAmm({ chainId: ChainId.Base, protocol: AmmProtocol.Uniswap, version: UniswapVersion.V3 });
794
+ const amm = steer.getAmm({
795
+ chainId: ChainId.Base,
796
+ protocol: AmmProtocol.Uniswap,
797
+ version: UniswapVersion.V3,
798
+ });
522
799
  const pools = await amm.listPools({ token: { symbol: "USDC" } });
523
800
  ```
524
801
 
@@ -535,10 +812,13 @@ the canonical indexer URL; API keys and proxy settings never become public deplo
535
812
  Per-request overrides remain available:
536
813
 
537
814
  ```ts
538
- await amm.listPools({ token: { symbol: "USDC" } }, {
539
- headers: { Authorization: `Bearer ${otherApiKey}` },
540
- timeoutMs: 5_000,
541
- });
815
+ await amm.listPools(
816
+ { token: { symbol: "USDC" } },
817
+ {
818
+ headers: { Authorization: `Bearer ${otherApiKey}` },
819
+ timeoutMs: 5_000,
820
+ },
821
+ );
542
822
  ```
543
823
 
544
824
  An explicit authorization header overrides the client key, using case-insensitive header matching.
@@ -587,11 +867,11 @@ Bearer authentication follows [The Graph API-key documentation](https://thegraph
587
867
 
588
868
  Successful Uniswap V3 operations currently make these calls through the configured `RpcTransport`:
589
869
 
590
- | Operation | Select latest block | Explicit block |
591
- | --- | ---: | ---: |
592
- | `amm.getPool()` | 3 | 2 |
593
- | `steer.getPool()` with discovery | 4 | 3 |
594
- | `amm.quote()` | 6 | 5 |
870
+ | Operation | Select latest block | Explicit block |
871
+ | -------------------------------- | ------------------: | -------------: |
872
+ | `amm.getPool()` | 3 | 2 |
873
+ | `steer.getPool()` with discovery | 4 | 3 |
874
+ | `amm.quote()` | 6 | 5 |
595
875
 
596
876
  Discovery reads the factory, selects an exact registered adapter, and dispatches to that adapter's
597
877
  `getPool()` method before any snapshot reads. Custom adapters can handle nonstandard metadata
@@ -640,9 +920,13 @@ batching remains a separate transport concern.
640
920
 
641
921
  ### Solidity reader development
642
922
 
923
+ Generated TypeScript lives under `src/generated/`, mirroring the AMM, vault and RPC paths.
924
+ This includes reader ABIs/bytecode, batch/tick limits and the package-sourced Steer ABIs.
925
+ The generators create their output directories; routine formatting excludes these files.
926
+
643
927
  The sources are `src/amm/adapters/uniswap-v3/reader/V3PoolReader.sol` and `src/vault/adapters/uniswap-v3/reader/V3VaultReader.sol`; their internal ABIs and
644
- creation/runtime bytecode are checked in under `src/amm/adapters/uniswap-v3/reader/artifact.ts` and
645
- `src/vault/adapters/uniswap-v3/reader/artifact.ts`. `npm run build:reader` regenerates both artifacts with
928
+ creation/runtime bytecode are checked in under `src/generated/amm/adapters/uniswap-v3/reader/artifact.ts` and
929
+ `src/generated/vault/adapters/uniswap-v3/reader/artifact.ts`. `npm run build:reader` regenerates both artifacts with
646
930
  the pinned development-only Solidity compiler, optimizer settings, and Paris EVM target.
647
931
  `npm run check:reader`, included in `npm run check`, detects stale artifacts. Consumers receive
648
932
  compiled JavaScript and bytecode and do not need Solidity tools.
@@ -716,6 +1000,12 @@ code identities; `rpc.ts` and `reader/` hold internal reads. Shared models and e
716
1000
  `vault/types.ts` and `vault/enums.ts`. The Steer data-adapter scaffold lives in
717
1001
  `vault/data-adapter.ts`; built-in indexed listing supports optional AMM/version and token filters.
718
1002
 
1003
+ Shared vault operations are grouped by responsibility: `vault/accounting/` contains snapshot
1004
+ validation, fee/reward accounting and user positions; `vault/transactions/` contains deposit
1005
+ quotes, deposit math, RPC reads and withdrawal preparation; `vault/resolution/` contains the
1006
+ resolver and its internal models; `vault/execution/` contains execution context and WASM orchestration.
1007
+ Family-specific operations stay with their adapters, and shared Solidity readers stay in `vault/reader/`.
1008
+
719
1009
  ## Automatic vault resolution
720
1010
 
721
1011
  `getVault(chainId, address, { blockNumber })` uses the network's SDK-owned `vaultResolver` configuration. Consumers
@@ -755,7 +1045,10 @@ Use the same automatic resolver and snapshot API. Consumers do not select a vaul
755
1045
  ```ts
756
1046
  const vault = await steer.getLiquidityVault(ChainId.Polygon, vaultAddress);
757
1047
  const snapshot = await vault.getSnapshot();
758
- const snapshots = await steer.getVaultSnapshots(ChainId.Polygon, vaultAddresses);
1048
+ const snapshots = await steer.getVaultSnapshots(
1049
+ ChainId.Polygon,
1050
+ vaultAddresses,
1051
+ );
759
1052
 
760
1053
  const page = await steer.listVaults({
761
1054
  chainId: ChainId.Polygon,
@@ -766,13 +1059,13 @@ const page = await steer.listVaults({
766
1059
 
767
1060
  Polygon mappings cover these exact beacon deployments:
768
1061
 
769
- | Indexed beacon | Underlying AMM | Accounting policy |
770
- | --- | --- | --- |
771
- | `MultiPositionUniswapV3` | Uniswap V3 | Vault `TOTAL_FEE` and accrued Steer/strategist reserves |
772
- | `MultiPositionSushi` | SushiSwap V3 | Same fixed-fee V3 layout |
773
- | `MultiPositionRetro` | Retro V3 | Same fixed-fee V3 layout |
774
- | `MultiPositionQuickSwap` | QuickSwap V3 / Legacy Algebra | Fixed fees; packed position key and six-field pool position ABI |
775
- | `MultiPositionQuickSwapIntegral` | QuickSwap V4 / Algebra Integral | Existing Integral fee-manager reader |
1062
+ | Indexed beacon | Underlying AMM | Accounting policy |
1063
+ | -------------------------------- | ------------------------------- | --------------------------------------------------------------- |
1064
+ | `MultiPositionUniswapV3` | Uniswap V3 | Vault `TOTAL_FEE` and accrued Steer/strategist reserves |
1065
+ | `MultiPositionSushi` | SushiSwap V3 | Same fixed-fee V3 layout |
1066
+ | `MultiPositionRetro` | Retro V3 | Same fixed-fee V3 layout |
1067
+ | `MultiPositionQuickSwap` | QuickSwap V3 / Legacy Algebra | Fixed fees; packed position key and six-field pool position ABI |
1068
+ | `MultiPositionQuickSwapIntegral` | QuickSwap V4 / Algebra Integral | Existing Integral fee-manager reader |
776
1069
 
777
1070
  The SDK verifies actual registry membership, stored beacon/registry, exact implementation and original
778
1071
  proxy/implementation runtime hashes, factory registration and token ordering. Indexed names only filter
@@ -822,32 +1115,36 @@ vault.amm.subgraphUrl;
822
1115
  vault.steer.subgraphUrl;
823
1116
  snapshot.blockNumber;
824
1117
  snapshot.pool;
825
- snapshot.idle.token0.formatted; // Available idle tokens after fee reserves.
826
- snapshot.balances.token0.raw; // Full vault ERC-20 holdings.
827
- snapshot.reservedFees.token0.raw; // Already collected protocol/strategist fees.
828
- snapshot.principal.token0.formatted; // Sum of deployed position principal.
829
- snapshot.fees.gross.token0.formatted; // All uncollected position fees.
830
- snapshot.fees.net.token0.formatted; // LP portion after the vault fee cut.
831
- snapshot.fees.recorded.token0.raw; // Sum of pool-checkpointed fees across positions.
1118
+ snapshot.idle.token0.formatted; // Available idle tokens after fee reserves.
1119
+ snapshot.balances.token0.raw; // Full vault ERC-20 holdings.
1120
+ snapshot.reservedFees.token0.raw; // Already collected protocol/strategist fees.
1121
+ snapshot.principal.token0.formatted; // Sum of deployed position principal.
1122
+ snapshot.fees.gross.token0.formatted; // All uncollected position fees.
1123
+ snapshot.fees.net.token0.formatted; // LP portion after the vault fee cut.
1124
+ snapshot.fees.recorded.token0.raw; // Sum of pool-checkpointed fees across positions.
832
1125
  snapshot.feeRateBps;
833
- snapshot.total.token0.formatted; // idle + principal + net fees.
1126
+ snapshot.total.token0.formatted; // idle + principal + net fees.
834
1127
  // Every amount pair also includes token1. Raw amounts are bigint; formatted amounts are strings.
835
1128
 
836
1129
  for (const position of snapshot.positions) {
837
- position.positionId; // keccak256(vault address, lowerTick, upperTick).
1130
+ position.positionId; // keccak256(vault address, lowerTick, upperTick).
838
1131
  position.lowerTick;
839
1132
  position.upperTick;
840
- position.relativeWeight; // Contract-configured units, not basis points.
841
- position.weightPercent; // Normalized percentage string, not a value allocation.
1133
+ position.relativeWeight; // Contract-configured units, not basis points.
1134
+ position.weightPercent; // Normalized percentage string, not a value allocation.
842
1135
  position.liquidity;
843
1136
  position.principal.token0.formatted;
844
- position.fees.gross.token0.formatted; // Gross collectible fees, including accrued growth.
1137
+ position.fees.gross.token0.formatted; // Gross collectible fees, including accrued growth.
845
1138
  position.fees.net.token0.formatted;
846
- position.fees.recorded.token0.raw; // Pool checkpoint tokensOwed, before new fee growth.
1139
+ position.fees.recorded.token0.raw; // Pool checkpoint tokensOwed, before new fee growth.
847
1140
  }
848
1141
 
849
- const positions = await vault.getPositions({ blockNumber: snapshot.blockNumber });
850
- const shares = await vault.getShareBalance(accountAddress, { blockNumber: snapshot.blockNumber });
1142
+ const positions = await vault.getPositions({
1143
+ blockNumber: snapshot.blockNumber,
1144
+ });
1145
+ const shares = await vault.getShareBalance(accountAddress, {
1146
+ blockNumber: snapshot.blockNumber,
1147
+ });
851
1148
  ```
852
1149
 
853
1150
  Omit `blockNumber` to choose the current RPC block, or pin it on resolution and each read. Each
@@ -916,10 +1213,10 @@ own `getTotalAmounts()` after a sequential `poke()` simulation in `eth_call`. No
916
1213
  const vault = await steer.getLiquidityVault(chainId, vaultAddress);
917
1214
  const position = await vault.getUserPosition(accountAddress);
918
1215
 
919
- position.shares; // Raw share balance (bigint)
920
- position.totalSupply; // Raw share supply (bigint)
1216
+ position.shares; // Raw share balance (bigint)
1217
+ position.totalSupply; // Raw share supply (bigint)
921
1218
  position.ownershipPercent; // Decimal string, e.g. "12.5"
922
- position.amounts.token0; // { raw: bigint, formatted: string }
1219
+ position.amounts.token0; // { raw: bigint, formatted: string }
923
1220
  position.amounts.token1;
924
1221
  position.blockNumber;
925
1222
  ```
@@ -955,7 +1252,12 @@ const positions = await steer.getVaultUserPositions(
955
1252
  );
956
1253
 
957
1254
  for (const { vault, position } of positions) {
958
- console.log(vault, position.shares, position.ownershipPercent, position.amounts);
1255
+ console.log(
1256
+ vault,
1257
+ position.shares,
1258
+ position.ownershipPercent,
1259
+ position.amounts,
1260
+ );
959
1261
  }
960
1262
  ```
961
1263
 
@@ -1027,19 +1329,19 @@ const request = {
1027
1329
  };
1028
1330
  const preview = await vault.previewDeposit(request, { blockNumber });
1029
1331
  preview.blockNumber;
1030
- preview.shares; // Expected raw share units
1332
+ preview.shares; // Expected raw share units
1031
1333
  preview.token0;
1032
1334
  preview.token1;
1033
- preview.amounts.token0; // Accepted input: { raw: bigint, formatted: string }
1335
+ preview.amounts.token0; // Accepted input: { raw: bigint, formatted: string }
1034
1336
  preview.amounts.token1;
1035
- preview.unusedAmounts; // Requested inputs which remain in the wallet
1337
+ preview.unusedAmounts; // Requested inputs which remain in the wallet
1036
1338
 
1037
1339
  const plan = await vault.prepareDeposit({ ...request, slippageBps: 50 });
1038
1340
  plan.account;
1039
1341
  plan.chainId;
1040
1342
  plan.preview;
1041
1343
  plan.minimumAmounts; // Minimum accepted amounts in token0/token1 order
1042
- plan.transactions; // Required capped approvals, then the unsigned deposit
1344
+ plan.transactions; // Required capped approvals, then the unsigned deposit
1043
1345
  ```
1044
1346
 
1045
1347
  Both methods accept `{ blockNumber }` as the second argument. Omitting it pins one latest block.
@@ -1056,9 +1358,9 @@ const quote = await vault.quoteDeposit({
1056
1358
  amount: 100_000000n,
1057
1359
  });
1058
1360
 
1059
- quote.assets; // Maxima for both tokens, already in token0/token1 order
1060
- quote.shares; // Expected raw shares
1061
- quote.amounts; // Expected accepted amounts, raw and formatted
1361
+ quote.assets; // Maxima for both tokens, already in token0/token1 order
1362
+ quote.shares; // Expected raw shares
1363
+ quote.amounts; // Expected accepted amounts, raw and formatted
1062
1364
  quote.unusedAmounts; // Any unused input caused by contract rounding
1063
1365
  quote.blockNumber;
1064
1366
 
@@ -1139,11 +1441,11 @@ preview.amounts.token0; // { raw: bigint, formatted: string }
1139
1441
  preview.amounts.token1;
1140
1442
 
1141
1443
  const plan = await vault.prepareWithdraw({ ...request, slippageBps: 50 });
1142
- plan.account; // Must submit the transaction: this account owns the shares
1444
+ plan.account; // Must submit the transaction: this account owns the shares
1143
1445
  plan.chainId;
1144
1446
  plan.preview;
1145
1447
  plan.minimumAmounts; // Raw and formatted minimum outputs for both tokens
1146
- plan.transactions; // Unsigned { to, data, value } requests
1448
+ plan.transactions; // Unsigned { to, data, value } requests
1147
1449
  ```
1148
1450
 
1149
1451
  Both methods accept `{ blockNumber }` as their optional second argument; omitting it pins one
@@ -1180,12 +1482,12 @@ const vault = await steer.getLiquidityVault(ChainId.Base, integralVaultAddress);
1180
1482
  const snapshot = await vault.getSnapshot();
1181
1483
 
1182
1484
  vault.amm.protocol; // AmmProtocol.QuickSwap
1183
- vault.amm.version; // QuickSwapVersion.V4
1485
+ vault.amm.version; // QuickSwapVersion.V4
1184
1486
  vault.amm.subgraphUrl;
1185
1487
  snapshot.positions;
1186
1488
  snapshot.idle;
1187
1489
  snapshot.principal;
1188
- snapshot.fees; // gross, net, recorded, each with token0/token1 raw and formatted amounts.
1490
+ snapshot.fees; // gross, net, recorded, each with token0/token1 raw and formatted amounts.
1189
1491
  snapshot.feeRateBps;
1190
1492
  snapshot.total;
1191
1493
  ```
@@ -1226,16 +1528,16 @@ No Graph credentials are required for vault reads.
1226
1528
 
1227
1529
  ## Extension points
1228
1530
 
1229
- | Base | Responsibility |
1230
- | --- | --- |
1231
- | `UniswapV3Adapter` | Address-based pool reads and V3 quote interface |
1232
- | `UniswapV4Adapter` | Reusable V4 pool-key/ID reads and quote implementation |
1233
- | `AaveV3Adapter` | Reserve reads and supply transaction preparation interface |
1234
- | `PoolDataAdapter`, `LendingDataAdapter` | Indexed protocol discovery |
1235
- | `SteerClient` | SDK entry point, network routing, and typed integration lookup |
1236
- | `SteerNetwork`, `SteerDataAdapter` | Verified vault loading and indexed vault discovery |
1237
- | `VaultResolver`, `VaultIdentityReader` | Exact implementation resolution and pinned identity reads |
1238
- | `SteerVault` | Identity, shares, and strategy positions |
1531
+ | Base | Responsibility |
1532
+ | --------------------------------------- | -------------------------------------------------------------- |
1533
+ | `UniswapV3Adapter` | Address-based pool reads and V3 quote interface |
1534
+ | `UniswapV4Adapter` | Reusable V4 pool-key/ID reads and quote implementation |
1535
+ | `AaveV3Adapter` | Reserve reads and supply transaction preparation interface |
1536
+ | `PoolDataAdapter`, `LendingDataAdapter` | Indexed protocol discovery |
1537
+ | `SteerClient` | SDK entry point, network routing, and typed integration lookup |
1538
+ | `SteerNetwork`, `SteerDataAdapter` | Verified vault loading and indexed vault discovery |
1539
+ | `VaultResolver`, `VaultIdentityReader` | Exact implementation resolution and pinned identity reads |
1540
+ | `SteerVault` | Identity, shares, and strategy positions |
1239
1541
 
1240
1542
  Vault categories describe strategy exposure, not an ERC-4626 or multi-asset ABI. Every vault has
1241
1543
  consistent direct methods: `previewDeposit`, `prepareDeposit`, and `prepareWithdraw`. Deposits use
@@ -1348,7 +1650,8 @@ as V3. Standalone V4 vault operations are qualified separately on BNB Chain belo
1348
1650
  ```ts
1349
1651
  const amm = steer.getAmm({
1350
1652
  chainId: ChainId.Base,
1351
- protocol: AmmProtocol.Uniswap, version: UniswapVersion.V4,
1653
+ protocol: AmmProtocol.Uniswap,
1654
+ version: UniswapVersion.V4,
1352
1655
  });
1353
1656
 
1354
1657
  const page = await amm.listPools({ token: { symbol: "USDC" }, limit: 20 });
@@ -1357,10 +1660,10 @@ await amm.listPools({ token: { address: usdcAddress } });
1357
1660
  await amm.listPools({ token: { name: "USD Coin" } });
1358
1661
 
1359
1662
  for (const key of page.items) {
1360
- const id = amm.getPoolId(key); // Pure ABI encoding and hashing; no network request.
1663
+ const id = amm.getPoolId(key); // Pure ABI encoding and hashing; no network request.
1361
1664
  const recovered = await amm.getPoolKey(id); // Verified indexed metadata lookup.
1362
1665
  const pool = await amm.getPool(key); // Complete keys require RPC only.
1363
- await amm.getPool({ poolId: id }); // Resolves metadata, then verifies onchain state.
1666
+ await amm.getPool({ poolId: id }); // Resolves metadata, then verifies onchain state.
1364
1667
 
1365
1668
  const quote = await amm.quote({
1366
1669
  pool: recovered,
@@ -1463,7 +1766,11 @@ const options = { blockNumber }; // Omit to pin the latest block once for the op
1463
1766
  const specific = await amm.getTicks(reference, [-120, 0, 120], options);
1464
1767
  const range = { lowerTick: lowerVisibleTick, upperTick: upperVisibleTick };
1465
1768
  const window = await amm.getSurroundingTicks(reference, range, options);
1466
- const distribution = await amm.getLiquidityDistribution(reference, range, options);
1769
+ const distribution = await amm.getLiquidityDistribution(
1770
+ reference,
1771
+ range,
1772
+ options,
1773
+ );
1467
1774
 
1468
1775
  // Reuse an already fetched window without another RPC call.
1469
1776
  const sameDistribution = buildLiquidityDistribution(window);
@@ -1513,7 +1820,8 @@ This is fee-free geometric liquidity depth, not an executable swap quote or hook
1513
1820
  ```ts
1514
1821
  const amm = steer.getAmm({
1515
1822
  chainId: ChainId.Base,
1516
- protocol: AmmProtocol.QuickSwap, version: QuickSwapVersion.V4,
1823
+ protocol: AmmProtocol.QuickSwap,
1824
+ version: QuickSwapVersion.V4,
1517
1825
  });
1518
1826
 
1519
1827
  const page = await amm.listPools({ token: { symbol: "USDC" } });
@@ -1527,10 +1835,14 @@ const quote = await amm.quote({
1527
1835
  outputCurrency: pool.token1,
1528
1836
  amountIn: 10n ** BigInt(pool.token0Decimals), // One whole token0.
1529
1837
  });
1530
- const ticks = await amm.getSurroundingTicks(reference, {
1531
- lowerTick: pool.tick - 100,
1532
- upperTick: pool.tick + 100,
1533
- }, { blockNumber: pool.blockNumber });
1838
+ const ticks = await amm.getSurroundingTicks(
1839
+ reference,
1840
+ {
1841
+ lowerTick: pool.tick - 100,
1842
+ upperTick: pool.tick + 100,
1843
+ },
1844
+ { blockNumber: pool.blockNumber },
1845
+ );
1534
1846
  ```
1535
1847
 
1536
1848
  Ordinary pools accept an address. Custom pools use `{ address, deployer }`; listings return the
@@ -1565,11 +1877,13 @@ suite. This qualifies the deployed Integral interface, without inferring a versi
1565
1877
  ```ts
1566
1878
  const quickswap = steer.getAmm({
1567
1879
  chainId: ChainId.Polygon,
1568
- protocol: AmmProtocol.QuickSwap, version: QuickSwapVersion.V3,
1880
+ protocol: AmmProtocol.QuickSwap,
1881
+ version: QuickSwapVersion.V3,
1569
1882
  });
1570
1883
  const camelot = steer.getAmm({
1571
1884
  chainId: ChainId.Arbitrum,
1572
- protocol: AmmProtocol.Camelot, version: CamelotVersion.V3,
1885
+ protocol: AmmProtocol.Camelot,
1886
+ version: CamelotVersion.V3,
1573
1887
  });
1574
1888
  const page = await camelot.listPools({ token: { symbol: "USDC" } });
1575
1889
  const address = page.items[0];
@@ -1634,12 +1948,12 @@ Pool fee data lives under `pool.fees`. Directional fields consistently use
1634
1948
  `currency0ToCurrency1` and `currency1ToCurrency0`, including pools with native currencies.
1635
1949
  Rates retain explicit units and meanings:
1636
1950
 
1637
- | Family | Swap/LP fee fields | Additional fields |
1638
- | --- | --- | --- |
1639
- | Uniswap V3 | `fees.swapFeePips`, `fees.swapFeePercent` | Fixed swap fee, including any protocol share deducted from it |
1640
- | Uniswap V4 | `fees.lpFeePips`, `fees.lpFeePercent` | Configured key fee, packed protocol fee and per-direction protocol fee rates |
1641
- | Algebra Legacy | `fees.currency0ToCurrency1.lastFeePips/lastFeePercent` and the reverse direction | Token-specific community shares in permille and percentage |
1642
- | Algebra Integral | `fees.lastFeePips`, `fees.lastFeePercent` | Community share in permille and percentage |
1951
+ | Family | Swap/LP fee fields | Additional fields |
1952
+ | ---------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
1953
+ | Uniswap V3 | `fees.swapFeePips`, `fees.swapFeePercent` | Fixed swap fee, including any protocol share deducted from it |
1954
+ | Uniswap V4 | `fees.lpFeePips`, `fees.lpFeePercent` | Configured key fee, packed protocol fee and per-direction protocol fee rates |
1955
+ | Algebra Legacy | `fees.currency0ToCurrency1.lastFeePips/lastFeePercent` and the reverse direction | Token-specific community shares in permille and percentage |
1956
+ | Algebra Integral | `fees.lastFeePips`, `fees.lastFeePercent` | Community share in permille and percentage |
1643
1957
 
1644
1958
  Stored dynamic fees are not promises of the next swap's execution fee. V4's LP fee and protocol
1645
1959
  fees remain separate; Algebra community shares are cuts of collected swap fees. The new SDK uses
@@ -1661,22 +1975,30 @@ const steer = new SteerClient({
1661
1975
  });
1662
1976
  const pancake = steer.getAmm({
1663
1977
  chainId: ChainId.BSC,
1664
- protocol: AmmProtocol.PancakeSwap, version: PancakeSwapVersion.V3,
1978
+ protocol: AmmProtocol.PancakeSwap,
1979
+ version: PancakeSwapVersion.V3,
1665
1980
  });
1666
1981
  const infinity = steer.getAmm({
1667
1982
  chainId: ChainId.BSC,
1668
- protocol: AmmProtocol.PancakeSwap, version: PancakeSwapVersion.InfinityCL,
1983
+ protocol: AmmProtocol.PancakeSwap,
1984
+ version: PancakeSwapVersion.InfinityCL,
1669
1985
  });
1670
1986
 
1671
1987
  const pool = await pancake.getPool(poolAddress);
1672
- const indexed = await infinity.listPools({ token: { symbol: "USDT" }, limit: 10 });
1988
+ const indexed = await infinity.listPools({
1989
+ token: { symbol: "USDT" },
1990
+ limit: 10,
1991
+ });
1673
1992
  // Infinity listings return { poolId }; canonical keys resolve directly from the manager.
1674
1993
  const reference = indexed.items[0];
1675
1994
  if (!reference) throw new Error("No matching Infinity CL pools");
1676
1995
  const infinityPool = await infinity.getPool(reference);
1677
1996
  const key = await infinity.getPoolKey(infinityPool.poolId);
1678
1997
  const id = infinity.getPoolId(key);
1679
- const ticks = await infinity.getSurroundingTicks(key, { lowerTick: -100, upperTick: 100 });
1998
+ const ticks = await infinity.getSurroundingTicks(key, {
1999
+ lowerTick: -100,
2000
+ upperTick: 100,
2001
+ });
1680
2002
  ```
1681
2003
 
1682
2004
  `getPoolKey` needs RPC, while
@@ -1692,12 +2014,12 @@ quotes accept optional `hookData` and reject input amounts outside positive `int
1692
2014
  Snapshots expose decoded prices, raw fees and percentages, and the shared `dynamicFee` flag.
1693
2015
  Infinity's Vault holds shared reserves; its snapshot does not expose those as pool balances.
1694
2016
 
1695
- | Pinned read | Pancake V3 | Infinity CL |
1696
- | --- | --- | --- |
1697
- | Snapshot | 2 / 3 / 7 | 2 / 3 / 5 |
1698
- | Specific ticks, one page | 2 / 3 / 8 | 2 / 3 / 6 |
1699
- | Nonempty initialized range, one page | 2 / 3 / 9 | 2 / 3 / 7 |
1700
- | Exact-input quote | 5 | 4 |
2017
+ | Pinned read | Pancake V3 | Infinity CL |
2018
+ | ------------------------------------ | ---------- | ----------- |
2019
+ | Snapshot | 2 / 3 / 7 | 2 / 3 / 5 |
2020
+ | Specific ticks, one page | 2 / 3 / 8 | 2 / 3 / 6 |
2021
+ | Nonempty initialized range, one page | 2 / 3 / 9 | 2 / 3 / 7 |
2022
+ | Exact-input quote | 5 | 4 |
1701
2023
 
1702
2024
  Counts show deployless / override / Multicall compatibility, including rejected feature probes.
1703
2025
  Latest-block reads add one RPC. Infinity ID and full-key references use the same budgets;
@@ -1743,7 +2065,10 @@ if (vault instanceof UniswapV4Vault) {
1743
2065
  const holdings = await vault.getUserPosition(account);
1744
2066
  const quote = await vault.quoteDeposit({ asset: snapshot.token0, amount });
1745
2067
  const plan = await vault.prepareDeposit({
1746
- account, receiver: account, assets: quote.assets, slippageBps: 50,
2068
+ account,
2069
+ receiver: account,
2070
+ assets: quote.assets,
2071
+ slippageBps: 50,
1747
2072
  });
1748
2073
  ```
1749
2074
 
@@ -1786,10 +2111,19 @@ const amm = steer.getAmm({
1786
2111
  version: AerodromeVersion.SlipstreamV3,
1787
2112
  });
1788
2113
  const pool = await amm.getPool(poolAddress);
1789
- const ticks = await amm.getSurroundingTicks(poolAddress, { lowerTick, upperTick });
1790
- const quote = await amm.quote({
1791
- pool: poolAddress, inputCurrency: pool.token0, outputCurrency: pool.token1, amountIn,
1792
- }, { account }); // Optional caller for fee-module discounts.
2114
+ const ticks = await amm.getSurroundingTicks(poolAddress, {
2115
+ lowerTick,
2116
+ upperTick,
2117
+ });
2118
+ const quote = await amm.quote(
2119
+ {
2120
+ pool: poolAddress,
2121
+ inputCurrency: pool.token0,
2122
+ outputCurrency: pool.token1,
2123
+ amountIn,
2124
+ },
2125
+ { account },
2126
+ ); // Optional caller for fee-module discounts.
1793
2127
  ```
1794
2128
 
1795
2129
  Use Slipstream, SlipstreamV2 or SlipstreamV3 explicitly; omitting version is ambiguous. All three
@@ -1843,7 +2177,10 @@ snapshot.rewards; // Separate reward asset, unclaimed rewards and unpaid Steer/s
1843
2177
 
1844
2178
  const page = await steer.listVaults({
1845
2179
  chainId: ChainId.Base,
1846
- amm: { protocol: AmmProtocol.Aerodrome, version: AerodromeVersion.SlipstreamV2 },
2180
+ amm: {
2181
+ protocol: AmmProtocol.Aerodrome,
2182
+ version: AerodromeVersion.SlipstreamV2,
2183
+ },
1847
2184
  token: { symbol: "USDC" },
1848
2185
  });
1849
2186
  ```
@@ -1865,7 +2202,12 @@ const withdrawal = await vault.previewWithdraw({ account, shares, receiver });
1865
2202
  withdrawal.amounts; // Raw/formatted token0 and token1 proceeds paid to receiver.
1866
2203
  withdrawal.rewards; // Aerodrome: { token, recipient: account, amount: { raw, formatted } }.
1867
2204
 
1868
- const plan = await vault.prepareWithdraw({ account, shares, receiver, slippageBps: 50 });
2205
+ const plan = await vault.prepareWithdraw({
2206
+ account,
2207
+ shares,
2208
+ receiver,
2209
+ slippageBps: 50,
2210
+ });
1869
2211
  // Submit plan.transactions from plan.account on plan.chainId using your wallet integration.
1870
2212
  ```
1871
2213
 
@@ -1879,10 +2221,17 @@ quote, preview and unsigned preparation API is supported:
1879
2221
 
1880
2222
  ```ts
1881
2223
  const quote = await vault.quoteDeposit({ asset: token0, amount });
1882
- const deposit = await vault.previewDeposit({ account, receiver, assets: quote.assets });
2224
+ const deposit = await vault.previewDeposit({
2225
+ account,
2226
+ receiver,
2227
+ assets: quote.assets,
2228
+ });
1883
2229
  deposit.rewards; // Aerodrome: existing receiver rewards, settled before new shares are minted.
1884
2230
  const depositPlan = await vault.prepareDeposit({
1885
- account, receiver, assets: quote.assets, slippageBps: 50,
2231
+ account,
2232
+ receiver,
2233
+ assets: quote.assets,
2234
+ slippageBps: 50,
1886
2235
  });
1887
2236
  ```
1888
2237
 
@@ -1897,7 +2246,6 @@ Aerodrome's qualified helper accepts one raw initial share. One-sided vaults quo
1897
2246
 
1898
2247
  See [vault qualification, accounting and budgets](src/vault/adapters/aerodrome/qualification.md).
1899
2248
 
1900
-
1901
2249
  ### Robinhood standalone Uniswap V4 snapshots
1902
2250
 
1903
2251
  `MultiPositionUniswapV4NoOracle` uses the same standalone V4 snapshot reader as the qualified
@@ -1913,7 +2261,6 @@ Live tests cover BSC Reserve and Robinhood NoOracle holders for both ERC20 and n
1913
2261
  including pagination and compatibility reads. Follow every `nextCursor`, even when `items`
1914
2262
  is empty. Incomplete indexed token metadata still limits token-filtered discovery.
1915
2263
 
1916
-
1917
2264
  ### V4 pool-ID lookup without a pool indexer
1918
2265
 
1919
2266
  `amm.getPool({ poolId })`, `amm.getPoolKey(poolId)` and client/bulk ID getters share the
@@ -1926,7 +2273,6 @@ is an error, not a fabricated key. Pool browsing remains separate and requires a
1926
2273
  Base retains its existing indexed adapter by default. BNB combines indexed browsing with RPC key
1927
2274
  resolution; Robinhood uses RPC key resolution without indexed pool browsing.
1928
2275
 
1929
-
1930
2276
  ### BSC PancakeSwap V3 vault snapshots
1931
2277
 
1932
2278
  `MultiPositionPancake` vaults on BSC use the shared `UniswapV3Vault` implementation and
@@ -1945,7 +2291,6 @@ See [Pancake vault qualification](src/vault/adapters/uniswap-v3/pancake-qualific
1945
2291
  identities, funded fixtures, retained-response regressions and live test coverage. Separate funded user-flow qualification now covers deposit quotes/preparation, partial/full
1946
2292
  withdrawals, user positions and indexed wallet balance scanning.
1947
2293
 
1948
-
1949
2294
  ### Camelot legacy vault snapshots on Arbitrum
1950
2295
 
1951
2296
  `MultiPositionCamelot` vaults on Arbitrum use the shared `AlgebraLegacyVault` implementation.
@@ -1964,7 +2309,6 @@ See [Camelot qualification](src/vault/adapters/algebra-legacy/camelot-qualificat
1964
2309
  Funded simulations now also verify deposit quotes/preparation, partial/full withdrawals, user
1965
2310
  positions and wallet discovery. Arbitrum deposit accounting uses the verified Multicall3 deployment.
1966
2311
 
1967
-
1968
2312
  ### Blackhole CL on Avalanche
1969
2313
 
1970
2314
  Select pools with `{ protocol: AmmProtocol.Blackhole, version: BlackholeVersion.CL }`.
@@ -1983,7 +2327,6 @@ compatibility calls, without a two-RPC guarantee.
1983
2327
  See [qualification](src/vault/adapters/blackhole/qualification.md) for exact identities and evidence.
1984
2328
  Run `npm run test:integration:blackhole` with `AVALANCHE_RPC_URL` for the paced historical live suite.
1985
2329
 
1986
-
1987
2330
  #### Blackhole AVAX deposit and exit
1988
2331
 
1989
2332
  Use the zero address as the native AVAX input when the vault holds WAVAX:
@@ -2011,7 +2354,6 @@ This prepares a separate WAVAX withdrawal transaction that pays AVAX to `recipie
2011
2354
  checking its balance. Use actual received amounts rather than the earlier withdrawal preview.
2012
2355
  Both stages are separate wallet operations; preparation sends nothing.
2013
2356
 
2014
-
2015
2357
  ### Pancake V3 and Camelot user-flow verification
2016
2358
 
2017
2359
  Run `npm run test:integration:vault-flows` with BSC and Arbitrum archival RPC URLs. This suite
@@ -2019,20 +2361,21 @@ repeats actual-contract deposits/withdrawals and balance-based discovery at reta
2019
2361
  It uses pinned public indexer responses and paces live RPC calls by 1.5 seconds. No Graph API
2020
2362
  credentials or Base RPC are needed for this isolated runner. Full historical state and temporary
2021
2363
  EOA code overrides must be supported. Native BNB/ETH wrapping helpers remain separate work.
2364
+
2022
2365
  ### Bulk vault execution coverage
2023
2366
 
2024
2367
  Accepting an array does not imply optimized execution. In `4.0.0-next.8`, BSC and Arbitrum
2025
2368
  accepted both bulk methods but used individual-vault reads with concurrency four. This change
2026
2369
  adds native bulk readers for their exact qualified deployments.
2027
2370
 
2028
- | Chain / qualified vaults | Bulk API | Efficient execution in `4.0.0-next.8` | Efficient execution after this change |
2029
- | --- | --- | --- | --- |
2030
- | Base: V3, Integral, Aerodrome | Yes | Yes | Yes |
2031
- | Polygon: registered V3, Legacy, Integral | Yes | Yes | Yes |
2032
- | BSC: Pancake V3 and Reserve V4 | Yes | No | Yes |
2033
- | Arbitrum: Camelot legacy | Yes | No | Yes |
2034
- | Robinhood: NoOracle V4 | Yes | No | Bounded individual reads |
2035
- | Avalanche: Blackhole | Yes | No | Bounded individual reads |
2371
+ | Chain / qualified vaults | Bulk API | Efficient execution in `4.0.0-next.8` | Efficient execution after this change |
2372
+ | ---------------------------------------- | -------- | ------------------------------------- | ------------------------------------- |
2373
+ | Base: V3, Integral, Aerodrome | Yes | Yes | Yes |
2374
+ | Polygon: registered V3, Legacy, Integral | Yes | Yes | Yes |
2375
+ | BSC: Pancake V3 and Reserve V4 | Yes | No | Yes |
2376
+ | Arbitrum: Camelot legacy | Yes | No | Yes |
2377
+ | Robinhood: NoOracle V4 | Yes | No | Bounded individual reads |
2378
+ | Avalanche: Blackhole | Yes | No | Bounded individual reads |
2036
2379
 
2037
2380
  On providers supporting code-only overrides, optimized resolution uses two pinned RPC calls per
2038
2381
  64-vault page. Snapshots use one original-code/position-count preflight per 16-vault page and one
@@ -2060,7 +2403,10 @@ for (const result of vaults) {
2060
2403
  else console.log(result.error);
2061
2404
  }
2062
2405
 
2063
- const pools = await amm.getPools(references, { blockNumber, allowFailure: true });
2406
+ const pools = await amm.getPools(references, {
2407
+ blockNumber,
2408
+ allowFailure: true,
2409
+ });
2064
2410
  for (const result of pools) {
2065
2411
  if (result.status === "success") console.log(result.pool);
2066
2412
  else console.log(result.error);
@@ -2075,7 +2421,6 @@ malformed shared RPC responses, and shared vault metadata request failures still
2075
2421
  `SteerClient.getPools(request)` discovery remains strict; partial pool reads use the selected
2076
2422
  AMM's `getPools(references, options)`.
2077
2423
 
2078
-
2079
2424
  V4 partial ID reads isolate `V4PoolKeyNotFoundError` for a verified absent indexed key or
2080
2425
  uninitialized onchain pool. Shared indexer, JSON/schema, RPC, and unexpected key-adapter errors
2081
2426
  reject the request, including through registered adapter overrides. Custom V4 data adapters