@owney/sdk 0.6.0-beta.0 → 0.6.0-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
@@ -32,13 +32,15 @@ module.exports = __toCommonJS(src_exports);
32
32
 
33
33
  // src/agents/zyfai/zyfai.agent.ts
34
34
  var import_sdk = require("@zyfai/sdk");
35
+ var import_viem = require("viem");
36
+ var import_chains = require("viem/chains");
35
37
 
36
38
  // src/types/config.ts
37
39
  var P_SUPPORTED_CHAINS = {
38
40
  chainIds: [8453, 42161],
39
41
  chains: ["BASE", "ARBITRUM"]
40
42
  };
41
- var SUPPORTED_TOKENS = ["USDC", "USDT"];
43
+ var SUPPORTED_TOKENS = ["USDC", "USDT", "WETH"];
42
44
  var SUPPORTED_CHAIN_IDS = P_SUPPORTED_CHAINS.chainIds;
43
45
  var SUPPORTED_CHAINS = P_SUPPORTED_CHAINS.chains;
44
46
  var SupportedAssets = [
@@ -59,6 +61,18 @@ var SupportedAssets = [
59
61
  symbol: "USDT",
60
62
  chainId: 42161,
61
63
  decimals: 6
64
+ },
65
+ {
66
+ address: `0x4200000000000000000000000000000000000006`,
67
+ symbol: "WETH",
68
+ chainId: 8453,
69
+ decimals: 18
70
+ },
71
+ {
72
+ address: `0x82aF49447D8a07e3bd95BD0d56f35241523fBab1`,
73
+ symbol: "WETH",
74
+ chainId: 42161,
75
+ decimals: 18
62
76
  }
63
77
  ];
64
78
 
@@ -157,6 +171,18 @@ var CHAIN_ID_TO_NAME = {
157
171
  8453: "BASE",
158
172
  42161: "ARBITRUM"
159
173
  };
174
+ var NAME_TO_CHAIN_ID = Object.entries(
175
+ CHAIN_ID_TO_NAME
176
+ ).reduce((acc, [id, name]) => {
177
+ acc[name.toLowerCase()] = Number(id);
178
+ return acc;
179
+ }, {});
180
+ function resolveChainId(chain) {
181
+ const asNum = Number(chain);
182
+ if (Number.isFinite(asNum) && asNum > 0)
183
+ return asNum;
184
+ return NAME_TO_CHAIN_ID[chain.trim().toLowerCase()] ?? NaN;
185
+ }
160
186
  function mapBalances(raw, _chainId, smartWallet) {
161
187
  const portfolio = raw.portfolio;
162
188
  const portfolioByChain = portfolio.portfolioByChain ?? {};
@@ -177,10 +203,27 @@ function mapBalances(raw, _chainId, smartWallet) {
177
203
  });
178
204
  }
179
205
  }
206
+ const positions = (portfolio.positions ?? []).map((p) => ({
207
+ chain: p.chain ?? "",
208
+ protocol: p.protocol_name ?? p.protocol_id ?? "",
209
+ protocolId: p.protocol_id,
210
+ pool: p.pool,
211
+ asset: p.token_symbol ?? "",
212
+ // 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")),
216
+ apy: p.pool_apy,
217
+ tvl: p.pool_tvl,
218
+ // PositionSlot has no `liquidity` field yet; Zyfai will add it. Narrow
219
+ // structural read keeps it undefined today and auto-populates later.
220
+ liquidity: p.liquidity
221
+ }));
180
222
  return {
181
223
  smartWallet,
182
224
  totalBalance: String(totalBalance),
183
- tokens
225
+ tokens,
226
+ positions
184
227
  };
185
228
  }
186
229
  function sumTokenValues(tokens) {
@@ -239,6 +282,14 @@ function mapApyHistory(raw) {
239
282
  apyByChainAndAsset: mapWeightedApyByChain(raw.weightedApyAfterFeeByChain)
240
283
  };
241
284
  }
285
+ function splitOpportunity(opportunity) {
286
+ const match = opportunity.match(/^(.*?)\s*\(([^)]*)\)\s*$/);
287
+ if (match) {
288
+ const pool = match[2].trim();
289
+ return { protocol: match[1].trim(), pool: pool || void 0 };
290
+ }
291
+ return { protocol: opportunity.trim() };
292
+ }
242
293
  function mapEntries(rawEntries, chainId) {
243
294
  const actionMap = {
244
295
  "Top up wallet": "Top up",
@@ -258,21 +309,26 @@ function mapEntries(rawEntries, chainId) {
258
309
  );
259
310
  oldApy = Number(rawLog.oldApy);
260
311
  newApy = Number(rawLog.newApy);
312
+ const from = splitOpportunity(rawLog.oldOpportunity);
313
+ const to = splitOpportunity(rawLog.newOpportunity);
261
314
  rebalanceLog = [
262
315
  {
263
- fromProtocol: rawLog.oldOpportunity.replace(/\s*\(.*?\)/g, "").trim(),
264
- toProtocol: rawLog.newOpportunity.replace(/\s*\(.*?\)/g, "").trim(),
316
+ fromProtocol: from.protocol,
317
+ toProtocol: to.protocol,
318
+ fromPool: from.pool,
319
+ toPool: to.pool,
265
320
  tokenSymbol: matched?.token_symbol ?? "",
266
321
  amount: matched ? String(hexToDecimal(matched.amount || "0x")) : "",
267
322
  status: "success"
268
323
  }
269
324
  ];
270
325
  }
326
+ const mappedAction = (entry.action ? actionMap[entry.action] : void 0) ?? entry.action;
271
327
  const rawAmount = entry?.positions?.[0]?.amount;
272
- const hasAmount = typeof rawAmount === "string" && rawAmount !== "" && rawAmount !== "0x" && rawAmount !== "0x0" && rawAmount !== "0";
328
+ const hasAmount = mappedAction !== "Withdraw" && typeof rawAmount === "string" && rawAmount !== "" && rawAmount !== "0x" && rawAmount !== "0x0" && rawAmount !== "0";
273
329
  return {
274
330
  agent: "zyfai",
275
- action: (entry.action ? actionMap[entry.action] : void 0) ?? entry.action,
331
+ action: mappedAction,
276
332
  date: entry.date || "",
277
333
  newApy: newApy === null ? null : String(newApy),
278
334
  oldApy: oldApy === null ? null : String(oldApy),
@@ -288,13 +344,6 @@ function mapEntries(rawEntries, chainId) {
288
344
  };
289
345
  });
290
346
  }
291
- function mapAgentStatus(raw) {
292
- return {
293
- success: raw.success,
294
- strategy: raw.strategy,
295
- protocols: raw.protocols
296
- };
297
- }
298
347
  function mapUserProfile(raw, userAddress) {
299
348
  return {
300
349
  address: userAddress,
@@ -330,6 +379,73 @@ function mapApyByStrategy(raw) {
330
379
  detailedApys: hasDetails ? { apyPerAsset } : void 0
331
380
  };
332
381
  }
382
+ function computeAllocationApy(positions) {
383
+ let weightedSum = 0;
384
+ let totalValue = 0;
385
+ const buckets = {};
386
+ for (const p of positions) {
387
+ const apy = p.apy;
388
+ const value = Number(p.amount);
389
+ if (typeof apy !== "number" || !Number.isFinite(value) || value <= 0) {
390
+ continue;
391
+ }
392
+ weightedSum += apy * value;
393
+ totalValue += value;
394
+ const chainId = resolveChainId(p.chain);
395
+ const token = p.asset;
396
+ if (!SUPPORTED_CHAIN_IDS.includes(chainId))
397
+ continue;
398
+ if (!SUPPORTED_TOKENS.includes(token))
399
+ continue;
400
+ const perAsset = buckets[chainId] ?? {};
401
+ const cell = perAsset[token] ?? { weightedSum: 0, value: 0 };
402
+ cell.weightedSum += apy * value;
403
+ cell.value += value;
404
+ perAsset[token] = cell;
405
+ buckets[chainId] = perAsset;
406
+ }
407
+ const apyByChainAndAsset = {};
408
+ for (const [chainKey, perAsset] of Object.entries(buckets)) {
409
+ const chainId = Number(chainKey);
410
+ const out = {};
411
+ for (const [token, cell] of Object.entries(perAsset ?? {})) {
412
+ if (cell && cell.value > 0) {
413
+ out[token] = cell.weightedSum / cell.value;
414
+ }
415
+ }
416
+ if (Object.keys(out).length > 0)
417
+ apyByChainAndAsset[chainId] = out;
418
+ }
419
+ const totalApy = totalValue > 0 ? String(weightedSum / totalValue) : "0";
420
+ return { totalApy, apyByChainAndAsset };
421
+ }
422
+
423
+ // src/agents/zyfai/zyfai.withdraw-amount.ts
424
+ var TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
425
+ function topicToAddress(topic) {
426
+ return `0x${(topic ?? "").slice(-40)}`.toLowerCase();
427
+ }
428
+ function extractWithdrawnAmount(logs, recipient, token, decimals) {
429
+ const wantRecipient = recipient.toLowerCase();
430
+ const wantToken = token.toLowerCase();
431
+ let total = 0n;
432
+ let matched = false;
433
+ for (const log of logs) {
434
+ if (log.address.toLowerCase() !== wantToken)
435
+ continue;
436
+ if ((log.topics[0] ?? "").toLowerCase() !== TRANSFER_TOPIC)
437
+ continue;
438
+ if (topicToAddress(log.topics[2]) !== wantRecipient)
439
+ continue;
440
+ if (!log.data || log.data === "0x")
441
+ continue;
442
+ total += BigInt(log.data);
443
+ matched = true;
444
+ }
445
+ if (!matched)
446
+ return void 0;
447
+ return String(hexToDecimal(`0x${total.toString(16)}`, decimals));
448
+ }
333
449
 
334
450
  // src/agents/zyfai/zyfai.history-cursor.ts
335
451
  var InvalidHistoryCursorError = class extends Error {
@@ -429,6 +545,11 @@ var DEFAULT_ZYFAI_RPC_URLS = {
429
545
  8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
430
546
  42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V"
431
547
  };
548
+ var WETH_ADDRESS_BY_CHAIN = {
549
+ 8453: "0x4200000000000000000000000000000000000006",
550
+ 42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"
551
+ };
552
+ var VIEM_CHAIN = { 8453: import_chains.base, 42161: import_chains.arbitrum };
432
553
  var ZyfaiAgent = class {
433
554
  id = "zyfai";
434
555
  supportedChainIds = [8453, 42161];
@@ -436,12 +557,18 @@ var ZyfaiAgent = class {
436
557
  {
437
558
  chainId: 8453,
438
559
  chain: "BASE",
439
- assets: [{ symbol: "USDC", minDepositAmount: "1500000" }]
560
+ assets: [
561
+ { symbol: "USDC", minDepositAmount: "1500000" },
562
+ { symbol: "WETH", minDepositAmount: "500000000000000" }
563
+ ]
440
564
  },
441
565
  {
442
566
  chainId: 42161,
443
567
  chain: "ARBITRUM",
444
- assets: [{ symbol: "USDC", minDepositAmount: "1500000" }]
568
+ assets: [
569
+ { symbol: "USDC", minDepositAmount: "1500000" },
570
+ { symbol: "WETH", minDepositAmount: "500000000000000" }
571
+ ]
445
572
  }
446
573
  ];
447
574
  sdk;
@@ -449,13 +576,83 @@ var ZyfaiAgent = class {
449
576
  connectedChainId = null;
450
577
  smartWalletCache = /* @__PURE__ */ new Map();
451
578
  connectAuthPromise = null;
579
+ rpcUrls;
580
+ publicClients = /* @__PURE__ */ new Map();
581
+ // txHash -> resolved withdrawn amount. Receipts are immutable, so a
582
+ // successful decode is cached forever; failures are NOT cached so a transient
583
+ // RPC error retries on the next fetch.
584
+ withdrawAmountCache = /* @__PURE__ */ new Map();
452
585
  constructor(apiKey, rpcUrls) {
586
+ this.rpcUrls = rpcUrls ?? DEFAULT_ZYFAI_RPC_URLS;
453
587
  this.sdk = new import_sdk.ZyfaiSDK({
454
588
  apiKey,
455
- rpcUrls: rpcUrls ?? DEFAULT_ZYFAI_RPC_URLS
589
+ rpcUrls: this.rpcUrls
456
590
  });
457
591
  this.installAuthCache();
458
592
  }
593
+ getPublicClient(chainId) {
594
+ const cached = this.publicClients.get(chainId);
595
+ if (cached)
596
+ return cached;
597
+ const client = (0, import_viem.createPublicClient)({
598
+ chain: VIEM_CHAIN[chainId],
599
+ transport: (0, import_viem.http)(this.rpcUrls[chainId])
600
+ });
601
+ this.publicClients.set(chainId, client);
602
+ return client;
603
+ }
604
+ /**
605
+ * Fills in the true withdrawn amount for `Withdraw` entries by decoding the
606
+ * USDC `Transfer` that lands in the user's wallet on-chain. The Zyfai history
607
+ * payload only reports the *remaining* position for a partial withdraw (see
608
+ * the note in `mapEntries`), so without this the UI shows the leftover
609
+ * balance instead of the amount withdrawn.
610
+ *
611
+ * Mutates `entries` in place. Runs all lookups concurrently and is tolerant
612
+ * of RPC failures: an entry whose amount can't be resolved keeps
613
+ * `amount: undefined`, which consumers render as a hidden/retryable row
614
+ * rather than a wrong number.
615
+ */
616
+ async resolveWithdrawnAmounts(entries, chainId, recipient) {
617
+ const asset = SupportedAssets.find(
618
+ (a) => a.chainId === chainId && a.symbol === "USDC"
619
+ );
620
+ if (!asset)
621
+ return;
622
+ const withdrawals = entries.filter(
623
+ (e) => e.action === "Withdraw" && e.transactions[0]?.txHashes[0]
624
+ );
625
+ if (withdrawals.length === 0)
626
+ return;
627
+ const client = this.getPublicClient(chainId);
628
+ await Promise.all(
629
+ withdrawals.map(async (entry) => {
630
+ const tx = entry.transactions[0];
631
+ const txHash = tx.txHashes[0];
632
+ const cached = this.withdrawAmountCache.get(txHash);
633
+ if (cached !== void 0) {
634
+ tx.amount = cached;
635
+ return;
636
+ }
637
+ try {
638
+ const receipt = await client.getTransactionReceipt({
639
+ hash: txHash
640
+ });
641
+ const amount = extractWithdrawnAmount(
642
+ receipt.logs,
643
+ recipient,
644
+ asset.address,
645
+ asset.decimals
646
+ );
647
+ if (amount !== void 0) {
648
+ this.withdrawAmountCache.set(txHash, amount);
649
+ tx.amount = amount;
650
+ }
651
+ } catch {
652
+ }
653
+ })
654
+ );
655
+ }
459
656
  /**
460
657
  * Patches `sdk.authenticateUser` so a fresh page can reuse the JWT from a
461
658
  * prior session instead of triggering a new SIWE prompt. Runs inside
@@ -607,7 +804,7 @@ var ZyfaiAgent = class {
607
804
  throw error;
608
805
  }
609
806
  }
610
- async deposit(state, chainId, amount, depositCallback) {
807
+ async deposit(state, chainId, amount, asset, depositCallback) {
611
808
  try {
612
809
  await this.connectFull(state, chainId);
613
810
  const validChainId = isValidChainId(chainId);
@@ -621,13 +818,15 @@ var ZyfaiAgent = class {
621
818
  validChainId,
622
819
  amount
623
820
  );
624
- await this.sdk.logDeposit(validChainId, txHash, amount);
821
+ const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
822
+ await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
625
823
  return { txHash, smartWallet: wallet.address, amount };
626
824
  }
627
825
  const raw = await this.sdk.depositFunds(
628
826
  this.getAddress(),
629
827
  validChainId,
630
- amount
828
+ amount,
829
+ asset
631
830
  );
632
831
  return mapDeposit(raw);
633
832
  } catch (error) {
@@ -706,23 +905,14 @@ var ZyfaiAgent = class {
706
905
  toDate,
707
906
  rawOffset: firstOvershoot ? firstOvershoot.rawIdx : offset
708
907
  }) : void 0;
908
+ const data = mapEntries(pageData, validChainId);
909
+ await this.resolveWithdrawnAmounts(data, validChainId, state.walletAddress);
709
910
  return {
710
- data: mapEntries(pageData, validChainId),
911
+ data,
711
912
  hasMore,
712
913
  nextCursor
713
914
  };
714
915
  }
715
- // --- IAgent: Agent control ---
716
- async pause(state, chainId) {
717
- await this.connectAuth(state, chainId);
718
- const raw = await this.sdk.pauseAgent();
719
- return mapAgentStatus(raw);
720
- }
721
- async resume(state, chainId) {
722
- await this.connectAuth(state, chainId);
723
- const raw = await this.sdk.resumeAgent();
724
- return mapAgentStatus(raw);
725
- }
726
916
  async getUserProfile(state, chainId) {
727
917
  await this.connectAuth(state, chainId);
728
918
  const validChainId = isValidChainId(chainId);
@@ -809,7 +999,7 @@ async function withFailureReporting(apiKey, agentType, fn) {
809
999
  }
810
1000
 
811
1001
  // src/lib/helpers/withdraw-helper.ts
812
- var import_viem = require("viem");
1002
+ var import_viem2 = require("viem");
813
1003
  function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
814
1004
  const target = asset.toUpperCase();
815
1005
  return agents.map((agent) => {
@@ -819,7 +1009,7 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
819
1009
  );
820
1010
  if (!tokenBalance)
821
1011
  return { agent, balance: 0n };
822
- return { agent, balance: (0, import_viem.parseUnits)(tokenBalance.amount, decimals) };
1012
+ return { agent, balance: (0, import_viem2.parseUnits)(tokenBalance.amount, decimals) };
823
1013
  });
824
1014
  }
825
1015
  function planProportionalShares(balances, requested, totalAvailable) {
@@ -934,6 +1124,130 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
934
1124
  return out;
935
1125
  }
936
1126
 
1127
+ // src/client.ts
1128
+ var import_viem4 = require("viem");
1129
+ var import_chains2 = require("viem/chains");
1130
+
1131
+ // src/lib/transfer-auth.ts
1132
+ var import_viem3 = require("viem");
1133
+ var ERC20_META_ABI = [
1134
+ { type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
1135
+ { type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
1136
+ ];
1137
+ function buildTransferWithAuthorizationTypedData(input) {
1138
+ return {
1139
+ domain: { name: input.tokenName, version: input.tokenVersion, chainId: input.chainId, verifyingContract: input.token },
1140
+ types: {
1141
+ TransferWithAuthorization: [
1142
+ { name: "from", type: "address" },
1143
+ { name: "to", type: "address" },
1144
+ { name: "value", type: "uint256" },
1145
+ { name: "validAfter", type: "uint256" },
1146
+ { name: "validBefore", type: "uint256" },
1147
+ { name: "nonce", type: "bytes32" }
1148
+ ]
1149
+ },
1150
+ primaryType: "TransferWithAuthorization",
1151
+ message: input.message
1152
+ };
1153
+ }
1154
+ async function readTokenMeta(publicClient, token) {
1155
+ const [tokenName, tokenVersion] = await Promise.all([
1156
+ publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "name" }),
1157
+ publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
1158
+ ]);
1159
+ return { tokenName, tokenVersion };
1160
+ }
1161
+ function randomAuthNonce() {
1162
+ const bytes = new Uint8Array(32);
1163
+ globalThis.crypto.getRandomValues(bytes);
1164
+ return (0, import_viem3.bytesToHex)(bytes);
1165
+ }
1166
+
1167
+ // src/lib/sponsor-client.ts
1168
+ var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
1169
+ async function postSponsorTransferAuth(input) {
1170
+ const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
1171
+ let res;
1172
+ try {
1173
+ res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
1174
+ method: "POST",
1175
+ headers: { "content-type": "application/json", "x-owney-api-key": input.apiKey },
1176
+ body: JSON.stringify(input.body)
1177
+ });
1178
+ } catch (networkError) {
1179
+ throw new OwneyError(
1180
+ "SPONSOR_REQUEST_FAILED",
1181
+ `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
1182
+ { cause: String(networkError) }
1183
+ );
1184
+ }
1185
+ const text = await res.text();
1186
+ let parsed = null;
1187
+ try {
1188
+ parsed = JSON.parse(text);
1189
+ } catch {
1190
+ }
1191
+ if (!res.ok || !parsed?.success || !parsed.data) {
1192
+ throw new OwneyError(
1193
+ "SPONSOR_REQUEST_FAILED",
1194
+ `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
1195
+ { statusCode: res.status, responseBody: text.slice(0, 500) }
1196
+ );
1197
+ }
1198
+ return parsed.data;
1199
+ }
1200
+
1201
+ // src/lib/sponsored-deposit.ts
1202
+ var AUTH_WINDOW_SECONDS = 15 * 60;
1203
+ function makeSponsoredDepositCallback(deps) {
1204
+ const post = deps.httpPost ?? postSponsorTransferAuth;
1205
+ return async (smartWallet, chainId, amount) => {
1206
+ const cid = chainId;
1207
+ const token = deps.tokenAddressByChain[cid];
1208
+ if (!token) {
1209
+ throw new OwneyError(
1210
+ "CHAIN_UNSUPPORTED",
1211
+ `No sponsored token configured for chain ${chainId}`
1212
+ );
1213
+ }
1214
+ const pub = deps.getPublicClient(cid);
1215
+ const wallet = deps.getWalletClient(cid);
1216
+ const { tokenName, tokenVersion } = await readTokenMeta(pub, token);
1217
+ const validAfter = 0n;
1218
+ const validBefore = BigInt(Math.floor(Date.now() / 1e3) + AUTH_WINDOW_SECONDS);
1219
+ const nonce = randomAuthNonce();
1220
+ const typedData = buildTransferWithAuthorizationTypedData({
1221
+ token,
1222
+ chainId: cid,
1223
+ tokenName,
1224
+ tokenVersion,
1225
+ message: { from: deps.ownerAddress, to: smartWallet, value: BigInt(amount), validAfter, validBefore, nonce }
1226
+ });
1227
+ const authSignature = await wallet.signTypedData(
1228
+ { account: deps.ownerAddress, ...typedData }
1229
+ );
1230
+ const result = await post({
1231
+ baseUrl: deps.baseUrl,
1232
+ apiKey: deps.apiKey,
1233
+ body: {
1234
+ chainId: cid,
1235
+ token,
1236
+ from: deps.ownerAddress,
1237
+ to: smartWallet,
1238
+ value: amount,
1239
+ validAfter: validAfter.toString(),
1240
+ validBefore: validBefore.toString(),
1241
+ nonce,
1242
+ authSignature,
1243
+ tokenName,
1244
+ tokenVersion
1245
+ }
1246
+ });
1247
+ return result.txHash;
1248
+ };
1249
+ }
1250
+
937
1251
  // src/client.ts
938
1252
  function encodeMultiAgentCursor(map) {
939
1253
  return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
@@ -955,6 +1269,14 @@ function decodeMultiAgentCursor(token) {
955
1269
  }
956
1270
  return parsed;
957
1271
  }
1272
+ var SPONSORED_USDC_BY_CHAIN = {
1273
+ 8453: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
1274
+ 42161: "0xaf88d065e77c8cc2239327c5edb3a432268e5831"
1275
+ };
1276
+ var VIEM_CHAIN2 = {
1277
+ 8453: import_chains2.base,
1278
+ 42161: import_chains2.arbitrum
1279
+ };
958
1280
  var OwneySDK = class {
959
1281
  agents = /* @__PURE__ */ new Map();
960
1282
  activeAgents = /* @__PURE__ */ new Set();
@@ -967,6 +1289,7 @@ var OwneySDK = class {
967
1289
  state = null;
968
1290
  apiKey;
969
1291
  zyfaiRpcUrls;
1292
+ cachedSponsoredCallback = null;
970
1293
  initializingAgentsPromise = null;
971
1294
  constructor(config) {
972
1295
  this.apiKey = config.apiKey;
@@ -990,6 +1313,7 @@ var OwneySDK = class {
990
1313
  );
991
1314
  }
992
1315
  this.state = { provider, walletAddress, chainId: null };
1316
+ this.cachedSponsoredCallback = null;
993
1317
  }
994
1318
  /**
995
1319
  * Disconnect from all agents and clear connection state.
@@ -1002,6 +1326,7 @@ var OwneySDK = class {
1002
1326
  this.activeAgents.clear();
1003
1327
  this.disabledAgents.clear();
1004
1328
  this.state = null;
1329
+ this.cachedSponsoredCallback = null;
1005
1330
  }
1006
1331
  isAgentDisabled(agentId) {
1007
1332
  return this.disabledAgents.has(agentId);
@@ -1035,6 +1360,46 @@ var OwneySDK = class {
1035
1360
  }
1036
1361
  return chainId;
1037
1362
  }
1363
+ requireConnectedProvider() {
1364
+ if (!this.state?.provider) {
1365
+ throw new OwneyError(
1366
+ "NOT_CONNECTED",
1367
+ "connect(provider) must be called before deposit",
1368
+ {}
1369
+ );
1370
+ }
1371
+ return this.state.provider;
1372
+ }
1373
+ /**
1374
+ * Lazily builds (and caches) the default EIP-3009 sponsored deposit callback
1375
+ * used when the caller omits `depositCallback`. Wraps the connected EIP-1193
1376
+ * provider with viem `custom(provider)` to read token meta and sign the
1377
+ * `TransferWithAuthorization`, then POSTs to the sponsor API.
1378
+ */
1379
+ getDefaultSponsoredCallback() {
1380
+ if (this.cachedSponsoredCallback)
1381
+ return this.cachedSponsoredCallback;
1382
+ const provider = this.requireConnectedProvider();
1383
+ const owner = this.state.walletAddress;
1384
+ this.cachedSponsoredCallback = makeSponsoredDepositCallback({
1385
+ apiKey: this.apiKey,
1386
+ ownerAddress: owner,
1387
+ tokenAddressByChain: SPONSORED_USDC_BY_CHAIN,
1388
+ // Casts work around viem's chain-narrowed Client vs the generic
1389
+ // PublicClient/WalletClient param types — structurally identical at
1390
+ // runtime, but the two share a name TS treats as unrelated.
1391
+ getPublicClient: (cid) => (0, import_viem4.createPublicClient)({
1392
+ chain: VIEM_CHAIN2[cid],
1393
+ transport: (0, import_viem4.custom)(provider)
1394
+ }),
1395
+ getWalletClient: (cid) => (0, import_viem4.createWalletClient)({
1396
+ account: owner,
1397
+ chain: VIEM_CHAIN2[cid],
1398
+ transport: (0, import_viem4.custom)(provider)
1399
+ })
1400
+ });
1401
+ return this.cachedSponsoredCallback;
1402
+ }
1038
1403
  getAgent(agentId) {
1039
1404
  const agent = this.agents.get(agentId);
1040
1405
  if (!agent) {
@@ -1188,6 +1553,7 @@ var OwneySDK = class {
1188
1553
  }
1189
1554
  const state = this.requireState();
1190
1555
  const chainId = this.requireChainId();
1556
+ const effectiveCallback = depositCallback ?? (asset === "WETH" ? void 0 : this.getDefaultSponsoredCallback());
1191
1557
  if (agentId) {
1192
1558
  if (this.isAgentDisabled(agentId)) {
1193
1559
  throw new OwneyError(
@@ -1202,7 +1568,13 @@ var OwneySDK = class {
1202
1568
  return withFailureReporting(
1203
1569
  this.apiKey,
1204
1570
  agent.id,
1205
- () => agent.deposit(state, chainId, amount, depositCallback)
1571
+ () => agent.deposit(
1572
+ state,
1573
+ chainId,
1574
+ amount,
1575
+ asset,
1576
+ effectiveCallback
1577
+ )
1206
1578
  );
1207
1579
  }
1208
1580
  const totalAmount = BigInt(amount);
@@ -1236,7 +1608,13 @@ var OwneySDK = class {
1236
1608
  agentResults[agent.id] = await withFailureReporting(
1237
1609
  this.apiKey,
1238
1610
  agent.id,
1239
- () => agent.deposit(state, chainId, agentAmount, depositCallback)
1611
+ () => agent.deposit(
1612
+ state,
1613
+ chainId,
1614
+ agentAmount,
1615
+ asset,
1616
+ effectiveCallback
1617
+ )
1240
1618
  );
1241
1619
  }
1242
1620
  return { agentResults };
@@ -1430,10 +1808,7 @@ var OwneySDK = class {
1430
1808
  disabledBalances,
1431
1809
  requested
1432
1810
  );
1433
- const enabledTotal = enabledBalances.reduce(
1434
- (s, b) => s + b.balance,
1435
- 0n
1436
- );
1811
+ const enabledTotal = enabledBalances.reduce((s, b) => s + b.balance, 0n);
1437
1812
  const enabledPlans = afterDrain > 0n ? planProportionalShares(enabledBalances, afterDrain, enabledTotal) : enabledBalances.map(({ agent, balance }) => ({
1438
1813
  agent,
1439
1814
  balance,
@@ -1704,27 +2079,6 @@ var OwneySDK = class {
1704
2079
  nextCursor: hasMore ? encodeMultiAgentCursor(nextCursorMap) : void 0
1705
2080
  };
1706
2081
  }
1707
- // --- Agent control ---
1708
- /**
1709
- * Pause an agent's automated operations. The agent will stop rebalancing until resumed.
1710
- * @param agentId - The agent to pause (e.g. "zyfai")
1711
- * @returns OwneyAgentStatus with success flag
1712
- */
1713
- async pauseAgent(agentId) {
1714
- const state = this.requireState();
1715
- const chainId = this.requireChainId();
1716
- return this.getAgent(agentId).pause(state, chainId);
1717
- }
1718
- /**
1719
- * Resume an agent's automated operations.
1720
- * @param agentId - The agent to resume (e.g. "zyfai")
1721
- * @returns OwneyAgentStatus with success flag
1722
- */
1723
- async resumeAgent(agentId) {
1724
- const state = this.requireState();
1725
- const chainId = this.requireChainId();
1726
- return this.getAgent(agentId).resume(state, chainId);
1727
- }
1728
2082
  /**
1729
2083
  * Get the user's profile for a specific agent, or all agents.
1730
2084
  * @param agentId - Optional. Agent to query. Omit for all agents.
@@ -1799,12 +2153,13 @@ var OwneySDK = class {
1799
2153
  const chainId = this.requireChainId();
1800
2154
  const activeAgents = this.getActiveAgents();
1801
2155
  const balances = {};
2156
+ const positionsByAgent = {};
1802
2157
  let totalBalance = 0;
1803
2158
  const entries = [...activeAgents.entries()];
1804
2159
  const balanceResults = await Promise.allSettled(
1805
2160
  entries.map(async ([id, agent]) => {
1806
2161
  const b = await agent.getBalances(state, chainId);
1807
- return [id, Number(b.totalBalance)];
2162
+ return [id, Number(b.totalBalance), b.positions ?? []];
1808
2163
  })
1809
2164
  );
1810
2165
  let successCount = 0;
@@ -1813,8 +2168,9 @@ var OwneySDK = class {
1813
2168
  const settledResult = balanceResults[i];
1814
2169
  const [agentId] = entries[i];
1815
2170
  if (settledResult.status === "fulfilled") {
1816
- const [id, bal] = settledResult.value;
2171
+ const [id, bal, agentPositions] = settledResult.value;
1817
2172
  balances[id] = bal;
2173
+ positionsByAgent[id] = agentPositions;
1818
2174
  totalBalance += bal;
1819
2175
  successCount += 1;
1820
2176
  } else {
@@ -1848,13 +2204,70 @@ var OwneySDK = class {
1848
2204
  }
1849
2205
  }
1850
2206
  return {
1851
- agentAllocation: allocation
2207
+ agentAllocation: allocation,
2208
+ agentPositions: positionsByAgent
2209
+ };
2210
+ }
2211
+ /**
2212
+ * Allocation-weighted APY from the current portfolio: each pool's APY
2213
+ * weighted by the user's position value in it. More accurate per-user than
2214
+ * the historical 30D APY. (ROUT-228)
2215
+ * @param options.agentId - Optional. Scope to one agent. Omit to aggregate
2216
+ * positions across all active agents.
2217
+ */
2218
+ async getAllocationApy({
2219
+ agentId
2220
+ } = {}) {
2221
+ const state = this.requireState();
2222
+ const chainId = this.requireChainId();
2223
+ const activeAgents = agentId ? /* @__PURE__ */ new Map([[agentId, this.getAgent(agentId)]]) : this.getActiveAgents();
2224
+ const entries = [...activeAgents.entries()];
2225
+ const balanceResults = await Promise.allSettled(
2226
+ entries.map(async ([id, agent]) => {
2227
+ const b = await agent.getBalances(state, chainId);
2228
+ return [id, b.positions ?? []];
2229
+ })
2230
+ );
2231
+ const agentApy = {};
2232
+ const allPositions = [];
2233
+ let successCount = 0;
2234
+ const agentErrors = {};
2235
+ for (let i = 0; i < balanceResults.length; i++) {
2236
+ const settled = balanceResults[i];
2237
+ const [aid] = entries[i];
2238
+ if (settled.status === "fulfilled") {
2239
+ const [id, positions] = settled.value;
2240
+ const per = computeAllocationApy(positions);
2241
+ agentApy[id] = {
2242
+ totalApy: per.totalApy,
2243
+ apyByChainAndAsset: per.apyByChainAndAsset
2244
+ };
2245
+ allPositions.push(...positions);
2246
+ successCount += 1;
2247
+ } else {
2248
+ const reason = settled.reason;
2249
+ agentErrors[aid] = reason instanceof Error ? reason.message : String(reason);
2250
+ console.error(`getAllocationApy agent "${aid}" failed:`, reason);
2251
+ }
2252
+ }
2253
+ if (successCount === 0) {
2254
+ throw new OwneyError(
2255
+ "ALLOCATION_ALL_FAILED",
2256
+ "Failed to fetch allocation APY: all agents failed.",
2257
+ { agentErrors }
2258
+ );
2259
+ }
2260
+ const overall = computeAllocationApy(allPositions);
2261
+ return {
2262
+ totalApy: overall.totalApy,
2263
+ apyByChainAndAsset: overall.apyByChainAndAsset,
2264
+ agentApy
1852
2265
  };
1853
2266
  }
1854
2267
  };
1855
2268
 
1856
2269
  // src/agents/zyfai/zyfai.siwx.ts
1857
- var import_viem2 = require("viem");
2270
+ var import_viem5 = require("viem");
1858
2271
  var import_siwe = require("siwe");
1859
2272
  var import_sdk2 = require("@zyfai/sdk");
1860
2273
 
@@ -1936,7 +2349,7 @@ function buildSIWXConfig(deps) {
1936
2349
  issuedAt,
1937
2350
  toString() {
1938
2351
  return new import_siwe.SiweMessage({
1939
- address: (0, import_viem2.getAddress)(accountAddress),
2352
+ address: (0, import_viem5.getAddress)(accountAddress),
1940
2353
  chainId: numericChainId(chainId),
1941
2354
  domain,
1942
2355
  uri,
@@ -2012,9 +2425,9 @@ function buildSIWXConfig(deps) {
2012
2425
  }
2013
2426
  function createOwneySIWX(config) {
2014
2427
  const zyfai = new import_sdk2.ZyfaiSDK({ apiKey: config.apiKey });
2015
- const http = zyfai.httpClient;
2428
+ const http2 = zyfai.httpClient;
2016
2429
  return buildSIWXConfig({
2017
- post: (url, data) => http.post(url, data),
2430
+ post: (url, data) => http2.post(url, data),
2018
2431
  referralSource: config.referralSource
2019
2432
  });
2020
2433
  }