@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 +484 -139
- package/dist/index.cjs +4214 -2747
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1650 -1481
- package/dist/index.d.mts +1650 -1481
- package/dist/index.mjs +4194 -2751
- package/dist/index.mjs.map +1 -1
- package/package.json +16 -11
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({
|
|
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(
|
|
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(
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
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")
|
|
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], {
|
|
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 {
|
|
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({
|
|
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 {
|
|
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;
|
|
430
|
-
pool.balances.token0.raw;
|
|
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({
|
|
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, {
|
|
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
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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
|
|
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: {
|
|
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({
|
|
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(
|
|
539
|
-
|
|
540
|
-
|
|
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
|
|
591
|
-
|
|
|
592
|
-
| `amm.getPool()`
|
|
593
|
-
| `steer.getPool()` with discovery |
|
|
594
|
-
| `amm.quote()`
|
|
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(
|
|
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
|
|
770
|
-
|
|
|
771
|
-
| `MultiPositionUniswapV3`
|
|
772
|
-
| `MultiPositionSushi`
|
|
773
|
-
| `MultiPositionRetro`
|
|
774
|
-
| `MultiPositionQuickSwap`
|
|
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;
|
|
826
|
-
snapshot.balances.token0.raw;
|
|
827
|
-
snapshot.reservedFees.token0.raw;
|
|
828
|
-
snapshot.principal.token0.formatted;
|
|
829
|
-
snapshot.fees.gross.token0.formatted;
|
|
830
|
-
snapshot.fees.net.token0.formatted;
|
|
831
|
-
snapshot.fees.recorded.token0.raw;
|
|
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;
|
|
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;
|
|
1130
|
+
position.positionId; // keccak256(vault address, lowerTick, upperTick).
|
|
838
1131
|
position.lowerTick;
|
|
839
1132
|
position.upperTick;
|
|
840
|
-
position.relativeWeight;
|
|
841
|
-
position.weightPercent;
|
|
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;
|
|
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;
|
|
1139
|
+
position.fees.recorded.token0.raw; // Pool checkpoint tokensOwed, before new fee growth.
|
|
847
1140
|
}
|
|
848
1141
|
|
|
849
|
-
const positions = await vault.getPositions({
|
|
850
|
-
|
|
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;
|
|
920
|
-
position.totalSupply;
|
|
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;
|
|
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(
|
|
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;
|
|
1332
|
+
preview.shares; // Expected raw share units
|
|
1031
1333
|
preview.token0;
|
|
1032
1334
|
preview.token1;
|
|
1033
|
-
preview.amounts.token0;
|
|
1335
|
+
preview.amounts.token0; // Accepted input: { raw: bigint, formatted: string }
|
|
1034
1336
|
preview.amounts.token1;
|
|
1035
|
-
preview.unusedAmounts;
|
|
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;
|
|
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;
|
|
1060
|
-
quote.shares;
|
|
1061
|
-
quote.amounts;
|
|
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;
|
|
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;
|
|
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;
|
|
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;
|
|
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
|
|
1230
|
-
|
|
|
1231
|
-
| `UniswapV3Adapter`
|
|
1232
|
-
| `UniswapV4Adapter`
|
|
1233
|
-
| `AaveV3Adapter`
|
|
1234
|
-
| `PoolDataAdapter`, `LendingDataAdapter` | Indexed protocol discovery
|
|
1235
|
-
| `SteerClient`
|
|
1236
|
-
| `SteerNetwork`, `SteerDataAdapter`
|
|
1237
|
-
| `VaultResolver`, `VaultIdentityReader`
|
|
1238
|
-
| `SteerVault`
|
|
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,
|
|
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);
|
|
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 });
|
|
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(
|
|
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,
|
|
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(
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
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,
|
|
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,
|
|
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
|
|
1638
|
-
|
|
|
1639
|
-
| Uniswap V3
|
|
1640
|
-
| Uniswap V4
|
|
1641
|
-
| Algebra Legacy
|
|
1642
|
-
| Algebra Integral | `fees.lastFeePips`, `fees.lastFeePercent`
|
|
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,
|
|
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,
|
|
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({
|
|
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, {
|
|
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
|
|
1696
|
-
|
|
|
1697
|
-
| Snapshot
|
|
1698
|
-
| Specific ticks, one page
|
|
1699
|
-
| Nonempty initialized range, one page | 2 / 3 / 9
|
|
1700
|
-
| Exact-input quote
|
|
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,
|
|
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, {
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
}
|
|
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: {
|
|
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({
|
|
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({
|
|
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,
|
|
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
|
|
2029
|
-
|
|
|
2030
|
-
| Base: V3, Integral, Aerodrome
|
|
2031
|
-
| Polygon: registered V3, Legacy, Integral | Yes
|
|
2032
|
-
| BSC: Pancake V3 and Reserve V4
|
|
2033
|
-
| Arbitrum: Camelot legacy
|
|
2034
|
-
| Robinhood: NoOracle V4
|
|
2035
|
-
| Avalanche: Blackhole
|
|
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, {
|
|
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
|