@steerprotocol/sdk 4.0.0-next.11 → 4.0.0-next.13

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,18 +54,156 @@ 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
+
53
102
  ## App and data-connector loader dependencies
54
103
 
55
104
  The SDK installs exact runtime dependencies `@steerprotocol/app-loader@3.2.6` and
56
- `@steerprotocol/data-connector-loader@4.1.0`. Their APIs remain separate from `SteerClient`;
105
+ `@steerprotocol/data-connector-loader@5.0.0-next.7`. Their APIs remain separate from `SteerClient`;
57
106
  they are not re-exported from the SDK root.
58
107
 
59
- Both loader packages support ESM imports. The published connector loader's CommonJS entry
60
- fails because its CommonJS `.js` files are inside a package marked `type: module`; use its
61
- ESM entry. The app loader supports both ESM and CommonJS. The connector loader declares a
62
- stable 3.x SDK dependency (3.8.1 in this lockfile), used for its execution-context types;
63
- its emitted JavaScript does not import SDK readers. The SDK's portable root does not import
64
- either loader.
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.
65
207
 
66
208
  ## Vault execution context
67
209
 
@@ -69,18 +211,36 @@ Liquidity vaults expose strategy inputs through the existing vault object:
69
211
 
70
212
  ```ts
71
213
  const blockNumber = 12345678n; // Caller-selected operation block.
72
- const vault = await steer.getLiquidityVault(chainId, vaultAddress, { blockNumber });
214
+ const vault = await steer.getLiquidityVault(chainId, vaultAddress, {
215
+ blockNumber,
216
+ });
73
217
  const context = await vault.getExecutionContext({
74
218
  blockNumber,
75
219
  epochTimestamp: 1793577600n, // Explicit Unix seconds, independent of the wall clock.
220
+ epochLength: 60, // Strategy epoch duration in seconds.
76
221
  });
77
222
  ```
78
223
 
79
- The immutable, JSON-safe `VaultExecutionContext` contains vault/beacon/AMM identity,
80
- address-based or V4 key/ID/manager pool identity, current tick/spacing, configured strategy
81
- ranges with relative weights, original contract totals as decimal strings, and indexed
82
- last-execution/elapsed seconds. It has no `provenance` field. V4 never fabricates a pool address;
83
- unused pool identity fields are `null`. Configured ranges are independent of active NFT inventory.
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.
84
244
 
85
245
  Each call revalidates the native vault binding and dispatches registered vault/AMM overrides.
86
246
  All onchain reads use the explicit block. `totalAmount0`/`totalAmount1` come from the original
@@ -89,9 +249,9 @@ they are not aliases for the fee-adjusted `getSnapshot().total` accounting field
89
249
 
90
250
  The Steer data adapter queries the latest indexed `vaultSnapshot` at that block, bounded by
91
251
  the earlier of the block timestamp and execution epoch. Its timestamp is the history source,
92
- not proof that the indexer has captured every execution. An empty history returns `null`
93
- for both history fields. Unsupported history adapters, unavailable blocks, indexing errors,
94
- malformed responses and RPC/HTTP failures throw instead of fabricating zero elapsed time.
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.
95
255
  Block numbers must fit the indexer's nonnegative GraphQL Int range. The optional second
96
256
  argument accepts the existing per-request HTTP overrides for the history query.
97
257
 
@@ -99,20 +259,21 @@ Custom Steer data adapters can implement `getLastVaultExecution(request, options
99
259
  history without a subgraph. The request contains `vaultAddress`, `blockNumber` and the bounded
100
260
  `timestamp`; return a nonnegative bigint no later than that timestamp, or `null`.
101
261
 
102
- The installed connector loader's runtime mapper accepts this provenance-free shape. Its
103
- published TypeScript declarations still require the legacy SDK's context, including legacy
104
- protocol types and `provenance`. Updating that upstream type contract is required for direct
105
- typed `executeWithContext` integration; this SDK does not invent provenance or cast away that
106
- mismatch. The offline test verifies runtime mapping, not a full WASM strategy execution.
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.
107
267
 
108
- Read-only live verification on November 2, 2026 passed 13 fixtures across six chains:
268
+ The underlying read path was verified live on November 2, 2026 using 13 fixtures across six chains:
109
269
  Base Uniswap V3, Base QuickSwap Integral, all three Base Aerodrome generations, Polygon
110
270
  QuickSwap Legacy, Arbitrum Camelot, Avalanche Blackhole, BSC Pancake V3, and ERC20/native
111
- standalone V4 vaults on both BSC and Robinhood. Each context was compared with independent
271
+ standalone V4 vaults on both BSC and Robinhood. The prior wrapper was compared with independent
112
272
  same-block original contract totals, configured ranges, registry/beacon identity, pool
113
- tick/spacing and identity, and a separate indexed history query. This qualifies those
114
- fixtures, not every deployment or full WASM strategy execution. Blackhole execution pool
115
- reads retain the custom deployer from the verified vault layout.
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.
116
277
 
117
278
  Reproduce with the explicit RPC/indexer settings in `.env.integration`:
118
279
 
@@ -138,7 +299,10 @@ for (const metadata of page.items) {
138
299
  }
139
300
 
140
301
  // Load a page or any explicit address list through the same resolver.
141
- 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
+ );
142
306
 
143
307
  if (page.nextCursor) {
144
308
  const next = await steer.listVaults({
@@ -215,10 +379,14 @@ its onchain identity checks remain the loading path.
215
379
  When catalog records are already available, supply them through the optional `VaultLoadOptions.metadata` field:
216
380
 
217
381
  ```ts
218
- const vaults = await steer.getVaults(ChainId.Base, records.map((record) => record.id), {
219
- blockNumber: 100000n,
220
- metadata: records,
221
- });
382
+ const vaults = await steer.getVaults(
383
+ ChainId.Base,
384
+ records.map((record) => record.id),
385
+ {
386
+ blockNumber: 100000n,
387
+ metadata: records,
388
+ },
389
+ );
222
390
  ```
223
391
 
224
392
  This skips metadata subgraph requests while retaining all onchain registration, implementation,
@@ -260,9 +428,10 @@ already checked by that reader and adds no RPC calls. Custom bindings without a
260
428
  `implementationCodeHash` omit the metadata. `beaconName` reuses the indexed descriptive name from
261
429
  the vault metadata, not onchain identity evidence. It does not assert fee-event completeness.
262
430
 
263
-
264
431
  ```ts
265
- const supported: boolean = steer.getDeployment(chainId).supportsVaultSnapshots(beaconName);
432
+ const supported: boolean = steer
433
+ .getDeployment(chainId)
434
+ .supportsVaultSnapshots(beaconName);
266
435
  ```
267
436
 
268
437
  This synchronous check performs no RPC or HTTP requests. It matches an exact indexed beacon name
@@ -292,7 +461,8 @@ const results = await steer.getVaultSnapshots(ChainId.Base, vaultAddresses, {
292
461
  allowFailure: true,
293
462
  });
294
463
  for (const [index, result] of results.entries()) {
295
- if (result.status === "success") consume(vaultAddresses[index], result.snapshot);
464
+ if (result.status === "success")
465
+ consume(vaultAddresses[index], result.snapshot);
296
466
  else report(vaultAddresses[index], result.error);
297
467
  }
298
468
  ```
@@ -348,7 +518,9 @@ const amm = steer.getAmm({
348
518
  protocol: AmmProtocol.Uniswap,
349
519
  version: UniswapVersion.V3,
350
520
  });
351
- const pools = await amm.getPools([poolAddress, anotherPoolAddress], { blockNumber });
521
+ const pools = await amm.getPools([poolAddress, anotherPoolAddress], {
522
+ blockNumber,
523
+ });
352
524
 
353
525
  // Let the family discover each pool's registered AMM.
354
526
  const discovered = await steer.getPools({
@@ -398,13 +570,22 @@ without a wrapper; install it in the application and use its numeric debug level
398
570
 
399
571
  ```ts
400
572
  import { createConsola } from "consola";
401
- import { AmmProtocol, ChainId, SteerClient, UniswapVersion } from "@steerprotocol/sdk";
573
+ import {
574
+ AmmProtocol,
575
+ ChainId,
576
+ SteerClient,
577
+ UniswapVersion,
578
+ } from "@steerprotocol/sdk";
402
579
 
403
580
  const steer = new SteerClient({
404
581
  logger: createConsola({ level: 4 }),
405
582
  indexers: { theGraph: { proxyUrl: "/api/the-graph" } },
406
583
  });
407
- 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
+ });
408
589
 
409
590
  await amm.listPools({ token: { symbol: "USDC" } });
410
591
  ```
@@ -477,7 +658,13 @@ new catalog as additional chains.
477
658
  ## Uniswap V3 on Base
478
659
 
479
660
  ```ts
480
- 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";
481
668
 
482
669
  const steer = new SteerClient({ transports: { [ChainId.Base]: rpcTransport } });
483
670
  const pool = await steer.getPool({
@@ -496,23 +683,32 @@ pool.token0Decimals;
496
683
  pool.token1Decimals;
497
684
  pool.price?.currency0InCurrency1; // Whole token1 per whole token0, as a decimal string.
498
685
  pool.price?.currency1InCurrency0; // Whole token0 per whole token1.
499
- pool.fees.swapFeePercent; // "0.05" for raw fee 500.
500
- 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).
501
688
  pool.balances.token0.formatted; // Exact decimal string using token0Decimals.
502
689
  pool.balances.token1.raw;
503
690
  pool.balances.token1.formatted;
504
691
 
505
- 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
+ });
506
697
  amm.contracts.factory.address;
507
698
  amm.subgraphUrl;
508
- const state = await amm.getPool(pool.address, { blockNumber: pool.blockNumber });
699
+ const state = await amm.getPool(pool.address, {
700
+ blockNumber: pool.blockNumber,
701
+ });
509
702
  // Also exposes token0, token1, fees.swapFeePips, tickSpacing, factory, and sqrtPriceX96.
510
- const quote = await amm.quote({
511
- pool: pool.address,
512
- inputCurrency: state.token0,
513
- outputCurrency: state.token1,
514
- amountIn: 1_000_000_000_000_000n,
515
- }, { 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
+ );
516
712
  ```
517
713
 
518
714
  `rpcTransport` implements `request(method, params): Promise<unknown>` and returns the JSON-RPC
@@ -560,13 +756,20 @@ indexed listing.
560
756
 
561
757
  ### List indexed pools
562
758
 
563
- 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.
564
760
 
565
761
  ```ts
566
762
  const steer = new SteerClient({
567
- 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,
568
772
  });
569
- const amm = steer.getAmm({ chainId: ChainId.Base, protocol: AmmProtocol.Uniswap, version: UniswapVersion.V3 });
570
773
  const first = await amm.listPools({ limit: 100, token: { symbol: "USDC" } });
571
774
 
572
775
  first.items; // Pool addresses; load one with amm.getPool(address) for verified RPC state.
@@ -588,7 +791,11 @@ For browser applications, configure a server proxy instead of putting a private
588
791
  const steer = new SteerClient({
589
792
  indexers: { theGraph: { proxyUrl: "/api/the-graph" } },
590
793
  });
591
- 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
+ });
592
799
  const pools = await amm.listPools({ token: { symbol: "USDC" } });
593
800
  ```
594
801
 
@@ -605,10 +812,13 @@ the canonical indexer URL; API keys and proxy settings never become public deplo
605
812
  Per-request overrides remain available:
606
813
 
607
814
  ```ts
608
- await amm.listPools({ token: { symbol: "USDC" } }, {
609
- headers: { Authorization: `Bearer ${otherApiKey}` },
610
- timeoutMs: 5_000,
611
- });
815
+ await amm.listPools(
816
+ { token: { symbol: "USDC" } },
817
+ {
818
+ headers: { Authorization: `Bearer ${otherApiKey}` },
819
+ timeoutMs: 5_000,
820
+ },
821
+ );
612
822
  ```
613
823
 
614
824
  An explicit authorization header overrides the client key, using case-insensitive header matching.
@@ -657,11 +867,11 @@ Bearer authentication follows [The Graph API-key documentation](https://thegraph
657
867
 
658
868
  Successful Uniswap V3 operations currently make these calls through the configured `RpcTransport`:
659
869
 
660
- | Operation | Select latest block | Explicit block |
661
- | --- | ---: | ---: |
662
- | `amm.getPool()` | 3 | 2 |
663
- | `steer.getPool()` with discovery | 4 | 3 |
664
- | `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 |
665
875
 
666
876
  Discovery reads the factory, selects an exact registered adapter, and dispatches to that adapter's
667
877
  `getPool()` method before any snapshot reads. Custom adapters can handle nonstandard metadata
@@ -710,9 +920,13 @@ batching remains a separate transport concern.
710
920
 
711
921
  ### Solidity reader development
712
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
+
713
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
714
- creation/runtime bytecode are checked in under `src/amm/adapters/uniswap-v3/reader/artifact.ts` and
715
- `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
716
930
  the pinned development-only Solidity compiler, optimizer settings, and Paris EVM target.
717
931
  `npm run check:reader`, included in `npm run check`, detects stale artifacts. Consumers receive
718
932
  compiled JavaScript and bytecode and do not need Solidity tools.
@@ -786,6 +1000,12 @@ code identities; `rpc.ts` and `reader/` hold internal reads. Shared models and e
786
1000
  `vault/types.ts` and `vault/enums.ts`. The Steer data-adapter scaffold lives in
787
1001
  `vault/data-adapter.ts`; built-in indexed listing supports optional AMM/version and token filters.
788
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
+
789
1009
  ## Automatic vault resolution
790
1010
 
791
1011
  `getVault(chainId, address, { blockNumber })` uses the network's SDK-owned `vaultResolver` configuration. Consumers
@@ -825,7 +1045,10 @@ Use the same automatic resolver and snapshot API. Consumers do not select a vaul
825
1045
  ```ts
826
1046
  const vault = await steer.getLiquidityVault(ChainId.Polygon, vaultAddress);
827
1047
  const snapshot = await vault.getSnapshot();
828
- const snapshots = await steer.getVaultSnapshots(ChainId.Polygon, vaultAddresses);
1048
+ const snapshots = await steer.getVaultSnapshots(
1049
+ ChainId.Polygon,
1050
+ vaultAddresses,
1051
+ );
829
1052
 
830
1053
  const page = await steer.listVaults({
831
1054
  chainId: ChainId.Polygon,
@@ -836,13 +1059,13 @@ const page = await steer.listVaults({
836
1059
 
837
1060
  Polygon mappings cover these exact beacon deployments:
838
1061
 
839
- | Indexed beacon | Underlying AMM | Accounting policy |
840
- | --- | --- | --- |
841
- | `MultiPositionUniswapV3` | Uniswap V3 | Vault `TOTAL_FEE` and accrued Steer/strategist reserves |
842
- | `MultiPositionSushi` | SushiSwap V3 | Same fixed-fee V3 layout |
843
- | `MultiPositionRetro` | Retro V3 | Same fixed-fee V3 layout |
844
- | `MultiPositionQuickSwap` | QuickSwap V3 / Legacy Algebra | Fixed fees; packed position key and six-field pool position ABI |
845
- | `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 |
846
1069
 
847
1070
  The SDK verifies actual registry membership, stored beacon/registry, exact implementation and original
848
1071
  proxy/implementation runtime hashes, factory registration and token ordering. Indexed names only filter
@@ -892,32 +1115,36 @@ vault.amm.subgraphUrl;
892
1115
  vault.steer.subgraphUrl;
893
1116
  snapshot.blockNumber;
894
1117
  snapshot.pool;
895
- snapshot.idle.token0.formatted; // Available idle tokens after fee reserves.
896
- snapshot.balances.token0.raw; // Full vault ERC-20 holdings.
897
- snapshot.reservedFees.token0.raw; // Already collected protocol/strategist fees.
898
- snapshot.principal.token0.formatted; // Sum of deployed position principal.
899
- snapshot.fees.gross.token0.formatted; // All uncollected position fees.
900
- snapshot.fees.net.token0.formatted; // LP portion after the vault fee cut.
901
- 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.
902
1125
  snapshot.feeRateBps;
903
- snapshot.total.token0.formatted; // idle + principal + net fees.
1126
+ snapshot.total.token0.formatted; // idle + principal + net fees.
904
1127
  // Every amount pair also includes token1. Raw amounts are bigint; formatted amounts are strings.
905
1128
 
906
1129
  for (const position of snapshot.positions) {
907
- position.positionId; // keccak256(vault address, lowerTick, upperTick).
1130
+ position.positionId; // keccak256(vault address, lowerTick, upperTick).
908
1131
  position.lowerTick;
909
1132
  position.upperTick;
910
- position.relativeWeight; // Contract-configured units, not basis points.
911
- 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.
912
1135
  position.liquidity;
913
1136
  position.principal.token0.formatted;
914
- position.fees.gross.token0.formatted; // Gross collectible fees, including accrued growth.
1137
+ position.fees.gross.token0.formatted; // Gross collectible fees, including accrued growth.
915
1138
  position.fees.net.token0.formatted;
916
- 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.
917
1140
  }
918
1141
 
919
- const positions = await vault.getPositions({ blockNumber: snapshot.blockNumber });
920
- 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
+ });
921
1148
  ```
922
1149
 
923
1150
  Omit `blockNumber` to choose the current RPC block, or pin it on resolution and each read. Each
@@ -986,10 +1213,10 @@ own `getTotalAmounts()` after a sequential `poke()` simulation in `eth_call`. No
986
1213
  const vault = await steer.getLiquidityVault(chainId, vaultAddress);
987
1214
  const position = await vault.getUserPosition(accountAddress);
988
1215
 
989
- position.shares; // Raw share balance (bigint)
990
- position.totalSupply; // Raw share supply (bigint)
1216
+ position.shares; // Raw share balance (bigint)
1217
+ position.totalSupply; // Raw share supply (bigint)
991
1218
  position.ownershipPercent; // Decimal string, e.g. "12.5"
992
- position.amounts.token0; // { raw: bigint, formatted: string }
1219
+ position.amounts.token0; // { raw: bigint, formatted: string }
993
1220
  position.amounts.token1;
994
1221
  position.blockNumber;
995
1222
  ```
@@ -1025,7 +1252,12 @@ const positions = await steer.getVaultUserPositions(
1025
1252
  );
1026
1253
 
1027
1254
  for (const { vault, position } of positions) {
1028
- console.log(vault, position.shares, position.ownershipPercent, position.amounts);
1255
+ console.log(
1256
+ vault,
1257
+ position.shares,
1258
+ position.ownershipPercent,
1259
+ position.amounts,
1260
+ );
1029
1261
  }
1030
1262
  ```
1031
1263
 
@@ -1097,19 +1329,19 @@ const request = {
1097
1329
  };
1098
1330
  const preview = await vault.previewDeposit(request, { blockNumber });
1099
1331
  preview.blockNumber;
1100
- preview.shares; // Expected raw share units
1332
+ preview.shares; // Expected raw share units
1101
1333
  preview.token0;
1102
1334
  preview.token1;
1103
- preview.amounts.token0; // Accepted input: { raw: bigint, formatted: string }
1335
+ preview.amounts.token0; // Accepted input: { raw: bigint, formatted: string }
1104
1336
  preview.amounts.token1;
1105
- preview.unusedAmounts; // Requested inputs which remain in the wallet
1337
+ preview.unusedAmounts; // Requested inputs which remain in the wallet
1106
1338
 
1107
1339
  const plan = await vault.prepareDeposit({ ...request, slippageBps: 50 });
1108
1340
  plan.account;
1109
1341
  plan.chainId;
1110
1342
  plan.preview;
1111
1343
  plan.minimumAmounts; // Minimum accepted amounts in token0/token1 order
1112
- plan.transactions; // Required capped approvals, then the unsigned deposit
1344
+ plan.transactions; // Required capped approvals, then the unsigned deposit
1113
1345
  ```
1114
1346
 
1115
1347
  Both methods accept `{ blockNumber }` as the second argument. Omitting it pins one latest block.
@@ -1126,9 +1358,9 @@ const quote = await vault.quoteDeposit({
1126
1358
  amount: 100_000000n,
1127
1359
  });
1128
1360
 
1129
- quote.assets; // Maxima for both tokens, already in token0/token1 order
1130
- quote.shares; // Expected raw shares
1131
- 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
1132
1364
  quote.unusedAmounts; // Any unused input caused by contract rounding
1133
1365
  quote.blockNumber;
1134
1366
 
@@ -1209,11 +1441,11 @@ preview.amounts.token0; // { raw: bigint, formatted: string }
1209
1441
  preview.amounts.token1;
1210
1442
 
1211
1443
  const plan = await vault.prepareWithdraw({ ...request, slippageBps: 50 });
1212
- plan.account; // Must submit the transaction: this account owns the shares
1444
+ plan.account; // Must submit the transaction: this account owns the shares
1213
1445
  plan.chainId;
1214
1446
  plan.preview;
1215
1447
  plan.minimumAmounts; // Raw and formatted minimum outputs for both tokens
1216
- plan.transactions; // Unsigned { to, data, value } requests
1448
+ plan.transactions; // Unsigned { to, data, value } requests
1217
1449
  ```
1218
1450
 
1219
1451
  Both methods accept `{ blockNumber }` as their optional second argument; omitting it pins one
@@ -1250,12 +1482,12 @@ const vault = await steer.getLiquidityVault(ChainId.Base, integralVaultAddress);
1250
1482
  const snapshot = await vault.getSnapshot();
1251
1483
 
1252
1484
  vault.amm.protocol; // AmmProtocol.QuickSwap
1253
- vault.amm.version; // QuickSwapVersion.V4
1485
+ vault.amm.version; // QuickSwapVersion.V4
1254
1486
  vault.amm.subgraphUrl;
1255
1487
  snapshot.positions;
1256
1488
  snapshot.idle;
1257
1489
  snapshot.principal;
1258
- 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.
1259
1491
  snapshot.feeRateBps;
1260
1492
  snapshot.total;
1261
1493
  ```
@@ -1296,16 +1528,16 @@ No Graph credentials are required for vault reads.
1296
1528
 
1297
1529
  ## Extension points
1298
1530
 
1299
- | Base | Responsibility |
1300
- | --- | --- |
1301
- | `UniswapV3Adapter` | Address-based pool reads and V3 quote interface |
1302
- | `UniswapV4Adapter` | Reusable V4 pool-key/ID reads and quote implementation |
1303
- | `AaveV3Adapter` | Reserve reads and supply transaction preparation interface |
1304
- | `PoolDataAdapter`, `LendingDataAdapter` | Indexed protocol discovery |
1305
- | `SteerClient` | SDK entry point, network routing, and typed integration lookup |
1306
- | `SteerNetwork`, `SteerDataAdapter` | Verified vault loading and indexed vault discovery |
1307
- | `VaultResolver`, `VaultIdentityReader` | Exact implementation resolution and pinned identity reads |
1308
- | `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 |
1309
1541
 
1310
1542
  Vault categories describe strategy exposure, not an ERC-4626 or multi-asset ABI. Every vault has
1311
1543
  consistent direct methods: `previewDeposit`, `prepareDeposit`, and `prepareWithdraw`. Deposits use
@@ -1418,7 +1650,8 @@ as V3. Standalone V4 vault operations are qualified separately on BNB Chain belo
1418
1650
  ```ts
1419
1651
  const amm = steer.getAmm({
1420
1652
  chainId: ChainId.Base,
1421
- protocol: AmmProtocol.Uniswap, version: UniswapVersion.V4,
1653
+ protocol: AmmProtocol.Uniswap,
1654
+ version: UniswapVersion.V4,
1422
1655
  });
1423
1656
 
1424
1657
  const page = await amm.listPools({ token: { symbol: "USDC" }, limit: 20 });
@@ -1427,10 +1660,10 @@ await amm.listPools({ token: { address: usdcAddress } });
1427
1660
  await amm.listPools({ token: { name: "USD Coin" } });
1428
1661
 
1429
1662
  for (const key of page.items) {
1430
- 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.
1431
1664
  const recovered = await amm.getPoolKey(id); // Verified indexed metadata lookup.
1432
1665
  const pool = await amm.getPool(key); // Complete keys require RPC only.
1433
- await amm.getPool({ poolId: id }); // Resolves metadata, then verifies onchain state.
1666
+ await amm.getPool({ poolId: id }); // Resolves metadata, then verifies onchain state.
1434
1667
 
1435
1668
  const quote = await amm.quote({
1436
1669
  pool: recovered,
@@ -1533,7 +1766,11 @@ const options = { blockNumber }; // Omit to pin the latest block once for the op
1533
1766
  const specific = await amm.getTicks(reference, [-120, 0, 120], options);
1534
1767
  const range = { lowerTick: lowerVisibleTick, upperTick: upperVisibleTick };
1535
1768
  const window = await amm.getSurroundingTicks(reference, range, options);
1536
- const distribution = await amm.getLiquidityDistribution(reference, range, options);
1769
+ const distribution = await amm.getLiquidityDistribution(
1770
+ reference,
1771
+ range,
1772
+ options,
1773
+ );
1537
1774
 
1538
1775
  // Reuse an already fetched window without another RPC call.
1539
1776
  const sameDistribution = buildLiquidityDistribution(window);
@@ -1583,7 +1820,8 @@ This is fee-free geometric liquidity depth, not an executable swap quote or hook
1583
1820
  ```ts
1584
1821
  const amm = steer.getAmm({
1585
1822
  chainId: ChainId.Base,
1586
- protocol: AmmProtocol.QuickSwap, version: QuickSwapVersion.V4,
1823
+ protocol: AmmProtocol.QuickSwap,
1824
+ version: QuickSwapVersion.V4,
1587
1825
  });
1588
1826
 
1589
1827
  const page = await amm.listPools({ token: { symbol: "USDC" } });
@@ -1597,10 +1835,14 @@ const quote = await amm.quote({
1597
1835
  outputCurrency: pool.token1,
1598
1836
  amountIn: 10n ** BigInt(pool.token0Decimals), // One whole token0.
1599
1837
  });
1600
- const ticks = await amm.getSurroundingTicks(reference, {
1601
- lowerTick: pool.tick - 100,
1602
- upperTick: pool.tick + 100,
1603
- }, { 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
+ );
1604
1846
  ```
1605
1847
 
1606
1848
  Ordinary pools accept an address. Custom pools use `{ address, deployer }`; listings return the
@@ -1635,11 +1877,13 @@ suite. This qualifies the deployed Integral interface, without inferring a versi
1635
1877
  ```ts
1636
1878
  const quickswap = steer.getAmm({
1637
1879
  chainId: ChainId.Polygon,
1638
- protocol: AmmProtocol.QuickSwap, version: QuickSwapVersion.V3,
1880
+ protocol: AmmProtocol.QuickSwap,
1881
+ version: QuickSwapVersion.V3,
1639
1882
  });
1640
1883
  const camelot = steer.getAmm({
1641
1884
  chainId: ChainId.Arbitrum,
1642
- protocol: AmmProtocol.Camelot, version: CamelotVersion.V3,
1885
+ protocol: AmmProtocol.Camelot,
1886
+ version: CamelotVersion.V3,
1643
1887
  });
1644
1888
  const page = await camelot.listPools({ token: { symbol: "USDC" } });
1645
1889
  const address = page.items[0];
@@ -1704,12 +1948,12 @@ Pool fee data lives under `pool.fees`. Directional fields consistently use
1704
1948
  `currency0ToCurrency1` and `currency1ToCurrency0`, including pools with native currencies.
1705
1949
  Rates retain explicit units and meanings:
1706
1950
 
1707
- | Family | Swap/LP fee fields | Additional fields |
1708
- | --- | --- | --- |
1709
- | Uniswap V3 | `fees.swapFeePips`, `fees.swapFeePercent` | Fixed swap fee, including any protocol share deducted from it |
1710
- | Uniswap V4 | `fees.lpFeePips`, `fees.lpFeePercent` | Configured key fee, packed protocol fee and per-direction protocol fee rates |
1711
- | Algebra Legacy | `fees.currency0ToCurrency1.lastFeePips/lastFeePercent` and the reverse direction | Token-specific community shares in permille and percentage |
1712
- | 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 |
1713
1957
 
1714
1958
  Stored dynamic fees are not promises of the next swap's execution fee. V4's LP fee and protocol
1715
1959
  fees remain separate; Algebra community shares are cuts of collected swap fees. The new SDK uses
@@ -1731,22 +1975,30 @@ const steer = new SteerClient({
1731
1975
  });
1732
1976
  const pancake = steer.getAmm({
1733
1977
  chainId: ChainId.BSC,
1734
- protocol: AmmProtocol.PancakeSwap, version: PancakeSwapVersion.V3,
1978
+ protocol: AmmProtocol.PancakeSwap,
1979
+ version: PancakeSwapVersion.V3,
1735
1980
  });
1736
1981
  const infinity = steer.getAmm({
1737
1982
  chainId: ChainId.BSC,
1738
- protocol: AmmProtocol.PancakeSwap, version: PancakeSwapVersion.InfinityCL,
1983
+ protocol: AmmProtocol.PancakeSwap,
1984
+ version: PancakeSwapVersion.InfinityCL,
1739
1985
  });
1740
1986
 
1741
1987
  const pool = await pancake.getPool(poolAddress);
1742
- const indexed = await infinity.listPools({ token: { symbol: "USDT" }, limit: 10 });
1988
+ const indexed = await infinity.listPools({
1989
+ token: { symbol: "USDT" },
1990
+ limit: 10,
1991
+ });
1743
1992
  // Infinity listings return { poolId }; canonical keys resolve directly from the manager.
1744
1993
  const reference = indexed.items[0];
1745
1994
  if (!reference) throw new Error("No matching Infinity CL pools");
1746
1995
  const infinityPool = await infinity.getPool(reference);
1747
1996
  const key = await infinity.getPoolKey(infinityPool.poolId);
1748
1997
  const id = infinity.getPoolId(key);
1749
- const ticks = await infinity.getSurroundingTicks(key, { lowerTick: -100, upperTick: 100 });
1998
+ const ticks = await infinity.getSurroundingTicks(key, {
1999
+ lowerTick: -100,
2000
+ upperTick: 100,
2001
+ });
1750
2002
  ```
1751
2003
 
1752
2004
  `getPoolKey` needs RPC, while
@@ -1762,12 +2014,12 @@ quotes accept optional `hookData` and reject input amounts outside positive `int
1762
2014
  Snapshots expose decoded prices, raw fees and percentages, and the shared `dynamicFee` flag.
1763
2015
  Infinity's Vault holds shared reserves; its snapshot does not expose those as pool balances.
1764
2016
 
1765
- | Pinned read | Pancake V3 | Infinity CL |
1766
- | --- | --- | --- |
1767
- | Snapshot | 2 / 3 / 7 | 2 / 3 / 5 |
1768
- | Specific ticks, one page | 2 / 3 / 8 | 2 / 3 / 6 |
1769
- | Nonempty initialized range, one page | 2 / 3 / 9 | 2 / 3 / 7 |
1770
- | 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 |
1771
2023
 
1772
2024
  Counts show deployless / override / Multicall compatibility, including rejected feature probes.
1773
2025
  Latest-block reads add one RPC. Infinity ID and full-key references use the same budgets;
@@ -1813,7 +2065,10 @@ if (vault instanceof UniswapV4Vault) {
1813
2065
  const holdings = await vault.getUserPosition(account);
1814
2066
  const quote = await vault.quoteDeposit({ asset: snapshot.token0, amount });
1815
2067
  const plan = await vault.prepareDeposit({
1816
- account, receiver: account, assets: quote.assets, slippageBps: 50,
2068
+ account,
2069
+ receiver: account,
2070
+ assets: quote.assets,
2071
+ slippageBps: 50,
1817
2072
  });
1818
2073
  ```
1819
2074
 
@@ -1856,10 +2111,19 @@ const amm = steer.getAmm({
1856
2111
  version: AerodromeVersion.SlipstreamV3,
1857
2112
  });
1858
2113
  const pool = await amm.getPool(poolAddress);
1859
- const ticks = await amm.getSurroundingTicks(poolAddress, { lowerTick, upperTick });
1860
- const quote = await amm.quote({
1861
- pool: poolAddress, inputCurrency: pool.token0, outputCurrency: pool.token1, amountIn,
1862
- }, { 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.
1863
2127
  ```
1864
2128
 
1865
2129
  Use Slipstream, SlipstreamV2 or SlipstreamV3 explicitly; omitting version is ambiguous. All three
@@ -1913,7 +2177,10 @@ snapshot.rewards; // Separate reward asset, unclaimed rewards and unpaid Steer/s
1913
2177
 
1914
2178
  const page = await steer.listVaults({
1915
2179
  chainId: ChainId.Base,
1916
- amm: { protocol: AmmProtocol.Aerodrome, version: AerodromeVersion.SlipstreamV2 },
2180
+ amm: {
2181
+ protocol: AmmProtocol.Aerodrome,
2182
+ version: AerodromeVersion.SlipstreamV2,
2183
+ },
1917
2184
  token: { symbol: "USDC" },
1918
2185
  });
1919
2186
  ```
@@ -1935,7 +2202,12 @@ const withdrawal = await vault.previewWithdraw({ account, shares, receiver });
1935
2202
  withdrawal.amounts; // Raw/formatted token0 and token1 proceeds paid to receiver.
1936
2203
  withdrawal.rewards; // Aerodrome: { token, recipient: account, amount: { raw, formatted } }.
1937
2204
 
1938
- 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
+ });
1939
2211
  // Submit plan.transactions from plan.account on plan.chainId using your wallet integration.
1940
2212
  ```
1941
2213
 
@@ -1949,10 +2221,17 @@ quote, preview and unsigned preparation API is supported:
1949
2221
 
1950
2222
  ```ts
1951
2223
  const quote = await vault.quoteDeposit({ asset: token0, amount });
1952
- 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
+ });
1953
2229
  deposit.rewards; // Aerodrome: existing receiver rewards, settled before new shares are minted.
1954
2230
  const depositPlan = await vault.prepareDeposit({
1955
- account, receiver, assets: quote.assets, slippageBps: 50,
2231
+ account,
2232
+ receiver,
2233
+ assets: quote.assets,
2234
+ slippageBps: 50,
1956
2235
  });
1957
2236
  ```
1958
2237
 
@@ -1967,7 +2246,6 @@ Aerodrome's qualified helper accepts one raw initial share. One-sided vaults quo
1967
2246
 
1968
2247
  See [vault qualification, accounting and budgets](src/vault/adapters/aerodrome/qualification.md).
1969
2248
 
1970
-
1971
2249
  ### Robinhood standalone Uniswap V4 snapshots
1972
2250
 
1973
2251
  `MultiPositionUniswapV4NoOracle` uses the same standalone V4 snapshot reader as the qualified
@@ -1983,7 +2261,6 @@ Live tests cover BSC Reserve and Robinhood NoOracle holders for both ERC20 and n
1983
2261
  including pagination and compatibility reads. Follow every `nextCursor`, even when `items`
1984
2262
  is empty. Incomplete indexed token metadata still limits token-filtered discovery.
1985
2263
 
1986
-
1987
2264
  ### V4 pool-ID lookup without a pool indexer
1988
2265
 
1989
2266
  `amm.getPool({ poolId })`, `amm.getPoolKey(poolId)` and client/bulk ID getters share the
@@ -1996,6 +2273,11 @@ is an error, not a fabricated key. Pool browsing remains separate and requires a
1996
2273
  Base retains its existing indexed adapter by default. BNB combines indexed browsing with RPC key
1997
2274
  resolution; Robinhood uses RPC key resolution without indexed pool browsing.
1998
2275
 
2276
+ ### BSC SushiSwap and Uniswap V3 snapshots
2277
+
2278
+ The exact qualified `MultiPositionSushi` and `MultiPositionUniswapV3` implementations now support vault resolution, single/bulk snapshots and direct pool reads. They reuse the fixed-fee V3 reader with 15% aggregate fee rounding. Normal bulk snapshot pages use two pinned RPC requests and preserve per-member failures with `allowFailure: true`.
2279
+
2280
+ AMM-filtered Steer vault listing works. Underlying AMM pool browsing remains unqualified: both inherited subgraphs returned pool rows without the expected factory identity and the SDK rejects those responses. Deposit/withdrawal flows were not newly qualified by this snapshot work. Base Sushi is separately qualified after the 30-vault migration, using its exact upgraded FeeManager/helper implementation; see [Base Sushi qualification](src/vault/adapters/uniswap-v3/base-sushi-qualification.md). See [source and live qualification](src/vault/adapters/uniswap-v3/bsc-qualification.md).
1999
2281
 
2000
2282
  ### BSC PancakeSwap V3 vault snapshots
2001
2283
 
@@ -2015,7 +2297,6 @@ See [Pancake vault qualification](src/vault/adapters/uniswap-v3/pancake-qualific
2015
2297
  identities, funded fixtures, retained-response regressions and live test coverage. Separate funded user-flow qualification now covers deposit quotes/preparation, partial/full
2016
2298
  withdrawals, user positions and indexed wallet balance scanning.
2017
2299
 
2018
-
2019
2300
  ### Camelot legacy vault snapshots on Arbitrum
2020
2301
 
2021
2302
  `MultiPositionCamelot` vaults on Arbitrum use the shared `AlgebraLegacyVault` implementation.
@@ -2034,7 +2315,6 @@ See [Camelot qualification](src/vault/adapters/algebra-legacy/camelot-qualificat
2034
2315
  Funded simulations now also verify deposit quotes/preparation, partial/full withdrawals, user
2035
2316
  positions and wallet discovery. Arbitrum deposit accounting uses the verified Multicall3 deployment.
2036
2317
 
2037
-
2038
2318
  ### Blackhole CL on Avalanche
2039
2319
 
2040
2320
  Select pools with `{ protocol: AmmProtocol.Blackhole, version: BlackholeVersion.CL }`.
@@ -2053,7 +2333,6 @@ compatibility calls, without a two-RPC guarantee.
2053
2333
  See [qualification](src/vault/adapters/blackhole/qualification.md) for exact identities and evidence.
2054
2334
  Run `npm run test:integration:blackhole` with `AVALANCHE_RPC_URL` for the paced historical live suite.
2055
2335
 
2056
-
2057
2336
  #### Blackhole AVAX deposit and exit
2058
2337
 
2059
2338
  Use the zero address as the native AVAX input when the vault holds WAVAX:
@@ -2081,7 +2360,6 @@ This prepares a separate WAVAX withdrawal transaction that pays AVAX to `recipie
2081
2360
  checking its balance. Use actual received amounts rather than the earlier withdrawal preview.
2082
2361
  Both stages are separate wallet operations; preparation sends nothing.
2083
2362
 
2084
-
2085
2363
  ### Pancake V3 and Camelot user-flow verification
2086
2364
 
2087
2365
  Run `npm run test:integration:vault-flows` with BSC and Arbitrum archival RPC URLs. This suite
@@ -2089,20 +2367,21 @@ repeats actual-contract deposits/withdrawals and balance-based discovery at reta
2089
2367
  It uses pinned public indexer responses and paces live RPC calls by 1.5 seconds. No Graph API
2090
2368
  credentials or Base RPC are needed for this isolated runner. Full historical state and temporary
2091
2369
  EOA code overrides must be supported. Native BNB/ETH wrapping helpers remain separate work.
2370
+
2092
2371
  ### Bulk vault execution coverage
2093
2372
 
2094
2373
  Accepting an array does not imply optimized execution. In `4.0.0-next.8`, BSC and Arbitrum
2095
2374
  accepted both bulk methods but used individual-vault reads with concurrency four. This change
2096
2375
  adds native bulk readers for their exact qualified deployments.
2097
2376
 
2098
- | Chain / qualified vaults | Bulk API | Efficient execution in `4.0.0-next.8` | Efficient execution after this change |
2099
- | --- | --- | --- | --- |
2100
- | Base: V3, Integral, Aerodrome | Yes | Yes | Yes |
2101
- | Polygon: registered V3, Legacy, Integral | Yes | Yes | Yes |
2102
- | BSC: Pancake V3 and Reserve V4 | Yes | No | Yes |
2103
- | Arbitrum: Camelot legacy | Yes | No | Yes |
2104
- | Robinhood: NoOracle V4 | Yes | No | Bounded individual reads |
2105
- | Avalanche: Blackhole | Yes | No | Bounded individual reads |
2377
+ | Chain / qualified vaults | Bulk API | Efficient execution in `4.0.0-next.8` | Efficient execution after this change |
2378
+ | ---------------------------------------- | -------- | ------------------------------------- | ------------------------------------- |
2379
+ | Base: V3, Integral, Aerodrome | Yes | Yes | Yes |
2380
+ | Polygon: registered V3, Legacy, Integral | Yes | Yes | Yes |
2381
+ | BSC: Pancake V3 and Reserve V4 | Yes | No | Yes |
2382
+ | Arbitrum: Camelot legacy | Yes | No | Yes |
2383
+ | Robinhood: NoOracle V4 | Yes | No | Bounded individual reads |
2384
+ | Avalanche: Blackhole | Yes | No | Bounded individual reads |
2106
2385
 
2107
2386
  On providers supporting code-only overrides, optimized resolution uses two pinned RPC calls per
2108
2387
  64-vault page. Snapshots use one original-code/position-count preflight per 16-vault page and one
@@ -2130,7 +2409,10 @@ for (const result of vaults) {
2130
2409
  else console.log(result.error);
2131
2410
  }
2132
2411
 
2133
- const pools = await amm.getPools(references, { blockNumber, allowFailure: true });
2412
+ const pools = await amm.getPools(references, {
2413
+ blockNumber,
2414
+ allowFailure: true,
2415
+ });
2134
2416
  for (const result of pools) {
2135
2417
  if (result.status === "success") console.log(result.pool);
2136
2418
  else console.log(result.error);
@@ -2145,7 +2427,6 @@ malformed shared RPC responses, and shared vault metadata request failures still
2145
2427
  `SteerClient.getPools(request)` discovery remains strict; partial pool reads use the selected
2146
2428
  AMM's `getPools(references, options)`.
2147
2429
 
2148
-
2149
2430
  V4 partial ID reads isolate `V4PoolKeyNotFoundError` for a verified absent indexed key or
2150
2431
  uninitialized onchain pool. Shared indexer, JSON/schema, RPC, and unexpected key-adapter errors
2151
2432
  reject the request, including through registered adapter overrides. Custom V4 data adapters