@owney/sdk 0.7.28-beta.0 → 0.7.28-beta.2

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/dist/index.cjs CHANGED
@@ -510,7 +510,7 @@ function rawPoolApyForChain(entry, chainId, tokenSymbol) {
510
510
  let weightedSum = 0;
511
511
  let totalBalance = 0;
512
512
  for (const p of entry.positions ?? []) {
513
- if (p.chainId !== chainId) continue;
513
+ if (chainId !== void 0 && p.chainId !== chainId) continue;
514
514
  if (tokenSymbol && p.tokenSymbol?.toUpperCase() !== tokenSymbol.toUpperCase()) continue;
515
515
  const balance = p.balance ?? 0;
516
516
  const apy = netApy(p.apy_withFee, p.apy, "apy_withFee");
@@ -520,6 +520,28 @@ function rawPoolApyForChain(entry, chainId, tokenSymbol) {
520
520
  }
521
521
  return totalBalance > 0 ? weightedSum / totalBalance : null;
522
522
  }
523
+ function historyCoverage(raw) {
524
+ const chains = /* @__PURE__ */ new Map();
525
+ const assets = /* @__PURE__ */ new Map();
526
+ for (const date of Object.keys(raw.history ?? {}).sort()) {
527
+ void date;
528
+ const seenChains = /* @__PURE__ */ new Set();
529
+ const seenAssets = /* @__PURE__ */ new Set();
530
+ for (const p of (raw.history ?? {})[date].positions ?? []) {
531
+ const balance = p.balance ?? 0;
532
+ const apy = netApy(p.apy_withFee, p.apy, "apy_withFee");
533
+ if (balance <= 0 || typeof apy !== "number") continue;
534
+ seenChains.add(p.chainId);
535
+ if (p.tokenSymbol) seenAssets.add(p.tokenSymbol.toUpperCase());
536
+ }
537
+ for (const c of seenChains) chains.set(c, (chains.get(c) ?? 0) + 1);
538
+ for (const a of seenAssets) assets.set(a, (assets.get(a) ?? 0) + 1);
539
+ }
540
+ return {
541
+ chains: [...chains].map(([chainId, days]) => ({ chainId, days })),
542
+ assets: [...assets].map(([asset, days]) => ({ asset, days }))
543
+ };
544
+ }
523
545
  function mapApyHistory(raw, chainId, tokenSymbol) {
524
546
  const history = Object.entries(raw.history ?? {}).map(([date, entry]) => ({
525
547
  date,
@@ -530,7 +552,7 @@ function mapApyHistory(raw, chainId, tokenSymbol) {
530
552
  ...tokenSymbol ? {
531
553
  historicalBalance: {
532
554
  amount: (entry.positions ?? []).filter(
533
- (p) => p.chainId === chainId && p.tokenSymbol?.toUpperCase() === tokenSymbol.toUpperCase() && Number.isFinite(p.balance) && p.balance > 0 && Number.isFinite(p.apy_withFee ?? p.apy)
555
+ (p) => (chainId === void 0 || p.chainId === chainId) && p.tokenSymbol?.toUpperCase() === tokenSymbol.toUpperCase() && Number.isFinite(p.balance) && p.balance > 0 && Number.isFinite(p.apy_withFee ?? p.apy)
534
556
  ).reduce((sum, p) => sum + p.balance, 0),
535
557
  unit: tokenSymbol.toUpperCase()
536
558
  }
@@ -540,7 +562,8 @@ function mapApyHistory(raw, chainId, tokenSymbol) {
540
562
  walletAddress: raw.walletAddress,
541
563
  weightedApyAfterFee: raw.weightedApyAfterFee ? sumTokenValues(raw.weightedApyAfterFee) : void 0,
542
564
  apyByChainAndAsset: mapWeightedApyByChain(raw.weightedApyAfterFeeByChain),
543
- history
565
+ history,
566
+ coverage: historyCoverage(raw)
544
567
  };
545
568
  }
546
569
  function splitOpportunity(opportunity) {
@@ -999,6 +1022,9 @@ function splittingNeedsUpdate(current, desired) {
999
1022
  }
1000
1023
 
1001
1024
  // src/agents/zyfai/pool-policy.ts
1025
+ function orgPolicyForConnect(policy, userPoolCustomization) {
1026
+ return userPoolCustomization ? { ...policy, poolPolicy: null } : policy;
1027
+ }
1002
1028
  function toZyfaiCustomizations(policy, availableByProtocol, chainsByProtocol) {
1003
1029
  const selectionByKey = /* @__PURE__ */ new Map();
1004
1030
  for (const selection of policy.selections) {
@@ -1090,6 +1116,47 @@ function protocolsPolicyNeedsUpdate(current, desiredProtocols, desiredAutoSelect
1090
1116
  if (desiredProtocols === null) return false;
1091
1117
  return !protocolListsEqual(current.protocols, desiredProtocols);
1092
1118
  }
1119
+ function mapAvailablePools(raw) {
1120
+ const byChain = raw ?? {};
1121
+ if (typeof byChain !== "object" || byChain === null) return [];
1122
+ const result = [];
1123
+ for (const [chainIdStr, byToken] of Object.entries(byChain)) {
1124
+ const chainId = Number(chainIdStr);
1125
+ if (!Number.isFinite(chainId)) continue;
1126
+ for (const [token, entry] of Object.entries(byToken ?? {})) {
1127
+ for (const pool of entry?.pools ?? []) {
1128
+ if (!pool?.name) continue;
1129
+ result.push({
1130
+ chainId,
1131
+ token,
1132
+ name: pool.name,
1133
+ apy: pool.info?.combined_apy ?? null,
1134
+ tvlUsd: pool.info?.tvlUsd ?? null,
1135
+ link: pool.info?.link ?? null
1136
+ });
1137
+ }
1138
+ }
1139
+ }
1140
+ return result;
1141
+ }
1142
+ function assemblePoolCatalog(protocols, poolsByProtocol, asset, chains) {
1143
+ const wanted = asset.toUpperCase();
1144
+ const allowedChains = chains && chains.length > 0 ? new Set(chains) : void 0;
1145
+ const catalog = [];
1146
+ for (const protocol of protocols) {
1147
+ const pools = (poolsByProtocol.get(protocol.id) ?? []).filter(
1148
+ (p) => p.token.toUpperCase() === wanted && (allowedChains === void 0 || allowedChains.has(p.chainId))
1149
+ );
1150
+ if (pools.length === 0) continue;
1151
+ catalog.push({
1152
+ protocolId: protocol.id,
1153
+ name: protocol.name,
1154
+ logo: protocol.imageUrl ?? null,
1155
+ pools
1156
+ });
1157
+ }
1158
+ return catalog;
1159
+ }
1093
1160
 
1094
1161
  // src/agents/zyfai/zyfai.agent.ts
1095
1162
  var ERC7579_IS_MODULE_INSTALLED_ABI = (0, import_viem.parseAbi)([
@@ -1516,7 +1583,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
1516
1583
  * connecting, so a Zyfai hiccup must leave the user connected with their
1517
1584
  * profile unchanged, never fail activation.
1518
1585
  */
1519
- async applyPoolPolicy(policy) {
1586
+ async applyPoolPolicy(policy, opts = {}) {
1520
1587
  try {
1521
1588
  const { protocols: universe } = await this.sdk.getAvailableProtocols(
1522
1589
  this.supportedChainIds[0]
@@ -1530,7 +1597,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
1530
1597
  universeIds,
1531
1598
  chainsByProtocol
1532
1599
  });
1533
- let strategy;
1600
+ let strategy = opts.strategy;
1534
1601
  const desiredProtocols = deriveProtocols(
1535
1602
  policy,
1536
1603
  universeIds,
@@ -1610,6 +1677,50 @@ var ZyfaiAgent = class _ZyfaiAgent {
1610
1677
  );
1611
1678
  }
1612
1679
  }
1680
+ /**
1681
+ * The deposit-time Pools step's catalogue: every protocol Zyfai offers this
1682
+ * user, each with the pools (name, APY, TVL, link) selectable for `asset`
1683
+ * under `strategy`. Sourced from the same two Zyfai calls `applyPoolPolicy`
1684
+ * uses, so "shown" and "selectable" are one set. A protocol whose pool
1685
+ * lookup fails is skipped rather than failing the whole catalogue.
1686
+ */
1687
+ async getPoolCatalog(params) {
1688
+ const { protocols: universe } = await this.sdk.getAvailableProtocols(
1689
+ this.supportedChainIds[0]
1690
+ );
1691
+ const poolResults = await Promise.all(
1692
+ universe.map(async (protocol) => {
1693
+ try {
1694
+ const raw = await this.sdk.getAvailablePools(
1695
+ protocol.id,
1696
+ params.strategy
1697
+ );
1698
+ return [protocol.id, mapAvailablePools(raw)];
1699
+ } catch (error) {
1700
+ console.warn(
1701
+ `[zyfai] getPoolCatalog: getAvailablePools(${protocol.id}) failed, skipping (non-fatal):`,
1702
+ error instanceof Error ? error.message : String(error)
1703
+ );
1704
+ return null;
1705
+ }
1706
+ })
1707
+ );
1708
+ const poolsByProtocol = /* @__PURE__ */ new Map();
1709
+ for (const result of poolResults) {
1710
+ if (result) poolsByProtocol.set(result[0], result[1]);
1711
+ }
1712
+ return assemblePoolCatalog(universe, poolsByProtocol, params.asset);
1713
+ }
1714
+ /**
1715
+ * Apply a user's own pool selection (the deposit-time Pools step) to their
1716
+ * Zyfai profile. Same two writes as the org path — this is `applyPoolPolicy`
1717
+ * driven by the user's `policy` instead of the organization's, with the
1718
+ * deposit's chosen `strategy` passed through so pool names validate against
1719
+ * the strategy the user just picked rather than their stored one.
1720
+ */
1721
+ async setPoolSelection(policy, opts = {}) {
1722
+ await this.applyPoolPolicy(policy, opts);
1723
+ }
1613
1724
  /**
1614
1725
  * True when `smartWallet` is the backend-managed pool wallet Zyfai assigned
1615
1726
  * to this EOA — the "new wallet" kind that is predeployed on the chains it
@@ -1736,11 +1847,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
1736
1847
  );
1737
1848
  return;
1738
1849
  }
1739
- await this.ensureSessionKey(
1740
- ownerAddress,
1741
- chainId,
1742
- wallet.address
1743
- );
1850
+ await this.ensureSessionKey(ownerAddress, chainId, wallet.address);
1744
1851
  } catch (error) {
1745
1852
  console.warn(
1746
1853
  "[zyfai] session-key check at activation failed (non-fatal):",
@@ -1851,11 +1958,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
1851
1958
  "zyfai:onboard",
1852
1959
  "Safe already has code -> no deploy needed, checking the session key"
1853
1960
  );
1854
- await this.ensureSessionKey(
1855
- ownerAddress,
1856
- chainId,
1857
- wallet.address
1858
- );
1961
+ await this.ensureSessionKey(ownerAddress, chainId, wallet.address);
1859
1962
  return wallet.address;
1860
1963
  }
1861
1964
  const poolFirst = await this.isPredeployedPoolWallet(
@@ -2022,14 +2125,17 @@ var ZyfaiAgent = class _ZyfaiAgent {
2022
2125
  this.earningsRefreshes.set(key2, pending);
2023
2126
  return pending;
2024
2127
  }
2025
- async getAccountApy(state, chainId, days, tokenSymbol) {
2128
+ async getAccountApy(state, chainId, days, tokenSymbol, scopeChainId) {
2026
2129
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
2027
2130
  const raw = await this.sdk.getDailyApyHistory(smartWallet, days);
2028
- return mapApyHistory(raw, chainId, tokenSymbol);
2131
+ const scope = scopeChainId === null ? void 0 : scopeChainId ?? chainId;
2132
+ return mapApyHistory(raw, scope, tokenSymbol);
2029
2133
  }
2030
2134
  async getDailyEarnings(state, chainId, days, tokenSymbol) {
2031
2135
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
2032
- const start = new Date(Date.now() - (DayFilterMapping[days] + 1) * 864e5);
2136
+ const start = new Date(
2137
+ Date.now() - (DayFilterMapping[days] + 1) * 864e5
2138
+ );
2033
2139
  const raw = await this.sdk.getDailyEarnings(
2034
2140
  smartWallet,
2035
2141
  start.toISOString().slice(0, 10)
@@ -2151,6 +2257,9 @@ var ZyfaiAgent = class _ZyfaiAgent {
2151
2257
 
2152
2258
  // src/lib/history-fanout.ts
2153
2259
  var SEPARATOR = "@";
2260
+ function isSupportedChainId(value) {
2261
+ return SUPPORTED_CHAIN_IDS.includes(value);
2262
+ }
2154
2263
  function fanoutKey(leg) {
2155
2264
  return leg.chainId === void 0 ? leg.agentId : `${leg.agentId}${SEPARATOR}${leg.chainId}`;
2156
2265
  }
@@ -2173,6 +2282,16 @@ function decodeFanoutCursor(token) {
2173
2282
  }
2174
2283
  return parsed;
2175
2284
  }
2285
+ function transactionChainId(entry) {
2286
+ let found;
2287
+ for (const tx of entry.transactions ?? []) {
2288
+ if (tx.chainId === void 0) continue;
2289
+ if (!isSupportedChainId(tx.chainId)) return void 0;
2290
+ if (found !== void 0 && found !== tx.chainId) return void 0;
2291
+ found = tx.chainId;
2292
+ }
2293
+ return found;
2294
+ }
2176
2295
  function mergeHistoryPages(pages) {
2177
2296
  const merged = [];
2178
2297
  const nextCursorMap = {};
@@ -2191,7 +2310,9 @@ function mergeHistoryPages(pages) {
2191
2310
  const prior = seen.get(id);
2192
2311
  if (prior) {
2193
2312
  prior.legs += 1;
2194
- if (prior.entry.chainId !== e.chainId) delete prior.entry.chainId;
2313
+ const own = transactionChainId(prior.entry);
2314
+ if (own === void 0) delete prior.entry.chainId;
2315
+ else prior.entry.chainId = own;
2195
2316
  continue;
2196
2317
  }
2197
2318
  const entry = leg.chainId === void 0 ? { ...e } : { ...e, chainId: leg.chainId };
@@ -4312,6 +4433,7 @@ var OwneySDK = class {
4312
4433
  orgAgentConfig;
4313
4434
  orgAgentConfigPromise = null;
4314
4435
  zyfaiRpcUrls;
4436
+ userPoolCustomization = false;
4315
4437
  routingApiBaseUrl;
4316
4438
  referralSource;
4317
4439
  cachedSponsoredCallback = null;
@@ -4337,6 +4459,7 @@ var OwneySDK = class {
4337
4459
  this.routingApiBaseUrl = config.routingApiBaseUrl;
4338
4460
  this.paymasterServiceUrl = config.paymasterServiceUrl;
4339
4461
  this.referralSource = config.referralSource;
4462
+ this.userPoolCustomization = config.userPoolCustomization ?? false;
4340
4463
  }
4341
4464
  /**
4342
4465
  * Establish connection state for all subsequent SDK calls.
@@ -4563,7 +4686,8 @@ var OwneySDK = class {
4563
4686
  try {
4564
4687
  const policy = await this.getOrgAgentConfig();
4565
4688
  if (!policy) return;
4566
- await agent.applyOrgPolicy(state, chainId, policy);
4689
+ const effective = orgPolicyForConnect(policy, this.userPoolCustomization);
4690
+ await agent.applyOrgPolicy(state, chainId, effective);
4567
4691
  } catch (error) {
4568
4692
  console.warn(
4569
4693
  `[owney-sdk] applyOrgPolicy(${agent.id}) failed (non-fatal):`,
@@ -6380,20 +6504,57 @@ var OwneySDK = class {
6380
6504
  chainId
6381
6505
  };
6382
6506
  }
6507
+ /**
6508
+ * The deposit-time pool catalogue (ROUT-569): every protocol with its
6509
+ * selectable pools (name, APY, TVL, link) for `asset` under `strategy`.
6510
+ * Defaults to the zyfai agent, the only one with pool customization today.
6511
+ */
6512
+ async getPoolCatalog(params) {
6513
+ await this.ensureAgentsInitialized();
6514
+ const agent = this.getAgent(params.agentId ?? "zyfai");
6515
+ if (!agent.getPoolCatalog) {
6516
+ throw new OwneyError(
6517
+ "POOL_CATALOG_UNSUPPORTED",
6518
+ `Agent ${agent.id} does not support pool customization`
6519
+ );
6520
+ }
6521
+ return agent.getPoolCatalog({
6522
+ asset: params.asset,
6523
+ strategy: params.strategy
6524
+ });
6525
+ }
6526
+ /**
6527
+ * Apply a user's deposit-time pool selection to their account (ROUT-569).
6528
+ * `strategy` is the one chosen in the deposit flow, passed through so pool
6529
+ * names validate against it rather than the user's stored strategy.
6530
+ */
6531
+ async setPoolSelection(params) {
6532
+ await this.ensureAgentsInitialized();
6533
+ const agent = this.getAgent(params.agentId ?? "zyfai");
6534
+ if (!agent.setPoolSelection) {
6535
+ throw new OwneyError(
6536
+ "POOL_SELECTION_UNSUPPORTED",
6537
+ `Agent ${agent.id} does not support pool customization`
6538
+ );
6539
+ }
6540
+ await agent.setPoolSelection(params.policy, { strategy: params.strategy });
6541
+ }
6383
6542
  async getAccountApy({
6384
6543
  agentId,
6385
6544
  days,
6386
- tokenSymbol
6545
+ tokenSymbol,
6546
+ chainId: scope
6387
6547
  }) {
6388
6548
  const state = this.requireState();
6389
6549
  const chainId = this.requireChainId();
6550
+ const scopeChainId = scope === "all" ? null : scope;
6390
6551
  if (agentId) {
6391
6552
  const agent = this.getAgent(agentId);
6392
6553
  return this.readAgent(
6393
6554
  agent,
6394
6555
  "accountApy",
6395
- () => agent.getAccountApy(state, chainId, days, tokenSymbol),
6396
- { days, tokenSymbol }
6556
+ () => agent.getAccountApy(state, chainId, days, tokenSymbol, scopeChainId),
6557
+ { days, tokenSymbol, scopeChainId }
6397
6558
  );
6398
6559
  }
6399
6560
  const activeAgents = this.getActiveAgents();
@@ -6404,8 +6565,14 @@ var OwneySDK = class {
6404
6565
  const apy = await this.readAgent(
6405
6566
  agent,
6406
6567
  "accountApy",
6407
- () => agent.getAccountApy(state, chainId, days, tokenSymbol),
6408
- { days, tokenSymbol }
6568
+ () => agent.getAccountApy(
6569
+ state,
6570
+ chainId,
6571
+ days,
6572
+ tokenSymbol,
6573
+ scopeChainId
6574
+ ),
6575
+ { days, tokenSymbol, scopeChainId }
6409
6576
  );
6410
6577
  return [id, apy];
6411
6578
  })
package/dist/index.d.cts CHANGED
@@ -276,6 +276,15 @@ interface OwneySDKConfig {
276
276
  * already-deployed app: set `globalThis.__OWNEY_DEBUG__ = true` in devtools.
277
277
  */
278
278
  debug?: boolean;
279
+ /**
280
+ * When true, the organization's pool policy is NOT applied on connect, so a
281
+ * user's own deposit-time pool selection (ROUT-569) is never overwritten by
282
+ * the org-level one on their next reconnect. Splitting and the rest of the
283
+ * org policy still apply. Turn this on in apps that expose the deposit-time
284
+ * Pools step (the spread flow); leave it off where the partner dashboard's
285
+ * pool customization is the source of truth.
286
+ */
287
+ userPoolCustomization?: boolean;
279
288
  /**
280
289
  * Absolute URL of an ERC-7677 paymaster service. When set, deposits go out as
281
290
  * an EIP-5792 `wallet_sendCalls` with `capabilities.paymasterService`, so a
@@ -374,6 +383,18 @@ type WithdrawOptions = {
374
383
  type AccountApyOptions = {
375
384
  agentId?: AgentId;
376
385
  days: LookbackDays;
386
+ /**
387
+ * Chain to scope the daily series to, instead of the one `activateAgent`
388
+ * last set (ROUT-479).
389
+ *
390
+ * `"all"` blends every chain the wallet held that day, which is what the
391
+ * chart's "All chains" filter asks for. Unlike the history and earnings
392
+ * reads this needs no fan-out: the provider's daily-APY payload is already
393
+ * wallet-wide and names each position's chain, so the scope is a filter over
394
+ * one response rather than several calls. Omit for the active chain, which
395
+ * stays the default.
396
+ */
397
+ chainId?: OwneySupportedChainId | "all";
377
398
  /**
378
399
  * Optional asset symbol (e.g. "USDC", "WETH") to scope the daily APY series
379
400
  * to a specific asset on the active chain. Without it the series blends every
@@ -476,6 +497,61 @@ type OrgAgentConfig = {
476
497
  poolPolicy?: OrgPoolPolicy | null;
477
498
  };
478
499
 
500
+ /**
501
+ * Pool policy: Owney's org-level protocol/pool policy -> Zyfai's two mismatched
502
+ * write shapes.
503
+ *
504
+ * Zyfai splits "which protocols and pools may this user's funds sit in" across
505
+ * two calls that do not share an axis:
506
+ *
507
+ * updateUserProfile({ protocols }) per ASSET, chain-agnostic
508
+ * customizeBatch([{ protocolId, per (PROTOCOL, CHAIN), asset-agnostic
509
+ * chainId, pools, autoselect }])
510
+ *
511
+ * `protocols` is an explicit ALLOW-LIST: a protocol left out of it is disabled
512
+ * for that asset, on every chain, immediately. `customizeBatch` is the opposite
513
+ * shape — one record per (protocol, chain) pair, asset-agnostic, and per
514
+ * docs.zyf.ai it "creates or updates a record for the (user, protocol, chain)
515
+ * combination"; "previous customizations for the same combination are
516
+ * overwritten".
517
+ *
518
+ * That second property is the reason this file exists rather than a single
519
+ * flat translation: customizeBatch overwrites only the combinations it is
520
+ * GIVEN. The partner's stored policy (`OrgPoolPolicy.selections`) is sparse —
521
+ * only the protocols they actually narrowed appear — but the payload we send
522
+ * must be EXHAUSTIVE over every (protocol, chain) pair Zyfai offers this user.
523
+ * If we only sent the narrowed pairs, a partner who restricts Morpho, saves,
524
+ * then later re-ticks every pool box would never be able to undo it: nothing
525
+ * in the (sparse) policy would tell us to send a record clearing it, and the
526
+ * old restrictive record would stay live on the user's profile forever.
527
+ *
528
+ * Pure functions, kept out of the agent so the mapping is testable without a
529
+ * Zyfai client. See splitting.ts for the same pattern on the other lever.
530
+ */
531
+
532
+ /** One selectable pool, with the figures the deposit-time Pools step shows. */
533
+ type AvailablePool = {
534
+ chainId: number;
535
+ token: string;
536
+ name: string;
537
+ /** combined_apy (pool + rewards), already a percent. null when absent. */
538
+ apy: number | null;
539
+ /** TVL in USD. null when absent. See ZyfCustomizationPoolInfo: `tvl` is in
540
+ * the pool's own token, `tvlUsd` is the dollar figure — only the latter is
541
+ * safe to show with a currency symbol. */
542
+ tvlUsd: number | null;
543
+ /** Per-pool deep link (e.g. the Morpho vault page). null when absent. */
544
+ link: string | null;
545
+ };
546
+ /** One protocol with its pools for the deposit asset — what the Pools step
547
+ * renders: a protocol row that opens to a list of selectable pools. */
548
+ type PoolCatalogProtocol = {
549
+ protocolId: string;
550
+ name: string;
551
+ logo: string | null;
552
+ pools: AvailablePool[];
553
+ };
554
+
479
555
  interface OwneyDepositResult {
480
556
  txHash: string;
481
557
  smartWallet: string;
@@ -606,6 +682,28 @@ interface ApyHistoryPoint {
606
682
  unit: string;
607
683
  };
608
684
  }
685
+ /**
686
+ * What the APY chart's chain/asset menus are allowed to offer, and how far
687
+ * back each option reaches (ROUT-479).
688
+ *
689
+ * An option only earns a place if some day in the window held a real position
690
+ * on it — a balance and a rate. Listing anything else would offer a filter
691
+ * that draws a blank chart. `days` is what the menu prints beside each row,
692
+ * and the legs genuinely differ: a wallet can hold USDC for a fortnight and
693
+ * WETH for three days.
694
+ */
695
+ interface ApyCoverage {
696
+ chains: {
697
+ chainId: number;
698
+ days: number;
699
+ }[];
700
+ /** Legs whose `tokenSymbol` the provider left unset are absent — they cannot
701
+ * be named, so they cannot be picked. Their chain still counts. */
702
+ assets: {
703
+ asset: string;
704
+ days: number;
705
+ }[];
706
+ }
609
707
  interface AccountAgentApy {
610
708
  walletAddress: string;
611
709
  weightedApyAfterFee?: number;
@@ -616,6 +714,8 @@ interface AccountAgentApy {
616
714
  * date. Empty when the provider returns no daily history.
617
715
  */
618
716
  history?: ApyHistoryPoint[];
717
+ /** Which chains/assets this history can be filtered down to. */
718
+ coverage?: ApyCoverage;
619
719
  }
620
720
  interface OwneyAccountApy {
621
721
  totalApy: string;
@@ -779,6 +879,23 @@ interface IAgent {
779
879
  * unchanged rather than break the connect.
780
880
  */
781
881
  applyOrgPolicy?(state: ConnectionState, chainId: number, policy: OrgAgentConfig): Promise<void>;
882
+ /**
883
+ * The deposit-time pool catalogue for one asset and strategy: protocols with
884
+ * their selectable pools and figures. Optional — an agent with no pool
885
+ * customization omits it. (ROUT-569)
886
+ */
887
+ getPoolCatalog?(params: {
888
+ asset: string;
889
+ strategy: "conservative" | "aggressive";
890
+ }): Promise<PoolCatalogProtocol[]>;
891
+ /**
892
+ * Apply a user's own pool selection to their account, driven by the
893
+ * deposit-time Pools step. Optional, and non-fatal like `applyOrgPolicy`.
894
+ * (ROUT-569)
895
+ */
896
+ setPoolSelection?(policy: OrgPoolPolicy, opts?: {
897
+ strategy?: "conservative" | "aggressive";
898
+ }): Promise<void>;
782
899
  deposit(state: ConnectionState, chainId: number, amount: string, asset: OwneySupportedTokens, depositCallback?: DepositCallback): Promise<OwneyDepositResult>;
783
900
  withdraw(state: ConnectionState, chainId: number, token: OwneySupportedTokens, amount?: string): Promise<AgentWithdrawResult>;
784
901
  getBalances(state: ConnectionState, chainId: number): Promise<AgentBalance>;
@@ -793,7 +910,14 @@ interface IAgent {
793
910
  * Optional asset symbol ("USDC" / "WETH") scoping the daily series to a
794
911
  * single asset on the chain. Agents without per-asset positions ignore it.
795
912
  */
796
- tokenSymbol?: string): Promise<AccountAgentApy>;
913
+ tokenSymbol?: string,
914
+ /**
915
+ * Chain the series is scoped to, when that differs from the chain the
916
+ * connection is on. `null` spans every chain the wallet held (ROUT-479);
917
+ * omitted means `chainId`. Kept apart from `chainId` because that one
918
+ * still resolves the wallet and must stay a real, connected chain.
919
+ */
920
+ scopeChainId?: number | null): Promise<AccountAgentApy>;
797
921
  /**
798
922
  * Daily cumulative NET earnings for the window, scoped to a chain/asset.
799
923
  * Backs the "recent earnings" subline, whose figure must reconcile with the
@@ -822,7 +946,7 @@ type AgentApyOptions = {
822
946
  chainId?: number;
823
947
  };
824
948
 
825
- type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "WALLET_NOT_DEPLOYED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "AGENT_DISABLED" | "CHAIN_UNSUPPORTED" | "CHAIN_NO_COMPATIBLE_AGENTS" | "CHAIN_MISMATCH" | "ASSET_UNSUPPORTED" | "ASSET_NO_COMPATIBLE_AGENTS" | "DEPOSIT_BALANCE_UNAVAILABLE" | "DEPOSIT_PARTIAL_FAILURE" | "DEPOSIT_AMOUNT_BELOW_MINIMUM" | "DEPOSIT_CALLBACK_REQUIRED" | "DEPOSIT_CALLBACK_INVALID" | "DEPOSIT_NO_PERMITTED_TOKENS" | "DEPOSIT_INSUFFICIENT_BALANCE" | "WITHDRAW_NO_PERMITTED_TOKENS" | "WITHDRAW_INSUFFICIENT_BALANCE" | "WITHDRAW_ALL_FAILED" | "WITHDRAW_NOTHING_TO_WITHDRAW" | "WITHDRAW_PARTIAL_FAILURE" | "WITHDRAW_FAILED" | "AGENT_RATE_LIMITED" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_NO_AGENTS" | "SPONSOR_REQUEST_FAILED" | "PERMIT2_APPROVAL_REQUIRED" | "SPONSORED_CALLS_UNSUPPORTED" | "SPONSORED_CALLS_NO_ID" | "SPONSORED_CALLS_NO_RECEIPT" | "SWAP_DISABLED" | "SWAP_RATE_LIMITED" | "SWAP_REQUEST_FAILED" | "SWAP_QUOTE_FAILED" | "SWAP_UNSUPPORTED_PAIR" | "SWAP_APPROVAL_REQUIRED" | "SWAP_BELOW_DEPOSIT_MINIMUM" | "SWAP_DEPOSIT_PENDING" | "SWAP_WALLET_PROMPT_TIMEOUT" | "SWAP_INSUFFICIENT_GAS" | "SWAP_NATIVE_UNSUPPORTED" | "SWAP_ORDER_EXPIRED" | "SWAP_ORDER_REFUNDED" | "SWAP_ORDER_CANCELLED" | "WITHDRAW_ARRIVAL_TIMEOUT" | "WITHDRAW_SWAP_FAILED" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "VALIDATION_INVALID_DAYS";
949
+ type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "WALLET_NOT_DEPLOYED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "AGENT_DISABLED" | "CHAIN_UNSUPPORTED" | "CHAIN_NO_COMPATIBLE_AGENTS" | "CHAIN_MISMATCH" | "ASSET_UNSUPPORTED" | "ASSET_NO_COMPATIBLE_AGENTS" | "DEPOSIT_BALANCE_UNAVAILABLE" | "DEPOSIT_PARTIAL_FAILURE" | "DEPOSIT_AMOUNT_BELOW_MINIMUM" | "DEPOSIT_CALLBACK_REQUIRED" | "DEPOSIT_CALLBACK_INVALID" | "DEPOSIT_NO_PERMITTED_TOKENS" | "DEPOSIT_INSUFFICIENT_BALANCE" | "WITHDRAW_NO_PERMITTED_TOKENS" | "WITHDRAW_INSUFFICIENT_BALANCE" | "WITHDRAW_ALL_FAILED" | "WITHDRAW_NOTHING_TO_WITHDRAW" | "WITHDRAW_PARTIAL_FAILURE" | "WITHDRAW_FAILED" | "AGENT_RATE_LIMITED" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_NO_AGENTS" | "SPONSOR_REQUEST_FAILED" | "PERMIT2_APPROVAL_REQUIRED" | "SPONSORED_CALLS_UNSUPPORTED" | "SPONSORED_CALLS_NO_ID" | "SPONSORED_CALLS_NO_RECEIPT" | "SWAP_DISABLED" | "SWAP_RATE_LIMITED" | "SWAP_REQUEST_FAILED" | "SWAP_QUOTE_FAILED" | "SWAP_UNSUPPORTED_PAIR" | "SWAP_APPROVAL_REQUIRED" | "SWAP_BELOW_DEPOSIT_MINIMUM" | "SWAP_DEPOSIT_PENDING" | "SWAP_WALLET_PROMPT_TIMEOUT" | "SWAP_INSUFFICIENT_GAS" | "SWAP_NATIVE_UNSUPPORTED" | "SWAP_ORDER_EXPIRED" | "SWAP_ORDER_REFUNDED" | "SWAP_ORDER_CANCELLED" | "WITHDRAW_ARRIVAL_TIMEOUT" | "WITHDRAW_SWAP_FAILED" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "POOL_CATALOG_UNSUPPORTED" | "POOL_SELECTION_UNSUPPORTED" | "VALIDATION_INVALID_DAYS";
826
950
  declare class OwneyError extends Error {
827
951
  readonly code: OwneyErrorCode;
828
952
  readonly details?: Record<string, unknown>;
@@ -986,6 +1110,7 @@ declare class OwneySDK {
986
1110
  private orgAgentConfig;
987
1111
  private orgAgentConfigPromise;
988
1112
  private zyfaiRpcUrls?;
1113
+ private userPoolCustomization;
989
1114
  private routingApiBaseUrl?;
990
1115
  private referralSource?;
991
1116
  private cachedSponsoredCallback;
@@ -1596,7 +1721,27 @@ declare class OwneySDK {
1596
1721
  * agents that answered.
1597
1722
  */
1598
1723
  getDailyEarnings({ agentId, days, tokenSymbol, chainIds, }: DailyEarningsOptions): Promise<AccountDailyEarnings>;
1599
- getAccountApy({ agentId, days, tokenSymbol, }: AccountApyOptions): Promise<OwneyAccountApy | AccountAgentApy>;
1724
+ /**
1725
+ * The deposit-time pool catalogue (ROUT-569): every protocol with its
1726
+ * selectable pools (name, APY, TVL, link) for `asset` under `strategy`.
1727
+ * Defaults to the zyfai agent, the only one with pool customization today.
1728
+ */
1729
+ getPoolCatalog(params: {
1730
+ asset: OwneySupportedTokens;
1731
+ strategy: "conservative" | "aggressive";
1732
+ agentId?: AgentId;
1733
+ }): Promise<PoolCatalogProtocol[]>;
1734
+ /**
1735
+ * Apply a user's deposit-time pool selection to their account (ROUT-569).
1736
+ * `strategy` is the one chosen in the deposit flow, passed through so pool
1737
+ * names validate against it rather than the user's stored strategy.
1738
+ */
1739
+ setPoolSelection(params: {
1740
+ policy: OrgPoolPolicy;
1741
+ strategy?: "conservative" | "aggressive";
1742
+ agentId?: AgentId;
1743
+ }): Promise<void>;
1744
+ getAccountApy({ agentId, days, tokenSymbol, chainId: scope, }: AccountApyOptions): Promise<OwneyAccountApy | AccountAgentApy>;
1600
1745
  /**
1601
1746
  * Get transaction history for a specific agent, or all agents.
1602
1747
  *
@@ -1738,4 +1883,4 @@ type StoredOrder = {
1738
1883
  */
1739
1884
  declare function listOrders(now?: number): StoredOrder[];
1740
1885
 
1741
- export { type AccountAgentApy, type AccountApyOptions, type AccountDailyEarnings, type AgentApy, type AgentApyDetails, type AgentBalance, AgentChainIncompatibleError, type AgentEarnings, type AgentHistoryEntry, type AgentHistoryPosition, type AgentId, AgentNotFoundError, type AgentSupportedAsset, type AgentSupportedAssets, type AgentUserProfile, type AgentWithdrawResult, type AgentsApyOptions, type AllocationAgentApy, type AllocationApyOptions, type ApyByChainAndAsset, type ApyHistoryPoint, type Asset, type AssetDailyEarnings, type AvailableAgent, type AvailableAgentsOptions, type ConnectionState, type DailyApyDays, type DailyEarningsOptions, type DailyEarningsPoint, type DepositCallback, type DepositOptions, type HistoryAction, type HistoryFilters, type HistoryOptions, type HistoryTransaction, type IAgent, InvalidHistoryCursorError, type LookbackDays, NotConnectedError, type OwneyAccountApy, type OwneyAgentApy, type OwneyAgentHistory, type OwneyAllocationApy, type OwneyBalances, type OwneyDepositResult, type OwneyEarnings, OwneyError, type OwneyErrorCode, type OwneyMultiDepositResult, type OwneyPosition, OwneySDK, type OwneySDKConfig, type OwneySIWXConfig, type OwneySupportedChainId, type OwneySupportedChains, type OwneySupportedTokens, type OwneyToken, type OwneyUserProfile, type OwneyWithdrawAllLeg, type OwneyWithdrawAllResult, type OwneyWithdrawResult, type PendingDeposit, type RebalanceLog, type SwapChainTokens, type SwapDirection, type SwapOrderStatus, type SwapQuote, type SwapQuoteParams, type SwapRail, type SwapStage, type SwapTokenInfo, type SwapTokenSearchResult, type WithdrawOptions, type WithdrawSwapRecord, type WithdrawSwapStatus, clearWithdrawSwap, createOwneySIWX, listPendingDeposits, listOrders as listPendingSwaps, listWithdrawSwaps, saveWithdrawSwap, setOwneyDebug, subscribeWithdrawSwaps, updateWithdrawSwap };
1886
+ export { type AccountAgentApy, type AccountApyOptions, type AccountDailyEarnings, type AgentApy, type AgentApyDetails, type AgentBalance, AgentChainIncompatibleError, type AgentEarnings, type AgentHistoryEntry, type AgentHistoryPosition, type AgentId, AgentNotFoundError, type AgentSupportedAsset, type AgentSupportedAssets, type AgentUserProfile, type AgentWithdrawResult, type AgentsApyOptions, type AllocationAgentApy, type AllocationApyOptions, type ApyByChainAndAsset, type ApyCoverage, type ApyHistoryPoint, type Asset, type AssetDailyEarnings, type AvailableAgent, type AvailableAgentsOptions, type AvailablePool, type ConnectionState, type DailyApyDays, type DailyEarningsOptions, type DailyEarningsPoint, type DepositCallback, type DepositOptions, type HistoryAction, type HistoryFilters, type HistoryOptions, type HistoryTransaction, type IAgent, InvalidHistoryCursorError, type LookbackDays, NotConnectedError, type OrgPoolPolicy, type OrgPoolSelection, type OwneyAccountApy, type OwneyAgentApy, type OwneyAgentHistory, type OwneyAllocationApy, type OwneyBalances, type OwneyDepositResult, type OwneyEarnings, OwneyError, type OwneyErrorCode, type OwneyMultiDepositResult, type OwneyPosition, OwneySDK, type OwneySDKConfig, type OwneySIWXConfig, type OwneySupportedChainId, type OwneySupportedChains, type OwneySupportedTokens, type OwneyToken, type OwneyUserProfile, type OwneyWithdrawAllLeg, type OwneyWithdrawAllResult, type OwneyWithdrawResult, type PendingDeposit, type PoolCatalogProtocol, type RebalanceLog, type SwapChainTokens, type SwapDirection, type SwapOrderStatus, type SwapQuote, type SwapQuoteParams, type SwapRail, type SwapStage, type SwapTokenInfo, type SwapTokenSearchResult, type WithdrawOptions, type WithdrawSwapRecord, type WithdrawSwapStatus, clearWithdrawSwap, createOwneySIWX, listPendingDeposits, listOrders as listPendingSwaps, listWithdrawSwaps, saveWithdrawSwap, setOwneyDebug, subscribeWithdrawSwaps, updateWithdrawSwap };
package/dist/index.d.ts CHANGED
@@ -276,6 +276,15 @@ interface OwneySDKConfig {
276
276
  * already-deployed app: set `globalThis.__OWNEY_DEBUG__ = true` in devtools.
277
277
  */
278
278
  debug?: boolean;
279
+ /**
280
+ * When true, the organization's pool policy is NOT applied on connect, so a
281
+ * user's own deposit-time pool selection (ROUT-569) is never overwritten by
282
+ * the org-level one on their next reconnect. Splitting and the rest of the
283
+ * org policy still apply. Turn this on in apps that expose the deposit-time
284
+ * Pools step (the spread flow); leave it off where the partner dashboard's
285
+ * pool customization is the source of truth.
286
+ */
287
+ userPoolCustomization?: boolean;
279
288
  /**
280
289
  * Absolute URL of an ERC-7677 paymaster service. When set, deposits go out as
281
290
  * an EIP-5792 `wallet_sendCalls` with `capabilities.paymasterService`, so a
@@ -374,6 +383,18 @@ type WithdrawOptions = {
374
383
  type AccountApyOptions = {
375
384
  agentId?: AgentId;
376
385
  days: LookbackDays;
386
+ /**
387
+ * Chain to scope the daily series to, instead of the one `activateAgent`
388
+ * last set (ROUT-479).
389
+ *
390
+ * `"all"` blends every chain the wallet held that day, which is what the
391
+ * chart's "All chains" filter asks for. Unlike the history and earnings
392
+ * reads this needs no fan-out: the provider's daily-APY payload is already
393
+ * wallet-wide and names each position's chain, so the scope is a filter over
394
+ * one response rather than several calls. Omit for the active chain, which
395
+ * stays the default.
396
+ */
397
+ chainId?: OwneySupportedChainId | "all";
377
398
  /**
378
399
  * Optional asset symbol (e.g. "USDC", "WETH") to scope the daily APY series
379
400
  * to a specific asset on the active chain. Without it the series blends every
@@ -476,6 +497,61 @@ type OrgAgentConfig = {
476
497
  poolPolicy?: OrgPoolPolicy | null;
477
498
  };
478
499
 
500
+ /**
501
+ * Pool policy: Owney's org-level protocol/pool policy -> Zyfai's two mismatched
502
+ * write shapes.
503
+ *
504
+ * Zyfai splits "which protocols and pools may this user's funds sit in" across
505
+ * two calls that do not share an axis:
506
+ *
507
+ * updateUserProfile({ protocols }) per ASSET, chain-agnostic
508
+ * customizeBatch([{ protocolId, per (PROTOCOL, CHAIN), asset-agnostic
509
+ * chainId, pools, autoselect }])
510
+ *
511
+ * `protocols` is an explicit ALLOW-LIST: a protocol left out of it is disabled
512
+ * for that asset, on every chain, immediately. `customizeBatch` is the opposite
513
+ * shape — one record per (protocol, chain) pair, asset-agnostic, and per
514
+ * docs.zyf.ai it "creates or updates a record for the (user, protocol, chain)
515
+ * combination"; "previous customizations for the same combination are
516
+ * overwritten".
517
+ *
518
+ * That second property is the reason this file exists rather than a single
519
+ * flat translation: customizeBatch overwrites only the combinations it is
520
+ * GIVEN. The partner's stored policy (`OrgPoolPolicy.selections`) is sparse —
521
+ * only the protocols they actually narrowed appear — but the payload we send
522
+ * must be EXHAUSTIVE over every (protocol, chain) pair Zyfai offers this user.
523
+ * If we only sent the narrowed pairs, a partner who restricts Morpho, saves,
524
+ * then later re-ticks every pool box would never be able to undo it: nothing
525
+ * in the (sparse) policy would tell us to send a record clearing it, and the
526
+ * old restrictive record would stay live on the user's profile forever.
527
+ *
528
+ * Pure functions, kept out of the agent so the mapping is testable without a
529
+ * Zyfai client. See splitting.ts for the same pattern on the other lever.
530
+ */
531
+
532
+ /** One selectable pool, with the figures the deposit-time Pools step shows. */
533
+ type AvailablePool = {
534
+ chainId: number;
535
+ token: string;
536
+ name: string;
537
+ /** combined_apy (pool + rewards), already a percent. null when absent. */
538
+ apy: number | null;
539
+ /** TVL in USD. null when absent. See ZyfCustomizationPoolInfo: `tvl` is in
540
+ * the pool's own token, `tvlUsd` is the dollar figure — only the latter is
541
+ * safe to show with a currency symbol. */
542
+ tvlUsd: number | null;
543
+ /** Per-pool deep link (e.g. the Morpho vault page). null when absent. */
544
+ link: string | null;
545
+ };
546
+ /** One protocol with its pools for the deposit asset — what the Pools step
547
+ * renders: a protocol row that opens to a list of selectable pools. */
548
+ type PoolCatalogProtocol = {
549
+ protocolId: string;
550
+ name: string;
551
+ logo: string | null;
552
+ pools: AvailablePool[];
553
+ };
554
+
479
555
  interface OwneyDepositResult {
480
556
  txHash: string;
481
557
  smartWallet: string;
@@ -606,6 +682,28 @@ interface ApyHistoryPoint {
606
682
  unit: string;
607
683
  };
608
684
  }
685
+ /**
686
+ * What the APY chart's chain/asset menus are allowed to offer, and how far
687
+ * back each option reaches (ROUT-479).
688
+ *
689
+ * An option only earns a place if some day in the window held a real position
690
+ * on it — a balance and a rate. Listing anything else would offer a filter
691
+ * that draws a blank chart. `days` is what the menu prints beside each row,
692
+ * and the legs genuinely differ: a wallet can hold USDC for a fortnight and
693
+ * WETH for three days.
694
+ */
695
+ interface ApyCoverage {
696
+ chains: {
697
+ chainId: number;
698
+ days: number;
699
+ }[];
700
+ /** Legs whose `tokenSymbol` the provider left unset are absent — they cannot
701
+ * be named, so they cannot be picked. Their chain still counts. */
702
+ assets: {
703
+ asset: string;
704
+ days: number;
705
+ }[];
706
+ }
609
707
  interface AccountAgentApy {
610
708
  walletAddress: string;
611
709
  weightedApyAfterFee?: number;
@@ -616,6 +714,8 @@ interface AccountAgentApy {
616
714
  * date. Empty when the provider returns no daily history.
617
715
  */
618
716
  history?: ApyHistoryPoint[];
717
+ /** Which chains/assets this history can be filtered down to. */
718
+ coverage?: ApyCoverage;
619
719
  }
620
720
  interface OwneyAccountApy {
621
721
  totalApy: string;
@@ -779,6 +879,23 @@ interface IAgent {
779
879
  * unchanged rather than break the connect.
780
880
  */
781
881
  applyOrgPolicy?(state: ConnectionState, chainId: number, policy: OrgAgentConfig): Promise<void>;
882
+ /**
883
+ * The deposit-time pool catalogue for one asset and strategy: protocols with
884
+ * their selectable pools and figures. Optional — an agent with no pool
885
+ * customization omits it. (ROUT-569)
886
+ */
887
+ getPoolCatalog?(params: {
888
+ asset: string;
889
+ strategy: "conservative" | "aggressive";
890
+ }): Promise<PoolCatalogProtocol[]>;
891
+ /**
892
+ * Apply a user's own pool selection to their account, driven by the
893
+ * deposit-time Pools step. Optional, and non-fatal like `applyOrgPolicy`.
894
+ * (ROUT-569)
895
+ */
896
+ setPoolSelection?(policy: OrgPoolPolicy, opts?: {
897
+ strategy?: "conservative" | "aggressive";
898
+ }): Promise<void>;
782
899
  deposit(state: ConnectionState, chainId: number, amount: string, asset: OwneySupportedTokens, depositCallback?: DepositCallback): Promise<OwneyDepositResult>;
783
900
  withdraw(state: ConnectionState, chainId: number, token: OwneySupportedTokens, amount?: string): Promise<AgentWithdrawResult>;
784
901
  getBalances(state: ConnectionState, chainId: number): Promise<AgentBalance>;
@@ -793,7 +910,14 @@ interface IAgent {
793
910
  * Optional asset symbol ("USDC" / "WETH") scoping the daily series to a
794
911
  * single asset on the chain. Agents without per-asset positions ignore it.
795
912
  */
796
- tokenSymbol?: string): Promise<AccountAgentApy>;
913
+ tokenSymbol?: string,
914
+ /**
915
+ * Chain the series is scoped to, when that differs from the chain the
916
+ * connection is on. `null` spans every chain the wallet held (ROUT-479);
917
+ * omitted means `chainId`. Kept apart from `chainId` because that one
918
+ * still resolves the wallet and must stay a real, connected chain.
919
+ */
920
+ scopeChainId?: number | null): Promise<AccountAgentApy>;
797
921
  /**
798
922
  * Daily cumulative NET earnings for the window, scoped to a chain/asset.
799
923
  * Backs the "recent earnings" subline, whose figure must reconcile with the
@@ -822,7 +946,7 @@ type AgentApyOptions = {
822
946
  chainId?: number;
823
947
  };
824
948
 
825
- type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "WALLET_NOT_DEPLOYED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "AGENT_DISABLED" | "CHAIN_UNSUPPORTED" | "CHAIN_NO_COMPATIBLE_AGENTS" | "CHAIN_MISMATCH" | "ASSET_UNSUPPORTED" | "ASSET_NO_COMPATIBLE_AGENTS" | "DEPOSIT_BALANCE_UNAVAILABLE" | "DEPOSIT_PARTIAL_FAILURE" | "DEPOSIT_AMOUNT_BELOW_MINIMUM" | "DEPOSIT_CALLBACK_REQUIRED" | "DEPOSIT_CALLBACK_INVALID" | "DEPOSIT_NO_PERMITTED_TOKENS" | "DEPOSIT_INSUFFICIENT_BALANCE" | "WITHDRAW_NO_PERMITTED_TOKENS" | "WITHDRAW_INSUFFICIENT_BALANCE" | "WITHDRAW_ALL_FAILED" | "WITHDRAW_NOTHING_TO_WITHDRAW" | "WITHDRAW_PARTIAL_FAILURE" | "WITHDRAW_FAILED" | "AGENT_RATE_LIMITED" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_NO_AGENTS" | "SPONSOR_REQUEST_FAILED" | "PERMIT2_APPROVAL_REQUIRED" | "SPONSORED_CALLS_UNSUPPORTED" | "SPONSORED_CALLS_NO_ID" | "SPONSORED_CALLS_NO_RECEIPT" | "SWAP_DISABLED" | "SWAP_RATE_LIMITED" | "SWAP_REQUEST_FAILED" | "SWAP_QUOTE_FAILED" | "SWAP_UNSUPPORTED_PAIR" | "SWAP_APPROVAL_REQUIRED" | "SWAP_BELOW_DEPOSIT_MINIMUM" | "SWAP_DEPOSIT_PENDING" | "SWAP_WALLET_PROMPT_TIMEOUT" | "SWAP_INSUFFICIENT_GAS" | "SWAP_NATIVE_UNSUPPORTED" | "SWAP_ORDER_EXPIRED" | "SWAP_ORDER_REFUNDED" | "SWAP_ORDER_CANCELLED" | "WITHDRAW_ARRIVAL_TIMEOUT" | "WITHDRAW_SWAP_FAILED" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "VALIDATION_INVALID_DAYS";
949
+ type OwneyErrorCode = "NOT_CONNECTED" | "NO_ACTIVE_CHAIN" | "WALLET_NO_ACCOUNTS" | "WALLET_ADDRESS_REQUIRED" | "WALLET_NOT_DEPLOYED" | "AGENT_NOT_FOUND" | "AGENT_CHAIN_INCOMPATIBLE" | "AGENT_EMPTY_LIST" | "AGENT_DISABLED" | "CHAIN_UNSUPPORTED" | "CHAIN_NO_COMPATIBLE_AGENTS" | "CHAIN_MISMATCH" | "ASSET_UNSUPPORTED" | "ASSET_NO_COMPATIBLE_AGENTS" | "DEPOSIT_BALANCE_UNAVAILABLE" | "DEPOSIT_PARTIAL_FAILURE" | "DEPOSIT_AMOUNT_BELOW_MINIMUM" | "DEPOSIT_CALLBACK_REQUIRED" | "DEPOSIT_CALLBACK_INVALID" | "DEPOSIT_NO_PERMITTED_TOKENS" | "DEPOSIT_INSUFFICIENT_BALANCE" | "WITHDRAW_NO_PERMITTED_TOKENS" | "WITHDRAW_INSUFFICIENT_BALANCE" | "WITHDRAW_ALL_FAILED" | "WITHDRAW_NOTHING_TO_WITHDRAW" | "WITHDRAW_PARTIAL_FAILURE" | "WITHDRAW_FAILED" | "AGENT_RATE_LIMITED" | "API_ROUTING_ERROR" | "API_ROUTING_FAILED" | "API_NO_AGENTS" | "SPONSOR_REQUEST_FAILED" | "PERMIT2_APPROVAL_REQUIRED" | "SPONSORED_CALLS_UNSUPPORTED" | "SPONSORED_CALLS_NO_ID" | "SPONSORED_CALLS_NO_RECEIPT" | "SWAP_DISABLED" | "SWAP_RATE_LIMITED" | "SWAP_REQUEST_FAILED" | "SWAP_QUOTE_FAILED" | "SWAP_UNSUPPORTED_PAIR" | "SWAP_APPROVAL_REQUIRED" | "SWAP_BELOW_DEPOSIT_MINIMUM" | "SWAP_DEPOSIT_PENDING" | "SWAP_WALLET_PROMPT_TIMEOUT" | "SWAP_INSUFFICIENT_GAS" | "SWAP_NATIVE_UNSUPPORTED" | "SWAP_ORDER_EXPIRED" | "SWAP_ORDER_REFUNDED" | "SWAP_ORDER_CANCELLED" | "WITHDRAW_ARRIVAL_TIMEOUT" | "WITHDRAW_SWAP_FAILED" | "BALANCE_ALL_FAILED" | "ALLOCATION_ALL_FAILED" | "POOL_CATALOG_UNSUPPORTED" | "POOL_SELECTION_UNSUPPORTED" | "VALIDATION_INVALID_DAYS";
826
950
  declare class OwneyError extends Error {
827
951
  readonly code: OwneyErrorCode;
828
952
  readonly details?: Record<string, unknown>;
@@ -986,6 +1110,7 @@ declare class OwneySDK {
986
1110
  private orgAgentConfig;
987
1111
  private orgAgentConfigPromise;
988
1112
  private zyfaiRpcUrls?;
1113
+ private userPoolCustomization;
989
1114
  private routingApiBaseUrl?;
990
1115
  private referralSource?;
991
1116
  private cachedSponsoredCallback;
@@ -1596,7 +1721,27 @@ declare class OwneySDK {
1596
1721
  * agents that answered.
1597
1722
  */
1598
1723
  getDailyEarnings({ agentId, days, tokenSymbol, chainIds, }: DailyEarningsOptions): Promise<AccountDailyEarnings>;
1599
- getAccountApy({ agentId, days, tokenSymbol, }: AccountApyOptions): Promise<OwneyAccountApy | AccountAgentApy>;
1724
+ /**
1725
+ * The deposit-time pool catalogue (ROUT-569): every protocol with its
1726
+ * selectable pools (name, APY, TVL, link) for `asset` under `strategy`.
1727
+ * Defaults to the zyfai agent, the only one with pool customization today.
1728
+ */
1729
+ getPoolCatalog(params: {
1730
+ asset: OwneySupportedTokens;
1731
+ strategy: "conservative" | "aggressive";
1732
+ agentId?: AgentId;
1733
+ }): Promise<PoolCatalogProtocol[]>;
1734
+ /**
1735
+ * Apply a user's deposit-time pool selection to their account (ROUT-569).
1736
+ * `strategy` is the one chosen in the deposit flow, passed through so pool
1737
+ * names validate against it rather than the user's stored strategy.
1738
+ */
1739
+ setPoolSelection(params: {
1740
+ policy: OrgPoolPolicy;
1741
+ strategy?: "conservative" | "aggressive";
1742
+ agentId?: AgentId;
1743
+ }): Promise<void>;
1744
+ getAccountApy({ agentId, days, tokenSymbol, chainId: scope, }: AccountApyOptions): Promise<OwneyAccountApy | AccountAgentApy>;
1600
1745
  /**
1601
1746
  * Get transaction history for a specific agent, or all agents.
1602
1747
  *
@@ -1738,4 +1883,4 @@ type StoredOrder = {
1738
1883
  */
1739
1884
  declare function listOrders(now?: number): StoredOrder[];
1740
1885
 
1741
- export { type AccountAgentApy, type AccountApyOptions, type AccountDailyEarnings, type AgentApy, type AgentApyDetails, type AgentBalance, AgentChainIncompatibleError, type AgentEarnings, type AgentHistoryEntry, type AgentHistoryPosition, type AgentId, AgentNotFoundError, type AgentSupportedAsset, type AgentSupportedAssets, type AgentUserProfile, type AgentWithdrawResult, type AgentsApyOptions, type AllocationAgentApy, type AllocationApyOptions, type ApyByChainAndAsset, type ApyHistoryPoint, type Asset, type AssetDailyEarnings, type AvailableAgent, type AvailableAgentsOptions, type ConnectionState, type DailyApyDays, type DailyEarningsOptions, type DailyEarningsPoint, type DepositCallback, type DepositOptions, type HistoryAction, type HistoryFilters, type HistoryOptions, type HistoryTransaction, type IAgent, InvalidHistoryCursorError, type LookbackDays, NotConnectedError, type OwneyAccountApy, type OwneyAgentApy, type OwneyAgentHistory, type OwneyAllocationApy, type OwneyBalances, type OwneyDepositResult, type OwneyEarnings, OwneyError, type OwneyErrorCode, type OwneyMultiDepositResult, type OwneyPosition, OwneySDK, type OwneySDKConfig, type OwneySIWXConfig, type OwneySupportedChainId, type OwneySupportedChains, type OwneySupportedTokens, type OwneyToken, type OwneyUserProfile, type OwneyWithdrawAllLeg, type OwneyWithdrawAllResult, type OwneyWithdrawResult, type PendingDeposit, type RebalanceLog, type SwapChainTokens, type SwapDirection, type SwapOrderStatus, type SwapQuote, type SwapQuoteParams, type SwapRail, type SwapStage, type SwapTokenInfo, type SwapTokenSearchResult, type WithdrawOptions, type WithdrawSwapRecord, type WithdrawSwapStatus, clearWithdrawSwap, createOwneySIWX, listPendingDeposits, listOrders as listPendingSwaps, listWithdrawSwaps, saveWithdrawSwap, setOwneyDebug, subscribeWithdrawSwaps, updateWithdrawSwap };
1886
+ export { type AccountAgentApy, type AccountApyOptions, type AccountDailyEarnings, type AgentApy, type AgentApyDetails, type AgentBalance, AgentChainIncompatibleError, type AgentEarnings, type AgentHistoryEntry, type AgentHistoryPosition, type AgentId, AgentNotFoundError, type AgentSupportedAsset, type AgentSupportedAssets, type AgentUserProfile, type AgentWithdrawResult, type AgentsApyOptions, type AllocationAgentApy, type AllocationApyOptions, type ApyByChainAndAsset, type ApyCoverage, type ApyHistoryPoint, type Asset, type AssetDailyEarnings, type AvailableAgent, type AvailableAgentsOptions, type AvailablePool, type ConnectionState, type DailyApyDays, type DailyEarningsOptions, type DailyEarningsPoint, type DepositCallback, type DepositOptions, type HistoryAction, type HistoryFilters, type HistoryOptions, type HistoryTransaction, type IAgent, InvalidHistoryCursorError, type LookbackDays, NotConnectedError, type OrgPoolPolicy, type OrgPoolSelection, type OwneyAccountApy, type OwneyAgentApy, type OwneyAgentHistory, type OwneyAllocationApy, type OwneyBalances, type OwneyDepositResult, type OwneyEarnings, OwneyError, type OwneyErrorCode, type OwneyMultiDepositResult, type OwneyPosition, OwneySDK, type OwneySDKConfig, type OwneySIWXConfig, type OwneySupportedChainId, type OwneySupportedChains, type OwneySupportedTokens, type OwneyToken, type OwneyUserProfile, type OwneyWithdrawAllLeg, type OwneyWithdrawAllResult, type OwneyWithdrawResult, type PendingDeposit, type PoolCatalogProtocol, type RebalanceLog, type SwapChainTokens, type SwapDirection, type SwapOrderStatus, type SwapQuote, type SwapQuoteParams, type SwapRail, type SwapStage, type SwapTokenInfo, type SwapTokenSearchResult, type WithdrawOptions, type WithdrawSwapRecord, type WithdrawSwapStatus, clearWithdrawSwap, createOwneySIWX, listPendingDeposits, listOrders as listPendingSwaps, listWithdrawSwaps, saveWithdrawSwap, setOwneyDebug, subscribeWithdrawSwaps, updateWithdrawSwap };
package/dist/index.js CHANGED
@@ -470,7 +470,7 @@ function rawPoolApyForChain(entry, chainId, tokenSymbol) {
470
470
  let weightedSum = 0;
471
471
  let totalBalance = 0;
472
472
  for (const p of entry.positions ?? []) {
473
- if (p.chainId !== chainId) continue;
473
+ if (chainId !== void 0 && p.chainId !== chainId) continue;
474
474
  if (tokenSymbol && p.tokenSymbol?.toUpperCase() !== tokenSymbol.toUpperCase()) continue;
475
475
  const balance = p.balance ?? 0;
476
476
  const apy = netApy(p.apy_withFee, p.apy, "apy_withFee");
@@ -480,6 +480,28 @@ function rawPoolApyForChain(entry, chainId, tokenSymbol) {
480
480
  }
481
481
  return totalBalance > 0 ? weightedSum / totalBalance : null;
482
482
  }
483
+ function historyCoverage(raw) {
484
+ const chains = /* @__PURE__ */ new Map();
485
+ const assets = /* @__PURE__ */ new Map();
486
+ for (const date of Object.keys(raw.history ?? {}).sort()) {
487
+ void date;
488
+ const seenChains = /* @__PURE__ */ new Set();
489
+ const seenAssets = /* @__PURE__ */ new Set();
490
+ for (const p of (raw.history ?? {})[date].positions ?? []) {
491
+ const balance = p.balance ?? 0;
492
+ const apy = netApy(p.apy_withFee, p.apy, "apy_withFee");
493
+ if (balance <= 0 || typeof apy !== "number") continue;
494
+ seenChains.add(p.chainId);
495
+ if (p.tokenSymbol) seenAssets.add(p.tokenSymbol.toUpperCase());
496
+ }
497
+ for (const c of seenChains) chains.set(c, (chains.get(c) ?? 0) + 1);
498
+ for (const a of seenAssets) assets.set(a, (assets.get(a) ?? 0) + 1);
499
+ }
500
+ return {
501
+ chains: [...chains].map(([chainId, days]) => ({ chainId, days })),
502
+ assets: [...assets].map(([asset, days]) => ({ asset, days }))
503
+ };
504
+ }
483
505
  function mapApyHistory(raw, chainId, tokenSymbol) {
484
506
  const history = Object.entries(raw.history ?? {}).map(([date, entry]) => ({
485
507
  date,
@@ -490,7 +512,7 @@ function mapApyHistory(raw, chainId, tokenSymbol) {
490
512
  ...tokenSymbol ? {
491
513
  historicalBalance: {
492
514
  amount: (entry.positions ?? []).filter(
493
- (p) => p.chainId === chainId && p.tokenSymbol?.toUpperCase() === tokenSymbol.toUpperCase() && Number.isFinite(p.balance) && p.balance > 0 && Number.isFinite(p.apy_withFee ?? p.apy)
515
+ (p) => (chainId === void 0 || p.chainId === chainId) && p.tokenSymbol?.toUpperCase() === tokenSymbol.toUpperCase() && Number.isFinite(p.balance) && p.balance > 0 && Number.isFinite(p.apy_withFee ?? p.apy)
494
516
  ).reduce((sum, p) => sum + p.balance, 0),
495
517
  unit: tokenSymbol.toUpperCase()
496
518
  }
@@ -500,7 +522,8 @@ function mapApyHistory(raw, chainId, tokenSymbol) {
500
522
  walletAddress: raw.walletAddress,
501
523
  weightedApyAfterFee: raw.weightedApyAfterFee ? sumTokenValues(raw.weightedApyAfterFee) : void 0,
502
524
  apyByChainAndAsset: mapWeightedApyByChain(raw.weightedApyAfterFeeByChain),
503
- history
525
+ history,
526
+ coverage: historyCoverage(raw)
504
527
  };
505
528
  }
506
529
  function splitOpportunity(opportunity) {
@@ -959,6 +982,9 @@ function splittingNeedsUpdate(current, desired) {
959
982
  }
960
983
 
961
984
  // src/agents/zyfai/pool-policy.ts
985
+ function orgPolicyForConnect(policy, userPoolCustomization) {
986
+ return userPoolCustomization ? { ...policy, poolPolicy: null } : policy;
987
+ }
962
988
  function toZyfaiCustomizations(policy, availableByProtocol, chainsByProtocol) {
963
989
  const selectionByKey = /* @__PURE__ */ new Map();
964
990
  for (const selection of policy.selections) {
@@ -1050,6 +1076,47 @@ function protocolsPolicyNeedsUpdate(current, desiredProtocols, desiredAutoSelect
1050
1076
  if (desiredProtocols === null) return false;
1051
1077
  return !protocolListsEqual(current.protocols, desiredProtocols);
1052
1078
  }
1079
+ function mapAvailablePools(raw) {
1080
+ const byChain = raw ?? {};
1081
+ if (typeof byChain !== "object" || byChain === null) return [];
1082
+ const result = [];
1083
+ for (const [chainIdStr, byToken] of Object.entries(byChain)) {
1084
+ const chainId = Number(chainIdStr);
1085
+ if (!Number.isFinite(chainId)) continue;
1086
+ for (const [token, entry] of Object.entries(byToken ?? {})) {
1087
+ for (const pool of entry?.pools ?? []) {
1088
+ if (!pool?.name) continue;
1089
+ result.push({
1090
+ chainId,
1091
+ token,
1092
+ name: pool.name,
1093
+ apy: pool.info?.combined_apy ?? null,
1094
+ tvlUsd: pool.info?.tvlUsd ?? null,
1095
+ link: pool.info?.link ?? null
1096
+ });
1097
+ }
1098
+ }
1099
+ }
1100
+ return result;
1101
+ }
1102
+ function assemblePoolCatalog(protocols, poolsByProtocol, asset, chains) {
1103
+ const wanted = asset.toUpperCase();
1104
+ const allowedChains = chains && chains.length > 0 ? new Set(chains) : void 0;
1105
+ const catalog = [];
1106
+ for (const protocol of protocols) {
1107
+ const pools = (poolsByProtocol.get(protocol.id) ?? []).filter(
1108
+ (p) => p.token.toUpperCase() === wanted && (allowedChains === void 0 || allowedChains.has(p.chainId))
1109
+ );
1110
+ if (pools.length === 0) continue;
1111
+ catalog.push({
1112
+ protocolId: protocol.id,
1113
+ name: protocol.name,
1114
+ logo: protocol.imageUrl ?? null,
1115
+ pools
1116
+ });
1117
+ }
1118
+ return catalog;
1119
+ }
1053
1120
 
1054
1121
  // src/agents/zyfai/zyfai.agent.ts
1055
1122
  var ERC7579_IS_MODULE_INSTALLED_ABI = parseAbi([
@@ -1476,7 +1543,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
1476
1543
  * connecting, so a Zyfai hiccup must leave the user connected with their
1477
1544
  * profile unchanged, never fail activation.
1478
1545
  */
1479
- async applyPoolPolicy(policy) {
1546
+ async applyPoolPolicy(policy, opts = {}) {
1480
1547
  try {
1481
1548
  const { protocols: universe } = await this.sdk.getAvailableProtocols(
1482
1549
  this.supportedChainIds[0]
@@ -1490,7 +1557,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
1490
1557
  universeIds,
1491
1558
  chainsByProtocol
1492
1559
  });
1493
- let strategy;
1560
+ let strategy = opts.strategy;
1494
1561
  const desiredProtocols = deriveProtocols(
1495
1562
  policy,
1496
1563
  universeIds,
@@ -1570,6 +1637,50 @@ var ZyfaiAgent = class _ZyfaiAgent {
1570
1637
  );
1571
1638
  }
1572
1639
  }
1640
+ /**
1641
+ * The deposit-time Pools step's catalogue: every protocol Zyfai offers this
1642
+ * user, each with the pools (name, APY, TVL, link) selectable for `asset`
1643
+ * under `strategy`. Sourced from the same two Zyfai calls `applyPoolPolicy`
1644
+ * uses, so "shown" and "selectable" are one set. A protocol whose pool
1645
+ * lookup fails is skipped rather than failing the whole catalogue.
1646
+ */
1647
+ async getPoolCatalog(params) {
1648
+ const { protocols: universe } = await this.sdk.getAvailableProtocols(
1649
+ this.supportedChainIds[0]
1650
+ );
1651
+ const poolResults = await Promise.all(
1652
+ universe.map(async (protocol) => {
1653
+ try {
1654
+ const raw = await this.sdk.getAvailablePools(
1655
+ protocol.id,
1656
+ params.strategy
1657
+ );
1658
+ return [protocol.id, mapAvailablePools(raw)];
1659
+ } catch (error) {
1660
+ console.warn(
1661
+ `[zyfai] getPoolCatalog: getAvailablePools(${protocol.id}) failed, skipping (non-fatal):`,
1662
+ error instanceof Error ? error.message : String(error)
1663
+ );
1664
+ return null;
1665
+ }
1666
+ })
1667
+ );
1668
+ const poolsByProtocol = /* @__PURE__ */ new Map();
1669
+ for (const result of poolResults) {
1670
+ if (result) poolsByProtocol.set(result[0], result[1]);
1671
+ }
1672
+ return assemblePoolCatalog(universe, poolsByProtocol, params.asset);
1673
+ }
1674
+ /**
1675
+ * Apply a user's own pool selection (the deposit-time Pools step) to their
1676
+ * Zyfai profile. Same two writes as the org path — this is `applyPoolPolicy`
1677
+ * driven by the user's `policy` instead of the organization's, with the
1678
+ * deposit's chosen `strategy` passed through so pool names validate against
1679
+ * the strategy the user just picked rather than their stored one.
1680
+ */
1681
+ async setPoolSelection(policy, opts = {}) {
1682
+ await this.applyPoolPolicy(policy, opts);
1683
+ }
1573
1684
  /**
1574
1685
  * True when `smartWallet` is the backend-managed pool wallet Zyfai assigned
1575
1686
  * to this EOA — the "new wallet" kind that is predeployed on the chains it
@@ -1696,11 +1807,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
1696
1807
  );
1697
1808
  return;
1698
1809
  }
1699
- await this.ensureSessionKey(
1700
- ownerAddress,
1701
- chainId,
1702
- wallet.address
1703
- );
1810
+ await this.ensureSessionKey(ownerAddress, chainId, wallet.address);
1704
1811
  } catch (error) {
1705
1812
  console.warn(
1706
1813
  "[zyfai] session-key check at activation failed (non-fatal):",
@@ -1811,11 +1918,7 @@ var ZyfaiAgent = class _ZyfaiAgent {
1811
1918
  "zyfai:onboard",
1812
1919
  "Safe already has code -> no deploy needed, checking the session key"
1813
1920
  );
1814
- await this.ensureSessionKey(
1815
- ownerAddress,
1816
- chainId,
1817
- wallet.address
1818
- );
1921
+ await this.ensureSessionKey(ownerAddress, chainId, wallet.address);
1819
1922
  return wallet.address;
1820
1923
  }
1821
1924
  const poolFirst = await this.isPredeployedPoolWallet(
@@ -1982,14 +2085,17 @@ var ZyfaiAgent = class _ZyfaiAgent {
1982
2085
  this.earningsRefreshes.set(key2, pending);
1983
2086
  return pending;
1984
2087
  }
1985
- async getAccountApy(state, chainId, days, tokenSymbol) {
2088
+ async getAccountApy(state, chainId, days, tokenSymbol, scopeChainId) {
1986
2089
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1987
2090
  const raw = await this.sdk.getDailyApyHistory(smartWallet, days);
1988
- return mapApyHistory(raw, chainId, tokenSymbol);
2091
+ const scope = scopeChainId === null ? void 0 : scopeChainId ?? chainId;
2092
+ return mapApyHistory(raw, scope, tokenSymbol);
1989
2093
  }
1990
2094
  async getDailyEarnings(state, chainId, days, tokenSymbol) {
1991
2095
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1992
- const start = new Date(Date.now() - (DayFilterMapping[days] + 1) * 864e5);
2096
+ const start = new Date(
2097
+ Date.now() - (DayFilterMapping[days] + 1) * 864e5
2098
+ );
1993
2099
  const raw = await this.sdk.getDailyEarnings(
1994
2100
  smartWallet,
1995
2101
  start.toISOString().slice(0, 10)
@@ -2111,6 +2217,9 @@ var ZyfaiAgent = class _ZyfaiAgent {
2111
2217
 
2112
2218
  // src/lib/history-fanout.ts
2113
2219
  var SEPARATOR = "@";
2220
+ function isSupportedChainId(value) {
2221
+ return SUPPORTED_CHAIN_IDS.includes(value);
2222
+ }
2114
2223
  function fanoutKey(leg) {
2115
2224
  return leg.chainId === void 0 ? leg.agentId : `${leg.agentId}${SEPARATOR}${leg.chainId}`;
2116
2225
  }
@@ -2133,6 +2242,16 @@ function decodeFanoutCursor(token) {
2133
2242
  }
2134
2243
  return parsed;
2135
2244
  }
2245
+ function transactionChainId(entry) {
2246
+ let found;
2247
+ for (const tx of entry.transactions ?? []) {
2248
+ if (tx.chainId === void 0) continue;
2249
+ if (!isSupportedChainId(tx.chainId)) return void 0;
2250
+ if (found !== void 0 && found !== tx.chainId) return void 0;
2251
+ found = tx.chainId;
2252
+ }
2253
+ return found;
2254
+ }
2136
2255
  function mergeHistoryPages(pages) {
2137
2256
  const merged = [];
2138
2257
  const nextCursorMap = {};
@@ -2151,7 +2270,9 @@ function mergeHistoryPages(pages) {
2151
2270
  const prior = seen.get(id);
2152
2271
  if (prior) {
2153
2272
  prior.legs += 1;
2154
- if (prior.entry.chainId !== e.chainId) delete prior.entry.chainId;
2273
+ const own = transactionChainId(prior.entry);
2274
+ if (own === void 0) delete prior.entry.chainId;
2275
+ else prior.entry.chainId = own;
2155
2276
  continue;
2156
2277
  }
2157
2278
  const entry = leg.chainId === void 0 ? { ...e } : { ...e, chainId: leg.chainId };
@@ -4293,6 +4414,7 @@ var OwneySDK = class {
4293
4414
  orgAgentConfig;
4294
4415
  orgAgentConfigPromise = null;
4295
4416
  zyfaiRpcUrls;
4417
+ userPoolCustomization = false;
4296
4418
  routingApiBaseUrl;
4297
4419
  referralSource;
4298
4420
  cachedSponsoredCallback = null;
@@ -4318,6 +4440,7 @@ var OwneySDK = class {
4318
4440
  this.routingApiBaseUrl = config.routingApiBaseUrl;
4319
4441
  this.paymasterServiceUrl = config.paymasterServiceUrl;
4320
4442
  this.referralSource = config.referralSource;
4443
+ this.userPoolCustomization = config.userPoolCustomization ?? false;
4321
4444
  }
4322
4445
  /**
4323
4446
  * Establish connection state for all subsequent SDK calls.
@@ -4544,7 +4667,8 @@ var OwneySDK = class {
4544
4667
  try {
4545
4668
  const policy = await this.getOrgAgentConfig();
4546
4669
  if (!policy) return;
4547
- await agent.applyOrgPolicy(state, chainId, policy);
4670
+ const effective = orgPolicyForConnect(policy, this.userPoolCustomization);
4671
+ await agent.applyOrgPolicy(state, chainId, effective);
4548
4672
  } catch (error) {
4549
4673
  console.warn(
4550
4674
  `[owney-sdk] applyOrgPolicy(${agent.id}) failed (non-fatal):`,
@@ -6361,20 +6485,57 @@ var OwneySDK = class {
6361
6485
  chainId
6362
6486
  };
6363
6487
  }
6488
+ /**
6489
+ * The deposit-time pool catalogue (ROUT-569): every protocol with its
6490
+ * selectable pools (name, APY, TVL, link) for `asset` under `strategy`.
6491
+ * Defaults to the zyfai agent, the only one with pool customization today.
6492
+ */
6493
+ async getPoolCatalog(params) {
6494
+ await this.ensureAgentsInitialized();
6495
+ const agent = this.getAgent(params.agentId ?? "zyfai");
6496
+ if (!agent.getPoolCatalog) {
6497
+ throw new OwneyError(
6498
+ "POOL_CATALOG_UNSUPPORTED",
6499
+ `Agent ${agent.id} does not support pool customization`
6500
+ );
6501
+ }
6502
+ return agent.getPoolCatalog({
6503
+ asset: params.asset,
6504
+ strategy: params.strategy
6505
+ });
6506
+ }
6507
+ /**
6508
+ * Apply a user's deposit-time pool selection to their account (ROUT-569).
6509
+ * `strategy` is the one chosen in the deposit flow, passed through so pool
6510
+ * names validate against it rather than the user's stored strategy.
6511
+ */
6512
+ async setPoolSelection(params) {
6513
+ await this.ensureAgentsInitialized();
6514
+ const agent = this.getAgent(params.agentId ?? "zyfai");
6515
+ if (!agent.setPoolSelection) {
6516
+ throw new OwneyError(
6517
+ "POOL_SELECTION_UNSUPPORTED",
6518
+ `Agent ${agent.id} does not support pool customization`
6519
+ );
6520
+ }
6521
+ await agent.setPoolSelection(params.policy, { strategy: params.strategy });
6522
+ }
6364
6523
  async getAccountApy({
6365
6524
  agentId,
6366
6525
  days,
6367
- tokenSymbol
6526
+ tokenSymbol,
6527
+ chainId: scope
6368
6528
  }) {
6369
6529
  const state = this.requireState();
6370
6530
  const chainId = this.requireChainId();
6531
+ const scopeChainId = scope === "all" ? null : scope;
6371
6532
  if (agentId) {
6372
6533
  const agent = this.getAgent(agentId);
6373
6534
  return this.readAgent(
6374
6535
  agent,
6375
6536
  "accountApy",
6376
- () => agent.getAccountApy(state, chainId, days, tokenSymbol),
6377
- { days, tokenSymbol }
6537
+ () => agent.getAccountApy(state, chainId, days, tokenSymbol, scopeChainId),
6538
+ { days, tokenSymbol, scopeChainId }
6378
6539
  );
6379
6540
  }
6380
6541
  const activeAgents = this.getActiveAgents();
@@ -6385,8 +6546,14 @@ var OwneySDK = class {
6385
6546
  const apy = await this.readAgent(
6386
6547
  agent,
6387
6548
  "accountApy",
6388
- () => agent.getAccountApy(state, chainId, days, tokenSymbol),
6389
- { days, tokenSymbol }
6549
+ () => agent.getAccountApy(
6550
+ state,
6551
+ chainId,
6552
+ days,
6553
+ tokenSymbol,
6554
+ scopeChainId
6555
+ ),
6556
+ { days, tokenSymbol, scopeChainId }
6390
6557
  );
6391
6558
  return [id, apy];
6392
6559
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owney/sdk",
3
- "version": "0.7.28-beta.0",
3
+ "version": "0.7.28-beta.2",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",