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

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
@@ -50,6 +50,76 @@ Base V3/Integral and Polygon V3/Legacy/Integral vault reads are implemented belo
50
50
  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
51
  SDK integration code owns Steer's per-network contracts and data adapters.
52
52
 
53
+ ## App and data-connector loader dependencies
54
+
55
+ 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`;
57
+ they are not re-exported from the SDK root.
58
+
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.
65
+
66
+ ## Vault execution context
67
+
68
+ Liquidity vaults expose strategy inputs through the existing vault object:
69
+
70
+ ```ts
71
+ const blockNumber = 12345678n; // Caller-selected operation block.
72
+ const vault = await steer.getLiquidityVault(chainId, vaultAddress, { blockNumber });
73
+ const context = await vault.getExecutionContext({
74
+ blockNumber,
75
+ epochTimestamp: 1793577600n, // Explicit Unix seconds, independent of the wall clock.
76
+ });
77
+ ```
78
+
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.
84
+
85
+ Each call revalidates the native vault binding and dispatches registered vault/AMM overrides.
86
+ All onchain reads use the explicit block. `totalAmount0`/`totalAmount1` come from the original
87
+ vault's `getTotalAmounts()` without poke, matching the prior execution-context semantics;
88
+ they are not aliases for the fee-adjusted `getSnapshot().total` accounting fields.
89
+
90
+ The Steer data adapter queries the latest indexed `vaultSnapshot` at that block, bounded by
91
+ 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.
95
+ Block numbers must fit the indexer's nonnegative GraphQL Int range. The optional second
96
+ argument accepts the existing per-request HTTP overrides for the history query.
97
+
98
+ Custom Steer data adapters can implement `getLastVaultExecution(request, options)` to supply
99
+ history without a subgraph. The request contains `vaultAddress`, `blockNumber` and the bounded
100
+ `timestamp`; return a nonnegative bigint no later than that timestamp, or `null`.
101
+
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.
107
+
108
+ Read-only live verification on November 2, 2026 passed 13 fixtures across six chains:
109
+ Base Uniswap V3, Base QuickSwap Integral, all three Base Aerodrome generations, Polygon
110
+ 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
112
+ 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.
116
+
117
+ Reproduce with the explicit RPC/indexer settings in `.env.integration`:
118
+
119
+ ```sh
120
+ npm run test:integration -- tests/integration/vault/execution-context.multichain.test.ts --reporter=verbose
121
+ ```
122
+
53
123
  ## Vault discovery
54
124
 
55
125
  List indexed vault metadata, then load a result through the identity-verified resolver:
package/dist/index.cjs CHANGED
@@ -2303,6 +2303,10 @@ var DataAdapter = class {
2303
2303
  var PoolDataAdapter = class extends DataAdapter {};
2304
2304
  var LendingDataAdapter = class extends DataAdapter {};
2305
2305
  var SteerDataAdapter = class extends DataAdapter {
2306
+ /** Latest indexed execution through the requested block and timestamp, or null if absent. */
2307
+ async getLastVaultExecution(_request, _options) {
2308
+ throw new Error("Vault execution history is not configured for this data adapter");
2309
+ }
2306
2310
  async listUserVaults(_request, _options) {
2307
2311
  throw new Error("User vault discovery is not configured for this data adapter");
2308
2312
  }
@@ -12778,6 +12782,29 @@ var CatalogSteerDataAdapter = class extends SteerDataAdapter {
12778
12782
  #factories = /* @__PURE__ */ new Map();
12779
12783
  #identity;
12780
12784
  #context;
12785
+ async getLastVaultExecution(request, options) {
12786
+ const vaultAddress = indexedAddress(request.vaultAddress);
12787
+ const { blockNumber, timestamp } = request;
12788
+ if (typeof blockNumber !== "bigint" || blockNumber < 0n || blockNumber > 2147483647n) throw new Error("Execution history block must fit a nonnegative GraphQL Int");
12789
+ if (typeof timestamp !== "bigint" || timestamp < 0n) throw new Error("Execution history timestamp must be nonnegative");
12790
+ const data = await this.#query(`query VaultExecutionHistory($vault: String!, $timestamp: BigInt!, $block: Int!) {
12791
+ vaultSnapshots(first: 1, orderBy: timestamp, orderDirection: desc,
12792
+ block: {number: $block}, where: {vaultAddress: $vault, timestamp_lte: $timestamp}) {
12793
+ timestamp
12794
+ }
12795
+ _meta(block: {number: $block}) { ${metaFields} }
12796
+ }`, {
12797
+ vault: vaultAddress,
12798
+ timestamp: timestamp.toString(),
12799
+ block: Number(blockNumber)
12800
+ }, options);
12801
+ if (BigInt(snapshot$1(data).block) !== blockNumber) throw new Error("Execution history returned another block");
12802
+ if (!Array.isArray(data.vaultSnapshots) || data.vaultSnapshots.length > 1) throw new Error("Malformed vault execution history");
12803
+ if (data.vaultSnapshots.length === 0) return null;
12804
+ const value = record(data.vaultSnapshots[0]).timestamp;
12805
+ if (typeof value !== "string" || !/^\d+$/.test(value) || BigInt(value) > timestamp) throw new Error("Invalid vault execution timestamp");
12806
+ return BigInt(value);
12807
+ }
12781
12808
  constructor(definition, context, mappings) {
12782
12809
  super(definition.dataAdapter);
12783
12810
  if (definition.dataAdapter.kind !== "subgraph") throw new Error("Steer vault listing requires a subgraph endpoint");
@@ -13428,7 +13455,7 @@ function captureAsset$1({ asset, amount }, allowNative = false) {
13428
13455
  amount
13429
13456
  });
13430
13457
  }
13431
- function captureRequest(input, allowNative) {
13458
+ function captureRequest$1(input, allowNative) {
13432
13459
  const { account, receiver } = input;
13433
13460
  requireAddress(account);
13434
13461
  requireAddress(receiver);
@@ -13486,7 +13513,7 @@ function buildPreview(snapshot, state, desired0, desired1, minimumInitialShares,
13486
13513
  });
13487
13514
  }
13488
13515
  async function previewLiquidityDeposit(vault, input, minimumInitialShares, options, accounting = defaultAccounting) {
13489
- const request = captureRequest(input, accounting.allowNative);
13516
+ const request = captureRequest$1(input, accounting.allowNative);
13490
13517
  const { snapshot, state, desired0, desired1 } = await readDepositInput(vault, request.assets, options, void 0, accounting, request.receiver);
13491
13518
  return buildPreview(snapshot, state, desired0, desired1, minimumInitialShares, accounting.singleSidedRounding);
13492
13519
  }
@@ -13508,7 +13535,7 @@ async function quoteLiquidityDeposit(vault, input, options, accounting = default
13508
13535
  });
13509
13536
  }
13510
13537
  async function prepareLiquidityDeposit(vault, input, minimumInitialShares, options, accounting = defaultAccounting) {
13511
- const request = captureRequest(input, accounting.allowNative);
13538
+ const request = captureRequest$1(input, accounting.allowNative);
13512
13539
  const slippageBps = input.slippageBps;
13513
13540
  if (!Number.isInteger(slippageBps) || slippageBps < 0 || slippageBps > 1e4) throw new Error("Deposit slippageBps must be an integer from 0 through 10000");
13514
13541
  const { snapshot, state, desired0, desired1 } = await readDepositInput(vault, request.assets, options, request.account, accounting, request.receiver);
@@ -14103,6 +14130,10 @@ var SteerVault = class {
14103
14130
  };
14104
14131
  var LiquidityVault = class extends SteerVault {
14105
14132
  kind = "liquidity";
14133
+ /** Read strategy inputs at an explicit block, with timestamp-bounded indexed execution history. */
14134
+ getExecutionContext(request, options) {
14135
+ return this.steer.readVaultExecutionContext(this, request, options);
14136
+ }
14106
14137
  /** Simulates the original withdrawal as account at one verified block. Never submits. */
14107
14138
  previewWithdraw(request, options) {
14108
14139
  return previewLiquidityWithdraw(this, request, options);
@@ -25385,6 +25416,133 @@ function createIndexerRequest(config, defaults) {
25385
25416
  });
25386
25417
  }
25387
25418
  //#endregion
25419
+ //#region src/vault/adapters/blackhole/execution.ts
25420
+ /** Custom-pool routing comes from the qualified vault layout, never an address-only guess. */
25421
+ async function readBlackholeExecutionPool(vault, blockNumber) {
25422
+ const { target } = await readBlackholePool({
25423
+ steer: vault.steer,
25424
+ address: vault.address,
25425
+ blockNumber
25426
+ });
25427
+ if (!sameAddress(target.address, vault.pool)) throw new Error("Blackhole vault pool changed");
25428
+ const pool = await vault.amm.getPool(Object.freeze({ ...target }), Object.freeze({ blockNumber }));
25429
+ if (!sameAddress(pool.deployer, target.deployer)) throw new Error("Blackhole execution pool deployer does not match the vault");
25430
+ return pool;
25431
+ }
25432
+ //#endregion
25433
+ //#region src/vault/execution-pool.ts
25434
+ function validatePoolState(block, tick, spacing, expected) {
25435
+ if (block !== expected || !Number.isInteger(tick) || tick < -887272 || tick > 887272 || !Number.isInteger(spacing) || spacing <= 0 || spacing > 8388607) throw new Error("Invalid execution pool state or block");
25436
+ }
25437
+ /** Dispatch actual registered adapters, then check the result against the verified vault pool. */
25438
+ async function readExecutionPool(vault, snapshot) {
25439
+ const amm = vault.amm;
25440
+ const options = Object.freeze({ blockNumber: snapshot.blockNumber });
25441
+ if (amm.context.chainId !== vault.chain.chainId || amm.context.transport !== vault.chain.transport) throw new Error("Execution AMM belongs to another chain or transport");
25442
+ if (typeof snapshot.pool !== "string") {
25443
+ if (!isUniswapV4Adapter(amm)) throw new Error("Execution pool requires a V4 adapter");
25444
+ const pool = await amm.getPool(snapshot.pool.key, options);
25445
+ validatePoolState(pool.blockNumber, pool.tick, pool.key.tickSpacing, snapshot.blockNumber);
25446
+ if (!sameLiquidityPool(snapshot.pool, {
25447
+ poolId: pool.poolId,
25448
+ poolManager: pool.poolManager,
25449
+ key: pool.key
25450
+ }) || !sameAddress(pool.poolManager, amm.contracts.poolManager.address) || !sameAddress(pool.key.currency0, snapshot.token0) || !sameAddress(pool.key.currency1, snapshot.token1)) throw new Error("Execution pool identity does not match the vault");
25451
+ return Object.freeze({
25452
+ poolFamily: "uniswap-v4",
25453
+ poolAddress: null,
25454
+ factoryAddress: null,
25455
+ poolId: pool.poolId,
25456
+ poolManagerAddress: pool.poolManager,
25457
+ poolKey: Object.freeze({ ...pool.key }),
25458
+ currentTick: pool.tick,
25459
+ tickSpacing: pool.key.tickSpacing
25460
+ });
25461
+ }
25462
+ if (!isUniswapV3Adapter(amm) && !isAlgebraLegacyAdapter(amm) && !isAlgebraIntegralAdapter(amm) && !(amm instanceof AerodromeSlipstreamAdapter)) throw new Error("Unsupported execution pool family");
25463
+ const factory = amm.contracts.factory.address;
25464
+ const pool = vault instanceof BlackholeVault ? await readBlackholeExecutionPool(vault, snapshot.blockNumber) : await amm.getPool(snapshot.pool, options);
25465
+ validatePoolState(pool.blockNumber, pool.tick, pool.tickSpacing, snapshot.blockNumber);
25466
+ if (!sameAddress(pool.address, snapshot.pool) || !sameAddress(pool.factory, factory) || !sameAddress(pool.token0, snapshot.token0) || !sameAddress(pool.token1, snapshot.token1)) throw new Error("Execution pool identity does not match the vault");
25467
+ const poolFamily = isUniswapV3Adapter(amm) ? "uniswap-v3-compatible" : isAlgebraLegacyAdapter(amm) ? amm instanceof AlgebraDirectionalAdapter ? "algebra-directional" : "algebra" : isAlgebraIntegralAdapter(amm) ? "algebra-integral" : "aerodrome-cl";
25468
+ return Object.freeze({
25469
+ poolFamily,
25470
+ poolAddress: pool.address,
25471
+ factoryAddress: pool.factory,
25472
+ poolId: null,
25473
+ poolManagerAddress: null,
25474
+ poolKey: null,
25475
+ currentTick: pool.tick,
25476
+ tickSpacing: pool.tickSpacing
25477
+ });
25478
+ }
25479
+ //#endregion
25480
+ //#region src/vault/execution-context.ts
25481
+ function captureRequest(request) {
25482
+ const { blockNumber, epochTimestamp } = request;
25483
+ if (typeof blockNumber !== "bigint" || blockNumber < 0n || blockNumber > 2147483647n) throw new Error("Execution block must fit a nonnegative GraphQL Int");
25484
+ if (typeof epochTimestamp !== "bigint" || epochTimestamp < 0n) throw new Error("Execution timestamp must be nonnegative Unix seconds");
25485
+ return Object.freeze({
25486
+ blockNumber,
25487
+ epochTimestamp
25488
+ });
25489
+ }
25490
+ function executionPositions(snapshot) {
25491
+ const seen = /* @__PURE__ */ new Set();
25492
+ return Object.freeze((snapshot.configuredPositions ?? snapshot.positions).map(({ lowerTick, upperTick, relativeWeight }) => {
25493
+ const key = `${lowerTick}:${upperTick}`;
25494
+ if (!Number.isInteger(lowerTick) || !Number.isInteger(upperTick) || lowerTick < -887272 || upperTick > 887272 || lowerTick >= upperTick || !Number.isInteger(relativeWeight) || relativeWeight < 0 || relativeWeight > 65535 || seen.has(key)) throw new Error("Invalid configured execution ranges");
25495
+ seen.add(key);
25496
+ return Object.freeze({
25497
+ lowerTick,
25498
+ upperTick,
25499
+ relativeWeight
25500
+ });
25501
+ }));
25502
+ }
25503
+ /** Keep original contract totals separate from the SDK's net-fee accounting snapshot. */
25504
+ async function readLiquidityExecutionContext(vault, beaconAddress, input, options) {
25505
+ const { blockNumber, epochTimestamp } = captureRequest(input);
25506
+ const { address: vaultAddress, chain, steer, metadata, amm } = vault;
25507
+ const beaconName = metadata.beaconName;
25508
+ const protocol = amm.protocol;
25509
+ const rpc = await vaultReadContext(chain, { blockNumber });
25510
+ const client = rpcClient(rpc.transport);
25511
+ const snapshot = await steer.readVaultSnapshot(vault, { blockNumber });
25512
+ const positions = executionPositions(snapshot);
25513
+ const pool = await readExecutionPool(vault, snapshot);
25514
+ const totals = await client.readContract({
25515
+ address: vaultAddress,
25516
+ abi: depositAbi,
25517
+ functionName: "getTotalAmounts",
25518
+ blockNumber
25519
+ });
25520
+ const block = await client.getBlock({ blockNumber });
25521
+ if (block.number !== blockNumber || typeof block.timestamp !== "bigint" || block.timestamp < 0n) throw new Error("Execution RPC returned an invalid block");
25522
+ const timestamp = block.timestamp < epochTimestamp ? block.timestamp : epochTimestamp;
25523
+ const lastExecution = await steer.dataAdapter.getLastVaultExecution({
25524
+ vaultAddress,
25525
+ blockNumber,
25526
+ timestamp
25527
+ }, options);
25528
+ if (lastExecution !== null && (typeof lastExecution !== "bigint" || lastExecution < 0n || lastExecution > timestamp)) throw new Error("Invalid vault execution timestamp");
25529
+ return Object.freeze({
25530
+ chainId: rpc.chainId,
25531
+ blockNumber: blockNumber.toString(),
25532
+ epochTimestamp: epochTimestamp.toString(),
25533
+ vaultAddress,
25534
+ protocol,
25535
+ beaconName,
25536
+ beaconAddress,
25537
+ ...pool,
25538
+ positions,
25539
+ totalAmount0: totals[0].toString(),
25540
+ totalAmount1: totals[1].toString(),
25541
+ lastExecutionTimestamp: lastExecution?.toString() ?? null,
25542
+ lastExecutionTimeSeconds: lastExecution === null ? null : (epochTimestamp - lastExecution).toString()
25543
+ });
25544
+ }
25545
+ //#endregion
25388
25546
  //#region src/vault/resolver.ts
25389
25547
  function addressKey(address) {
25390
25548
  if (!/^0x[0-9a-fA-F]{40}$/.test(address) || /^0x0{40}$/i.test(address)) throw new VaultResolutionError("identity-mismatch", "Invalid identity address");
@@ -25484,6 +25642,12 @@ var VaultResolver = class {
25484
25642
  if (snapshot.blockNumber !== blockNumber || !vault.underlying.some((position) => position.kind === "liquidity" && sameLiquidityPool(position.pool, snapshot.pool))) throw new Error("Vault returned another accounting snapshot");
25485
25643
  return readValidatedVaultSnapshot(vault, snapshot, vault.steer.context);
25486
25644
  }
25645
+ /** @internal The native snapshot reader revalidates this binding at the requested block. */
25646
+ executionContext(vault, request, options) {
25647
+ const binding = this.#snapshotBindings.get(vault);
25648
+ if (!binding?.readSnapshot) throw new Error("No verified execution reader configured for vault");
25649
+ return readLiquidityExecutionContext(vault, binding.beaconAddress, request, options);
25650
+ }
25487
25651
  #snapshotImplementation(snapshot, binding, beaconName) {
25488
25652
  const { implementation: _implementation, ...accounting } = snapshot;
25489
25653
  return Object.freeze({
@@ -25761,6 +25925,11 @@ var SteerNetwork = class {
25761
25925
  if (vault.steer !== this) throw new VaultResolutionError("identity-mismatch", "Vault belongs to another Steer deployment");
25762
25926
  return this.#vaultResolver().snapshot(vault, options);
25763
25927
  }
25928
+ /** @internal Retain the resolved vault's exact binding and registered overrides. */
25929
+ readVaultExecutionContext(vault, request, options) {
25930
+ if (vault.steer !== this) throw new Error("Vault belongs to another Steer deployment");
25931
+ return this.#vaultResolver().executionContext(vault, request, options);
25932
+ }
25764
25933
  #vaultResolver() {
25765
25934
  if (!this.#resolver) throw new VaultResolutionError("not-configured", "No vault resolver configured for this Steer deployment");
25766
25935
  return this.#resolver;