@owney/sdk 0.6.0-beta.0 → 0.6.0-beta.1

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
@@ -6,7 +6,7 @@ var P_SUPPORTED_CHAINS = {
6
6
  chainIds: [8453, 42161],
7
7
  chains: ["BASE", "ARBITRUM"]
8
8
  };
9
- var SUPPORTED_TOKENS = ["USDC", "USDT"];
9
+ var SUPPORTED_TOKENS = ["USDC", "USDT", "WETH"];
10
10
  var SUPPORTED_CHAIN_IDS = P_SUPPORTED_CHAINS.chainIds;
11
11
  var SUPPORTED_CHAINS = P_SUPPORTED_CHAINS.chains;
12
12
  var SupportedAssets = [
@@ -27,6 +27,18 @@ var SupportedAssets = [
27
27
  symbol: "USDT",
28
28
  chainId: 42161,
29
29
  decimals: 6
30
+ },
31
+ {
32
+ address: `0x4200000000000000000000000000000000000006`,
33
+ symbol: "WETH",
34
+ chainId: 8453,
35
+ decimals: 18
36
+ },
37
+ {
38
+ address: `0x82aF49447D8a07e3bd95BD0d56f35241523fBab1`,
39
+ symbol: "WETH",
40
+ chainId: 42161,
41
+ decimals: 18
30
42
  }
31
43
  ];
32
44
 
@@ -125,6 +137,18 @@ var CHAIN_ID_TO_NAME = {
125
137
  8453: "BASE",
126
138
  42161: "ARBITRUM"
127
139
  };
140
+ var NAME_TO_CHAIN_ID = Object.entries(
141
+ CHAIN_ID_TO_NAME
142
+ ).reduce((acc, [id, name]) => {
143
+ acc[name.toLowerCase()] = Number(id);
144
+ return acc;
145
+ }, {});
146
+ function resolveChainId(chain) {
147
+ const asNum = Number(chain);
148
+ if (Number.isFinite(asNum) && asNum > 0)
149
+ return asNum;
150
+ return NAME_TO_CHAIN_ID[chain.trim().toLowerCase()] ?? NaN;
151
+ }
128
152
  function mapBalances(raw, _chainId, smartWallet) {
129
153
  const portfolio = raw.portfolio;
130
154
  const portfolioByChain = portfolio.portfolioByChain ?? {};
@@ -145,10 +169,27 @@ function mapBalances(raw, _chainId, smartWallet) {
145
169
  });
146
170
  }
147
171
  }
172
+ const positions = (portfolio.positions ?? []).map((p) => ({
173
+ chain: p.chain ?? "",
174
+ protocol: p.protocol_name ?? p.protocol_id ?? "",
175
+ protocolId: p.protocol_id,
176
+ pool: p.pool,
177
+ asset: p.token_symbol ?? "",
178
+ // Position amount is hex-encoded base units (like the token balances
179
+ // above). Supported assets (USDC/USDT) are 6-decimal — hexToDecimal's
180
+ // default — so no per-token decimals lookup is needed.
181
+ amount: String(hexToDecimal(p.amount || "0")),
182
+ apy: p.pool_apy,
183
+ tvl: p.pool_tvl,
184
+ // PositionSlot has no `liquidity` field yet; Zyfai will add it. Narrow
185
+ // structural read keeps it undefined today and auto-populates later.
186
+ liquidity: p.liquidity
187
+ }));
148
188
  return {
149
189
  smartWallet,
150
190
  totalBalance: String(totalBalance),
151
- tokens
191
+ tokens,
192
+ positions
152
193
  };
153
194
  }
154
195
  function sumTokenValues(tokens) {
@@ -256,13 +297,6 @@ function mapEntries(rawEntries, chainId) {
256
297
  };
257
298
  });
258
299
  }
259
- function mapAgentStatus(raw) {
260
- return {
261
- success: raw.success,
262
- strategy: raw.strategy,
263
- protocols: raw.protocols
264
- };
265
- }
266
300
  function mapUserProfile(raw, userAddress) {
267
301
  return {
268
302
  address: userAddress,
@@ -298,6 +332,46 @@ function mapApyByStrategy(raw) {
298
332
  detailedApys: hasDetails ? { apyPerAsset } : void 0
299
333
  };
300
334
  }
335
+ function computeAllocationApy(positions) {
336
+ let weightedSum = 0;
337
+ let totalValue = 0;
338
+ const buckets = {};
339
+ for (const p of positions) {
340
+ const apy = p.apy;
341
+ const value = Number(p.amount);
342
+ if (typeof apy !== "number" || !Number.isFinite(value) || value <= 0) {
343
+ continue;
344
+ }
345
+ weightedSum += apy * value;
346
+ totalValue += value;
347
+ const chainId = resolveChainId(p.chain);
348
+ const token = p.asset;
349
+ if (!SUPPORTED_CHAIN_IDS.includes(chainId))
350
+ continue;
351
+ if (!SUPPORTED_TOKENS.includes(token))
352
+ continue;
353
+ const perAsset = buckets[chainId] ?? {};
354
+ const cell = perAsset[token] ?? { weightedSum: 0, value: 0 };
355
+ cell.weightedSum += apy * value;
356
+ cell.value += value;
357
+ perAsset[token] = cell;
358
+ buckets[chainId] = perAsset;
359
+ }
360
+ const apyByChainAndAsset = {};
361
+ for (const [chainKey, perAsset] of Object.entries(buckets)) {
362
+ const chainId = Number(chainKey);
363
+ const out = {};
364
+ for (const [token, cell] of Object.entries(perAsset ?? {})) {
365
+ if (cell && cell.value > 0) {
366
+ out[token] = cell.weightedSum / cell.value;
367
+ }
368
+ }
369
+ if (Object.keys(out).length > 0)
370
+ apyByChainAndAsset[chainId] = out;
371
+ }
372
+ const totalApy = totalValue > 0 ? String(weightedSum / totalValue) : "0";
373
+ return { totalApy, apyByChainAndAsset };
374
+ }
301
375
 
302
376
  // src/agents/zyfai/zyfai.history-cursor.ts
303
377
  var InvalidHistoryCursorError = class extends Error {
@@ -397,6 +471,10 @@ var DEFAULT_ZYFAI_RPC_URLS = {
397
471
  8453: "https://base-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V",
398
472
  42161: "https://arb-mainnet.g.alchemy.com/v2/ZWyVU-9XfS3z8Rn-xkq7V"
399
473
  };
474
+ var WETH_ADDRESS_BY_CHAIN = {
475
+ 8453: "0x4200000000000000000000000000000000000006",
476
+ 42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"
477
+ };
400
478
  var ZyfaiAgent = class {
401
479
  id = "zyfai";
402
480
  supportedChainIds = [8453, 42161];
@@ -404,12 +482,18 @@ var ZyfaiAgent = class {
404
482
  {
405
483
  chainId: 8453,
406
484
  chain: "BASE",
407
- assets: [{ symbol: "USDC", minDepositAmount: "1500000" }]
485
+ assets: [
486
+ { symbol: "USDC", minDepositAmount: "1500000" },
487
+ { symbol: "WETH", minDepositAmount: "500000000000000" }
488
+ ]
408
489
  },
409
490
  {
410
491
  chainId: 42161,
411
492
  chain: "ARBITRUM",
412
- assets: [{ symbol: "USDC", minDepositAmount: "1500000" }]
493
+ assets: [
494
+ { symbol: "USDC", minDepositAmount: "1500000" },
495
+ { symbol: "WETH", minDepositAmount: "500000000000000" }
496
+ ]
413
497
  }
414
498
  ];
415
499
  sdk;
@@ -575,7 +659,7 @@ var ZyfaiAgent = class {
575
659
  throw error;
576
660
  }
577
661
  }
578
- async deposit(state, chainId, amount, depositCallback) {
662
+ async deposit(state, chainId, amount, asset, depositCallback) {
579
663
  try {
580
664
  await this.connectFull(state, chainId);
581
665
  const validChainId = isValidChainId(chainId);
@@ -589,13 +673,15 @@ var ZyfaiAgent = class {
589
673
  validChainId,
590
674
  amount
591
675
  );
592
- await this.sdk.logDeposit(validChainId, txHash, amount);
676
+ const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
677
+ await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
593
678
  return { txHash, smartWallet: wallet.address, amount };
594
679
  }
595
680
  const raw = await this.sdk.depositFunds(
596
681
  this.getAddress(),
597
682
  validChainId,
598
- amount
683
+ amount,
684
+ asset
599
685
  );
600
686
  return mapDeposit(raw);
601
687
  } catch (error) {
@@ -680,17 +766,6 @@ var ZyfaiAgent = class {
680
766
  nextCursor
681
767
  };
682
768
  }
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
769
  async getUserProfile(state, chainId) {
695
770
  await this.connectAuth(state, chainId);
696
771
  const validChainId = isValidChainId(chainId);
@@ -902,6 +977,134 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
902
977
  return out;
903
978
  }
904
979
 
980
+ // src/client.ts
981
+ import {
982
+ createPublicClient,
983
+ createWalletClient,
984
+ custom
985
+ } from "viem";
986
+ import { base, arbitrum } from "viem/chains";
987
+
988
+ // src/lib/transfer-auth.ts
989
+ import { bytesToHex } from "viem";
990
+ var ERC20_META_ABI = [
991
+ { type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
992
+ { type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
993
+ ];
994
+ function buildTransferWithAuthorizationTypedData(input) {
995
+ return {
996
+ domain: { name: input.tokenName, version: input.tokenVersion, chainId: input.chainId, verifyingContract: input.token },
997
+ types: {
998
+ TransferWithAuthorization: [
999
+ { name: "from", type: "address" },
1000
+ { name: "to", type: "address" },
1001
+ { name: "value", type: "uint256" },
1002
+ { name: "validAfter", type: "uint256" },
1003
+ { name: "validBefore", type: "uint256" },
1004
+ { name: "nonce", type: "bytes32" }
1005
+ ]
1006
+ },
1007
+ primaryType: "TransferWithAuthorization",
1008
+ message: input.message
1009
+ };
1010
+ }
1011
+ async function readTokenMeta(publicClient, token) {
1012
+ const [tokenName, tokenVersion] = await Promise.all([
1013
+ publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "name" }),
1014
+ publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
1015
+ ]);
1016
+ return { tokenName, tokenVersion };
1017
+ }
1018
+ function randomAuthNonce() {
1019
+ const bytes = new Uint8Array(32);
1020
+ globalThis.crypto.getRandomValues(bytes);
1021
+ return bytesToHex(bytes);
1022
+ }
1023
+
1024
+ // src/lib/sponsor-client.ts
1025
+ var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
1026
+ async function postSponsorTransferAuth(input) {
1027
+ const base2 = input.baseUrl ?? ROUTING_API_BASE_URL3;
1028
+ let res;
1029
+ try {
1030
+ res = await fetch(`${base2}/api/v1/sponsor/erc20-transfer-auth`, {
1031
+ method: "POST",
1032
+ headers: { "content-type": "application/json", "x-owney-api-key": input.apiKey },
1033
+ body: JSON.stringify(input.body)
1034
+ });
1035
+ } catch (networkError) {
1036
+ throw new OwneyError(
1037
+ "SPONSOR_REQUEST_FAILED",
1038
+ `Sponsor API network error: ${networkError instanceof Error ? networkError.message : String(networkError)}`,
1039
+ { cause: String(networkError) }
1040
+ );
1041
+ }
1042
+ const text = await res.text();
1043
+ let parsed = null;
1044
+ try {
1045
+ parsed = JSON.parse(text);
1046
+ } catch {
1047
+ }
1048
+ if (!res.ok || !parsed?.success || !parsed.data) {
1049
+ throw new OwneyError(
1050
+ "SPONSOR_REQUEST_FAILED",
1051
+ `Sponsor API error ${res.status}: ${parsed?.message ?? text.slice(0, 500)}`,
1052
+ { statusCode: res.status, responseBody: text.slice(0, 500) }
1053
+ );
1054
+ }
1055
+ return parsed.data;
1056
+ }
1057
+
1058
+ // src/lib/sponsored-deposit.ts
1059
+ var AUTH_WINDOW_SECONDS = 15 * 60;
1060
+ function makeSponsoredDepositCallback(deps) {
1061
+ const post = deps.httpPost ?? postSponsorTransferAuth;
1062
+ return async (smartWallet, chainId, amount) => {
1063
+ const cid = chainId;
1064
+ const token = deps.tokenAddressByChain[cid];
1065
+ if (!token) {
1066
+ throw new OwneyError(
1067
+ "CHAIN_UNSUPPORTED",
1068
+ `No sponsored token configured for chain ${chainId}`
1069
+ );
1070
+ }
1071
+ const pub = deps.getPublicClient(cid);
1072
+ const wallet = deps.getWalletClient(cid);
1073
+ const { tokenName, tokenVersion } = await readTokenMeta(pub, token);
1074
+ const validAfter = 0n;
1075
+ const validBefore = BigInt(Math.floor(Date.now() / 1e3) + AUTH_WINDOW_SECONDS);
1076
+ const nonce = randomAuthNonce();
1077
+ const typedData = buildTransferWithAuthorizationTypedData({
1078
+ token,
1079
+ chainId: cid,
1080
+ tokenName,
1081
+ tokenVersion,
1082
+ message: { from: deps.ownerAddress, to: smartWallet, value: BigInt(amount), validAfter, validBefore, nonce }
1083
+ });
1084
+ const authSignature = await wallet.signTypedData(
1085
+ { account: deps.ownerAddress, ...typedData }
1086
+ );
1087
+ const result = await post({
1088
+ baseUrl: deps.baseUrl,
1089
+ apiKey: deps.apiKey,
1090
+ body: {
1091
+ chainId: cid,
1092
+ token,
1093
+ from: deps.ownerAddress,
1094
+ to: smartWallet,
1095
+ value: amount,
1096
+ validAfter: validAfter.toString(),
1097
+ validBefore: validBefore.toString(),
1098
+ nonce,
1099
+ authSignature,
1100
+ tokenName,
1101
+ tokenVersion
1102
+ }
1103
+ });
1104
+ return result.txHash;
1105
+ };
1106
+ }
1107
+
905
1108
  // src/client.ts
906
1109
  function encodeMultiAgentCursor(map) {
907
1110
  return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
@@ -923,6 +1126,14 @@ function decodeMultiAgentCursor(token) {
923
1126
  }
924
1127
  return parsed;
925
1128
  }
1129
+ var SPONSORED_USDC_BY_CHAIN = {
1130
+ 8453: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
1131
+ 42161: "0xaf88d065e77c8cc2239327c5edb3a432268e5831"
1132
+ };
1133
+ var VIEM_CHAIN = {
1134
+ 8453: base,
1135
+ 42161: arbitrum
1136
+ };
926
1137
  var OwneySDK = class {
927
1138
  agents = /* @__PURE__ */ new Map();
928
1139
  activeAgents = /* @__PURE__ */ new Set();
@@ -935,6 +1146,7 @@ var OwneySDK = class {
935
1146
  state = null;
936
1147
  apiKey;
937
1148
  zyfaiRpcUrls;
1149
+ cachedSponsoredCallback = null;
938
1150
  initializingAgentsPromise = null;
939
1151
  constructor(config) {
940
1152
  this.apiKey = config.apiKey;
@@ -958,6 +1170,7 @@ var OwneySDK = class {
958
1170
  );
959
1171
  }
960
1172
  this.state = { provider, walletAddress, chainId: null };
1173
+ this.cachedSponsoredCallback = null;
961
1174
  }
962
1175
  /**
963
1176
  * Disconnect from all agents and clear connection state.
@@ -970,6 +1183,7 @@ var OwneySDK = class {
970
1183
  this.activeAgents.clear();
971
1184
  this.disabledAgents.clear();
972
1185
  this.state = null;
1186
+ this.cachedSponsoredCallback = null;
973
1187
  }
974
1188
  isAgentDisabled(agentId) {
975
1189
  return this.disabledAgents.has(agentId);
@@ -1003,6 +1217,46 @@ var OwneySDK = class {
1003
1217
  }
1004
1218
  return chainId;
1005
1219
  }
1220
+ requireConnectedProvider() {
1221
+ if (!this.state?.provider) {
1222
+ throw new OwneyError(
1223
+ "NOT_CONNECTED",
1224
+ "connect(provider) must be called before deposit",
1225
+ {}
1226
+ );
1227
+ }
1228
+ return this.state.provider;
1229
+ }
1230
+ /**
1231
+ * Lazily builds (and caches) the default EIP-3009 sponsored deposit callback
1232
+ * used when the caller omits `depositCallback`. Wraps the connected EIP-1193
1233
+ * provider with viem `custom(provider)` to read token meta and sign the
1234
+ * `TransferWithAuthorization`, then POSTs to the sponsor API.
1235
+ */
1236
+ getDefaultSponsoredCallback() {
1237
+ if (this.cachedSponsoredCallback)
1238
+ return this.cachedSponsoredCallback;
1239
+ const provider = this.requireConnectedProvider();
1240
+ const owner = this.state.walletAddress;
1241
+ this.cachedSponsoredCallback = makeSponsoredDepositCallback({
1242
+ apiKey: this.apiKey,
1243
+ ownerAddress: owner,
1244
+ tokenAddressByChain: SPONSORED_USDC_BY_CHAIN,
1245
+ // Casts work around viem's chain-narrowed Client vs the generic
1246
+ // PublicClient/WalletClient param types — structurally identical at
1247
+ // runtime, but the two share a name TS treats as unrelated.
1248
+ getPublicClient: (cid) => createPublicClient({
1249
+ chain: VIEM_CHAIN[cid],
1250
+ transport: custom(provider)
1251
+ }),
1252
+ getWalletClient: (cid) => createWalletClient({
1253
+ account: owner,
1254
+ chain: VIEM_CHAIN[cid],
1255
+ transport: custom(provider)
1256
+ })
1257
+ });
1258
+ return this.cachedSponsoredCallback;
1259
+ }
1006
1260
  getAgent(agentId) {
1007
1261
  const agent = this.agents.get(agentId);
1008
1262
  if (!agent) {
@@ -1156,6 +1410,7 @@ var OwneySDK = class {
1156
1410
  }
1157
1411
  const state = this.requireState();
1158
1412
  const chainId = this.requireChainId();
1413
+ const effectiveCallback = depositCallback ?? (asset === "WETH" ? void 0 : this.getDefaultSponsoredCallback());
1159
1414
  if (agentId) {
1160
1415
  if (this.isAgentDisabled(agentId)) {
1161
1416
  throw new OwneyError(
@@ -1170,7 +1425,13 @@ var OwneySDK = class {
1170
1425
  return withFailureReporting(
1171
1426
  this.apiKey,
1172
1427
  agent.id,
1173
- () => agent.deposit(state, chainId, amount, depositCallback)
1428
+ () => agent.deposit(
1429
+ state,
1430
+ chainId,
1431
+ amount,
1432
+ asset,
1433
+ effectiveCallback
1434
+ )
1174
1435
  );
1175
1436
  }
1176
1437
  const totalAmount = BigInt(amount);
@@ -1204,7 +1465,13 @@ var OwneySDK = class {
1204
1465
  agentResults[agent.id] = await withFailureReporting(
1205
1466
  this.apiKey,
1206
1467
  agent.id,
1207
- () => agent.deposit(state, chainId, agentAmount, depositCallback)
1468
+ () => agent.deposit(
1469
+ state,
1470
+ chainId,
1471
+ agentAmount,
1472
+ asset,
1473
+ effectiveCallback
1474
+ )
1208
1475
  );
1209
1476
  }
1210
1477
  return { agentResults };
@@ -1398,10 +1665,7 @@ var OwneySDK = class {
1398
1665
  disabledBalances,
1399
1666
  requested
1400
1667
  );
1401
- const enabledTotal = enabledBalances.reduce(
1402
- (s, b) => s + b.balance,
1403
- 0n
1404
- );
1668
+ const enabledTotal = enabledBalances.reduce((s, b) => s + b.balance, 0n);
1405
1669
  const enabledPlans = afterDrain > 0n ? planProportionalShares(enabledBalances, afterDrain, enabledTotal) : enabledBalances.map(({ agent, balance }) => ({
1406
1670
  agent,
1407
1671
  balance,
@@ -1672,27 +1936,6 @@ var OwneySDK = class {
1672
1936
  nextCursor: hasMore ? encodeMultiAgentCursor(nextCursorMap) : void 0
1673
1937
  };
1674
1938
  }
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
1939
  /**
1697
1940
  * Get the user's profile for a specific agent, or all agents.
1698
1941
  * @param agentId - Optional. Agent to query. Omit for all agents.
@@ -1767,12 +2010,13 @@ var OwneySDK = class {
1767
2010
  const chainId = this.requireChainId();
1768
2011
  const activeAgents = this.getActiveAgents();
1769
2012
  const balances = {};
2013
+ const positionsByAgent = {};
1770
2014
  let totalBalance = 0;
1771
2015
  const entries = [...activeAgents.entries()];
1772
2016
  const balanceResults = await Promise.allSettled(
1773
2017
  entries.map(async ([id, agent]) => {
1774
2018
  const b = await agent.getBalances(state, chainId);
1775
- return [id, Number(b.totalBalance)];
2019
+ return [id, Number(b.totalBalance), b.positions ?? []];
1776
2020
  })
1777
2021
  );
1778
2022
  let successCount = 0;
@@ -1781,8 +2025,9 @@ var OwneySDK = class {
1781
2025
  const settledResult = balanceResults[i];
1782
2026
  const [agentId] = entries[i];
1783
2027
  if (settledResult.status === "fulfilled") {
1784
- const [id, bal] = settledResult.value;
2028
+ const [id, bal, agentPositions] = settledResult.value;
1785
2029
  balances[id] = bal;
2030
+ positionsByAgent[id] = agentPositions;
1786
2031
  totalBalance += bal;
1787
2032
  successCount += 1;
1788
2033
  } else {
@@ -1816,7 +2061,64 @@ var OwneySDK = class {
1816
2061
  }
1817
2062
  }
1818
2063
  return {
1819
- agentAllocation: allocation
2064
+ agentAllocation: allocation,
2065
+ agentPositions: positionsByAgent
2066
+ };
2067
+ }
2068
+ /**
2069
+ * Allocation-weighted APY from the current portfolio: each pool's APY
2070
+ * weighted by the user's position value in it. More accurate per-user than
2071
+ * the historical 30D APY. (ROUT-228)
2072
+ * @param options.agentId - Optional. Scope to one agent. Omit to aggregate
2073
+ * positions across all active agents.
2074
+ */
2075
+ async getAllocationApy({
2076
+ agentId
2077
+ } = {}) {
2078
+ const state = this.requireState();
2079
+ const chainId = this.requireChainId();
2080
+ const activeAgents = agentId ? /* @__PURE__ */ new Map([[agentId, this.getAgent(agentId)]]) : this.getActiveAgents();
2081
+ const entries = [...activeAgents.entries()];
2082
+ const balanceResults = await Promise.allSettled(
2083
+ entries.map(async ([id, agent]) => {
2084
+ const b = await agent.getBalances(state, chainId);
2085
+ return [id, b.positions ?? []];
2086
+ })
2087
+ );
2088
+ const agentApy = {};
2089
+ const allPositions = [];
2090
+ let successCount = 0;
2091
+ const agentErrors = {};
2092
+ for (let i = 0; i < balanceResults.length; i++) {
2093
+ const settled = balanceResults[i];
2094
+ const [aid] = entries[i];
2095
+ if (settled.status === "fulfilled") {
2096
+ const [id, positions] = settled.value;
2097
+ const per = computeAllocationApy(positions);
2098
+ agentApy[id] = {
2099
+ totalApy: per.totalApy,
2100
+ apyByChainAndAsset: per.apyByChainAndAsset
2101
+ };
2102
+ allPositions.push(...positions);
2103
+ successCount += 1;
2104
+ } else {
2105
+ const reason = settled.reason;
2106
+ agentErrors[aid] = reason instanceof Error ? reason.message : String(reason);
2107
+ console.error(`getAllocationApy agent "${aid}" failed:`, reason);
2108
+ }
2109
+ }
2110
+ if (successCount === 0) {
2111
+ throw new OwneyError(
2112
+ "ALLOCATION_ALL_FAILED",
2113
+ "Failed to fetch allocation APY: all agents failed.",
2114
+ { agentErrors }
2115
+ );
2116
+ }
2117
+ const overall = computeAllocationApy(allPositions);
2118
+ return {
2119
+ totalApy: overall.totalApy,
2120
+ apyByChainAndAsset: overall.apyByChainAndAsset,
2121
+ agentApy
1820
2122
  };
1821
2123
  }
1822
2124
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owney/sdk",
3
- "version": "0.6.0-beta.0",
3
+ "version": "0.6.0-beta.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",