@owney/sdk 0.6.5-beta.0 → 0.6.5-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
@@ -18,8 +18,8 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
 
20
20
  // src/index.ts
21
- var src_exports = {};
22
- __export(src_exports, {
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
23
  AgentChainIncompatibleError: () => AgentChainIncompatibleError,
24
24
  AgentNotFoundError: () => AgentNotFoundError,
25
25
  InvalidHistoryCursorError: () => InvalidHistoryCursorError,
@@ -28,7 +28,7 @@ __export(src_exports, {
28
28
  OwneySDK: () => OwneySDK,
29
29
  createOwneySIWX: () => createOwneySIWX
30
30
  });
31
- module.exports = __toCommonJS(src_exports);
31
+ module.exports = __toCommonJS(index_exports);
32
32
 
33
33
  // src/agents/zyfai/zyfai.agent.ts
34
34
  var import_sdk = require("@zyfai/sdk");
@@ -37,8 +37,8 @@ var import_chains = require("viem/chains");
37
37
 
38
38
  // src/types/config.ts
39
39
  var P_SUPPORTED_CHAINS = {
40
- chainIds: [8453, 42161],
41
- chains: ["BASE", "ARBITRUM"]
40
+ chainIds: [8453, 42161, 1],
41
+ chains: ["BASE", "ARBITRUM", "ETHEREUM"]
42
42
  };
43
43
  var SUPPORTED_TOKENS = ["USDC", "USDT", "WETH"];
44
44
  var SUPPORTED_CHAIN_IDS = P_SUPPORTED_CHAINS.chainIds;
@@ -73,6 +73,12 @@ var SupportedAssets = [
73
73
  symbol: "WETH",
74
74
  chainId: 42161,
75
75
  decimals: 18
76
+ },
77
+ {
78
+ address: `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48`,
79
+ symbol: "USDC",
80
+ chainId: 1,
81
+ decimals: 6
76
82
  }
77
83
  ];
78
84
 
@@ -139,8 +145,7 @@ var isValidChainId = (chainId) => {
139
145
  function hexToDecimal(hex, decimals = 6) {
140
146
  const normalized = hex.startsWith("0x") || hex.startsWith("0X") ? hex : `0x${hex}`;
141
147
  const parsed = BigInt(normalized);
142
- if (decimals <= 0)
143
- return Number(parsed);
148
+ if (decimals <= 0) return Number(parsed);
144
149
  const divisor = BigInt(10 ** decimals);
145
150
  const whole = parsed / divisor;
146
151
  const remainder = parsed % divisor;
@@ -153,6 +158,20 @@ var DayFilterMapping = {
153
158
  };
154
159
 
155
160
  // src/agents/zyfai/zyfai.mapper.ts
161
+ var DECIMALS_BY_SYMBOL = {
162
+ usdc: 6,
163
+ usdt: 6,
164
+ weth: 18,
165
+ eth: 18,
166
+ cbeth: 18,
167
+ wbtc: 8,
168
+ cbbtc: 8
169
+ };
170
+ function tokenDecimals(symbol, explicit) {
171
+ if (typeof explicit === "number" && Number.isFinite(explicit))
172
+ return explicit;
173
+ return DECIMALS_BY_SYMBOL[(symbol ?? "").toLowerCase()] ?? 6;
174
+ }
156
175
  function mapDeposit(raw) {
157
176
  return {
158
177
  txHash: raw.txHash,
@@ -169,7 +188,8 @@ function mapWithdraw(raw) {
169
188
  }
170
189
  var CHAIN_ID_TO_NAME = {
171
190
  8453: "BASE",
172
- 42161: "ARBITRUM"
191
+ 42161: "ARBITRUM",
192
+ 1: "ETHEREUM"
173
193
  };
174
194
  var NAME_TO_CHAIN_ID = Object.entries(
175
195
  CHAIN_ID_TO_NAME
@@ -179,8 +199,7 @@ var NAME_TO_CHAIN_ID = Object.entries(
179
199
  }, {});
180
200
  function resolveChainId(chain) {
181
201
  const asNum = Number(chain);
182
- if (Number.isFinite(asNum) && asNum > 0)
183
- return asNum;
202
+ if (Number.isFinite(asNum) && asNum > 0) return asNum;
184
203
  return NAME_TO_CHAIN_ID[chain.trim().toLowerCase()] ?? NaN;
185
204
  }
186
205
  function mapBalances(raw, _chainId, smartWallet) {
@@ -210,9 +229,16 @@ function mapBalances(raw, _chainId, smartWallet) {
210
229
  pool: p.pool,
211
230
  asset: p.token_symbol ?? "",
212
231
  // Position amount is hex-encoded base units (like the token balances
213
- // above). Supported assets (USDC/USDT) are 6-decimal — hexToDecimal's
214
- // default — so no per-token decimals lookup is needed.
215
- amount: String(hexToDecimal(p.amount || "0")),
232
+ // above). `PositionSlot` doesn't type a `decimals` field, but runtime
233
+ // getBalances payloads often carry one (e.g. 18 for WETH) — prefer that
234
+ // when present, otherwise fall back to the per-symbol lookup so
235
+ // non-6-decimal assets like WETH don't render 1e12 too large.
236
+ amount: String(
237
+ hexToDecimal(
238
+ p.amount || "0",
239
+ tokenDecimals(p.token_symbol, p.decimals)
240
+ )
241
+ ),
216
242
  apy: p.pool_apy,
217
243
  tvl: p.pool_tvl,
218
244
  // PositionSlot has no `liquidity` field yet; Zyfai will add it. Narrow
@@ -257,41 +283,39 @@ function mapEarnings(raw, smartWallet) {
257
283
  };
258
284
  }
259
285
  function mapWeightedApyByChain(raw) {
260
- if (!raw)
261
- return void 0;
286
+ if (!raw) return void 0;
262
287
  const out = {};
263
288
  for (const [chainKey, tokenApy] of Object.entries(raw)) {
264
289
  const chainId = Number(chainKey);
265
- if (!SUPPORTED_CHAIN_IDS.includes(chainId))
266
- continue;
290
+ if (!SUPPORTED_CHAIN_IDS.includes(chainId)) continue;
267
291
  const perAsset = {};
268
292
  for (const [symbol, apy] of Object.entries(tokenApy)) {
269
293
  const token = symbol;
270
- if (!SUPPORTED_TOKENS.includes(token))
271
- continue;
294
+ if (!SUPPORTED_TOKENS.includes(token)) continue;
272
295
  perAsset[token] = apy;
273
296
  }
274
- if (Object.keys(perAsset).length > 0)
275
- out[chainId] = perAsset;
297
+ if (Object.keys(perAsset).length > 0) out[chainId] = perAsset;
276
298
  }
277
299
  return Object.keys(out).length > 0 ? out : void 0;
278
300
  }
279
- function rawPoolApyForChain(entry, chainId) {
301
+ function rawPoolApyForChain(entry, chainId, tokenSymbol) {
280
302
  let weightedSum = 0;
281
303
  let totalBalance = 0;
282
304
  for (const p of entry.positions ?? []) {
283
- if (p.chainId !== chainId)
284
- continue;
305
+ if (p.chainId !== chainId) continue;
306
+ if (tokenSymbol && p.tokenSymbol !== tokenSymbol) continue;
285
307
  const balance = p.balance ?? 0;
286
- if (balance <= 0 || typeof p.apy !== "number")
287
- continue;
308
+ if (balance <= 0 || typeof p.apy !== "number") continue;
288
309
  weightedSum += p.apy * balance;
289
310
  totalBalance += balance;
290
311
  }
291
312
  return totalBalance > 0 ? weightedSum / totalBalance : null;
292
313
  }
293
- function mapApyHistory(raw, chainId) {
294
- const history = Object.entries(raw.history ?? {}).map(([date, entry]) => ({ date, apy: rawPoolApyForChain(entry, chainId) })).filter((p) => p.apy !== null).sort((a, b) => a.date.localeCompare(b.date));
314
+ function mapApyHistory(raw, chainId, tokenSymbol) {
315
+ const history = Object.entries(raw.history ?? {}).map(([date, entry]) => ({
316
+ date,
317
+ apy: rawPoolApyForChain(entry, chainId, tokenSymbol)
318
+ })).filter((p) => p.apy !== null).sort((a, b) => a.date.localeCompare(b.date));
295
319
  return {
296
320
  walletAddress: raw.walletAddress,
297
321
  weightedApyAfterFee: raw.weightedApyAfterFee ? sumTokenValues(raw.weightedApyAfterFee) : void 0,
@@ -314,6 +338,7 @@ function mapEntries(rawEntries, chainId) {
314
338
  "Withdraw from protocol": "Withdraw",
315
339
  Rebalance: "Rebalance"
316
340
  };
341
+ void chainId;
317
342
  return rawEntries.map((entry) => {
318
343
  const rawLog = entry.rebalanceLog;
319
344
  const positions = entry.positions ?? [];
@@ -335,7 +360,15 @@ function mapEntries(rawEntries, chainId) {
335
360
  fromPool: from.pool,
336
361
  toPool: to.pool,
337
362
  tokenSymbol: matched?.token_symbol ?? "",
338
- amount: matched ? String(hexToDecimal(matched.amount || "0x")) : "",
363
+ amount: matched ? String(
364
+ hexToDecimal(
365
+ matched.amount || "0x",
366
+ tokenDecimals(
367
+ matched.token_symbol,
368
+ matched.decimals
369
+ )
370
+ )
371
+ ) : "",
339
372
  status: "success"
340
373
  }
341
374
  ];
@@ -354,7 +387,12 @@ function mapEntries(rawEntries, chainId) {
354
387
  txHashes: entry.transactionHash ? [entry.transactionHash] : [],
355
388
  chainId: entry.chainId,
356
389
  tokenSymbol: entry?.positions?.[0]?.token_symbol,
357
- amount: hasAmount ? String(hexToDecimal(rawAmount)) : void 0
390
+ amount: hasAmount ? String(
391
+ hexToDecimal(
392
+ rawAmount,
393
+ tokenDecimals(entry?.positions?.[0]?.token_symbol)
394
+ )
395
+ ) : void 0
358
396
  }
359
397
  ],
360
398
  rebalanceLog
@@ -379,8 +417,7 @@ function mapApyByStrategy(raw) {
379
417
  const supported = SupportedAssets.find(
380
418
  (asset) => asset.chainId === entry.chain_id && asset.symbol === entry.token_symbol
381
419
  );
382
- if (!supported)
383
- continue;
420
+ if (!supported) continue;
384
421
  const chainKey = supported.chainId;
385
422
  const symbolKey = supported.symbol;
386
423
  const bucket = apyPerAsset[chainKey] ?? {};
@@ -410,10 +447,8 @@ function computeAllocationApy(positions) {
410
447
  totalValue += value;
411
448
  const chainId = resolveChainId(p.chain);
412
449
  const token = p.asset;
413
- if (!SUPPORTED_CHAIN_IDS.includes(chainId))
414
- continue;
415
- if (!SUPPORTED_TOKENS.includes(token))
416
- continue;
450
+ if (!SUPPORTED_CHAIN_IDS.includes(chainId)) continue;
451
+ if (!SUPPORTED_TOKENS.includes(token)) continue;
417
452
  const perAsset = buckets[chainId] ?? {};
418
453
  const cell = perAsset[token] ?? { weightedSum: 0, value: 0 };
419
454
  cell.weightedSum += apy * value;
@@ -430,8 +465,7 @@ function computeAllocationApy(positions) {
430
465
  out[token] = cell.weightedSum / cell.value;
431
466
  }
432
467
  }
433
- if (Object.keys(out).length > 0)
434
- apyByChainAndAsset[chainId] = out;
468
+ if (Object.keys(out).length > 0) apyByChainAndAsset[chainId] = out;
435
469
  }
436
470
  const totalApy = totalValue > 0 ? String(weightedSum / totalValue) : "0";
437
471
  return { totalApy, apyByChainAndAsset };
@@ -448,19 +482,14 @@ function extractWithdrawnAmount(logs, recipient, token, decimals) {
448
482
  let total = 0n;
449
483
  let matched = false;
450
484
  for (const log of logs) {
451
- if (log.address.toLowerCase() !== wantToken)
452
- continue;
453
- if ((log.topics[0] ?? "").toLowerCase() !== TRANSFER_TOPIC)
454
- continue;
455
- if (topicToAddress(log.topics[2]) !== wantRecipient)
456
- continue;
457
- if (!log.data || log.data === "0x")
458
- continue;
485
+ if (log.address.toLowerCase() !== wantToken) continue;
486
+ if ((log.topics[0] ?? "").toLowerCase() !== TRANSFER_TOPIC) continue;
487
+ if (topicToAddress(log.topics[2]) !== wantRecipient) continue;
488
+ if (!log.data || log.data === "0x") continue;
459
489
  total += BigInt(log.data);
460
490
  matched = true;
461
491
  }
462
- if (!matched)
463
- return void 0;
492
+ if (!matched) return void 0;
464
493
  return String(hexToDecimal(`0x${total.toString(16)}`, decimals));
465
494
  }
466
495
 
@@ -476,8 +505,7 @@ function encodeHistoryCursor(payload) {
476
505
  return Buffer.from(JSON.stringify(payload), "utf8").toString("base64");
477
506
  }
478
507
  function decodeHistoryCursor(token) {
479
- if (!token)
480
- throw new InvalidHistoryCursorError("Cursor is empty.");
508
+ if (!token) throw new InvalidHistoryCursorError("Cursor is empty.");
481
509
  let parsed;
482
510
  try {
483
511
  const json = Buffer.from(token, "base64").toString("utf8");
@@ -494,8 +522,7 @@ function decodeHistoryCursor(token) {
494
522
  // src/agents/zyfai/zyfai.auth-cache.ts
495
523
  var KEY_PREFIX = "owney.zyfai.session";
496
524
  var storage = () => {
497
- if (typeof window === "undefined")
498
- return null;
525
+ if (typeof window === "undefined") return null;
499
526
  try {
500
527
  return window.localStorage;
501
528
  } catch {
@@ -505,15 +532,13 @@ var storage = () => {
505
532
  var buildKey = (address, chainId) => `${KEY_PREFIX}:${address.toLowerCase()}:${chainId}`;
506
533
  var isJwtExpired = (token) => {
507
534
  const parts = token.split(".");
508
- if (parts.length !== 3)
509
- return false;
535
+ if (parts.length !== 3) return false;
510
536
  try {
511
537
  const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
512
538
  const padded = payload + "=".repeat((4 - payload.length % 4) % 4);
513
539
  const decoded = typeof atob === "function" ? atob(padded) : Buffer.from(padded, "base64").toString("utf8");
514
540
  const claims = JSON.parse(decoded);
515
- if (typeof claims.exp !== "number")
516
- return false;
541
+ if (typeof claims.exp !== "number") return false;
517
542
  return Date.now() / 1e3 >= claims.exp - 30;
518
543
  } catch {
519
544
  return false;
@@ -521,11 +546,9 @@ var isJwtExpired = (token) => {
521
546
  };
522
547
  var readSession = (address, chainId) => {
523
548
  const store = storage();
524
- if (!store)
525
- return null;
549
+ if (!store) return null;
526
550
  const raw = store.getItem(buildKey(address, chainId));
527
- if (!raw)
528
- return null;
551
+ if (!raw) return null;
529
552
  try {
530
553
  const parsed = JSON.parse(raw);
531
554
  if (!parsed.token || isJwtExpired(parsed.token)) {
@@ -540,8 +563,7 @@ var readSession = (address, chainId) => {
540
563
  };
541
564
  var writeSession = (address, chainId, session) => {
542
565
  const store = storage();
543
- if (!store)
544
- return;
566
+ if (!store) return;
545
567
  try {
546
568
  store.setItem(buildKey(address, chainId), JSON.stringify(session));
547
569
  } catch {
@@ -549,8 +571,7 @@ var writeSession = (address, chainId, session) => {
549
571
  };
550
572
  var clearSession = (address, chainId) => {
551
573
  const store = storage();
552
- if (!store)
553
- return;
574
+ if (!store) return;
554
575
  try {
555
576
  store.removeItem(buildKey(address, chainId));
556
577
  } catch {
@@ -559,8 +580,7 @@ var clearSession = (address, chainId) => {
559
580
 
560
581
  // src/agents/zyfai/autoSelect.ts
561
582
  function buildAutoSelectUpdate(current, asset) {
562
- if (current.autoSelectProtocols === true)
563
- return null;
583
+ if (current.autoSelectProtocols === true) return null;
564
584
  return {
565
585
  asset,
566
586
  autoSelectProtocols: true,
@@ -571,16 +591,18 @@ function buildAutoSelectUpdate(current, asset) {
571
591
  // src/agents/zyfai/zyfai.agent.ts
572
592
  var DEFAULT_ZYFAI_RPC_URLS = {
573
593
  8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
574
- 42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V"
594
+ 42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
595
+ 1: "https://eth-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V"
575
596
  };
576
597
  var WETH_ADDRESS_BY_CHAIN = {
577
598
  8453: "0x4200000000000000000000000000000000000006",
578
- 42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"
599
+ 42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
600
+ 1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
579
601
  };
580
- var VIEM_CHAIN = { 8453: import_chains.base, 42161: import_chains.arbitrum };
602
+ var VIEM_CHAIN = { 8453: import_chains.base, 42161: import_chains.arbitrum, 1: import_chains.mainnet };
581
603
  var ZyfaiAgent = class {
582
604
  id = "zyfai";
583
- supportedChainIds = [8453, 42161];
605
+ supportedChainIds = [8453, 42161, 1];
584
606
  supportedAssets = [
585
607
  {
586
608
  chainId: 8453,
@@ -597,6 +619,13 @@ var ZyfaiAgent = class {
597
619
  { symbol: "USDC", minDepositAmount: "1500000" },
598
620
  { symbol: "WETH", minDepositAmount: "500000000000000" }
599
621
  ]
622
+ },
623
+ {
624
+ chainId: 1,
625
+ chain: "ETHEREUM",
626
+ // Zyfai enforces a 10,000 USDC minimum portfolio on Ethereum mainnet
627
+ // (mainnet gas only pays off on large positions). 10_000 * 1e6 raw units.
628
+ assets: [{ symbol: "USDC", minDepositAmount: "10000000000" }]
600
629
  }
601
630
  ];
602
631
  sdk;
@@ -620,8 +649,7 @@ var ZyfaiAgent = class {
620
649
  }
621
650
  getPublicClient(chainId) {
622
651
  const cached = this.publicClients.get(chainId);
623
- if (cached)
624
- return cached;
652
+ if (cached) return cached;
625
653
  const client = (0, import_viem.createPublicClient)({
626
654
  chain: VIEM_CHAIN[chainId],
627
655
  transport: (0, import_viem.http)(this.rpcUrls[chainId])
@@ -631,33 +659,43 @@ var ZyfaiAgent = class {
631
659
  }
632
660
  /**
633
661
  * Fills in the true withdrawn amount for `Withdraw` entries by decoding the
634
- * USDC `Transfer` that lands in the user's wallet on-chain. The Zyfai history
662
+ * on-chain `Transfer` that lands in the user's wallet. The Zyfai history
635
663
  * payload only reports the *remaining* position for a partial withdraw (see
636
664
  * the note in `mapEntries`), so without this the UI shows the leftover
637
665
  * balance instead of the amount withdrawn.
638
666
  *
667
+ * The withdrawn asset varies per entry (USDC or WETH), so it's resolved
668
+ * from `tx.tokenSymbol` (set by `mapEntries` from the entry's own
669
+ * position) rather than assumed to always be USDC. Zyfai sometimes
670
+ * denominates WETH as lowercase "eth" — that's normalized to WETH.
671
+ * Entries with no symbol (older payload shapes) fall back to USDC to
672
+ * preserve prior behavior.
673
+ *
639
674
  * Mutates `entries` in place. Runs all lookups concurrently and is tolerant
640
675
  * of RPC failures: an entry whose amount can't be resolved keeps
641
676
  * `amount: undefined`, which consumers render as a hidden/retryable row
642
677
  * rather than a wrong number.
643
678
  */
644
679
  async resolveWithdrawnAmounts(entries, chainId, recipient) {
645
- const asset = SupportedAssets.find(
646
- (a) => a.chainId === chainId && a.symbol === "USDC"
647
- );
648
- if (!asset)
649
- return;
650
680
  const withdrawals = entries.filter(
651
681
  (e) => e.action === "Withdraw" && e.transactions[0]?.txHashes[0]
652
682
  );
653
- if (withdrawals.length === 0)
654
- return;
683
+ if (withdrawals.length === 0) return;
655
684
  const client = this.getPublicClient(chainId);
656
685
  await Promise.all(
657
686
  withdrawals.map(async (entry) => {
658
687
  const tx = entry.transactions[0];
659
688
  const txHash = tx.txHashes[0];
660
- const cached = this.withdrawAmountCache.get(txHash);
689
+ const rawSymbol = tx.tokenSymbol;
690
+ const normalizedSymbol = rawSymbol ? rawSymbol.toLowerCase() === "eth" ? "WETH" : rawSymbol.toUpperCase() : "USDC";
691
+ const asset = SupportedAssets.find(
692
+ (a) => a.chainId === chainId && a.symbol === normalizedSymbol
693
+ ) ?? SupportedAssets.find(
694
+ (a) => a.chainId === chainId && a.symbol === "USDC"
695
+ );
696
+ if (!asset) return;
697
+ const cacheKey = `${txHash}:${asset.address}`;
698
+ const cached = this.withdrawAmountCache.get(cacheKey);
661
699
  if (cached !== void 0) {
662
700
  tx.amount = cached;
663
701
  return;
@@ -673,7 +711,7 @@ var ZyfaiAgent = class {
673
711
  asset.decimals
674
712
  );
675
713
  if (amount !== void 0) {
676
- this.withdrawAmountCache.set(txHash, amount);
714
+ this.withdrawAmountCache.set(cacheKey, amount);
677
715
  tx.amount = amount;
678
716
  }
679
717
  } catch {
@@ -692,13 +730,11 @@ var ZyfaiAgent = class {
692
730
  * for some apps and the patch only matters in the browser anyway.
693
731
  */
694
732
  installAuthCache() {
695
- if (typeof window === "undefined")
696
- return;
733
+ if (typeof window === "undefined") return;
697
734
  const internals = this.sdk;
698
735
  const original = internals.authenticateUser.bind(internals);
699
736
  internals.authenticateUser = async () => {
700
- if (internals.authenticatedUserId !== null)
701
- return;
737
+ if (internals.authenticatedUserId !== null) return;
702
738
  const address = internals.walletClient?.account?.address;
703
739
  const chainId = internals.currentChainId;
704
740
  if (address && chainId) {
@@ -773,12 +809,10 @@ var ZyfaiAgent = class {
773
809
  return await this.sdk.connectAccount(provider, chainId);
774
810
  } catch (error) {
775
811
  const message = error instanceof Error ? error.message : String(error);
776
- if (!message.toLowerCase().includes("unauthorized"))
777
- throw error;
812
+ if (!message.toLowerCase().includes("unauthorized")) throw error;
778
813
  const internals = this.sdk;
779
814
  const address = internals.walletClient?.account?.address;
780
- if (address)
781
- clearSession(address, chainId);
815
+ if (address) clearSession(address, chainId);
782
816
  return await this.sdk.connectAccount(provider, chainId);
783
817
  }
784
818
  }
@@ -908,10 +942,10 @@ var ZyfaiAgent = class {
908
942
  const raw = await this.sdk.getOnchainEarnings(smartWallet);
909
943
  return mapEarnings(raw, smartWallet);
910
944
  }
911
- async getAccountApy(state, chainId, days) {
945
+ async getAccountApy(state, chainId, days, tokenSymbol) {
912
946
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
913
947
  const raw = await this.sdk.getDailyApyHistory(smartWallet, days);
914
- return mapApyHistory(raw, chainId);
948
+ return mapApyHistory(raw, chainId, tokenSymbol);
915
949
  }
916
950
  async getHistory(state, chainId, options) {
917
951
  const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
@@ -976,8 +1010,7 @@ var ZyfaiAgent = class {
976
1010
  },
977
1011
  asset
978
1012
  );
979
- if (!update)
980
- return false;
1013
+ if (!update) return false;
981
1014
  await this.sdk.updateUserProfile(update);
982
1015
  return true;
983
1016
  }
@@ -1066,8 +1099,7 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
1066
1099
  const tokenBalance = agentBalance?.tokens.find(
1067
1100
  (t) => t.chainId === chainId && t.asset.toUpperCase() === target
1068
1101
  );
1069
- if (!tokenBalance)
1070
- return { agent, balance: 0n };
1102
+ if (!tokenBalance) return { agent, balance: 0n };
1071
1103
  return { agent, balance: (0, import_viem2.parseUnits)(tokenBalance.amount, decimals) };
1072
1104
  });
1073
1105
  }
@@ -1084,11 +1116,9 @@ function planProportionalShares(balances, requested, totalAvailable) {
1084
1116
  return diff > 0n ? 1 : diff < 0n ? -1 : 0;
1085
1117
  });
1086
1118
  for (const p of byHeadroom) {
1087
- if (remainder === 0n)
1088
- break;
1119
+ if (remainder === 0n) break;
1089
1120
  const headroom = p.balance - p.planned;
1090
- if (headroom <= 0n)
1091
- continue;
1121
+ if (headroom <= 0n) continue;
1092
1122
  const take = headroom < remainder ? headroom : remainder;
1093
1123
  p.planned += take;
1094
1124
  remainder -= take;
@@ -1117,8 +1147,7 @@ function redistributeShare(plans, fromIndex, amount, candidatePool) {
1117
1147
  (s, c) => s + (c.balance - c.planned),
1118
1148
  0n
1119
1149
  );
1120
- if (totalHeadroom === 0n)
1121
- return;
1150
+ if (totalHeadroom === 0n) return;
1122
1151
  let distributed = 0n;
1123
1152
  for (const c of candidates) {
1124
1153
  const headroom = c.balance - c.planned;
@@ -1129,11 +1158,9 @@ function redistributeShare(plans, fromIndex, amount, candidatePool) {
1129
1158
  }
1130
1159
  let leftover = amount - distributed;
1131
1160
  for (const c of candidates) {
1132
- if (leftover === 0n)
1133
- break;
1161
+ if (leftover === 0n) break;
1134
1162
  const headroom = c.balance - c.planned;
1135
- if (headroom <= 0n)
1136
- continue;
1163
+ if (headroom <= 0n) continue;
1137
1164
  const take = headroom < leftover ? headroom : leftover;
1138
1165
  c.planned += take;
1139
1166
  leftover -= take;
@@ -1150,16 +1177,13 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
1150
1177
  for (const id of Object.keys(agentApys)) {
1151
1178
  const cells = agentApys[id].apyByChainAndAsset;
1152
1179
  const balance = agentBalances[id] ?? 0;
1153
- if (!cells || balance <= 0)
1154
- continue;
1180
+ if (!cells || balance <= 0) continue;
1155
1181
  for (const [chainKey, perAsset] of Object.entries(cells)) {
1156
- if (!perAsset)
1157
- continue;
1182
+ if (!perAsset) continue;
1158
1183
  const chainId = Number(chainKey);
1159
1184
  for (const [asset, apyValue] of Object.entries(perAsset)) {
1160
1185
  const apy = Number(apyValue ?? 0);
1161
- if (apy === 0)
1162
- continue;
1186
+ if (apy === 0) continue;
1163
1187
  sums[chainId] ??= {};
1164
1188
  weights[chainId] ??= {};
1165
1189
  sums[chainId][asset] = (sums[chainId][asset] ?? 0) + apy * balance;
@@ -1173,8 +1197,7 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
1173
1197
  const perAssetOut = {};
1174
1198
  for (const asset of Object.keys(sums[chainId])) {
1175
1199
  const w = weights[chainId][asset];
1176
- if (w > 0)
1177
- perAssetOut[asset] = sums[chainId][asset] / w;
1200
+ if (w > 0) perAssetOut[asset] = sums[chainId][asset] / w;
1178
1201
  }
1179
1202
  if (Object.keys(perAssetOut).length > 0) {
1180
1203
  out[chainId] = perAssetOut;
@@ -1231,7 +1254,10 @@ async function postSponsorTransferAuth(input) {
1231
1254
  try {
1232
1255
  res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
1233
1256
  method: "POST",
1234
- headers: { "content-type": "application/json", "x-owney-api-key": input.apiKey },
1257
+ headers: {
1258
+ "content-type": "application/json",
1259
+ "x-owney-api-key": input.apiKey
1260
+ },
1235
1261
  body: JSON.stringify(input.body)
1236
1262
  });
1237
1263
  } catch (networkError) {
@@ -1251,7 +1277,13 @@ async function postSponsorTransferAuth(input) {
1251
1277
  throw new OwneyError(
1252
1278
  "SPONSOR_REQUEST_FAILED",
1253
1279
  `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
1254
- { statusCode: res.status, responseBody: text.slice(0, 500) }
1280
+ {
1281
+ statusCode: res.status,
1282
+ responseBody: text.slice(0, 500),
1283
+ // 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
1284
+ // before broadcast, so it is safe to fall back to a user-paid deposit.
1285
+ safeToFallback: res.status === 503
1286
+ }
1255
1287
  );
1256
1288
  }
1257
1289
  return parsed.data;
@@ -1262,7 +1294,10 @@ async function postSponsorPermit2Transfer(input) {
1262
1294
  try {
1263
1295
  res = await fetch(`${base3}/api/v1/sponsor/permit2-transfer`, {
1264
1296
  method: "POST",
1265
- headers: { "content-type": "application/json", "x-owney-api-key": input.apiKey },
1297
+ headers: {
1298
+ "content-type": "application/json",
1299
+ "x-owney-api-key": input.apiKey
1300
+ },
1266
1301
  body: JSON.stringify(input.body)
1267
1302
  });
1268
1303
  } catch (networkError) {
@@ -1285,7 +1320,7 @@ async function postSponsorPermit2Transfer(input) {
1285
1320
  {
1286
1321
  statusCode: res.status,
1287
1322
  responseBody: text.slice(0, 500),
1288
- safeToFallback: res.status >= 400 && res.status < 500
1323
+ safeToFallback: res.status >= 400 && res.status < 500 || res.status === 503
1289
1324
  }
1290
1325
  );
1291
1326
  }
@@ -1295,9 +1330,12 @@ async function getSponsorRelayerAddress(input) {
1295
1330
  const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
1296
1331
  let res;
1297
1332
  try {
1298
- res = await fetch(`${base3}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`, {
1299
- headers: { "x-owney-api-key": input.apiKey }
1300
- });
1333
+ res = await fetch(
1334
+ `${base3}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
1335
+ {
1336
+ headers: { "x-owney-api-key": input.apiKey }
1337
+ }
1338
+ );
1301
1339
  } catch (networkError) {
1302
1340
  throw new OwneyError(
1303
1341
  "SPONSOR_REQUEST_FAILED",
@@ -1315,7 +1353,11 @@ async function getSponsorRelayerAddress(input) {
1315
1353
  throw new OwneyError(
1316
1354
  "SPONSOR_REQUEST_FAILED",
1317
1355
  `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
1318
- { statusCode: res.status, responseBody: text.slice(0, 500), safeToFallback: true }
1356
+ {
1357
+ statusCode: res.status,
1358
+ responseBody: text.slice(0, 500),
1359
+ safeToFallback: true
1360
+ }
1319
1361
  );
1320
1362
  }
1321
1363
  return parsed.data.relayer;
@@ -1502,18 +1544,21 @@ function decodeMultiAgentCursor(token) {
1502
1544
  }
1503
1545
  var SPONSORED_USDC_BY_CHAIN = {
1504
1546
  8453: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
1505
- 42161: "0xaf88d065e77c8cc2239327c5edb3a432268e5831"
1547
+ 42161: "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
1548
+ 1: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
1506
1549
  };
1507
1550
  var VIEM_CHAIN2 = {
1508
1551
  8453: import_chains2.base,
1509
- 42161: import_chains2.arbitrum
1552
+ 42161: import_chains2.arbitrum,
1553
+ 1: import_chains2.mainnet
1510
1554
  };
1511
1555
  var SPONSORED_WETH_BY_CHAIN = {
1512
1556
  8453: "0x4200000000000000000000000000000000000006",
1513
- 42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"
1557
+ 42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
1558
+ 1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
1514
1559
  };
1515
1560
  function shouldFallbackToUserPaid(error, asset, appCallback) {
1516
- return asset === "WETH" && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
1561
+ return (asset === "WETH" || asset === "USDC") && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
1517
1562
  }
1518
1563
  var OwneySDK = class {
1519
1564
  agents = /* @__PURE__ */ new Map();
@@ -1527,12 +1572,14 @@ var OwneySDK = class {
1527
1572
  state = null;
1528
1573
  apiKey;
1529
1574
  zyfaiRpcUrls;
1575
+ routingApiBaseUrl;
1530
1576
  cachedSponsoredCallback = null;
1531
1577
  cachedWethSponsoredCallback = null;
1532
1578
  initializingAgentsPromise = null;
1533
1579
  constructor(config) {
1534
1580
  this.apiKey = config.apiKey;
1535
1581
  this.zyfaiRpcUrls = config.zyfaiRpcUrls;
1582
+ this.routingApiBaseUrl = config.routingApiBaseUrl;
1536
1583
  }
1537
1584
  /**
1538
1585
  * Establish connection state for all subsequent SDK calls.
@@ -1587,8 +1634,7 @@ var OwneySDK = class {
1587
1634
  return this.state?.chainId ?? null;
1588
1635
  }
1589
1636
  requireState() {
1590
- if (!this.state)
1591
- throw new NotConnectedError();
1637
+ if (!this.state) throw new NotConnectedError();
1592
1638
  return this.state;
1593
1639
  }
1594
1640
  requireChainId() {
@@ -1618,12 +1664,12 @@ var OwneySDK = class {
1618
1664
  * `TransferWithAuthorization`, then POSTs to the sponsor API.
1619
1665
  */
1620
1666
  getDefaultSponsoredCallback() {
1621
- if (this.cachedSponsoredCallback)
1622
- return this.cachedSponsoredCallback;
1667
+ if (this.cachedSponsoredCallback) return this.cachedSponsoredCallback;
1623
1668
  const provider = this.requireConnectedProvider();
1624
1669
  const owner = this.state.walletAddress;
1625
1670
  this.cachedSponsoredCallback = makeSponsoredDepositCallback({
1626
1671
  apiKey: this.apiKey,
1672
+ baseUrl: this.routingApiBaseUrl,
1627
1673
  ownerAddress: owner,
1628
1674
  tokenAddressByChain: SPONSORED_USDC_BY_CHAIN,
1629
1675
  // Casts work around viem's chain-narrowed Client vs the generic
@@ -1654,6 +1700,7 @@ var OwneySDK = class {
1654
1700
  const owner = this.state.walletAddress;
1655
1701
  this.cachedWethSponsoredCallback = makeSponsoredWethCallback({
1656
1702
  apiKey: this.apiKey,
1703
+ baseUrl: this.routingApiBaseUrl,
1657
1704
  ownerAddress: owner,
1658
1705
  tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
1659
1706
  // Casts work around viem's chain-narrowed Client vs the generic
@@ -1682,22 +1729,19 @@ var OwneySDK = class {
1682
1729
  const active = /* @__PURE__ */ new Map();
1683
1730
  for (const id of this.activeAgents) {
1684
1731
  const agent = this.agents.get(id);
1685
- if (agent)
1686
- active.set(id, agent);
1732
+ if (agent) active.set(id, agent);
1687
1733
  }
1688
1734
  return active;
1689
1735
  }
1690
1736
  async ensureAgentsInitialized() {
1691
- if (this.agents.size > 0)
1692
- return;
1737
+ if (this.agents.size > 0) return;
1693
1738
  if (!this.initializingAgentsPromise) {
1694
1739
  this.initializingAgentsPromise = (async () => {
1695
1740
  const agentKeys = await fetchAgentKeys(this.apiKey);
1696
1741
  this.disabledAgents.clear();
1697
1742
  for (const { key, agent_type, is_enabled } of agentKeys) {
1698
1743
  const agent = this.createAgent(agent_type, key);
1699
- if (!agent)
1700
- continue;
1744
+ if (!agent) continue;
1701
1745
  this.agents.set(agent_type, agent);
1702
1746
  if (is_enabled === false) {
1703
1747
  this.disabledAgents.add(agent_type);
@@ -1936,10 +1980,9 @@ var OwneySDK = class {
1936
1980
  await this.approvePermit2();
1937
1981
  continue;
1938
1982
  }
1939
- if (!shouldFallbackToUserPaid(error, asset, appCallback))
1940
- throw error;
1983
+ if (!shouldFallbackToUserPaid(error, asset, appCallback)) throw error;
1941
1984
  console.warn(
1942
- "[owney-sdk] WETH sponsorship unavailable, falling back to user-paid deposit:",
1985
+ `[owney-sdk] ${asset} sponsorship unavailable, falling back to user-paid deposit:`,
1943
1986
  error
1944
1987
  );
1945
1988
  return attempt(void 0);
@@ -1954,8 +1997,7 @@ var OwneySDK = class {
1954
1997
  return BigInt(assetEntry?.minDepositAmount ?? "0");
1955
1998
  }
1956
1999
  splitDepositAmount(totalAmount, agents, chainId, asset, exempt = /* @__PURE__ */ new Set()) {
1957
- if (agents.length === 0)
1958
- return [];
2000
+ if (agents.length === 0) return [];
1959
2001
  const perAgent = totalAmount / BigInt(agents.length);
1960
2002
  const remainder = totalAmount % BigInt(agents.length);
1961
2003
  const splits = agents.map((agent, i) => ({
@@ -1979,10 +2021,8 @@ var OwneySDK = class {
1979
2021
  async validateMinDepositAmount(agent, state, chainId, asset, amount) {
1980
2022
  const minDepositAmount = this.getMinDepositAmount(agent, chainId, asset);
1981
2023
  const parsedAmount = BigInt(amount);
1982
- if (parsedAmount >= minDepositAmount)
1983
- return;
1984
- if (await this.hasExistingBalance(agent, state, chainId, asset))
1985
- return;
2024
+ if (parsedAmount >= minDepositAmount) return;
2025
+ if (await this.hasExistingBalance(agent, state, chainId, asset)) return;
1986
2026
  throw new OwneyError(
1987
2027
  "DEPOSIT_AMOUNT_BELOW_MINIMUM",
1988
2028
  `Amount "${amount}" is below the required minimum deposit "${minDepositAmount.toString()}" for agent "${agent.id}" on chain ${chainId} for asset "${asset}".`,
@@ -2146,8 +2186,7 @@ var OwneySDK = class {
2146
2186
  const agentErrors = {};
2147
2187
  for (let i = 0; i < plans.length; i++) {
2148
2188
  const p = plans[i];
2149
- if (p.planned === 0n)
2150
- continue;
2189
+ if (p.planned === 0n) continue;
2151
2190
  try {
2152
2191
  results[p.agent.id] = await p.agent.withdraw(
2153
2192
  state,
@@ -2281,19 +2320,30 @@ var OwneySDK = class {
2281
2320
  */
2282
2321
  async getAccountApy({
2283
2322
  agentId,
2284
- days
2323
+ days,
2324
+ tokenSymbol
2285
2325
  }) {
2286
2326
  const state = this.requireState();
2287
2327
  const chainId = this.requireChainId();
2288
2328
  if (agentId) {
2289
- return this.getAgent(agentId).getAccountApy(state, chainId, days);
2329
+ return this.getAgent(agentId).getAccountApy(
2330
+ state,
2331
+ chainId,
2332
+ days,
2333
+ tokenSymbol
2334
+ );
2290
2335
  }
2291
2336
  const activeAgents = this.getActiveAgents();
2292
2337
  const entries = [...activeAgents.entries()];
2293
2338
  const [apyResults, balanceResults] = await Promise.all([
2294
2339
  Promise.all(
2295
2340
  entries.map(async ([id, agent]) => {
2296
- const apy = await agent.getAccountApy(state, chainId, days);
2341
+ const apy = await agent.getAccountApy(
2342
+ state,
2343
+ chainId,
2344
+ days,
2345
+ tokenSymbol
2346
+ );
2297
2347
  return [id, apy];
2298
2348
  })
2299
2349
  ),
@@ -2316,15 +2366,13 @@ var OwneySDK = class {
2316
2366
  let relevantBalance = 0;
2317
2367
  for (const id of Object.keys(results)) {
2318
2368
  const agentApy = Number(results[id].weightedApyAfterFee ?? 0);
2319
- if (agentApy === 0)
2320
- continue;
2369
+ if (agentApy === 0) continue;
2321
2370
  relevantBalance += balances[id];
2322
2371
  }
2323
2372
  if (relevantBalance > 0) {
2324
2373
  for (const id of Object.keys(results)) {
2325
2374
  const agentApy = Number(results[id].weightedApyAfterFee ?? 0);
2326
- if (agentApy === 0)
2327
- continue;
2375
+ if (agentApy === 0) continue;
2328
2376
  const weight = balances[id] / relevantBalance;
2329
2377
  totalApy += agentApy * weight;
2330
2378
  }
@@ -2379,8 +2427,7 @@ var OwneySDK = class {
2379
2427
  const nextCursorMap = {};
2380
2428
  let hasMore = false;
2381
2429
  for (const { agentId: id, page } of perAgent) {
2382
- if (!page)
2383
- continue;
2430
+ if (!page) continue;
2384
2431
  merged.push(...page.data);
2385
2432
  if (page.hasMore && page.nextCursor) {
2386
2433
  nextCursorMap[id] = page.nextCursor;
@@ -2389,16 +2436,12 @@ var OwneySDK = class {
2389
2436
  }
2390
2437
  merged.sort((a, b) => {
2391
2438
  const dateDiff = new Date(b.date).getTime() - new Date(a.date).getTime();
2392
- if (dateDiff !== 0)
2393
- return dateDiff;
2394
- if (a.agent !== b.agent)
2395
- return a.agent < b.agent ? -1 : 1;
2439
+ if (dateDiff !== 0) return dateDiff;
2440
+ if (a.agent !== b.agent) return a.agent < b.agent ? -1 : 1;
2396
2441
  const aTx = a.transactions?.[0]?.txHashes?.[0] ?? "";
2397
2442
  const bTx = b.transactions?.[0]?.txHashes?.[0] ?? "";
2398
- if (aTx !== bTx)
2399
- return aTx < bTx ? -1 : 1;
2400
- if (a.action !== b.action)
2401
- return a.action < b.action ? -1 : 1;
2443
+ if (aTx !== bTx) return aTx < bTx ? -1 : 1;
2444
+ if (a.action !== b.action) return a.action < b.action ? -1 : 1;
2402
2445
  return 0;
2403
2446
  });
2404
2447
  return {
@@ -2444,8 +2487,7 @@ var OwneySDK = class {
2444
2487
  const state = this.requireState();
2445
2488
  const chainId = this.requireChainId();
2446
2489
  const agent = this.getAgent(agentId);
2447
- if (typeof agent.ensureAutoSelectProtocols !== "function")
2448
- return false;
2490
+ if (typeof agent.ensureAutoSelectProtocols !== "function") return false;
2449
2491
  return agent.ensureAutoSelectProtocols(state, chainId, asset);
2450
2492
  }
2451
2493
  /**
@@ -2457,8 +2499,10 @@ var OwneySDK = class {
2457
2499
  * @returns the approval transaction hash.
2458
2500
  */
2459
2501
  async approvePermit2(asset = "WETH") {
2502
+ void asset;
2460
2503
  const state = this.requireState();
2461
2504
  const chainId = this.requireChainId();
2505
+ this.validateAssetSupport(this.getAgent("zyfai"), chainId, "WETH");
2462
2506
  const token = SPONSORED_WETH_BY_CHAIN[chainId];
2463
2507
  if (!token) {
2464
2508
  throw new OwneyError(
@@ -2598,8 +2642,7 @@ var import_sdk2 = require("@zyfai/sdk");
2598
2642
  // src/agents/zyfai/zyfai.siwx-cache.ts
2599
2643
  var KEY_PREFIX2 = "owney.siwx.session";
2600
2644
  var storage2 = () => {
2601
- if (typeof window === "undefined")
2602
- return null;
2645
+ if (typeof window === "undefined") return null;
2603
2646
  try {
2604
2647
  return window.localStorage;
2605
2648
  } catch {
@@ -2609,11 +2652,9 @@ var storage2 = () => {
2609
2652
  var buildKey2 = (address, chainId) => `${KEY_PREFIX2}:${address.toLowerCase()}:${chainId}`;
2610
2653
  var readSiwxSession = (address, chainId) => {
2611
2654
  const store = storage2();
2612
- if (!store)
2613
- return null;
2655
+ if (!store) return null;
2614
2656
  const raw = store.getItem(buildKey2(address, chainId));
2615
- if (!raw)
2616
- return null;
2657
+ if (!raw) return null;
2617
2658
  try {
2618
2659
  return JSON.parse(raw);
2619
2660
  } catch {
@@ -2623,8 +2664,7 @@ var readSiwxSession = (address, chainId) => {
2623
2664
  };
2624
2665
  var writeSiwxSession = (address, chainId, session) => {
2625
2666
  const store = storage2();
2626
- if (!store)
2627
- return;
2667
+ if (!store) return;
2628
2668
  try {
2629
2669
  store.setItem(buildKey2(address, chainId), JSON.stringify(session));
2630
2670
  } catch {
@@ -2632,8 +2672,7 @@ var writeSiwxSession = (address, chainId, session) => {
2632
2672
  };
2633
2673
  var clearSiwxSession = (address, chainId) => {
2634
2674
  const store = storage2();
2635
- if (!store)
2636
- return;
2675
+ if (!store) return;
2637
2676
  try {
2638
2677
  store.removeItem(buildKey2(address, chainId));
2639
2678
  } catch {
@@ -2696,12 +2735,10 @@ function buildSIWXConfig(deps) {
2696
2735
  const jwt = readSession(address, id);
2697
2736
  const stored = readSiwxSession(address, id);
2698
2737
  if (!jwt) {
2699
- if (stored)
2700
- clearSiwxSession(address, id);
2738
+ if (stored) clearSiwxSession(address, id);
2701
2739
  return [];
2702
2740
  }
2703
- if (!stored)
2704
- return [];
2741
+ if (!stored) return [];
2705
2742
  const session = {
2706
2743
  // `stored.data.chainId` is a plain string in the cache (kept AppKit-free);
2707
2744
  // it always holds a valid CaipNetworkId here, so narrow at the boundary.