@steerprotocol/sdk 4.0.0-next.2 → 4.0.0-next.4

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
@@ -52,7 +52,7 @@ SDK integration code owns Steer's per-network contracts and data adapters.
52
52
 
53
53
  ## Vault discovery
54
54
 
55
- List indexed vault addresses, then load a result through the normal identity-verified resolver:
55
+ List indexed vault metadata, then load a result through the identity-verified resolver:
56
56
 
57
57
  ```ts
58
58
  const page = await steer.listVaults({
@@ -62,12 +62,13 @@ const page = await steer.listVaults({
62
62
  limit: 20,
63
63
  });
64
64
 
65
- for (const address of page.items) {
66
- const vault = await steer.getVault(ChainId.Base, address);
65
+ for (const metadata of page.items) {
66
+ const vault = await steer.getVault(ChainId.Base, metadata.id);
67
+ vault.metadata.name; // Complete indexed metadata, separate from verified accounting.
67
68
  }
68
69
 
69
70
  // Load a page or any explicit address list through the same resolver.
70
- const vaults = await steer.getVaults(ChainId.Base, page.items);
71
+ const vaults = await steer.getVaults(ChainId.Base, page.items.map((metadata) => metadata.id));
71
72
 
72
73
  if (page.nextCursor) {
73
74
  const next = await steer.listVaults({
@@ -78,6 +79,41 @@ if (page.nextCursor) {
78
79
  }
79
80
  ```
80
81
 
82
+ `listVaults()` returns `Page<VaultMetadata>` with immutable indexed records:
83
+
84
+ ```ts
85
+ interface VaultMetadata {
86
+ readonly id: Address;
87
+ readonly name: string | null;
88
+ readonly symbol: string | null;
89
+ readonly decimals: string | null;
90
+ readonly beaconName: string;
91
+ readonly createdAt: string;
92
+ readonly state: string | null;
93
+ readonly pool: Address | null;
94
+ readonly poolKey?: string | null;
95
+ readonly token0: Address | null;
96
+ readonly token1: Address | null;
97
+ readonly payloadIpfs: string | null;
98
+ readonly strategyToken: { readonly id: string } | null;
99
+ }
100
+ ```
101
+
102
+ This replaces address-only pages. Use `item.id` when passing a listed result to a vault getter.
103
+ `decimals` and `createdAt` retain their indexed string representation. Null fields remain null;
104
+ `poolKey` is omitted when the subgraph schema does not expose it. Only an explicit rejection of
105
+ that optional field permits querying without it; other GraphQL and transport errors propagate.
106
+
107
+ Every loaded `SteerVault` exposes the same model as `vault.metadata`. Vault construction loads
108
+ indexed metadata once per resolution/snapshot batch, preserving input order and duplicates.
109
+ Loading a vault, including internal construction during bulk snapshots, now requires indexed
110
+ metadata in addition to RPC identity verification. Missing records and indexer failures propagate.
111
+ Existing resolved vault operations retain that metadata snapshot. It reflects the indexer's head
112
+ at construction, independently of an explicitly selected RPC block; state, pool and token hints
113
+ never replace the verified underlying references or accounting used by vault methods.
114
+ Custom Steer data adapters implement `getVaultMetadata(addresses, options)` as well as listing;
115
+ custom vault constructors pass `context.metadata` to the base constructor.
116
+
81
117
  `amm` and `token` are optional. Token filters accept `address`, `symbol`, and `name`;
82
118
  symbol and token name use case-insensitive substring matches. Combined criteria must match
83
119
  the same token on either side. `name` refers to the underlying token, not the vault's name.
@@ -86,9 +122,10 @@ AMM selection uses the same typed protocol/version/deployment selectors and ambi
86
122
  on Base, plus Polygon Uniswap V3, SushiSwap V3, Retro V3, QuickSwap V3 and QuickSwap V4.
87
123
  Other AMM filters fail explicitly until their exact indexed beacon mappings are qualified.
88
124
 
89
- Listing costs zero RPC requests: two HTTP requests for the first page (snapshot and vaults),
90
- then one per continuation. Cursors retain filters and pin every page to the indexed block and
91
- subgraph deployment. If supplied again, filters must agree with the cursor. Results are indexed
125
+ Unfiltered listing costs zero RPC requests: two HTTP requests for the first page (snapshot and vaults),
126
+ then one per continuation. An older schema without poolKey adds one HTTP request for field negotiation.
127
+ AMM filters with factory qualification also perform RPC checks at the indexed block. Cursors retain filters and pin every page to the indexed block and
128
+ subgraph deployment. If supplied again, filters must agree with the cursor. Results contain indexed metadata for
92
129
  candidates, which may include implementations unsupported by `getVault()`; listing does not
93
130
  establish current onchain identity or add resolver support. Indexing, HTTP, and GraphQL errors propagate.
94
131
 
@@ -97,6 +134,23 @@ entry points honor client `indexers`/`http` configuration and accept per-request
97
134
  timeout, and cancellation overrides as the second argument. The existing `getVault()` API and
98
135
  its onchain identity checks remain the loading path.
99
136
 
137
+ When catalog records are already available, supply them through the optional `VaultLoadOptions.metadata` field:
138
+
139
+ ```ts
140
+ const vaults = await steer.getVaults(ChainId.Base, records.map((record) => record.id), {
141
+ blockNumber: 100000n,
142
+ metadata: records,
143
+ });
144
+ ```
145
+
146
+ This skips metadata subgraph requests while retaining all onchain registration, implementation,
147
+ protocol and pool checks. Records are matched by address case-insensitively and copied before IO.
148
+ Supply one complete record per unique requested address. Missing, duplicate, unrequested or malformed
149
+ records fail without a metadata fetch. Results preserve requested order and duplicate addresses.
150
+ Omit `metadata` to retain the existing subgraph lookup. The same options work on `getVault`, typed
151
+ vault getters and the connected-network loading methods. Metadata remains descriptive and is never
152
+ forwarded to viem/RPC or used to establish verified identity. Snapshot/accounting options are unchanged.
153
+
100
154
  `getVaults(chainId, addresses, options)` returns the same resolved vault objects as `getVault()`
101
155
  in input order, including duplicate inputs. Base V3/Integral resolution uses a bulk Solidity reader:
102
156
  **two RPC requests per page of at most 64 vaults** at a pinned block. Latest reads add one shared
@@ -500,6 +554,9 @@ shared internally; no snapshot is preloaded or passed through discovery. Each op
500
554
  the RPC chain, with no cross-operation identity cache or additional consumer options.
501
555
 
502
556
  Quotes read pool identity, the token pair and fee, verify factory registration, and call QuoterV2.
557
+ V3 and Aerodrome Slipstream quotes reject results at either default price boundary because
558
+ QuoterV2 does not establish the consumed input there. Requests that cannot establish full input
559
+ consumption fail explicitly.
503
560
  They don't load token decimals, balances, ticks, or liquidity. Discovering a pool and then quoting
504
561
  at its block uses 9 RPC calls in total. Counts exclude retries and provider-specific transport
505
562
  behavior.
package/dist/index.cjs CHANGED
@@ -2063,6 +2063,7 @@ var AerodromeSlipstreamAdapter = class extends AmmAdapter {
2063
2063
  blockNumber: context.blockNumber,
2064
2064
  ...captured.account ? { account: captured.account } : {}
2065
2065
  });
2066
+ if (result[1] <= sqrtRatioAtTick(-887272) + 1n || result[1] >= sqrtRatioAtTick(887272) - 1n) throw new Error("Slipstream quote reached the price limit; full input consumption is not established");
2066
2067
  return Object.freeze({
2067
2068
  amountIn: input.amountIn,
2068
2069
  amountOut: result[0]
@@ -2103,6 +2104,9 @@ var SteerDataAdapter = class extends DataAdapter {
2103
2104
  async listUserVaults(_request, _options) {
2104
2105
  throw new Error("User vault discovery is not configured for this data adapter");
2105
2106
  }
2107
+ async getVaultMetadata(_addresses, _options) {
2108
+ throw new Error("Vault metadata is not configured for this data adapter");
2109
+ }
2106
2110
  };
2107
2111
  //#endregion
2108
2112
  //#region src/data/subgraph.ts
@@ -7743,6 +7747,7 @@ var RpcUniswapV3Adapter = class RpcUniswapV3Adapter extends UniswapV3Adapter {
7743
7747
  fee: pool.fee,
7744
7748
  sqrtPriceLimitX96: 0n
7745
7749
  }]);
7750
+ if (result[1] <= sqrtRatioAtTick(-887272) + 1n || result[1] >= sqrtRatioAtTick(887272) - 1n) throw new Error("V3 quote reached the price limit; full input consumption is not established");
7746
7751
  return {
7747
7752
  amountIn: request.amountIn,
7748
7753
  amountOut: result[0]
@@ -11399,6 +11404,50 @@ var DeploymentRegistry = class {
11399
11404
  }
11400
11405
  };
11401
11406
  //#endregion
11407
+ //#region src/vault/metadata.ts
11408
+ function nullableString(value) {
11409
+ if (value === null || typeof value === "string") return value;
11410
+ throw new Error("Malformed vault metadata string");
11411
+ }
11412
+ /** Validate and detach indexed data, preserving nulls and optional pool keys. */
11413
+ function vaultMetadata(value) {
11414
+ const row = record(value);
11415
+ if (typeof row.beaconName !== "string" || !row.beaconName || typeof row.createdAt !== "string" || !/^\d+$/.test(row.createdAt)) throw new Error("Malformed vault metadata identity");
11416
+ const decimals = nullableString(row.decimals);
11417
+ if (decimals !== null && !/^\d+$/.test(decimals)) throw new Error("Malformed vault metadata decimals");
11418
+ const strategy = row.strategyToken === null ? null : record(row.strategyToken);
11419
+ if (strategy !== null && (typeof strategy.id !== "string" || !strategy.id)) throw new Error("Malformed vault strategy token");
11420
+ return Object.freeze({
11421
+ id: indexedAddress(row.id),
11422
+ name: nullableString(row.name),
11423
+ symbol: nullableString(row.symbol),
11424
+ decimals,
11425
+ beaconName: row.beaconName,
11426
+ createdAt: row.createdAt,
11427
+ state: nullableString(row.state),
11428
+ pool: row.pool === null ? null : indexedAddress(row.pool, true),
11429
+ ...row.poolKey === void 0 ? {} : { poolKey: nullableString(row.poolKey) },
11430
+ token0: row.token0 === null ? null : indexedAddress(row.token0, true),
11431
+ token1: row.token1 === null ? null : indexedAddress(row.token1, true),
11432
+ payloadIpfs: nullableString(row.payloadIpfs),
11433
+ strategyToken: strategy === null ? null : Object.freeze({ id: String(strategy.id) })
11434
+ });
11435
+ }
11436
+ /** Validate and detach caller-owned metadata before any asynchronous resolution. */
11437
+ function captureVaultMetadata(addresses, values) {
11438
+ if (!Array.isArray(values)) throw new Error("Supplied vault metadata must be an array");
11439
+ const requested = new Set(addresses.map((address) => address.toLowerCase()));
11440
+ const records = /* @__PURE__ */ new Map();
11441
+ for (const value of values) {
11442
+ const metadata = vaultMetadata(value);
11443
+ if (!requested.has(metadata.id)) throw new Error(`Supplied vault metadata contains an unrequested address: ${metadata.id}`);
11444
+ if (records.has(metadata.id)) throw new Error(`Supplied vault metadata contains a duplicate address: ${metadata.id}`);
11445
+ records.set(metadata.id, metadata);
11446
+ }
11447
+ for (const address of requested) if (!records.has(address)) throw new Error(`Supplied vault metadata is missing address: ${address}`);
11448
+ return records;
11449
+ }
11450
+ //#endregion
11402
11451
  //#region src/vault/resolver-types.ts
11403
11452
  let ProtocolContractRole = /* @__PURE__ */ function(ProtocolContractRole) {
11404
11453
  ProtocolContractRole["AmmFactory"] = "amm-factory";
@@ -11786,18 +11835,25 @@ async function filterVaultsByFactory(context, vaults, factory, blockNumber) {
11786
11835
  //#endregion
11787
11836
  //#region src/vault/data-adapter.ts
11788
11837
  const nonShareBeacons = ["ScheduledJobs", "DynamicJobs"];
11789
- const vaultsQuery = `query Vaults($first: Int!, $where: Vault_filter!, $block: Int!) {
11838
+ const metadataFields = `id name symbol decimals beaconName createdAt state pool poolKey token0 token1 payloadIpfs strategyToken { id }`;
11839
+ const metadataQuery = `query VaultMetadata($ids: [ID!]!, $first: Int!) {
11840
+ vaults(first: $first, orderBy: id, orderDirection: asc, where: {id_in: $ids}) { ${metadataFields} }
11841
+ _meta { ${metaFields} }
11842
+ }`;
11843
+ const vaultsQuery = (balances) => `query Vaults($first: Int!, $where: Vault_filter!, $block: Int!) {
11790
11844
  vaults(first: $first, orderBy: id, orderDirection: asc, where: $where, block: {number: $block}) {
11791
- id beaconName token0 token1 token0Symbol token1Symbol token0Name token1Name
11845
+ ${balances ? "id beaconName token0 token1" : metadataFields}
11846
+ token0Symbol token1Symbol token0Name token1Name
11792
11847
  }
11793
11848
  _meta(block: {number: $block}) { ${metaFields} }
11794
11849
  }`;
11795
11850
  function matchesToken(row, token, side) {
11796
- const address = indexedAddress(row[`token${side}`]);
11851
+ if (row[`token${side}`] === null) return false;
11852
+ const address = indexedAddress(row[`token${side}`], true);
11797
11853
  const symbol = row[`token${side}Symbol`];
11798
11854
  const name = row[`token${side}Name`];
11799
- if (typeof symbol !== "string" || typeof name !== "string") throw new Error("Malformed vault token metadata");
11800
- return (!token.address || token.address === address) && (!token.symbol || symbol.toLowerCase().includes(token.symbol)) && (!token.name || name.toLowerCase().includes(token.name));
11855
+ if (symbol !== null && typeof symbol !== "string" || name !== null && typeof name !== "string") throw new Error("Malformed vault token metadata");
11856
+ return (!token.address || token.address === address) && (!token.symbol || typeof symbol === "string" && symbol.toLowerCase().includes(token.symbol)) && (!token.name || typeof name === "string" && name.toLowerCase().includes(token.name));
11801
11857
  }
11802
11858
  function vaultFilter(token, after, beacons, balances) {
11803
11859
  const base = {
@@ -11886,7 +11942,7 @@ var CatalogSteerDataAdapter = class extends SteerDataAdapter {
11886
11942
  ...cursor === void 0 ? {} : { cursor },
11887
11943
  limit: input.limit ?? 1e3
11888
11944
  }, options, true);
11889
- const items = await scanVaultBalances(this.#context, page.items, input.account, page.indexedBlock);
11945
+ const items = await scanVaultBalances(this.#context, page.items.map((row) => indexedAddress(row.id)), input.account, page.indexedBlock);
11890
11946
  return Object.freeze({
11891
11947
  items,
11892
11948
  indexedBlock: page.indexedBlock,
@@ -11897,9 +11953,44 @@ var CatalogSteerDataAdapter = class extends SteerDataAdapter {
11897
11953
  }) } : {}
11898
11954
  });
11899
11955
  }
11956
+ async #metadataQuery(query, variables, options) {
11957
+ try {
11958
+ return await this.#query(query, variables, options);
11959
+ } catch (error) {
11960
+ const errors = error instanceof Error ? error.cause : void 0;
11961
+ if (!query.includes("poolKey") || !Array.isArray(errors) || !errors.length || !errors.every((value) => typeof value?.message === "string" && /^Cannot query field "poolKey" on type "Vault"\.(?: Did you mean .*\?)?$/.test(value.message))) throw error;
11962
+ return this.#query(query.replace(/\bpoolKey\b/g, ""), variables, options);
11963
+ }
11964
+ }
11965
+ async getVaultMetadata(addresses, options = {}) {
11966
+ const ids = addresses.map((address) => indexedAddress(address));
11967
+ const unique = [...new Set(ids)];
11968
+ if (!unique.length) return Object.freeze([]);
11969
+ if (unique.length > 1e3) throw new Error("Vault metadata lookup supports at most 1000 addresses");
11970
+ const data = await this.#metadataQuery(metadataQuery, {
11971
+ ids: unique,
11972
+ first: unique.length
11973
+ }, options);
11974
+ snapshot(data);
11975
+ if (!Array.isArray(data.vaults) || data.vaults.length > unique.length) throw new Error("Malformed vault metadata response");
11976
+ const records = /* @__PURE__ */ new Map();
11977
+ for (const value of data.vaults) {
11978
+ const metadata = vaultMetadata(value);
11979
+ if (!unique.includes(metadata.id) || records.has(metadata.id)) throw new Error("Unexpected or duplicate vault metadata");
11980
+ records.set(metadata.id, metadata);
11981
+ }
11982
+ return Object.freeze(ids.map((id) => {
11983
+ const metadata = records.get(id);
11984
+ if (!metadata) throw new Error("Vault metadata is not indexed");
11985
+ return metadata;
11986
+ }));
11987
+ }
11900
11988
  async listVaults(request = {}, options = {}) {
11901
- const { indexedBlock: _, ...page } = await this.#listVaults(request, options, false);
11902
- return Object.freeze(page);
11989
+ const { indexedBlock: _, items, ...page } = await this.#listVaults(request, options, false);
11990
+ return Object.freeze({
11991
+ ...page,
11992
+ items: Object.freeze(items.map(vaultMetadata))
11993
+ });
11903
11994
  }
11904
11995
  async #listVaults(request = {}, options = {}, balances = false) {
11905
11996
  const limit = request.limit ?? 100;
@@ -11932,7 +12023,7 @@ var CatalogSteerDataAdapter = class extends SteerDataAdapter {
11932
12023
  } });
11933
12024
  } else expected = snapshot(await this.#query(snapshotQuery, {}, options));
11934
12025
  const beacons = amm ? this.#beacons.get(amm) : void 0;
11935
- const data = await this.#query(vaultsQuery, {
12026
+ const data = await this.#metadataQuery(vaultsQuery(balances), {
11936
12027
  first: limit,
11937
12028
  where: vaultFilter(token, after, beacons, balances),
11938
12029
  block: expected.block
@@ -11949,13 +12040,15 @@ var CatalogSteerDataAdapter = class extends SteerDataAdapter {
11949
12040
  if (balances && (typeof row.beaconName !== "string" || nonShareBeacons.includes(row.beaconName))) throw new Error("User vault listing returned a non-share deployment");
11950
12041
  if (beacons && (typeof row.beaconName !== "string" || !beacons.includes(row.beaconName))) throw new Error("Vault listing does not match the AMM beacon mapping");
11951
12042
  if (token && !matchesToken(row, token, 0) && !matchesToken(row, token, 1)) throw new Error("Vault listing does not match the token filter");
11952
- return address;
12043
+ if (!balances) vaultMetadata(row);
12044
+ return row;
11953
12045
  });
11954
- const last = items.at(-1);
12046
+ const last = items.at(-1)?.id;
11955
12047
  const factory = amm ? this.#factories.get(amm) : void 0;
11956
- const filtered = factory ? await filterVaultsByFactory(this.#context, items, factory, BigInt(expected.block)) : items;
12048
+ const filtered = factory ? await filterVaultsByFactory(this.#context, items.map((row) => indexedAddress(row.id)), factory, BigInt(expected.block)) : items.map((row) => indexedAddress(row.id));
12049
+ const included = new Set(filtered);
11957
12050
  return Object.freeze({
11958
- items: Object.freeze(filtered),
12051
+ items: Object.freeze(items.filter((row) => included.has(indexedAddress(row.id)))),
11959
12052
  indexedBlock: BigInt(expected.block),
11960
12053
  ...items.length === limit && last ? { nextCursor: JSON.stringify({
11961
12054
  version: 1,
@@ -13759,10 +13852,13 @@ var SteerVault = class {
13759
13852
  chain;
13760
13853
  steer;
13761
13854
  underlying;
13762
- constructor(address, chain, steer, underlying) {
13855
+ metadata;
13856
+ constructor(address, chain, steer, underlying, metadata) {
13763
13857
  this.address = address;
13764
13858
  this.chain = chain;
13765
13859
  this.steer = steer;
13860
+ this.metadata = vaultMetadata(metadata);
13861
+ if (this.metadata.id !== address.toLowerCase()) throw new Error("Vault metadata belongs to another vault");
13766
13862
  this.underlying = Object.freeze([...underlying]);
13767
13863
  }
13768
13864
  };
@@ -14424,7 +14520,7 @@ var AerodromeVault = class extends LiquidityVault {
14424
14520
  #positionManager;
14425
14521
  /** @internal */
14426
14522
  constructor(context, identity) {
14427
- super(context.address, context.steer.context, context.steer, context.underlying);
14523
+ super(context.address, context.steer.context, context.steer, context.underlying, context.metadata);
14428
14524
  const underlying = context.underlying[0];
14429
14525
  if (context.underlying.length !== 1 || underlying?.kind !== "liquidity" || !(underlying.amm instanceof AerodromeSlipstreamAdapter) || typeof underlying.pool !== "string") identityMismatch("Expected one Aerodrome vault pool");
14430
14526
  requireAddress(underlying.pool);
@@ -14528,6 +14624,7 @@ async function readAerodromeBulkSnapshot(vault, data, rpc, beacon) {
14528
14624
  steer: vault.steer,
14529
14625
  address: vault.address,
14530
14626
  underlying: vault.underlying,
14627
+ metadata: vault.metadata,
14531
14628
  blockNumber: rpc.blockNumber
14532
14629
  }, identity, {
14533
14630
  raw: decodeAerodromeSnapshot(data),
@@ -15567,6 +15664,7 @@ async function readIntegralBulkSnapshot(vault, data, rpc) {
15567
15664
  steer: vault.steer,
15568
15665
  address: vault.address,
15569
15666
  underlying: vault.underlying,
15667
+ metadata: vault.metadata,
15570
15668
  blockNumber: rpc.blockNumber
15571
15669
  }, {
15572
15670
  raw,
@@ -15580,7 +15678,7 @@ var AlgebraIntegralVault = class extends LiquidityVault {
15580
15678
  amm;
15581
15679
  pool;
15582
15680
  constructor(context) {
15583
- super(context.address, context.steer.context, context.steer, context.underlying);
15681
+ super(context.address, context.steer.context, context.steer, context.underlying, context.metadata);
15584
15682
  const underlying = context.underlying[0];
15585
15683
  if (context.underlying.length !== 1 || underlying?.kind !== "liquidity" || !isAlgebraIntegralAdapter(underlying.amm) || typeof underlying.pool !== "string") identityMismatch("Expected one Integral vault pool");
15586
15684
  requireAddress(underlying.pool);
@@ -17179,6 +17277,7 @@ async function readV3BulkSnapshot(vault, data, rpc) {
17179
17277
  steer: vault.steer,
17180
17278
  address: vault.address,
17181
17279
  underlying: vault.underlying,
17280
+ metadata: vault.metadata,
17182
17281
  blockNumber: rpc.blockNumber
17183
17282
  }, {
17184
17283
  raw,
@@ -17192,7 +17291,7 @@ var UniswapV3Vault = class extends LiquidityVault {
17192
17291
  amm;
17193
17292
  pool;
17194
17293
  constructor(context) {
17195
- super(context.address, context.steer.context, context.steer, context.underlying);
17294
+ super(context.address, context.steer.context, context.steer, context.underlying, context.metadata);
17196
17295
  const underlying = context.underlying[0];
17197
17296
  if (context.underlying.length !== 1 || underlying?.kind !== "liquidity" || !isUniswapV3Adapter(underlying.amm) || typeof underlying.pool !== "string") identityMismatch("Expected one V3 vault pool");
17198
17297
  requireAddress(underlying.pool);
@@ -18825,7 +18924,7 @@ var UniswapV4Vault = class extends LiquidityVault {
18825
18924
  amm;
18826
18925
  pool;
18827
18926
  constructor(context) {
18828
- super(context.address, context.steer.context, context.steer, context.underlying);
18927
+ super(context.address, context.steer.context, context.steer, context.underlying, context.metadata);
18829
18928
  const underlying = context.underlying[0];
18830
18929
  if (context.underlying.length !== 1 || underlying?.kind !== "liquidity" || !(underlying.amm instanceof UniswapV4Adapter) || !isLiquidityPool(underlying.pool) || typeof underlying.pool === "string") identityMismatch("Expected one standalone V4 vault pool");
18831
18930
  this.amm = underlying.amm;
@@ -20205,6 +20304,7 @@ async function readLegacyBulkSnapshot(vault, data, rpc, beacon) {
20205
20304
  steer: vault.steer,
20206
20305
  address: vault.address,
20207
20306
  underlying: vault.underlying,
20307
+ metadata: vault.metadata,
20208
20308
  blockNumber: rpc.blockNumber
20209
20309
  }, {
20210
20310
  raw,
@@ -20218,7 +20318,7 @@ var AlgebraLegacyVault = class extends LiquidityVault {
20218
20318
  amm;
20219
20319
  pool;
20220
20320
  constructor(context) {
20221
- super(context.address, context.steer.context, context.steer, context.underlying);
20321
+ super(context.address, context.steer.context, context.steer, context.underlying, context.metadata);
20222
20322
  const underlying = context.underlying[0];
20223
20323
  if (context.underlying.length !== 1 || underlying?.kind !== "liquidity" || !isAlgebraLegacyAdapter(underlying.amm) || typeof underlying.pool !== "string") identityMismatch("Expected one Legacy vault pool");
20224
20324
  requireAddress(underlying.pool);
@@ -20825,7 +20925,7 @@ var VaultResolver = class {
20825
20925
  if (!vault) throw new Error("Missing resolved vault");
20826
20926
  return vault;
20827
20927
  }
20828
- async #createVault(context, identity, verified) {
20928
+ async #createVault(context, identity, loadMetadata, verified) {
20829
20929
  const { steer, address } = context;
20830
20930
  if (!identity) throw new VaultResolutionError("not-registered", "Vault is not registered with Steer");
20831
20931
  if (identity.chainId !== steer.context.chainId || addressKey(identity.vaultAddress) !== addressKey(address) || addressKey(identity.registryAddress) !== addressKey(steer.contracts.VaultRegistry.address) || addressKey(identity.beaconAddress) !== addressKey(identity.proxyBeaconAddress)) throw new VaultResolutionError("identity-mismatch", "Vault identity does not match the request");
@@ -20837,9 +20937,12 @@ var VaultResolver = class {
20837
20937
  lending: /* @__PURE__ */ new Map()
20838
20938
  };
20839
20939
  const resolvedUnderlying = Object.freeze(underlying.map((position) => this.#resolveUnderlying(position, adapters)));
20940
+ const metadata = await loadMetadata();
20941
+ if (metadata.id.toLowerCase() !== address.toLowerCase()) throw new Error("Vault metadata belongs to another vault");
20840
20942
  const resolved = Object.freeze({
20841
20943
  ...context,
20842
- underlying: resolvedUnderlying
20944
+ underlying: resolvedUnderlying,
20945
+ metadata
20843
20946
  });
20844
20947
  const vault = binding.createVault(resolved);
20845
20948
  this.#validateVault(vault, binding, resolved);
@@ -20859,21 +20962,39 @@ var VaultResolver = class {
20859
20962
  steer: vault.steer,
20860
20963
  address: vault.address,
20861
20964
  underlying: vault.underlying,
20965
+ metadata: vault.metadata,
20862
20966
  blockNumber
20863
20967
  });
20864
20968
  if (snapshot.blockNumber !== blockNumber || !vault.underlying.some((position) => position.kind === "liquidity" && sameLiquidityPool(position.pool, snapshot.pool))) throw new Error("Vault returned another accounting snapshot");
20865
20969
  return readValidatedVaultSnapshot(vault, snapshot, vault.steer.context);
20866
20970
  }
20971
+ #metadata(steer, addresses, supplied) {
20972
+ let pending;
20973
+ return async (address) => {
20974
+ if (supplied) {
20975
+ const metadata = supplied.get(address.toLowerCase());
20976
+ if (!metadata) throw new Error(`Supplied vault metadata is missing address: ${address}`);
20977
+ return metadata;
20978
+ }
20979
+ pending ??= steer.dataAdapter.getVaultMetadata(addresses);
20980
+ const metadata = (await pending).find((record) => record.id.toLowerCase() === address.toLowerCase());
20981
+ if (!metadata) throw new Error("Vault metadata is not indexed");
20982
+ return metadata;
20983
+ };
20984
+ }
20867
20985
  /** Bounded bulk identity pages at one block; compatibility uses four active loads. */
20868
20986
  async resolveMany(steer, addresses, options) {
20869
20987
  const inputs = [...addresses];
20870
20988
  for (const address of inputs) addressKey(address);
20989
+ const supplied = options?.metadata;
20990
+ const metadata = supplied === void 0 ? void 0 : captureVaultMetadata(inputs, supplied);
20871
20991
  if (!inputs.length) return Object.freeze([]);
20872
20992
  const blockNumber = await this.#blockNumber(steer, options);
20873
20993
  const result = [];
20874
20994
  let bulkSupported = !!this.#reader.readMany;
20875
20995
  for (let start = 0; start < inputs.length; start += 64) {
20876
20996
  const batch = inputs.slice(start, start + 64);
20997
+ const loadMetadata = this.#metadata(steer, batch, metadata);
20877
20998
  const contexts = batch.map((address) => Object.freeze({
20878
20999
  steer,
20879
21000
  address,
@@ -20885,13 +21006,13 @@ var VaultResolver = class {
20885
21006
  for (const [i, context] of contexts.entries()) {
20886
21007
  const read = reads[i];
20887
21008
  if (!read) throw new VaultResolutionError("identity-mismatch", "Missing bulk vault identity");
20888
- result.push((await this.#createVault(context, read.identity, read)).vault);
21009
+ result.push((await this.#createVault(context, read.identity, () => loadMetadata(context.address), read)).vault);
20889
21010
  }
20890
21011
  } else {
20891
21012
  bulkSupported = false;
20892
21013
  for (let i = 0; i < batch.length; i += 4) result.push(...await Promise.all(contexts.slice(i, i + 4).map(async (context) => {
20893
21014
  const identity = await this.#reader.readIdentity(context);
20894
- return (await this.#createVault(context, identity)).vault;
21015
+ return (await this.#createVault(context, identity, () => loadMetadata(context.address))).vault;
20895
21016
  })));
20896
21017
  }
20897
21018
  }
@@ -20907,6 +21028,7 @@ var VaultResolver = class {
20907
21028
  let bulkSupported = !!this.#reader.readSnapshots;
20908
21029
  for (let start = 0; start < inputs.length; start += 16) {
20909
21030
  const batch = inputs.slice(start, start + 16);
21031
+ const loadMetadata = this.#metadata(steer, batch);
20910
21032
  const contexts = batch.map((address) => Object.freeze({
20911
21033
  steer,
20912
21034
  address,
@@ -20918,7 +21040,7 @@ var VaultResolver = class {
20918
21040
  for (const [i, context] of contexts.entries()) {
20919
21041
  const read = reads[i];
20920
21042
  if (!read) throw new Error("Missing bulk vault snapshot");
20921
- const { vault } = await this.#createVault(context, read.identity, read);
21043
+ const { vault } = await this.#createVault(context, read.identity, () => loadMetadata(context.address), read);
20922
21044
  if (vault.kind !== "liquidity") throw new VaultResolutionError("invalid-vault", "Vault does not support liquidity snapshots");
20923
21045
  result.push(await read.readSnapshot(vault));
20924
21046
  }
@@ -20931,7 +21053,7 @@ var VaultResolver = class {
20931
21053
  blockNumber
20932
21054
  });
20933
21055
  const identity = await this.#reader.readIdentity(context);
20934
- const { vault, binding, context: resolved } = await this.#createVault(context, identity);
21056
+ const { vault, binding, context: resolved } = await this.#createVault(context, identity, () => loadMetadata(context.address));
20935
21057
  if (vault.kind !== "liquidity") throw new VaultResolutionError("invalid-vault", "Vault does not support liquidity snapshots");
20936
21058
  if (!binding.readSnapshot) throw new VaultResolutionError("not-configured", "No verified snapshot reader configured for vault implementation");
20937
21059
  const snapshot = await binding.readSnapshot(resolved);
@@ -21029,7 +21151,10 @@ var SteerNetwork = class {
21029
21151
  return this.#vaultResolver().resolve(this, address, options);
21030
21152
  }
21031
21153
  async getVaults(addresses, options) {
21032
- if (!addresses.length) return Object.freeze([]);
21154
+ if (!addresses.length) {
21155
+ if (options?.metadata !== void 0) captureVaultMetadata(addresses, options.metadata);
21156
+ return Object.freeze([]);
21157
+ }
21033
21158
  return this.#vaultResolver().resolveMany(this, addresses, options);
21034
21159
  }
21035
21160
  getVaultSnapshots(addresses, options) {