@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.js CHANGED
@@ -1,12 +1,12 @@
1
1
  // src/agents/zyfai/zyfai.agent.ts
2
2
  import { ZyfaiSDK } from "@zyfai/sdk";
3
3
  import { createPublicClient, http } from "viem";
4
- import { base, arbitrum } from "viem/chains";
4
+ import { base, arbitrum, mainnet } from "viem/chains";
5
5
 
6
6
  // src/types/config.ts
7
7
  var P_SUPPORTED_CHAINS = {
8
- chainIds: [8453, 42161],
9
- chains: ["BASE", "ARBITRUM"]
8
+ chainIds: [8453, 42161, 1],
9
+ chains: ["BASE", "ARBITRUM", "ETHEREUM"]
10
10
  };
11
11
  var SUPPORTED_TOKENS = ["USDC", "USDT", "WETH"];
12
12
  var SUPPORTED_CHAIN_IDS = P_SUPPORTED_CHAINS.chainIds;
@@ -41,6 +41,12 @@ var SupportedAssets = [
41
41
  symbol: "WETH",
42
42
  chainId: 42161,
43
43
  decimals: 18
44
+ },
45
+ {
46
+ address: `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48`,
47
+ symbol: "USDC",
48
+ chainId: 1,
49
+ decimals: 6
44
50
  }
45
51
  ];
46
52
 
@@ -107,8 +113,7 @@ var isValidChainId = (chainId) => {
107
113
  function hexToDecimal(hex, decimals = 6) {
108
114
  const normalized = hex.startsWith("0x") || hex.startsWith("0X") ? hex : `0x${hex}`;
109
115
  const parsed = BigInt(normalized);
110
- if (decimals <= 0)
111
- return Number(parsed);
116
+ if (decimals <= 0) return Number(parsed);
112
117
  const divisor = BigInt(10 ** decimals);
113
118
  const whole = parsed / divisor;
114
119
  const remainder = parsed % divisor;
@@ -121,6 +126,20 @@ var DayFilterMapping = {
121
126
  };
122
127
 
123
128
  // src/agents/zyfai/zyfai.mapper.ts
129
+ var DECIMALS_BY_SYMBOL = {
130
+ usdc: 6,
131
+ usdt: 6,
132
+ weth: 18,
133
+ eth: 18,
134
+ cbeth: 18,
135
+ wbtc: 8,
136
+ cbbtc: 8
137
+ };
138
+ function tokenDecimals(symbol, explicit) {
139
+ if (typeof explicit === "number" && Number.isFinite(explicit))
140
+ return explicit;
141
+ return DECIMALS_BY_SYMBOL[(symbol ?? "").toLowerCase()] ?? 6;
142
+ }
124
143
  function mapDeposit(raw) {
125
144
  return {
126
145
  txHash: raw.txHash,
@@ -137,7 +156,8 @@ function mapWithdraw(raw) {
137
156
  }
138
157
  var CHAIN_ID_TO_NAME = {
139
158
  8453: "BASE",
140
- 42161: "ARBITRUM"
159
+ 42161: "ARBITRUM",
160
+ 1: "ETHEREUM"
141
161
  };
142
162
  var NAME_TO_CHAIN_ID = Object.entries(
143
163
  CHAIN_ID_TO_NAME
@@ -147,8 +167,7 @@ var NAME_TO_CHAIN_ID = Object.entries(
147
167
  }, {});
148
168
  function resolveChainId(chain) {
149
169
  const asNum = Number(chain);
150
- if (Number.isFinite(asNum) && asNum > 0)
151
- return asNum;
170
+ if (Number.isFinite(asNum) && asNum > 0) return asNum;
152
171
  return NAME_TO_CHAIN_ID[chain.trim().toLowerCase()] ?? NaN;
153
172
  }
154
173
  function mapBalances(raw, _chainId, smartWallet) {
@@ -178,9 +197,16 @@ function mapBalances(raw, _chainId, smartWallet) {
178
197
  pool: p.pool,
179
198
  asset: p.token_symbol ?? "",
180
199
  // Position amount is hex-encoded base units (like the token balances
181
- // above). Supported assets (USDC/USDT) are 6-decimal — hexToDecimal's
182
- // default — so no per-token decimals lookup is needed.
183
- amount: String(hexToDecimal(p.amount || "0")),
200
+ // above). `PositionSlot` doesn't type a `decimals` field, but runtime
201
+ // getBalances payloads often carry one (e.g. 18 for WETH) — prefer that
202
+ // when present, otherwise fall back to the per-symbol lookup so
203
+ // non-6-decimal assets like WETH don't render 1e12 too large.
204
+ amount: String(
205
+ hexToDecimal(
206
+ p.amount || "0",
207
+ tokenDecimals(p.token_symbol, p.decimals)
208
+ )
209
+ ),
184
210
  apy: p.pool_apy,
185
211
  tvl: p.pool_tvl,
186
212
  // PositionSlot has no `liquidity` field yet; Zyfai will add it. Narrow
@@ -225,41 +251,39 @@ function mapEarnings(raw, smartWallet) {
225
251
  };
226
252
  }
227
253
  function mapWeightedApyByChain(raw) {
228
- if (!raw)
229
- return void 0;
254
+ if (!raw) return void 0;
230
255
  const out = {};
231
256
  for (const [chainKey, tokenApy] of Object.entries(raw)) {
232
257
  const chainId = Number(chainKey);
233
- if (!SUPPORTED_CHAIN_IDS.includes(chainId))
234
- continue;
258
+ if (!SUPPORTED_CHAIN_IDS.includes(chainId)) continue;
235
259
  const perAsset = {};
236
260
  for (const [symbol, apy] of Object.entries(tokenApy)) {
237
261
  const token = symbol;
238
- if (!SUPPORTED_TOKENS.includes(token))
239
- continue;
262
+ if (!SUPPORTED_TOKENS.includes(token)) continue;
240
263
  perAsset[token] = apy;
241
264
  }
242
- if (Object.keys(perAsset).length > 0)
243
- out[chainId] = perAsset;
265
+ if (Object.keys(perAsset).length > 0) out[chainId] = perAsset;
244
266
  }
245
267
  return Object.keys(out).length > 0 ? out : void 0;
246
268
  }
247
- function rawPoolApyForChain(entry, chainId) {
269
+ function rawPoolApyForChain(entry, chainId, tokenSymbol) {
248
270
  let weightedSum = 0;
249
271
  let totalBalance = 0;
250
272
  for (const p of entry.positions ?? []) {
251
- if (p.chainId !== chainId)
252
- continue;
273
+ if (p.chainId !== chainId) continue;
274
+ if (tokenSymbol && p.tokenSymbol !== tokenSymbol) continue;
253
275
  const balance = p.balance ?? 0;
254
- if (balance <= 0 || typeof p.apy !== "number")
255
- continue;
276
+ if (balance <= 0 || typeof p.apy !== "number") continue;
256
277
  weightedSum += p.apy * balance;
257
278
  totalBalance += balance;
258
279
  }
259
280
  return totalBalance > 0 ? weightedSum / totalBalance : null;
260
281
  }
261
- function mapApyHistory(raw, chainId) {
262
- 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));
282
+ function mapApyHistory(raw, chainId, tokenSymbol) {
283
+ const history = Object.entries(raw.history ?? {}).map(([date, entry]) => ({
284
+ date,
285
+ apy: rawPoolApyForChain(entry, chainId, tokenSymbol)
286
+ })).filter((p) => p.apy !== null).sort((a, b) => a.date.localeCompare(b.date));
263
287
  return {
264
288
  walletAddress: raw.walletAddress,
265
289
  weightedApyAfterFee: raw.weightedApyAfterFee ? sumTokenValues(raw.weightedApyAfterFee) : void 0,
@@ -282,6 +306,7 @@ function mapEntries(rawEntries, chainId) {
282
306
  "Withdraw from protocol": "Withdraw",
283
307
  Rebalance: "Rebalance"
284
308
  };
309
+ void chainId;
285
310
  return rawEntries.map((entry) => {
286
311
  const rawLog = entry.rebalanceLog;
287
312
  const positions = entry.positions ?? [];
@@ -303,7 +328,15 @@ function mapEntries(rawEntries, chainId) {
303
328
  fromPool: from.pool,
304
329
  toPool: to.pool,
305
330
  tokenSymbol: matched?.token_symbol ?? "",
306
- amount: matched ? String(hexToDecimal(matched.amount || "0x")) : "",
331
+ amount: matched ? String(
332
+ hexToDecimal(
333
+ matched.amount || "0x",
334
+ tokenDecimals(
335
+ matched.token_symbol,
336
+ matched.decimals
337
+ )
338
+ )
339
+ ) : "",
307
340
  status: "success"
308
341
  }
309
342
  ];
@@ -322,7 +355,12 @@ function mapEntries(rawEntries, chainId) {
322
355
  txHashes: entry.transactionHash ? [entry.transactionHash] : [],
323
356
  chainId: entry.chainId,
324
357
  tokenSymbol: entry?.positions?.[0]?.token_symbol,
325
- amount: hasAmount ? String(hexToDecimal(rawAmount)) : void 0
358
+ amount: hasAmount ? String(
359
+ hexToDecimal(
360
+ rawAmount,
361
+ tokenDecimals(entry?.positions?.[0]?.token_symbol)
362
+ )
363
+ ) : void 0
326
364
  }
327
365
  ],
328
366
  rebalanceLog
@@ -347,8 +385,7 @@ function mapApyByStrategy(raw) {
347
385
  const supported = SupportedAssets.find(
348
386
  (asset) => asset.chainId === entry.chain_id && asset.symbol === entry.token_symbol
349
387
  );
350
- if (!supported)
351
- continue;
388
+ if (!supported) continue;
352
389
  const chainKey = supported.chainId;
353
390
  const symbolKey = supported.symbol;
354
391
  const bucket = apyPerAsset[chainKey] ?? {};
@@ -378,10 +415,8 @@ function computeAllocationApy(positions) {
378
415
  totalValue += value;
379
416
  const chainId = resolveChainId(p.chain);
380
417
  const token = p.asset;
381
- if (!SUPPORTED_CHAIN_IDS.includes(chainId))
382
- continue;
383
- if (!SUPPORTED_TOKENS.includes(token))
384
- continue;
418
+ if (!SUPPORTED_CHAIN_IDS.includes(chainId)) continue;
419
+ if (!SUPPORTED_TOKENS.includes(token)) continue;
385
420
  const perAsset = buckets[chainId] ?? {};
386
421
  const cell = perAsset[token] ?? { weightedSum: 0, value: 0 };
387
422
  cell.weightedSum += apy * value;
@@ -398,8 +433,7 @@ function computeAllocationApy(positions) {
398
433
  out[token] = cell.weightedSum / cell.value;
399
434
  }
400
435
  }
401
- if (Object.keys(out).length > 0)
402
- apyByChainAndAsset[chainId] = out;
436
+ if (Object.keys(out).length > 0) apyByChainAndAsset[chainId] = out;
403
437
  }
404
438
  const totalApy = totalValue > 0 ? String(weightedSum / totalValue) : "0";
405
439
  return { totalApy, apyByChainAndAsset };
@@ -416,19 +450,14 @@ function extractWithdrawnAmount(logs, recipient, token, decimals) {
416
450
  let total = 0n;
417
451
  let matched = false;
418
452
  for (const log of logs) {
419
- if (log.address.toLowerCase() !== wantToken)
420
- continue;
421
- if ((log.topics[0] ?? "").toLowerCase() !== TRANSFER_TOPIC)
422
- continue;
423
- if (topicToAddress(log.topics[2]) !== wantRecipient)
424
- continue;
425
- if (!log.data || log.data === "0x")
426
- continue;
453
+ if (log.address.toLowerCase() !== wantToken) continue;
454
+ if ((log.topics[0] ?? "").toLowerCase() !== TRANSFER_TOPIC) continue;
455
+ if (topicToAddress(log.topics[2]) !== wantRecipient) continue;
456
+ if (!log.data || log.data === "0x") continue;
427
457
  total += BigInt(log.data);
428
458
  matched = true;
429
459
  }
430
- if (!matched)
431
- return void 0;
460
+ if (!matched) return void 0;
432
461
  return String(hexToDecimal(`0x${total.toString(16)}`, decimals));
433
462
  }
434
463
 
@@ -444,8 +473,7 @@ function encodeHistoryCursor(payload) {
444
473
  return Buffer.from(JSON.stringify(payload), "utf8").toString("base64");
445
474
  }
446
475
  function decodeHistoryCursor(token) {
447
- if (!token)
448
- throw new InvalidHistoryCursorError("Cursor is empty.");
476
+ if (!token) throw new InvalidHistoryCursorError("Cursor is empty.");
449
477
  let parsed;
450
478
  try {
451
479
  const json = Buffer.from(token, "base64").toString("utf8");
@@ -462,8 +490,7 @@ function decodeHistoryCursor(token) {
462
490
  // src/agents/zyfai/zyfai.auth-cache.ts
463
491
  var KEY_PREFIX = "owney.zyfai.session";
464
492
  var storage = () => {
465
- if (typeof window === "undefined")
466
- return null;
493
+ if (typeof window === "undefined") return null;
467
494
  try {
468
495
  return window.localStorage;
469
496
  } catch {
@@ -473,15 +500,13 @@ var storage = () => {
473
500
  var buildKey = (address, chainId) => `${KEY_PREFIX}:${address.toLowerCase()}:${chainId}`;
474
501
  var isJwtExpired = (token) => {
475
502
  const parts = token.split(".");
476
- if (parts.length !== 3)
477
- return false;
503
+ if (parts.length !== 3) return false;
478
504
  try {
479
505
  const payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
480
506
  const padded = payload + "=".repeat((4 - payload.length % 4) % 4);
481
507
  const decoded = typeof atob === "function" ? atob(padded) : Buffer.from(padded, "base64").toString("utf8");
482
508
  const claims = JSON.parse(decoded);
483
- if (typeof claims.exp !== "number")
484
- return false;
509
+ if (typeof claims.exp !== "number") return false;
485
510
  return Date.now() / 1e3 >= claims.exp - 30;
486
511
  } catch {
487
512
  return false;
@@ -489,11 +514,9 @@ var isJwtExpired = (token) => {
489
514
  };
490
515
  var readSession = (address, chainId) => {
491
516
  const store = storage();
492
- if (!store)
493
- return null;
517
+ if (!store) return null;
494
518
  const raw = store.getItem(buildKey(address, chainId));
495
- if (!raw)
496
- return null;
519
+ if (!raw) return null;
497
520
  try {
498
521
  const parsed = JSON.parse(raw);
499
522
  if (!parsed.token || isJwtExpired(parsed.token)) {
@@ -508,8 +531,7 @@ var readSession = (address, chainId) => {
508
531
  };
509
532
  var writeSession = (address, chainId, session) => {
510
533
  const store = storage();
511
- if (!store)
512
- return;
534
+ if (!store) return;
513
535
  try {
514
536
  store.setItem(buildKey(address, chainId), JSON.stringify(session));
515
537
  } catch {
@@ -517,8 +539,7 @@ var writeSession = (address, chainId, session) => {
517
539
  };
518
540
  var clearSession = (address, chainId) => {
519
541
  const store = storage();
520
- if (!store)
521
- return;
542
+ if (!store) return;
522
543
  try {
523
544
  store.removeItem(buildKey(address, chainId));
524
545
  } catch {
@@ -527,8 +548,7 @@ var clearSession = (address, chainId) => {
527
548
 
528
549
  // src/agents/zyfai/autoSelect.ts
529
550
  function buildAutoSelectUpdate(current, asset) {
530
- if (current.autoSelectProtocols === true)
531
- return null;
551
+ if (current.autoSelectProtocols === true) return null;
532
552
  return {
533
553
  asset,
534
554
  autoSelectProtocols: true,
@@ -539,16 +559,18 @@ function buildAutoSelectUpdate(current, asset) {
539
559
  // src/agents/zyfai/zyfai.agent.ts
540
560
  var DEFAULT_ZYFAI_RPC_URLS = {
541
561
  8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
542
- 42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V"
562
+ 42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
563
+ 1: "https://eth-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V"
543
564
  };
544
565
  var WETH_ADDRESS_BY_CHAIN = {
545
566
  8453: "0x4200000000000000000000000000000000000006",
546
- 42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"
567
+ 42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
568
+ 1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
547
569
  };
548
- var VIEM_CHAIN = { 8453: base, 42161: arbitrum };
570
+ var VIEM_CHAIN = { 8453: base, 42161: arbitrum, 1: mainnet };
549
571
  var ZyfaiAgent = class {
550
572
  id = "zyfai";
551
- supportedChainIds = [8453, 42161];
573
+ supportedChainIds = [8453, 42161, 1];
552
574
  supportedAssets = [
553
575
  {
554
576
  chainId: 8453,
@@ -565,6 +587,13 @@ var ZyfaiAgent = class {
565
587
  { symbol: "USDC", minDepositAmount: "1500000" },
566
588
  { symbol: "WETH", minDepositAmount: "500000000000000" }
567
589
  ]
590
+ },
591
+ {
592
+ chainId: 1,
593
+ chain: "ETHEREUM",
594
+ // Zyfai enforces a 10,000 USDC minimum portfolio on Ethereum mainnet
595
+ // (mainnet gas only pays off on large positions). 10_000 * 1e6 raw units.
596
+ assets: [{ symbol: "USDC", minDepositAmount: "10000000000" }]
568
597
  }
569
598
  ];
570
599
  sdk;
@@ -588,8 +617,7 @@ var ZyfaiAgent = class {
588
617
  }
589
618
  getPublicClient(chainId) {
590
619
  const cached = this.publicClients.get(chainId);
591
- if (cached)
592
- return cached;
620
+ if (cached) return cached;
593
621
  const client = createPublicClient({
594
622
  chain: VIEM_CHAIN[chainId],
595
623
  transport: http(this.rpcUrls[chainId])
@@ -599,33 +627,43 @@ var ZyfaiAgent = class {
599
627
  }
600
628
  /**
601
629
  * Fills in the true withdrawn amount for `Withdraw` entries by decoding the
602
- * USDC `Transfer` that lands in the user's wallet on-chain. The Zyfai history
630
+ * on-chain `Transfer` that lands in the user's wallet. The Zyfai history
603
631
  * payload only reports the *remaining* position for a partial withdraw (see
604
632
  * the note in `mapEntries`), so without this the UI shows the leftover
605
633
  * balance instead of the amount withdrawn.
606
634
  *
635
+ * The withdrawn asset varies per entry (USDC or WETH), so it's resolved
636
+ * from `tx.tokenSymbol` (set by `mapEntries` from the entry's own
637
+ * position) rather than assumed to always be USDC. Zyfai sometimes
638
+ * denominates WETH as lowercase "eth" — that's normalized to WETH.
639
+ * Entries with no symbol (older payload shapes) fall back to USDC to
640
+ * preserve prior behavior.
641
+ *
607
642
  * Mutates `entries` in place. Runs all lookups concurrently and is tolerant
608
643
  * of RPC failures: an entry whose amount can't be resolved keeps
609
644
  * `amount: undefined`, which consumers render as a hidden/retryable row
610
645
  * rather than a wrong number.
611
646
  */
612
647
  async resolveWithdrawnAmounts(entries, chainId, recipient) {
613
- const asset = SupportedAssets.find(
614
- (a) => a.chainId === chainId && a.symbol === "USDC"
615
- );
616
- if (!asset)
617
- return;
618
648
  const withdrawals = entries.filter(
619
649
  (e) => e.action === "Withdraw" && e.transactions[0]?.txHashes[0]
620
650
  );
621
- if (withdrawals.length === 0)
622
- return;
651
+ if (withdrawals.length === 0) return;
623
652
  const client = this.getPublicClient(chainId);
624
653
  await Promise.all(
625
654
  withdrawals.map(async (entry) => {
626
655
  const tx = entry.transactions[0];
627
656
  const txHash = tx.txHashes[0];
628
- const cached = this.withdrawAmountCache.get(txHash);
657
+ const rawSymbol = tx.tokenSymbol;
658
+ const normalizedSymbol = rawSymbol ? rawSymbol.toLowerCase() === "eth" ? "WETH" : rawSymbol.toUpperCase() : "USDC";
659
+ const asset = SupportedAssets.find(
660
+ (a) => a.chainId === chainId && a.symbol === normalizedSymbol
661
+ ) ?? SupportedAssets.find(
662
+ (a) => a.chainId === chainId && a.symbol === "USDC"
663
+ );
664
+ if (!asset) return;
665
+ const cacheKey = `${txHash}:${asset.address}`;
666
+ const cached = this.withdrawAmountCache.get(cacheKey);
629
667
  if (cached !== void 0) {
630
668
  tx.amount = cached;
631
669
  return;
@@ -641,7 +679,7 @@ var ZyfaiAgent = class {
641
679
  asset.decimals
642
680
  );
643
681
  if (amount !== void 0) {
644
- this.withdrawAmountCache.set(txHash, amount);
682
+ this.withdrawAmountCache.set(cacheKey, amount);
645
683
  tx.amount = amount;
646
684
  }
647
685
  } catch {
@@ -660,13 +698,11 @@ var ZyfaiAgent = class {
660
698
  * for some apps and the patch only matters in the browser anyway.
661
699
  */
662
700
  installAuthCache() {
663
- if (typeof window === "undefined")
664
- return;
701
+ if (typeof window === "undefined") return;
665
702
  const internals = this.sdk;
666
703
  const original = internals.authenticateUser.bind(internals);
667
704
  internals.authenticateUser = async () => {
668
- if (internals.authenticatedUserId !== null)
669
- return;
705
+ if (internals.authenticatedUserId !== null) return;
670
706
  const address = internals.walletClient?.account?.address;
671
707
  const chainId = internals.currentChainId;
672
708
  if (address && chainId) {
@@ -741,12 +777,10 @@ var ZyfaiAgent = class {
741
777
  return await this.sdk.connectAccount(provider, chainId);
742
778
  } catch (error) {
743
779
  const message = error instanceof Error ? error.message : String(error);
744
- if (!message.toLowerCase().includes("unauthorized"))
745
- throw error;
780
+ if (!message.toLowerCase().includes("unauthorized")) throw error;
746
781
  const internals = this.sdk;
747
782
  const address = internals.walletClient?.account?.address;
748
- if (address)
749
- clearSession(address, chainId);
783
+ if (address) clearSession(address, chainId);
750
784
  return await this.sdk.connectAccount(provider, chainId);
751
785
  }
752
786
  }
@@ -876,10 +910,10 @@ var ZyfaiAgent = class {
876
910
  const raw = await this.sdk.getOnchainEarnings(smartWallet);
877
911
  return mapEarnings(raw, smartWallet);
878
912
  }
879
- async getAccountApy(state, chainId, days) {
913
+ async getAccountApy(state, chainId, days, tokenSymbol) {
880
914
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
881
915
  const raw = await this.sdk.getDailyApyHistory(smartWallet, days);
882
- return mapApyHistory(raw, chainId);
916
+ return mapApyHistory(raw, chainId, tokenSymbol);
883
917
  }
884
918
  async getHistory(state, chainId, options) {
885
919
  const { smartWallet, chainId: validChainId } = await this.resolveSmartWallet(state, chainId);
@@ -944,8 +978,7 @@ var ZyfaiAgent = class {
944
978
  },
945
979
  asset
946
980
  );
947
- if (!update)
948
- return false;
981
+ if (!update) return false;
949
982
  await this.sdk.updateUserProfile(update);
950
983
  return true;
951
984
  }
@@ -1034,8 +1067,7 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
1034
1067
  const tokenBalance = agentBalance?.tokens.find(
1035
1068
  (t) => t.chainId === chainId && t.asset.toUpperCase() === target
1036
1069
  );
1037
- if (!tokenBalance)
1038
- return { agent, balance: 0n };
1070
+ if (!tokenBalance) return { agent, balance: 0n };
1039
1071
  return { agent, balance: parseUnits(tokenBalance.amount, decimals) };
1040
1072
  });
1041
1073
  }
@@ -1052,11 +1084,9 @@ function planProportionalShares(balances, requested, totalAvailable) {
1052
1084
  return diff > 0n ? 1 : diff < 0n ? -1 : 0;
1053
1085
  });
1054
1086
  for (const p of byHeadroom) {
1055
- if (remainder === 0n)
1056
- break;
1087
+ if (remainder === 0n) break;
1057
1088
  const headroom = p.balance - p.planned;
1058
- if (headroom <= 0n)
1059
- continue;
1089
+ if (headroom <= 0n) continue;
1060
1090
  const take = headroom < remainder ? headroom : remainder;
1061
1091
  p.planned += take;
1062
1092
  remainder -= take;
@@ -1085,8 +1115,7 @@ function redistributeShare(plans, fromIndex, amount, candidatePool) {
1085
1115
  (s, c) => s + (c.balance - c.planned),
1086
1116
  0n
1087
1117
  );
1088
- if (totalHeadroom === 0n)
1089
- return;
1118
+ if (totalHeadroom === 0n) return;
1090
1119
  let distributed = 0n;
1091
1120
  for (const c of candidates) {
1092
1121
  const headroom = c.balance - c.planned;
@@ -1097,11 +1126,9 @@ function redistributeShare(plans, fromIndex, amount, candidatePool) {
1097
1126
  }
1098
1127
  let leftover = amount - distributed;
1099
1128
  for (const c of candidates) {
1100
- if (leftover === 0n)
1101
- break;
1129
+ if (leftover === 0n) break;
1102
1130
  const headroom = c.balance - c.planned;
1103
- if (headroom <= 0n)
1104
- continue;
1131
+ if (headroom <= 0n) continue;
1105
1132
  const take = headroom < leftover ? headroom : leftover;
1106
1133
  c.planned += take;
1107
1134
  leftover -= take;
@@ -1118,16 +1145,13 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
1118
1145
  for (const id of Object.keys(agentApys)) {
1119
1146
  const cells = agentApys[id].apyByChainAndAsset;
1120
1147
  const balance = agentBalances[id] ?? 0;
1121
- if (!cells || balance <= 0)
1122
- continue;
1148
+ if (!cells || balance <= 0) continue;
1123
1149
  for (const [chainKey, perAsset] of Object.entries(cells)) {
1124
- if (!perAsset)
1125
- continue;
1150
+ if (!perAsset) continue;
1126
1151
  const chainId = Number(chainKey);
1127
1152
  for (const [asset, apyValue] of Object.entries(perAsset)) {
1128
1153
  const apy = Number(apyValue ?? 0);
1129
- if (apy === 0)
1130
- continue;
1154
+ if (apy === 0) continue;
1131
1155
  sums[chainId] ??= {};
1132
1156
  weights[chainId] ??= {};
1133
1157
  sums[chainId][asset] = (sums[chainId][asset] ?? 0) + apy * balance;
@@ -1141,8 +1165,7 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
1141
1165
  const perAssetOut = {};
1142
1166
  for (const asset of Object.keys(sums[chainId])) {
1143
1167
  const w = weights[chainId][asset];
1144
- if (w > 0)
1145
- perAssetOut[asset] = sums[chainId][asset] / w;
1168
+ if (w > 0) perAssetOut[asset] = sums[chainId][asset] / w;
1146
1169
  }
1147
1170
  if (Object.keys(perAssetOut).length > 0) {
1148
1171
  out[chainId] = perAssetOut;
@@ -1157,7 +1180,7 @@ import {
1157
1180
  createWalletClient,
1158
1181
  custom
1159
1182
  } from "viem";
1160
- import { base as base2, arbitrum as arbitrum2 } from "viem/chains";
1183
+ import { base as base2, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
1161
1184
 
1162
1185
  // src/lib/transfer-auth.ts
1163
1186
  import { bytesToHex } from "viem";
@@ -1203,7 +1226,10 @@ async function postSponsorTransferAuth(input) {
1203
1226
  try {
1204
1227
  res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
1205
1228
  method: "POST",
1206
- headers: { "content-type": "application/json", "x-owney-api-key": input.apiKey },
1229
+ headers: {
1230
+ "content-type": "application/json",
1231
+ "x-owney-api-key": input.apiKey
1232
+ },
1207
1233
  body: JSON.stringify(input.body)
1208
1234
  });
1209
1235
  } catch (networkError) {
@@ -1223,7 +1249,13 @@ async function postSponsorTransferAuth(input) {
1223
1249
  throw new OwneyError(
1224
1250
  "SPONSOR_REQUEST_FAILED",
1225
1251
  `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
1226
- { statusCode: res.status, responseBody: text.slice(0, 500) }
1252
+ {
1253
+ statusCode: res.status,
1254
+ responseBody: text.slice(0, 500),
1255
+ // 503 SPONSOR_UNAVAILABLE = relayer out of gas; the tx was rejected
1256
+ // before broadcast, so it is safe to fall back to a user-paid deposit.
1257
+ safeToFallback: res.status === 503
1258
+ }
1227
1259
  );
1228
1260
  }
1229
1261
  return parsed.data;
@@ -1234,7 +1266,10 @@ async function postSponsorPermit2Transfer(input) {
1234
1266
  try {
1235
1267
  res = await fetch(`${base3}/api/v1/sponsor/permit2-transfer`, {
1236
1268
  method: "POST",
1237
- headers: { "content-type": "application/json", "x-owney-api-key": input.apiKey },
1269
+ headers: {
1270
+ "content-type": "application/json",
1271
+ "x-owney-api-key": input.apiKey
1272
+ },
1238
1273
  body: JSON.stringify(input.body)
1239
1274
  });
1240
1275
  } catch (networkError) {
@@ -1257,7 +1292,7 @@ async function postSponsorPermit2Transfer(input) {
1257
1292
  {
1258
1293
  statusCode: res.status,
1259
1294
  responseBody: text.slice(0, 500),
1260
- safeToFallback: res.status >= 400 && res.status < 500
1295
+ safeToFallback: res.status >= 400 && res.status < 500 || res.status === 503
1261
1296
  }
1262
1297
  );
1263
1298
  }
@@ -1267,9 +1302,12 @@ async function getSponsorRelayerAddress(input) {
1267
1302
  const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
1268
1303
  let res;
1269
1304
  try {
1270
- res = await fetch(`${base3}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`, {
1271
- headers: { "x-owney-api-key": input.apiKey }
1272
- });
1305
+ res = await fetch(
1306
+ `${base3}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
1307
+ {
1308
+ headers: { "x-owney-api-key": input.apiKey }
1309
+ }
1310
+ );
1273
1311
  } catch (networkError) {
1274
1312
  throw new OwneyError(
1275
1313
  "SPONSOR_REQUEST_FAILED",
@@ -1287,7 +1325,11 @@ async function getSponsorRelayerAddress(input) {
1287
1325
  throw new OwneyError(
1288
1326
  "SPONSOR_REQUEST_FAILED",
1289
1327
  `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
1290
- { statusCode: res.status, responseBody: text.slice(0, 500), safeToFallback: true }
1328
+ {
1329
+ statusCode: res.status,
1330
+ responseBody: text.slice(0, 500),
1331
+ safeToFallback: true
1332
+ }
1291
1333
  );
1292
1334
  }
1293
1335
  return parsed.data.relayer;
@@ -1474,18 +1516,21 @@ function decodeMultiAgentCursor(token) {
1474
1516
  }
1475
1517
  var SPONSORED_USDC_BY_CHAIN = {
1476
1518
  8453: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
1477
- 42161: "0xaf88d065e77c8cc2239327c5edb3a432268e5831"
1519
+ 42161: "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
1520
+ 1: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
1478
1521
  };
1479
1522
  var VIEM_CHAIN2 = {
1480
1523
  8453: base2,
1481
- 42161: arbitrum2
1524
+ 42161: arbitrum2,
1525
+ 1: mainnet2
1482
1526
  };
1483
1527
  var SPONSORED_WETH_BY_CHAIN = {
1484
1528
  8453: "0x4200000000000000000000000000000000000006",
1485
- 42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"
1529
+ 42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
1530
+ 1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
1486
1531
  };
1487
1532
  function shouldFallbackToUserPaid(error, asset, appCallback) {
1488
- return asset === "WETH" && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
1533
+ return (asset === "WETH" || asset === "USDC") && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
1489
1534
  }
1490
1535
  var OwneySDK = class {
1491
1536
  agents = /* @__PURE__ */ new Map();
@@ -1499,12 +1544,14 @@ var OwneySDK = class {
1499
1544
  state = null;
1500
1545
  apiKey;
1501
1546
  zyfaiRpcUrls;
1547
+ routingApiBaseUrl;
1502
1548
  cachedSponsoredCallback = null;
1503
1549
  cachedWethSponsoredCallback = null;
1504
1550
  initializingAgentsPromise = null;
1505
1551
  constructor(config) {
1506
1552
  this.apiKey = config.apiKey;
1507
1553
  this.zyfaiRpcUrls = config.zyfaiRpcUrls;
1554
+ this.routingApiBaseUrl = config.routingApiBaseUrl;
1508
1555
  }
1509
1556
  /**
1510
1557
  * Establish connection state for all subsequent SDK calls.
@@ -1559,8 +1606,7 @@ var OwneySDK = class {
1559
1606
  return this.state?.chainId ?? null;
1560
1607
  }
1561
1608
  requireState() {
1562
- if (!this.state)
1563
- throw new NotConnectedError();
1609
+ if (!this.state) throw new NotConnectedError();
1564
1610
  return this.state;
1565
1611
  }
1566
1612
  requireChainId() {
@@ -1590,12 +1636,12 @@ var OwneySDK = class {
1590
1636
  * `TransferWithAuthorization`, then POSTs to the sponsor API.
1591
1637
  */
1592
1638
  getDefaultSponsoredCallback() {
1593
- if (this.cachedSponsoredCallback)
1594
- return this.cachedSponsoredCallback;
1639
+ if (this.cachedSponsoredCallback) return this.cachedSponsoredCallback;
1595
1640
  const provider = this.requireConnectedProvider();
1596
1641
  const owner = this.state.walletAddress;
1597
1642
  this.cachedSponsoredCallback = makeSponsoredDepositCallback({
1598
1643
  apiKey: this.apiKey,
1644
+ baseUrl: this.routingApiBaseUrl,
1599
1645
  ownerAddress: owner,
1600
1646
  tokenAddressByChain: SPONSORED_USDC_BY_CHAIN,
1601
1647
  // Casts work around viem's chain-narrowed Client vs the generic
@@ -1626,6 +1672,7 @@ var OwneySDK = class {
1626
1672
  const owner = this.state.walletAddress;
1627
1673
  this.cachedWethSponsoredCallback = makeSponsoredWethCallback({
1628
1674
  apiKey: this.apiKey,
1675
+ baseUrl: this.routingApiBaseUrl,
1629
1676
  ownerAddress: owner,
1630
1677
  tokenAddressByChain: SPONSORED_WETH_BY_CHAIN,
1631
1678
  // Casts work around viem's chain-narrowed Client vs the generic
@@ -1654,22 +1701,19 @@ var OwneySDK = class {
1654
1701
  const active = /* @__PURE__ */ new Map();
1655
1702
  for (const id of this.activeAgents) {
1656
1703
  const agent = this.agents.get(id);
1657
- if (agent)
1658
- active.set(id, agent);
1704
+ if (agent) active.set(id, agent);
1659
1705
  }
1660
1706
  return active;
1661
1707
  }
1662
1708
  async ensureAgentsInitialized() {
1663
- if (this.agents.size > 0)
1664
- return;
1709
+ if (this.agents.size > 0) return;
1665
1710
  if (!this.initializingAgentsPromise) {
1666
1711
  this.initializingAgentsPromise = (async () => {
1667
1712
  const agentKeys = await fetchAgentKeys(this.apiKey);
1668
1713
  this.disabledAgents.clear();
1669
1714
  for (const { key, agent_type, is_enabled } of agentKeys) {
1670
1715
  const agent = this.createAgent(agent_type, key);
1671
- if (!agent)
1672
- continue;
1716
+ if (!agent) continue;
1673
1717
  this.agents.set(agent_type, agent);
1674
1718
  if (is_enabled === false) {
1675
1719
  this.disabledAgents.add(agent_type);
@@ -1908,10 +1952,9 @@ var OwneySDK = class {
1908
1952
  await this.approvePermit2();
1909
1953
  continue;
1910
1954
  }
1911
- if (!shouldFallbackToUserPaid(error, asset, appCallback))
1912
- throw error;
1955
+ if (!shouldFallbackToUserPaid(error, asset, appCallback)) throw error;
1913
1956
  console.warn(
1914
- "[owney-sdk] WETH sponsorship unavailable, falling back to user-paid deposit:",
1957
+ `[owney-sdk] ${asset} sponsorship unavailable, falling back to user-paid deposit:`,
1915
1958
  error
1916
1959
  );
1917
1960
  return attempt(void 0);
@@ -1926,8 +1969,7 @@ var OwneySDK = class {
1926
1969
  return BigInt(assetEntry?.minDepositAmount ?? "0");
1927
1970
  }
1928
1971
  splitDepositAmount(totalAmount, agents, chainId, asset, exempt = /* @__PURE__ */ new Set()) {
1929
- if (agents.length === 0)
1930
- return [];
1972
+ if (agents.length === 0) return [];
1931
1973
  const perAgent = totalAmount / BigInt(agents.length);
1932
1974
  const remainder = totalAmount % BigInt(agents.length);
1933
1975
  const splits = agents.map((agent, i) => ({
@@ -1951,10 +1993,8 @@ var OwneySDK = class {
1951
1993
  async validateMinDepositAmount(agent, state, chainId, asset, amount) {
1952
1994
  const minDepositAmount = this.getMinDepositAmount(agent, chainId, asset);
1953
1995
  const parsedAmount = BigInt(amount);
1954
- if (parsedAmount >= minDepositAmount)
1955
- return;
1956
- if (await this.hasExistingBalance(agent, state, chainId, asset))
1957
- return;
1996
+ if (parsedAmount >= minDepositAmount) return;
1997
+ if (await this.hasExistingBalance(agent, state, chainId, asset)) return;
1958
1998
  throw new OwneyError(
1959
1999
  "DEPOSIT_AMOUNT_BELOW_MINIMUM",
1960
2000
  `Amount "${amount}" is below the required minimum deposit "${minDepositAmount.toString()}" for agent "${agent.id}" on chain ${chainId} for asset "${asset}".`,
@@ -2118,8 +2158,7 @@ var OwneySDK = class {
2118
2158
  const agentErrors = {};
2119
2159
  for (let i = 0; i < plans.length; i++) {
2120
2160
  const p = plans[i];
2121
- if (p.planned === 0n)
2122
- continue;
2161
+ if (p.planned === 0n) continue;
2123
2162
  try {
2124
2163
  results[p.agent.id] = await p.agent.withdraw(
2125
2164
  state,
@@ -2253,19 +2292,30 @@ var OwneySDK = class {
2253
2292
  */
2254
2293
  async getAccountApy({
2255
2294
  agentId,
2256
- days
2295
+ days,
2296
+ tokenSymbol
2257
2297
  }) {
2258
2298
  const state = this.requireState();
2259
2299
  const chainId = this.requireChainId();
2260
2300
  if (agentId) {
2261
- return this.getAgent(agentId).getAccountApy(state, chainId, days);
2301
+ return this.getAgent(agentId).getAccountApy(
2302
+ state,
2303
+ chainId,
2304
+ days,
2305
+ tokenSymbol
2306
+ );
2262
2307
  }
2263
2308
  const activeAgents = this.getActiveAgents();
2264
2309
  const entries = [...activeAgents.entries()];
2265
2310
  const [apyResults, balanceResults] = await Promise.all([
2266
2311
  Promise.all(
2267
2312
  entries.map(async ([id, agent]) => {
2268
- const apy = await agent.getAccountApy(state, chainId, days);
2313
+ const apy = await agent.getAccountApy(
2314
+ state,
2315
+ chainId,
2316
+ days,
2317
+ tokenSymbol
2318
+ );
2269
2319
  return [id, apy];
2270
2320
  })
2271
2321
  ),
@@ -2288,15 +2338,13 @@ var OwneySDK = class {
2288
2338
  let relevantBalance = 0;
2289
2339
  for (const id of Object.keys(results)) {
2290
2340
  const agentApy = Number(results[id].weightedApyAfterFee ?? 0);
2291
- if (agentApy === 0)
2292
- continue;
2341
+ if (agentApy === 0) continue;
2293
2342
  relevantBalance += balances[id];
2294
2343
  }
2295
2344
  if (relevantBalance > 0) {
2296
2345
  for (const id of Object.keys(results)) {
2297
2346
  const agentApy = Number(results[id].weightedApyAfterFee ?? 0);
2298
- if (agentApy === 0)
2299
- continue;
2347
+ if (agentApy === 0) continue;
2300
2348
  const weight = balances[id] / relevantBalance;
2301
2349
  totalApy += agentApy * weight;
2302
2350
  }
@@ -2351,8 +2399,7 @@ var OwneySDK = class {
2351
2399
  const nextCursorMap = {};
2352
2400
  let hasMore = false;
2353
2401
  for (const { agentId: id, page } of perAgent) {
2354
- if (!page)
2355
- continue;
2402
+ if (!page) continue;
2356
2403
  merged.push(...page.data);
2357
2404
  if (page.hasMore && page.nextCursor) {
2358
2405
  nextCursorMap[id] = page.nextCursor;
@@ -2361,16 +2408,12 @@ var OwneySDK = class {
2361
2408
  }
2362
2409
  merged.sort((a, b) => {
2363
2410
  const dateDiff = new Date(b.date).getTime() - new Date(a.date).getTime();
2364
- if (dateDiff !== 0)
2365
- return dateDiff;
2366
- if (a.agent !== b.agent)
2367
- return a.agent < b.agent ? -1 : 1;
2411
+ if (dateDiff !== 0) return dateDiff;
2412
+ if (a.agent !== b.agent) return a.agent < b.agent ? -1 : 1;
2368
2413
  const aTx = a.transactions?.[0]?.txHashes?.[0] ?? "";
2369
2414
  const bTx = b.transactions?.[0]?.txHashes?.[0] ?? "";
2370
- if (aTx !== bTx)
2371
- return aTx < bTx ? -1 : 1;
2372
- if (a.action !== b.action)
2373
- return a.action < b.action ? -1 : 1;
2415
+ if (aTx !== bTx) return aTx < bTx ? -1 : 1;
2416
+ if (a.action !== b.action) return a.action < b.action ? -1 : 1;
2374
2417
  return 0;
2375
2418
  });
2376
2419
  return {
@@ -2416,8 +2459,7 @@ var OwneySDK = class {
2416
2459
  const state = this.requireState();
2417
2460
  const chainId = this.requireChainId();
2418
2461
  const agent = this.getAgent(agentId);
2419
- if (typeof agent.ensureAutoSelectProtocols !== "function")
2420
- return false;
2462
+ if (typeof agent.ensureAutoSelectProtocols !== "function") return false;
2421
2463
  return agent.ensureAutoSelectProtocols(state, chainId, asset);
2422
2464
  }
2423
2465
  /**
@@ -2429,8 +2471,10 @@ var OwneySDK = class {
2429
2471
  * @returns the approval transaction hash.
2430
2472
  */
2431
2473
  async approvePermit2(asset = "WETH") {
2474
+ void asset;
2432
2475
  const state = this.requireState();
2433
2476
  const chainId = this.requireChainId();
2477
+ this.validateAssetSupport(this.getAgent("zyfai"), chainId, "WETH");
2434
2478
  const token = SPONSORED_WETH_BY_CHAIN[chainId];
2435
2479
  if (!token) {
2436
2480
  throw new OwneyError(
@@ -2570,8 +2614,7 @@ import { ZyfaiSDK as ZyfaiSDK2 } from "@zyfai/sdk";
2570
2614
  // src/agents/zyfai/zyfai.siwx-cache.ts
2571
2615
  var KEY_PREFIX2 = "owney.siwx.session";
2572
2616
  var storage2 = () => {
2573
- if (typeof window === "undefined")
2574
- return null;
2617
+ if (typeof window === "undefined") return null;
2575
2618
  try {
2576
2619
  return window.localStorage;
2577
2620
  } catch {
@@ -2581,11 +2624,9 @@ var storage2 = () => {
2581
2624
  var buildKey2 = (address, chainId) => `${KEY_PREFIX2}:${address.toLowerCase()}:${chainId}`;
2582
2625
  var readSiwxSession = (address, chainId) => {
2583
2626
  const store = storage2();
2584
- if (!store)
2585
- return null;
2627
+ if (!store) return null;
2586
2628
  const raw = store.getItem(buildKey2(address, chainId));
2587
- if (!raw)
2588
- return null;
2629
+ if (!raw) return null;
2589
2630
  try {
2590
2631
  return JSON.parse(raw);
2591
2632
  } catch {
@@ -2595,8 +2636,7 @@ var readSiwxSession = (address, chainId) => {
2595
2636
  };
2596
2637
  var writeSiwxSession = (address, chainId, session) => {
2597
2638
  const store = storage2();
2598
- if (!store)
2599
- return;
2639
+ if (!store) return;
2600
2640
  try {
2601
2641
  store.setItem(buildKey2(address, chainId), JSON.stringify(session));
2602
2642
  } catch {
@@ -2604,8 +2644,7 @@ var writeSiwxSession = (address, chainId, session) => {
2604
2644
  };
2605
2645
  var clearSiwxSession = (address, chainId) => {
2606
2646
  const store = storage2();
2607
- if (!store)
2608
- return;
2647
+ if (!store) return;
2609
2648
  try {
2610
2649
  store.removeItem(buildKey2(address, chainId));
2611
2650
  } catch {
@@ -2668,12 +2707,10 @@ function buildSIWXConfig(deps) {
2668
2707
  const jwt = readSession(address, id);
2669
2708
  const stored = readSiwxSession(address, id);
2670
2709
  if (!jwt) {
2671
- if (stored)
2672
- clearSiwxSession(address, id);
2710
+ if (stored) clearSiwxSession(address, id);
2673
2711
  return [];
2674
2712
  }
2675
- if (!stored)
2676
- return [];
2713
+ if (!stored) return [];
2677
2714
  const session = {
2678
2715
  // `stored.data.chainId` is a plain string in the cache (kept AppKit-free);
2679
2716
  // it always holds a valid CaipNetworkId here, so narrow at the boundary.