@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.js CHANGED
@@ -1,12 +1,14 @@
1
1
  // src/agents/zyfai/zyfai.agent.ts
2
2
  import { ZyfaiSDK } from "@zyfai/sdk";
3
+ import { createPublicClient, http } from "viem";
4
+ import { base, arbitrum } from "viem/chains";
3
5
 
4
6
  // src/types/config.ts
5
7
  var P_SUPPORTED_CHAINS = {
6
8
  chainIds: [8453, 42161],
7
9
  chains: ["BASE", "ARBITRUM"]
8
10
  };
9
- var SUPPORTED_TOKENS = ["USDC", "USDT"];
11
+ var SUPPORTED_TOKENS = ["USDC", "USDT", "WETH"];
10
12
  var SUPPORTED_CHAIN_IDS = P_SUPPORTED_CHAINS.chainIds;
11
13
  var SUPPORTED_CHAINS = P_SUPPORTED_CHAINS.chains;
12
14
  var SupportedAssets = [
@@ -27,6 +29,18 @@ var SupportedAssets = [
27
29
  symbol: "USDT",
28
30
  chainId: 42161,
29
31
  decimals: 6
32
+ },
33
+ {
34
+ address: `0x4200000000000000000000000000000000000006`,
35
+ symbol: "WETH",
36
+ chainId: 8453,
37
+ decimals: 18
38
+ },
39
+ {
40
+ address: `0x82aF49447D8a07e3bd95BD0d56f35241523fBab1`,
41
+ symbol: "WETH",
42
+ chainId: 42161,
43
+ decimals: 18
30
44
  }
31
45
  ];
32
46
 
@@ -125,6 +139,18 @@ var CHAIN_ID_TO_NAME = {
125
139
  8453: "BASE",
126
140
  42161: "ARBITRUM"
127
141
  };
142
+ var NAME_TO_CHAIN_ID = Object.entries(
143
+ CHAIN_ID_TO_NAME
144
+ ).reduce((acc, [id, name]) => {
145
+ acc[name.toLowerCase()] = Number(id);
146
+ return acc;
147
+ }, {});
148
+ function resolveChainId(chain) {
149
+ const asNum = Number(chain);
150
+ if (Number.isFinite(asNum) && asNum > 0)
151
+ return asNum;
152
+ return NAME_TO_CHAIN_ID[chain.trim().toLowerCase()] ?? NaN;
153
+ }
128
154
  function mapBalances(raw, _chainId, smartWallet) {
129
155
  const portfolio = raw.portfolio;
130
156
  const portfolioByChain = portfolio.portfolioByChain ?? {};
@@ -145,10 +171,27 @@ function mapBalances(raw, _chainId, smartWallet) {
145
171
  });
146
172
  }
147
173
  }
174
+ const positions = (portfolio.positions ?? []).map((p) => ({
175
+ chain: p.chain ?? "",
176
+ protocol: p.protocol_name ?? p.protocol_id ?? "",
177
+ protocolId: p.protocol_id,
178
+ pool: p.pool,
179
+ asset: p.token_symbol ?? "",
180
+ // 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")),
184
+ apy: p.pool_apy,
185
+ tvl: p.pool_tvl,
186
+ // PositionSlot has no `liquidity` field yet; Zyfai will add it. Narrow
187
+ // structural read keeps it undefined today and auto-populates later.
188
+ liquidity: p.liquidity
189
+ }));
148
190
  return {
149
191
  smartWallet,
150
192
  totalBalance: String(totalBalance),
151
- tokens
193
+ tokens,
194
+ positions
152
195
  };
153
196
  }
154
197
  function sumTokenValues(tokens) {
@@ -207,6 +250,14 @@ function mapApyHistory(raw) {
207
250
  apyByChainAndAsset: mapWeightedApyByChain(raw.weightedApyAfterFeeByChain)
208
251
  };
209
252
  }
253
+ function splitOpportunity(opportunity) {
254
+ const match = opportunity.match(/^(.*?)\s*\(([^)]*)\)\s*$/);
255
+ if (match) {
256
+ const pool = match[2].trim();
257
+ return { protocol: match[1].trim(), pool: pool || void 0 };
258
+ }
259
+ return { protocol: opportunity.trim() };
260
+ }
210
261
  function mapEntries(rawEntries, chainId) {
211
262
  const actionMap = {
212
263
  "Top up wallet": "Top up",
@@ -226,21 +277,26 @@ function mapEntries(rawEntries, chainId) {
226
277
  );
227
278
  oldApy = Number(rawLog.oldApy);
228
279
  newApy = Number(rawLog.newApy);
280
+ const from = splitOpportunity(rawLog.oldOpportunity);
281
+ const to = splitOpportunity(rawLog.newOpportunity);
229
282
  rebalanceLog = [
230
283
  {
231
- fromProtocol: rawLog.oldOpportunity.replace(/\s*\(.*?\)/g, "").trim(),
232
- toProtocol: rawLog.newOpportunity.replace(/\s*\(.*?\)/g, "").trim(),
284
+ fromProtocol: from.protocol,
285
+ toProtocol: to.protocol,
286
+ fromPool: from.pool,
287
+ toPool: to.pool,
233
288
  tokenSymbol: matched?.token_symbol ?? "",
234
289
  amount: matched ? String(hexToDecimal(matched.amount || "0x")) : "",
235
290
  status: "success"
236
291
  }
237
292
  ];
238
293
  }
294
+ const mappedAction = (entry.action ? actionMap[entry.action] : void 0) ?? entry.action;
239
295
  const rawAmount = entry?.positions?.[0]?.amount;
240
- const hasAmount = typeof rawAmount === "string" && rawAmount !== "" && rawAmount !== "0x" && rawAmount !== "0x0" && rawAmount !== "0";
296
+ const hasAmount = mappedAction !== "Withdraw" && typeof rawAmount === "string" && rawAmount !== "" && rawAmount !== "0x" && rawAmount !== "0x0" && rawAmount !== "0";
241
297
  return {
242
298
  agent: "zyfai",
243
- action: (entry.action ? actionMap[entry.action] : void 0) ?? entry.action,
299
+ action: mappedAction,
244
300
  date: entry.date || "",
245
301
  newApy: newApy === null ? null : String(newApy),
246
302
  oldApy: oldApy === null ? null : String(oldApy),
@@ -256,13 +312,6 @@ function mapEntries(rawEntries, chainId) {
256
312
  };
257
313
  });
258
314
  }
259
- function mapAgentStatus(raw) {
260
- return {
261
- success: raw.success,
262
- strategy: raw.strategy,
263
- protocols: raw.protocols
264
- };
265
- }
266
315
  function mapUserProfile(raw, userAddress) {
267
316
  return {
268
317
  address: userAddress,
@@ -298,6 +347,73 @@ function mapApyByStrategy(raw) {
298
347
  detailedApys: hasDetails ? { apyPerAsset } : void 0
299
348
  };
300
349
  }
350
+ function computeAllocationApy(positions) {
351
+ let weightedSum = 0;
352
+ let totalValue = 0;
353
+ const buckets = {};
354
+ for (const p of positions) {
355
+ const apy = p.apy;
356
+ const value = Number(p.amount);
357
+ if (typeof apy !== "number" || !Number.isFinite(value) || value <= 0) {
358
+ continue;
359
+ }
360
+ weightedSum += apy * value;
361
+ totalValue += value;
362
+ const chainId = resolveChainId(p.chain);
363
+ const token = p.asset;
364
+ if (!SUPPORTED_CHAIN_IDS.includes(chainId))
365
+ continue;
366
+ if (!SUPPORTED_TOKENS.includes(token))
367
+ continue;
368
+ const perAsset = buckets[chainId] ?? {};
369
+ const cell = perAsset[token] ?? { weightedSum: 0, value: 0 };
370
+ cell.weightedSum += apy * value;
371
+ cell.value += value;
372
+ perAsset[token] = cell;
373
+ buckets[chainId] = perAsset;
374
+ }
375
+ const apyByChainAndAsset = {};
376
+ for (const [chainKey, perAsset] of Object.entries(buckets)) {
377
+ const chainId = Number(chainKey);
378
+ const out = {};
379
+ for (const [token, cell] of Object.entries(perAsset ?? {})) {
380
+ if (cell && cell.value > 0) {
381
+ out[token] = cell.weightedSum / cell.value;
382
+ }
383
+ }
384
+ if (Object.keys(out).length > 0)
385
+ apyByChainAndAsset[chainId] = out;
386
+ }
387
+ const totalApy = totalValue > 0 ? String(weightedSum / totalValue) : "0";
388
+ return { totalApy, apyByChainAndAsset };
389
+ }
390
+
391
+ // src/agents/zyfai/zyfai.withdraw-amount.ts
392
+ var TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
393
+ function topicToAddress(topic) {
394
+ return `0x${(topic ?? "").slice(-40)}`.toLowerCase();
395
+ }
396
+ function extractWithdrawnAmount(logs, recipient, token, decimals) {
397
+ const wantRecipient = recipient.toLowerCase();
398
+ const wantToken = token.toLowerCase();
399
+ let total = 0n;
400
+ let matched = false;
401
+ for (const log of logs) {
402
+ if (log.address.toLowerCase() !== wantToken)
403
+ continue;
404
+ if ((log.topics[0] ?? "").toLowerCase() !== TRANSFER_TOPIC)
405
+ continue;
406
+ if (topicToAddress(log.topics[2]) !== wantRecipient)
407
+ continue;
408
+ if (!log.data || log.data === "0x")
409
+ continue;
410
+ total += BigInt(log.data);
411
+ matched = true;
412
+ }
413
+ if (!matched)
414
+ return void 0;
415
+ return String(hexToDecimal(`0x${total.toString(16)}`, decimals));
416
+ }
301
417
 
302
418
  // src/agents/zyfai/zyfai.history-cursor.ts
303
419
  var InvalidHistoryCursorError = class extends Error {
@@ -397,6 +513,11 @@ var DEFAULT_ZYFAI_RPC_URLS = {
397
513
  8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
398
514
  42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V"
399
515
  };
516
+ var WETH_ADDRESS_BY_CHAIN = {
517
+ 8453: "0x4200000000000000000000000000000000000006",
518
+ 42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"
519
+ };
520
+ var VIEM_CHAIN = { 8453: base, 42161: arbitrum };
400
521
  var ZyfaiAgent = class {
401
522
  id = "zyfai";
402
523
  supportedChainIds = [8453, 42161];
@@ -404,12 +525,18 @@ var ZyfaiAgent = class {
404
525
  {
405
526
  chainId: 8453,
406
527
  chain: "BASE",
407
- assets: [{ symbol: "USDC", minDepositAmount: "1500000" }]
528
+ assets: [
529
+ { symbol: "USDC", minDepositAmount: "1500000" },
530
+ { symbol: "WETH", minDepositAmount: "500000000000000" }
531
+ ]
408
532
  },
409
533
  {
410
534
  chainId: 42161,
411
535
  chain: "ARBITRUM",
412
- assets: [{ symbol: "USDC", minDepositAmount: "1500000" }]
536
+ assets: [
537
+ { symbol: "USDC", minDepositAmount: "1500000" },
538
+ { symbol: "WETH", minDepositAmount: "500000000000000" }
539
+ ]
413
540
  }
414
541
  ];
415
542
  sdk;
@@ -417,13 +544,83 @@ var ZyfaiAgent = class {
417
544
  connectedChainId = null;
418
545
  smartWalletCache = /* @__PURE__ */ new Map();
419
546
  connectAuthPromise = null;
547
+ rpcUrls;
548
+ publicClients = /* @__PURE__ */ new Map();
549
+ // txHash -> resolved withdrawn amount. Receipts are immutable, so a
550
+ // successful decode is cached forever; failures are NOT cached so a transient
551
+ // RPC error retries on the next fetch.
552
+ withdrawAmountCache = /* @__PURE__ */ new Map();
420
553
  constructor(apiKey, rpcUrls) {
554
+ this.rpcUrls = rpcUrls ?? DEFAULT_ZYFAI_RPC_URLS;
421
555
  this.sdk = new ZyfaiSDK({
422
556
  apiKey,
423
- rpcUrls: rpcUrls ?? DEFAULT_ZYFAI_RPC_URLS
557
+ rpcUrls: this.rpcUrls
424
558
  });
425
559
  this.installAuthCache();
426
560
  }
561
+ getPublicClient(chainId) {
562
+ const cached = this.publicClients.get(chainId);
563
+ if (cached)
564
+ return cached;
565
+ const client = createPublicClient({
566
+ chain: VIEM_CHAIN[chainId],
567
+ transport: http(this.rpcUrls[chainId])
568
+ });
569
+ this.publicClients.set(chainId, client);
570
+ return client;
571
+ }
572
+ /**
573
+ * Fills in the true withdrawn amount for `Withdraw` entries by decoding the
574
+ * USDC `Transfer` that lands in the user's wallet on-chain. The Zyfai history
575
+ * payload only reports the *remaining* position for a partial withdraw (see
576
+ * the note in `mapEntries`), so without this the UI shows the leftover
577
+ * balance instead of the amount withdrawn.
578
+ *
579
+ * Mutates `entries` in place. Runs all lookups concurrently and is tolerant
580
+ * of RPC failures: an entry whose amount can't be resolved keeps
581
+ * `amount: undefined`, which consumers render as a hidden/retryable row
582
+ * rather than a wrong number.
583
+ */
584
+ async resolveWithdrawnAmounts(entries, chainId, recipient) {
585
+ const asset = SupportedAssets.find(
586
+ (a) => a.chainId === chainId && a.symbol === "USDC"
587
+ );
588
+ if (!asset)
589
+ return;
590
+ const withdrawals = entries.filter(
591
+ (e) => e.action === "Withdraw" && e.transactions[0]?.txHashes[0]
592
+ );
593
+ if (withdrawals.length === 0)
594
+ return;
595
+ const client = this.getPublicClient(chainId);
596
+ await Promise.all(
597
+ withdrawals.map(async (entry) => {
598
+ const tx = entry.transactions[0];
599
+ const txHash = tx.txHashes[0];
600
+ const cached = this.withdrawAmountCache.get(txHash);
601
+ if (cached !== void 0) {
602
+ tx.amount = cached;
603
+ return;
604
+ }
605
+ try {
606
+ const receipt = await client.getTransactionReceipt({
607
+ hash: txHash
608
+ });
609
+ const amount = extractWithdrawnAmount(
610
+ receipt.logs,
611
+ recipient,
612
+ asset.address,
613
+ asset.decimals
614
+ );
615
+ if (amount !== void 0) {
616
+ this.withdrawAmountCache.set(txHash, amount);
617
+ tx.amount = amount;
618
+ }
619
+ } catch {
620
+ }
621
+ })
622
+ );
623
+ }
427
624
  /**
428
625
  * Patches `sdk.authenticateUser` so a fresh page can reuse the JWT from a
429
626
  * prior session instead of triggering a new SIWE prompt. Runs inside
@@ -575,7 +772,7 @@ var ZyfaiAgent = class {
575
772
  throw error;
576
773
  }
577
774
  }
578
- async deposit(state, chainId, amount, depositCallback) {
775
+ async deposit(state, chainId, amount, asset, depositCallback) {
579
776
  try {
580
777
  await this.connectFull(state, chainId);
581
778
  const validChainId = isValidChainId(chainId);
@@ -589,13 +786,15 @@ var ZyfaiAgent = class {
589
786
  validChainId,
590
787
  amount
591
788
  );
592
- await this.sdk.logDeposit(validChainId, txHash, amount);
789
+ const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
790
+ await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
593
791
  return { txHash, smartWallet: wallet.address, amount };
594
792
  }
595
793
  const raw = await this.sdk.depositFunds(
596
794
  this.getAddress(),
597
795
  validChainId,
598
- amount
796
+ amount,
797
+ asset
599
798
  );
600
799
  return mapDeposit(raw);
601
800
  } catch (error) {
@@ -674,23 +873,14 @@ var ZyfaiAgent = class {
674
873
  toDate,
675
874
  rawOffset: firstOvershoot ? firstOvershoot.rawIdx : offset
676
875
  }) : void 0;
876
+ const data = mapEntries(pageData, validChainId);
877
+ await this.resolveWithdrawnAmounts(data, validChainId, state.walletAddress);
677
878
  return {
678
- data: mapEntries(pageData, validChainId),
879
+ data,
679
880
  hasMore,
680
881
  nextCursor
681
882
  };
682
883
  }
683
- // --- IAgent: Agent control ---
684
- async pause(state, chainId) {
685
- await this.connectAuth(state, chainId);
686
- const raw = await this.sdk.pauseAgent();
687
- return mapAgentStatus(raw);
688
- }
689
- async resume(state, chainId) {
690
- await this.connectAuth(state, chainId);
691
- const raw = await this.sdk.resumeAgent();
692
- return mapAgentStatus(raw);
693
- }
694
884
  async getUserProfile(state, chainId) {
695
885
  await this.connectAuth(state, chainId);
696
886
  const validChainId = isValidChainId(chainId);
@@ -902,6 +1092,134 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
902
1092
  return out;
903
1093
  }
904
1094
 
1095
+ // src/client.ts
1096
+ import {
1097
+ createPublicClient as createPublicClient2,
1098
+ createWalletClient,
1099
+ custom
1100
+ } from "viem";
1101
+ import { base as base2, arbitrum as arbitrum2 } from "viem/chains";
1102
+
1103
+ // src/lib/transfer-auth.ts
1104
+ import { bytesToHex } from "viem";
1105
+ var ERC20_META_ABI = [
1106
+ { type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
1107
+ { type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
1108
+ ];
1109
+ function buildTransferWithAuthorizationTypedData(input) {
1110
+ return {
1111
+ domain: { name: input.tokenName, version: input.tokenVersion, chainId: input.chainId, verifyingContract: input.token },
1112
+ types: {
1113
+ TransferWithAuthorization: [
1114
+ { name: "from", type: "address" },
1115
+ { name: "to", type: "address" },
1116
+ { name: "value", type: "uint256" },
1117
+ { name: "validAfter", type: "uint256" },
1118
+ { name: "validBefore", type: "uint256" },
1119
+ { name: "nonce", type: "bytes32" }
1120
+ ]
1121
+ },
1122
+ primaryType: "TransferWithAuthorization",
1123
+ message: input.message
1124
+ };
1125
+ }
1126
+ async function readTokenMeta(publicClient, token) {
1127
+ const [tokenName, tokenVersion] = await Promise.all([
1128
+ publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "name" }),
1129
+ publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
1130
+ ]);
1131
+ return { tokenName, tokenVersion };
1132
+ }
1133
+ function randomAuthNonce() {
1134
+ const bytes = new Uint8Array(32);
1135
+ globalThis.crypto.getRandomValues(bytes);
1136
+ return bytesToHex(bytes);
1137
+ }
1138
+
1139
+ // src/lib/sponsor-client.ts
1140
+ var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
1141
+ async function postSponsorTransferAuth(input) {
1142
+ const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
1143
+ let res;
1144
+ try {
1145
+ res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
1146
+ method: "POST",
1147
+ headers: { "content-type": "application/json", "x-owney-api-key": input.apiKey },
1148
+ body: JSON.stringify(input.body)
1149
+ });
1150
+ } catch (networkError) {
1151
+ throw new OwneyError(
1152
+ "SPONSOR_REQUEST_FAILED",
1153
+ `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
1154
+ { cause: String(networkError) }
1155
+ );
1156
+ }
1157
+ const text = await res.text();
1158
+ let parsed = null;
1159
+ try {
1160
+ parsed = JSON.parse(text);
1161
+ } catch {
1162
+ }
1163
+ if (!res.ok || !parsed?.success || !parsed.data) {
1164
+ throw new OwneyError(
1165
+ "SPONSOR_REQUEST_FAILED",
1166
+ `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
1167
+ { statusCode: res.status, responseBody: text.slice(0, 500) }
1168
+ );
1169
+ }
1170
+ return parsed.data;
1171
+ }
1172
+
1173
+ // src/lib/sponsored-deposit.ts
1174
+ var AUTH_WINDOW_SECONDS = 15 * 60;
1175
+ function makeSponsoredDepositCallback(deps) {
1176
+ const post = deps.httpPost ?? postSponsorTransferAuth;
1177
+ return async (smartWallet, chainId, amount) => {
1178
+ const cid = chainId;
1179
+ const token = deps.tokenAddressByChain[cid];
1180
+ if (!token) {
1181
+ throw new OwneyError(
1182
+ "CHAIN_UNSUPPORTED",
1183
+ `No sponsored token configured for chain ${chainId}`
1184
+ );
1185
+ }
1186
+ const pub = deps.getPublicClient(cid);
1187
+ const wallet = deps.getWalletClient(cid);
1188
+ const { tokenName, tokenVersion } = await readTokenMeta(pub, token);
1189
+ const validAfter = 0n;
1190
+ const validBefore = BigInt(Math.floor(Date.now() / 1e3) + AUTH_WINDOW_SECONDS);
1191
+ const nonce = randomAuthNonce();
1192
+ const typedData = buildTransferWithAuthorizationTypedData({
1193
+ token,
1194
+ chainId: cid,
1195
+ tokenName,
1196
+ tokenVersion,
1197
+ message: { from: deps.ownerAddress, to: smartWallet, value: BigInt(amount), validAfter, validBefore, nonce }
1198
+ });
1199
+ const authSignature = await wallet.signTypedData(
1200
+ { account: deps.ownerAddress, ...typedData }
1201
+ );
1202
+ const result = await post({
1203
+ baseUrl: deps.baseUrl,
1204
+ apiKey: deps.apiKey,
1205
+ body: {
1206
+ chainId: cid,
1207
+ token,
1208
+ from: deps.ownerAddress,
1209
+ to: smartWallet,
1210
+ value: amount,
1211
+ validAfter: validAfter.toString(),
1212
+ validBefore: validBefore.toString(),
1213
+ nonce,
1214
+ authSignature,
1215
+ tokenName,
1216
+ tokenVersion
1217
+ }
1218
+ });
1219
+ return result.txHash;
1220
+ };
1221
+ }
1222
+
905
1223
  // src/client.ts
906
1224
  function encodeMultiAgentCursor(map) {
907
1225
  return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
@@ -923,6 +1241,14 @@ function decodeMultiAgentCursor(token) {
923
1241
  }
924
1242
  return parsed;
925
1243
  }
1244
+ var SPONSORED_USDC_BY_CHAIN = {
1245
+ 8453: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
1246
+ 42161: "0xaf88d065e77c8cc2239327c5edb3a432268e5831"
1247
+ };
1248
+ var VIEM_CHAIN2 = {
1249
+ 8453: base2,
1250
+ 42161: arbitrum2
1251
+ };
926
1252
  var OwneySDK = class {
927
1253
  agents = /* @__PURE__ */ new Map();
928
1254
  activeAgents = /* @__PURE__ */ new Set();
@@ -935,6 +1261,7 @@ var OwneySDK = class {
935
1261
  state = null;
936
1262
  apiKey;
937
1263
  zyfaiRpcUrls;
1264
+ cachedSponsoredCallback = null;
938
1265
  initializingAgentsPromise = null;
939
1266
  constructor(config) {
940
1267
  this.apiKey = config.apiKey;
@@ -958,6 +1285,7 @@ var OwneySDK = class {
958
1285
  );
959
1286
  }
960
1287
  this.state = { provider, walletAddress, chainId: null };
1288
+ this.cachedSponsoredCallback = null;
961
1289
  }
962
1290
  /**
963
1291
  * Disconnect from all agents and clear connection state.
@@ -970,6 +1298,7 @@ var OwneySDK = class {
970
1298
  this.activeAgents.clear();
971
1299
  this.disabledAgents.clear();
972
1300
  this.state = null;
1301
+ this.cachedSponsoredCallback = null;
973
1302
  }
974
1303
  isAgentDisabled(agentId) {
975
1304
  return this.disabledAgents.has(agentId);
@@ -1003,6 +1332,46 @@ var OwneySDK = class {
1003
1332
  }
1004
1333
  return chainId;
1005
1334
  }
1335
+ requireConnectedProvider() {
1336
+ if (!this.state?.provider) {
1337
+ throw new OwneyError(
1338
+ "NOT_CONNECTED",
1339
+ "connect(provider) must be called before deposit",
1340
+ {}
1341
+ );
1342
+ }
1343
+ return this.state.provider;
1344
+ }
1345
+ /**
1346
+ * Lazily builds (and caches) the default EIP-3009 sponsored deposit callback
1347
+ * used when the caller omits `depositCallback`. Wraps the connected EIP-1193
1348
+ * provider with viem `custom(provider)` to read token meta and sign the
1349
+ * `TransferWithAuthorization`, then POSTs to the sponsor API.
1350
+ */
1351
+ getDefaultSponsoredCallback() {
1352
+ if (this.cachedSponsoredCallback)
1353
+ return this.cachedSponsoredCallback;
1354
+ const provider = this.requireConnectedProvider();
1355
+ const owner = this.state.walletAddress;
1356
+ this.cachedSponsoredCallback = makeSponsoredDepositCallback({
1357
+ apiKey: this.apiKey,
1358
+ ownerAddress: owner,
1359
+ tokenAddressByChain: SPONSORED_USDC_BY_CHAIN,
1360
+ // Casts work around viem's chain-narrowed Client vs the generic
1361
+ // PublicClient/WalletClient param types — structurally identical at
1362
+ // runtime, but the two share a name TS treats as unrelated.
1363
+ getPublicClient: (cid) => createPublicClient2({
1364
+ chain: VIEM_CHAIN2[cid],
1365
+ transport: custom(provider)
1366
+ }),
1367
+ getWalletClient: (cid) => createWalletClient({
1368
+ account: owner,
1369
+ chain: VIEM_CHAIN2[cid],
1370
+ transport: custom(provider)
1371
+ })
1372
+ });
1373
+ return this.cachedSponsoredCallback;
1374
+ }
1006
1375
  getAgent(agentId) {
1007
1376
  const agent = this.agents.get(agentId);
1008
1377
  if (!agent) {
@@ -1156,6 +1525,7 @@ var OwneySDK = class {
1156
1525
  }
1157
1526
  const state = this.requireState();
1158
1527
  const chainId = this.requireChainId();
1528
+ const effectiveCallback = depositCallback ?? (asset === "WETH" ? void 0 : this.getDefaultSponsoredCallback());
1159
1529
  if (agentId) {
1160
1530
  if (this.isAgentDisabled(agentId)) {
1161
1531
  throw new OwneyError(
@@ -1170,7 +1540,13 @@ var OwneySDK = class {
1170
1540
  return withFailureReporting(
1171
1541
  this.apiKey,
1172
1542
  agent.id,
1173
- () => agent.deposit(state, chainId, amount, depositCallback)
1543
+ () => agent.deposit(
1544
+ state,
1545
+ chainId,
1546
+ amount,
1547
+ asset,
1548
+ effectiveCallback
1549
+ )
1174
1550
  );
1175
1551
  }
1176
1552
  const totalAmount = BigInt(amount);
@@ -1204,7 +1580,13 @@ var OwneySDK = class {
1204
1580
  agentResults[agent.id] = await withFailureReporting(
1205
1581
  this.apiKey,
1206
1582
  agent.id,
1207
- () => agent.deposit(state, chainId, agentAmount, depositCallback)
1583
+ () => agent.deposit(
1584
+ state,
1585
+ chainId,
1586
+ agentAmount,
1587
+ asset,
1588
+ effectiveCallback
1589
+ )
1208
1590
  );
1209
1591
  }
1210
1592
  return { agentResults };
@@ -1398,10 +1780,7 @@ var OwneySDK = class {
1398
1780
  disabledBalances,
1399
1781
  requested
1400
1782
  );
1401
- const enabledTotal = enabledBalances.reduce(
1402
- (s, b) => s + b.balance,
1403
- 0n
1404
- );
1783
+ const enabledTotal = enabledBalances.reduce((s, b) => s + b.balance, 0n);
1405
1784
  const enabledPlans = afterDrain > 0n ? planProportionalShares(enabledBalances, afterDrain, enabledTotal) : enabledBalances.map(({ agent, balance }) => ({
1406
1785
  agent,
1407
1786
  balance,
@@ -1672,27 +2051,6 @@ var OwneySDK = class {
1672
2051
  nextCursor: hasMore ? encodeMultiAgentCursor(nextCursorMap) : void 0
1673
2052
  };
1674
2053
  }
1675
- // --- Agent control ---
1676
- /**
1677
- * Pause an agent's automated operations. The agent will stop rebalancing until resumed.
1678
- * @param agentId - The agent to pause (e.g. "zyfai")
1679
- * @returns OwneyAgentStatus with success flag
1680
- */
1681
- async pauseAgent(agentId) {
1682
- const state = this.requireState();
1683
- const chainId = this.requireChainId();
1684
- return this.getAgent(agentId).pause(state, chainId);
1685
- }
1686
- /**
1687
- * Resume an agent's automated operations.
1688
- * @param agentId - The agent to resume (e.g. "zyfai")
1689
- * @returns OwneyAgentStatus with success flag
1690
- */
1691
- async resumeAgent(agentId) {
1692
- const state = this.requireState();
1693
- const chainId = this.requireChainId();
1694
- return this.getAgent(agentId).resume(state, chainId);
1695
- }
1696
2054
  /**
1697
2055
  * Get the user's profile for a specific agent, or all agents.
1698
2056
  * @param agentId - Optional. Agent to query. Omit for all agents.
@@ -1767,12 +2125,13 @@ var OwneySDK = class {
1767
2125
  const chainId = this.requireChainId();
1768
2126
  const activeAgents = this.getActiveAgents();
1769
2127
  const balances = {};
2128
+ const positionsByAgent = {};
1770
2129
  let totalBalance = 0;
1771
2130
  const entries = [...activeAgents.entries()];
1772
2131
  const balanceResults = await Promise.allSettled(
1773
2132
  entries.map(async ([id, agent]) => {
1774
2133
  const b = await agent.getBalances(state, chainId);
1775
- return [id, Number(b.totalBalance)];
2134
+ return [id, Number(b.totalBalance), b.positions ?? []];
1776
2135
  })
1777
2136
  );
1778
2137
  let successCount = 0;
@@ -1781,8 +2140,9 @@ var OwneySDK = class {
1781
2140
  const settledResult = balanceResults[i];
1782
2141
  const [agentId] = entries[i];
1783
2142
  if (settledResult.status === "fulfilled") {
1784
- const [id, bal] = settledResult.value;
2143
+ const [id, bal, agentPositions] = settledResult.value;
1785
2144
  balances[id] = bal;
2145
+ positionsByAgent[id] = agentPositions;
1786
2146
  totalBalance += bal;
1787
2147
  successCount += 1;
1788
2148
  } else {
@@ -1816,7 +2176,64 @@ var OwneySDK = class {
1816
2176
  }
1817
2177
  }
1818
2178
  return {
1819
- agentAllocation: allocation
2179
+ agentAllocation: allocation,
2180
+ agentPositions: positionsByAgent
2181
+ };
2182
+ }
2183
+ /**
2184
+ * Allocation-weighted APY from the current portfolio: each pool's APY
2185
+ * weighted by the user's position value in it. More accurate per-user than
2186
+ * the historical 30D APY. (ROUT-228)
2187
+ * @param options.agentId - Optional. Scope to one agent. Omit to aggregate
2188
+ * positions across all active agents.
2189
+ */
2190
+ async getAllocationApy({
2191
+ agentId
2192
+ } = {}) {
2193
+ const state = this.requireState();
2194
+ const chainId = this.requireChainId();
2195
+ const activeAgents = agentId ? /* @__PURE__ */ new Map([[agentId, this.getAgent(agentId)]]) : this.getActiveAgents();
2196
+ const entries = [...activeAgents.entries()];
2197
+ const balanceResults = await Promise.allSettled(
2198
+ entries.map(async ([id, agent]) => {
2199
+ const b = await agent.getBalances(state, chainId);
2200
+ return [id, b.positions ?? []];
2201
+ })
2202
+ );
2203
+ const agentApy = {};
2204
+ const allPositions = [];
2205
+ let successCount = 0;
2206
+ const agentErrors = {};
2207
+ for (let i = 0; i < balanceResults.length; i++) {
2208
+ const settled = balanceResults[i];
2209
+ const [aid] = entries[i];
2210
+ if (settled.status === "fulfilled") {
2211
+ const [id, positions] = settled.value;
2212
+ const per = computeAllocationApy(positions);
2213
+ agentApy[id] = {
2214
+ totalApy: per.totalApy,
2215
+ apyByChainAndAsset: per.apyByChainAndAsset
2216
+ };
2217
+ allPositions.push(...positions);
2218
+ successCount += 1;
2219
+ } else {
2220
+ const reason = settled.reason;
2221
+ agentErrors[aid] = reason instanceof Error ? reason.message : String(reason);
2222
+ console.error(`getAllocationApy agent "${aid}" failed:`, reason);
2223
+ }
2224
+ }
2225
+ if (successCount === 0) {
2226
+ throw new OwneyError(
2227
+ "ALLOCATION_ALL_FAILED",
2228
+ "Failed to fetch allocation APY: all agents failed.",
2229
+ { agentErrors }
2230
+ );
2231
+ }
2232
+ const overall = computeAllocationApy(allPositions);
2233
+ return {
2234
+ totalApy: overall.totalApy,
2235
+ apyByChainAndAsset: overall.apyByChainAndAsset,
2236
+ agentApy
1820
2237
  };
1821
2238
  }
1822
2239
  };
@@ -1980,9 +2397,9 @@ function buildSIWXConfig(deps) {
1980
2397
  }
1981
2398
  function createOwneySIWX(config) {
1982
2399
  const zyfai = new ZyfaiSDK2({ apiKey: config.apiKey });
1983
- const http = zyfai.httpClient;
2400
+ const http2 = zyfai.httpClient;
1984
2401
  return buildSIWXConfig({
1985
- post: (url, data) => http.post(url, data),
2402
+ post: (url, data) => http2.post(url, data),
1986
2403
  referralSource: config.referralSource
1987
2404
  });
1988
2405
  }