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

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
 
@@ -500,6 +537,9 @@ shared internally; no snapshot is preloaded or passed through discovery. Each op
500
537
  the RPC chain, with no cross-operation identity cache or additional consumer options.
501
538
 
502
539
  Quotes read pool identity, the token pair and fee, verify factory registration, and call QuoterV2.
540
+ V3 and Aerodrome Slipstream quotes reject results at either default price boundary because
541
+ QuoterV2 does not establish the consumed input there. Requests that cannot establish full input
542
+ consumption fail explicitly.
503
543
  They don't load token decimals, balances, ticks, or liquidity. Discovering a pool and then quoting
504
544
  at its block uses 9 RPC calls in total. Counts exclude retries and provider-specific transport
505
545
  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,36 @@ 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
+ //#endregion
11402
11437
  //#region src/vault/resolver-types.ts
11403
11438
  let ProtocolContractRole = /* @__PURE__ */ function(ProtocolContractRole) {
11404
11439
  ProtocolContractRole["AmmFactory"] = "amm-factory";
@@ -11786,18 +11821,25 @@ async function filterVaultsByFactory(context, vaults, factory, blockNumber) {
11786
11821
  //#endregion
11787
11822
  //#region src/vault/data-adapter.ts
11788
11823
  const nonShareBeacons = ["ScheduledJobs", "DynamicJobs"];
11789
- const vaultsQuery = `query Vaults($first: Int!, $where: Vault_filter!, $block: Int!) {
11824
+ const metadataFields = `id name symbol decimals beaconName createdAt state pool poolKey token0 token1 payloadIpfs strategyToken { id }`;
11825
+ const metadataQuery = `query VaultMetadata($ids: [ID!]!, $first: Int!) {
11826
+ vaults(first: $first, orderBy: id, orderDirection: asc, where: {id_in: $ids}) { ${metadataFields} }
11827
+ _meta { ${metaFields} }
11828
+ }`;
11829
+ const vaultsQuery = (balances) => `query Vaults($first: Int!, $where: Vault_filter!, $block: Int!) {
11790
11830
  vaults(first: $first, orderBy: id, orderDirection: asc, where: $where, block: {number: $block}) {
11791
- id beaconName token0 token1 token0Symbol token1Symbol token0Name token1Name
11831
+ ${balances ? "id beaconName token0 token1" : metadataFields}
11832
+ token0Symbol token1Symbol token0Name token1Name
11792
11833
  }
11793
11834
  _meta(block: {number: $block}) { ${metaFields} }
11794
11835
  }`;
11795
11836
  function matchesToken(row, token, side) {
11796
- const address = indexedAddress(row[`token${side}`]);
11837
+ if (row[`token${side}`] === null) return false;
11838
+ const address = indexedAddress(row[`token${side}`], true);
11797
11839
  const symbol = row[`token${side}Symbol`];
11798
11840
  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));
11841
+ if (symbol !== null && typeof symbol !== "string" || name !== null && typeof name !== "string") throw new Error("Malformed vault token metadata");
11842
+ 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
11843
  }
11802
11844
  function vaultFilter(token, after, beacons, balances) {
11803
11845
  const base = {
@@ -11886,7 +11928,7 @@ var CatalogSteerDataAdapter = class extends SteerDataAdapter {
11886
11928
  ...cursor === void 0 ? {} : { cursor },
11887
11929
  limit: input.limit ?? 1e3
11888
11930
  }, options, true);
11889
- const items = await scanVaultBalances(this.#context, page.items, input.account, page.indexedBlock);
11931
+ const items = await scanVaultBalances(this.#context, page.items.map((row) => indexedAddress(row.id)), input.account, page.indexedBlock);
11890
11932
  return Object.freeze({
11891
11933
  items,
11892
11934
  indexedBlock: page.indexedBlock,
@@ -11897,9 +11939,44 @@ var CatalogSteerDataAdapter = class extends SteerDataAdapter {
11897
11939
  }) } : {}
11898
11940
  });
11899
11941
  }
11942
+ async #metadataQuery(query, variables, options) {
11943
+ try {
11944
+ return await this.#query(query, variables, options);
11945
+ } catch (error) {
11946
+ const errors = error instanceof Error ? error.cause : void 0;
11947
+ 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;
11948
+ return this.#query(query.replace(/\bpoolKey\b/g, ""), variables, options);
11949
+ }
11950
+ }
11951
+ async getVaultMetadata(addresses, options = {}) {
11952
+ const ids = addresses.map((address) => indexedAddress(address));
11953
+ const unique = [...new Set(ids)];
11954
+ if (!unique.length) return Object.freeze([]);
11955
+ if (unique.length > 1e3) throw new Error("Vault metadata lookup supports at most 1000 addresses");
11956
+ const data = await this.#metadataQuery(metadataQuery, {
11957
+ ids: unique,
11958
+ first: unique.length
11959
+ }, options);
11960
+ snapshot(data);
11961
+ if (!Array.isArray(data.vaults) || data.vaults.length > unique.length) throw new Error("Malformed vault metadata response");
11962
+ const records = /* @__PURE__ */ new Map();
11963
+ for (const value of data.vaults) {
11964
+ const metadata = vaultMetadata(value);
11965
+ if (!unique.includes(metadata.id) || records.has(metadata.id)) throw new Error("Unexpected or duplicate vault metadata");
11966
+ records.set(metadata.id, metadata);
11967
+ }
11968
+ return Object.freeze(ids.map((id) => {
11969
+ const metadata = records.get(id);
11970
+ if (!metadata) throw new Error("Vault metadata is not indexed");
11971
+ return metadata;
11972
+ }));
11973
+ }
11900
11974
  async listVaults(request = {}, options = {}) {
11901
- const { indexedBlock: _, ...page } = await this.#listVaults(request, options, false);
11902
- return Object.freeze(page);
11975
+ const { indexedBlock: _, items, ...page } = await this.#listVaults(request, options, false);
11976
+ return Object.freeze({
11977
+ ...page,
11978
+ items: Object.freeze(items.map(vaultMetadata))
11979
+ });
11903
11980
  }
11904
11981
  async #listVaults(request = {}, options = {}, balances = false) {
11905
11982
  const limit = request.limit ?? 100;
@@ -11932,7 +12009,7 @@ var CatalogSteerDataAdapter = class extends SteerDataAdapter {
11932
12009
  } });
11933
12010
  } else expected = snapshot(await this.#query(snapshotQuery, {}, options));
11934
12011
  const beacons = amm ? this.#beacons.get(amm) : void 0;
11935
- const data = await this.#query(vaultsQuery, {
12012
+ const data = await this.#metadataQuery(vaultsQuery(balances), {
11936
12013
  first: limit,
11937
12014
  where: vaultFilter(token, after, beacons, balances),
11938
12015
  block: expected.block
@@ -11949,13 +12026,15 @@ var CatalogSteerDataAdapter = class extends SteerDataAdapter {
11949
12026
  if (balances && (typeof row.beaconName !== "string" || nonShareBeacons.includes(row.beaconName))) throw new Error("User vault listing returned a non-share deployment");
11950
12027
  if (beacons && (typeof row.beaconName !== "string" || !beacons.includes(row.beaconName))) throw new Error("Vault listing does not match the AMM beacon mapping");
11951
12028
  if (token && !matchesToken(row, token, 0) && !matchesToken(row, token, 1)) throw new Error("Vault listing does not match the token filter");
11952
- return address;
12029
+ if (!balances) vaultMetadata(row);
12030
+ return row;
11953
12031
  });
11954
- const last = items.at(-1);
12032
+ const last = items.at(-1)?.id;
11955
12033
  const factory = amm ? this.#factories.get(amm) : void 0;
11956
- const filtered = factory ? await filterVaultsByFactory(this.#context, items, factory, BigInt(expected.block)) : items;
12034
+ const filtered = factory ? await filterVaultsByFactory(this.#context, items.map((row) => indexedAddress(row.id)), factory, BigInt(expected.block)) : items.map((row) => indexedAddress(row.id));
12035
+ const included = new Set(filtered);
11957
12036
  return Object.freeze({
11958
- items: Object.freeze(filtered),
12037
+ items: Object.freeze(items.filter((row) => included.has(indexedAddress(row.id)))),
11959
12038
  indexedBlock: BigInt(expected.block),
11960
12039
  ...items.length === limit && last ? { nextCursor: JSON.stringify({
11961
12040
  version: 1,
@@ -13759,10 +13838,13 @@ var SteerVault = class {
13759
13838
  chain;
13760
13839
  steer;
13761
13840
  underlying;
13762
- constructor(address, chain, steer, underlying) {
13841
+ metadata;
13842
+ constructor(address, chain, steer, underlying, metadata) {
13763
13843
  this.address = address;
13764
13844
  this.chain = chain;
13765
13845
  this.steer = steer;
13846
+ this.metadata = vaultMetadata(metadata);
13847
+ if (this.metadata.id !== address.toLowerCase()) throw new Error("Vault metadata belongs to another vault");
13766
13848
  this.underlying = Object.freeze([...underlying]);
13767
13849
  }
13768
13850
  };
@@ -14424,7 +14506,7 @@ var AerodromeVault = class extends LiquidityVault {
14424
14506
  #positionManager;
14425
14507
  /** @internal */
14426
14508
  constructor(context, identity) {
14427
- super(context.address, context.steer.context, context.steer, context.underlying);
14509
+ super(context.address, context.steer.context, context.steer, context.underlying, context.metadata);
14428
14510
  const underlying = context.underlying[0];
14429
14511
  if (context.underlying.length !== 1 || underlying?.kind !== "liquidity" || !(underlying.amm instanceof AerodromeSlipstreamAdapter) || typeof underlying.pool !== "string") identityMismatch("Expected one Aerodrome vault pool");
14430
14512
  requireAddress(underlying.pool);
@@ -14528,6 +14610,7 @@ async function readAerodromeBulkSnapshot(vault, data, rpc, beacon) {
14528
14610
  steer: vault.steer,
14529
14611
  address: vault.address,
14530
14612
  underlying: vault.underlying,
14613
+ metadata: vault.metadata,
14531
14614
  blockNumber: rpc.blockNumber
14532
14615
  }, identity, {
14533
14616
  raw: decodeAerodromeSnapshot(data),
@@ -15567,6 +15650,7 @@ async function readIntegralBulkSnapshot(vault, data, rpc) {
15567
15650
  steer: vault.steer,
15568
15651
  address: vault.address,
15569
15652
  underlying: vault.underlying,
15653
+ metadata: vault.metadata,
15570
15654
  blockNumber: rpc.blockNumber
15571
15655
  }, {
15572
15656
  raw,
@@ -15580,7 +15664,7 @@ var AlgebraIntegralVault = class extends LiquidityVault {
15580
15664
  amm;
15581
15665
  pool;
15582
15666
  constructor(context) {
15583
- super(context.address, context.steer.context, context.steer, context.underlying);
15667
+ super(context.address, context.steer.context, context.steer, context.underlying, context.metadata);
15584
15668
  const underlying = context.underlying[0];
15585
15669
  if (context.underlying.length !== 1 || underlying?.kind !== "liquidity" || !isAlgebraIntegralAdapter(underlying.amm) || typeof underlying.pool !== "string") identityMismatch("Expected one Integral vault pool");
15586
15670
  requireAddress(underlying.pool);
@@ -17179,6 +17263,7 @@ async function readV3BulkSnapshot(vault, data, rpc) {
17179
17263
  steer: vault.steer,
17180
17264
  address: vault.address,
17181
17265
  underlying: vault.underlying,
17266
+ metadata: vault.metadata,
17182
17267
  blockNumber: rpc.blockNumber
17183
17268
  }, {
17184
17269
  raw,
@@ -17192,7 +17277,7 @@ var UniswapV3Vault = class extends LiquidityVault {
17192
17277
  amm;
17193
17278
  pool;
17194
17279
  constructor(context) {
17195
- super(context.address, context.steer.context, context.steer, context.underlying);
17280
+ super(context.address, context.steer.context, context.steer, context.underlying, context.metadata);
17196
17281
  const underlying = context.underlying[0];
17197
17282
  if (context.underlying.length !== 1 || underlying?.kind !== "liquidity" || !isUniswapV3Adapter(underlying.amm) || typeof underlying.pool !== "string") identityMismatch("Expected one V3 vault pool");
17198
17283
  requireAddress(underlying.pool);
@@ -18825,7 +18910,7 @@ var UniswapV4Vault = class extends LiquidityVault {
18825
18910
  amm;
18826
18911
  pool;
18827
18912
  constructor(context) {
18828
- super(context.address, context.steer.context, context.steer, context.underlying);
18913
+ super(context.address, context.steer.context, context.steer, context.underlying, context.metadata);
18829
18914
  const underlying = context.underlying[0];
18830
18915
  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
18916
  this.amm = underlying.amm;
@@ -20205,6 +20290,7 @@ async function readLegacyBulkSnapshot(vault, data, rpc, beacon) {
20205
20290
  steer: vault.steer,
20206
20291
  address: vault.address,
20207
20292
  underlying: vault.underlying,
20293
+ metadata: vault.metadata,
20208
20294
  blockNumber: rpc.blockNumber
20209
20295
  }, {
20210
20296
  raw,
@@ -20218,7 +20304,7 @@ var AlgebraLegacyVault = class extends LiquidityVault {
20218
20304
  amm;
20219
20305
  pool;
20220
20306
  constructor(context) {
20221
- super(context.address, context.steer.context, context.steer, context.underlying);
20307
+ super(context.address, context.steer.context, context.steer, context.underlying, context.metadata);
20222
20308
  const underlying = context.underlying[0];
20223
20309
  if (context.underlying.length !== 1 || underlying?.kind !== "liquidity" || !isAlgebraLegacyAdapter(underlying.amm) || typeof underlying.pool !== "string") identityMismatch("Expected one Legacy vault pool");
20224
20310
  requireAddress(underlying.pool);
@@ -20825,7 +20911,7 @@ var VaultResolver = class {
20825
20911
  if (!vault) throw new Error("Missing resolved vault");
20826
20912
  return vault;
20827
20913
  }
20828
- async #createVault(context, identity, verified) {
20914
+ async #createVault(context, identity, loadMetadata, verified) {
20829
20915
  const { steer, address } = context;
20830
20916
  if (!identity) throw new VaultResolutionError("not-registered", "Vault is not registered with Steer");
20831
20917
  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 +20923,12 @@ var VaultResolver = class {
20837
20923
  lending: /* @__PURE__ */ new Map()
20838
20924
  };
20839
20925
  const resolvedUnderlying = Object.freeze(underlying.map((position) => this.#resolveUnderlying(position, adapters)));
20926
+ const metadata = await loadMetadata();
20927
+ if (metadata.id.toLowerCase() !== address.toLowerCase()) throw new Error("Vault metadata belongs to another vault");
20840
20928
  const resolved = Object.freeze({
20841
20929
  ...context,
20842
- underlying: resolvedUnderlying
20930
+ underlying: resolvedUnderlying,
20931
+ metadata
20843
20932
  });
20844
20933
  const vault = binding.createVault(resolved);
20845
20934
  this.#validateVault(vault, binding, resolved);
@@ -20859,11 +20948,21 @@ var VaultResolver = class {
20859
20948
  steer: vault.steer,
20860
20949
  address: vault.address,
20861
20950
  underlying: vault.underlying,
20951
+ metadata: vault.metadata,
20862
20952
  blockNumber
20863
20953
  });
20864
20954
  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
20955
  return readValidatedVaultSnapshot(vault, snapshot, vault.steer.context);
20866
20956
  }
20957
+ #metadata(steer, addresses) {
20958
+ let pending;
20959
+ return async (address) => {
20960
+ pending ??= steer.dataAdapter.getVaultMetadata(addresses);
20961
+ const metadata = (await pending).find((record) => record.id.toLowerCase() === address.toLowerCase());
20962
+ if (!metadata) throw new Error("Vault metadata is not indexed");
20963
+ return metadata;
20964
+ };
20965
+ }
20867
20966
  /** Bounded bulk identity pages at one block; compatibility uses four active loads. */
20868
20967
  async resolveMany(steer, addresses, options) {
20869
20968
  const inputs = [...addresses];
@@ -20874,6 +20973,7 @@ var VaultResolver = class {
20874
20973
  let bulkSupported = !!this.#reader.readMany;
20875
20974
  for (let start = 0; start < inputs.length; start += 64) {
20876
20975
  const batch = inputs.slice(start, start + 64);
20976
+ const loadMetadata = this.#metadata(steer, batch);
20877
20977
  const contexts = batch.map((address) => Object.freeze({
20878
20978
  steer,
20879
20979
  address,
@@ -20885,13 +20985,13 @@ var VaultResolver = class {
20885
20985
  for (const [i, context] of contexts.entries()) {
20886
20986
  const read = reads[i];
20887
20987
  if (!read) throw new VaultResolutionError("identity-mismatch", "Missing bulk vault identity");
20888
- result.push((await this.#createVault(context, read.identity, read)).vault);
20988
+ result.push((await this.#createVault(context, read.identity, () => loadMetadata(context.address), read)).vault);
20889
20989
  }
20890
20990
  } else {
20891
20991
  bulkSupported = false;
20892
20992
  for (let i = 0; i < batch.length; i += 4) result.push(...await Promise.all(contexts.slice(i, i + 4).map(async (context) => {
20893
20993
  const identity = await this.#reader.readIdentity(context);
20894
- return (await this.#createVault(context, identity)).vault;
20994
+ return (await this.#createVault(context, identity, () => loadMetadata(context.address))).vault;
20895
20995
  })));
20896
20996
  }
20897
20997
  }
@@ -20907,6 +21007,7 @@ var VaultResolver = class {
20907
21007
  let bulkSupported = !!this.#reader.readSnapshots;
20908
21008
  for (let start = 0; start < inputs.length; start += 16) {
20909
21009
  const batch = inputs.slice(start, start + 16);
21010
+ const loadMetadata = this.#metadata(steer, batch);
20910
21011
  const contexts = batch.map((address) => Object.freeze({
20911
21012
  steer,
20912
21013
  address,
@@ -20918,7 +21019,7 @@ var VaultResolver = class {
20918
21019
  for (const [i, context] of contexts.entries()) {
20919
21020
  const read = reads[i];
20920
21021
  if (!read) throw new Error("Missing bulk vault snapshot");
20921
- const { vault } = await this.#createVault(context, read.identity, read);
21022
+ const { vault } = await this.#createVault(context, read.identity, () => loadMetadata(context.address), read);
20922
21023
  if (vault.kind !== "liquidity") throw new VaultResolutionError("invalid-vault", "Vault does not support liquidity snapshots");
20923
21024
  result.push(await read.readSnapshot(vault));
20924
21025
  }
@@ -20931,7 +21032,7 @@ var VaultResolver = class {
20931
21032
  blockNumber
20932
21033
  });
20933
21034
  const identity = await this.#reader.readIdentity(context);
20934
- const { vault, binding, context: resolved } = await this.#createVault(context, identity);
21035
+ const { vault, binding, context: resolved } = await this.#createVault(context, identity, () => loadMetadata(context.address));
20935
21036
  if (vault.kind !== "liquidity") throw new VaultResolutionError("invalid-vault", "Vault does not support liquidity snapshots");
20936
21037
  if (!binding.readSnapshot) throw new VaultResolutionError("not-configured", "No verified snapshot reader configured for vault implementation");
20937
21038
  const snapshot = await binding.readSnapshot(resolved);