@owney/sdk 0.7.7 → 0.7.9

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/README.md CHANGED
@@ -263,6 +263,25 @@ console.log(allEarnings.agentEarnings.zyfai.lifetimeEarnings); // 42.5
263
263
  console.log(allEarnings.agentEarnings.zyfai.tokens); // per-chain, per-asset breakdown
264
264
  ```
265
265
 
266
+ ### `refreshEarnings(agentId?)`
267
+
268
+ Refresh earnings for agents whose earnings data may be outdated. If the latest
269
+ snapshot is less than one hour old, the SDK returns it without recalculating.
270
+
271
+ | Param | Type | Description |
272
+ | --------- | -------------------- | ------------------------------------ |
273
+ | `agentId` | `"zyfai"` (optional) | Agent to refresh. Omit for all agents. |
274
+
275
+ Returns: `AgentEarnings` (single agent) or `OwneyEarnings` (all agents)
276
+
277
+ ```typescript
278
+ // Single agent
279
+ const refreshed = await sdk.refreshEarnings("zyfai");
280
+
281
+ // All agents
282
+ const allRefreshed = await sdk.refreshEarnings();
283
+ ```
284
+
266
285
  ### `getAccountApy({ agentId?, days })`
267
286
 
268
287
  Get the weighted APY for the user's account. When querying all agents, the total APY is a balance-weighted average.
package/dist/index.cjs CHANGED
@@ -250,9 +250,10 @@ function mapBalances(raw, _chainId, smartWallet) {
250
250
  const tokens = [];
251
251
  for (const [chainIdKey, tokensBySymbol] of Object.entries(portfolioByChain)) {
252
252
  const positionChainId = Number(chainIdKey);
253
- for (const [symbol, { balance, balanceWithFee, decimals }] of Object.entries(
254
- tokensBySymbol ?? {}
255
- )) {
253
+ for (const [
254
+ symbol,
255
+ { balance, balanceWithFee, decimals }
256
+ ] of Object.entries(tokensBySymbol ?? {})) {
256
257
  const amount = hexToDecimal(balanceWithFee ?? balance ?? "0", decimals);
257
258
  totalBalance += amount;
258
259
  tokens.push({
@@ -391,6 +392,11 @@ function mapEntries(rawEntries, chainId) {
391
392
  "Top up wallet": "Top up",
392
393
  "Partial withdraw from protocol": "Withdraw",
393
394
  "Withdraw from protocol": "Withdraw",
395
+ // The agent's own leg of a top up: the funds the user sent moving into the
396
+ // pool that earns the APY. Zyfai emits it as a separate entry seconds after
397
+ // "Top up wallet" and for slightly less (gas). `Deposit` already exists in
398
+ // the public HistoryAction union, so no consumer type change is needed.
399
+ "Deposit to protocol": "Deposit",
394
400
  Rebalance: "Rebalance"
395
401
  };
396
402
  void chainId;
@@ -431,12 +437,19 @@ function mapEntries(rawEntries, chainId) {
431
437
  const mappedAction = (entry.action ? actionMap[entry.action] : void 0) ?? entry.action;
432
438
  const rawAmount = entry?.positions?.[0]?.amount;
433
439
  const hasAmount = mappedAction !== "Withdraw" && typeof rawAmount === "string" && rawAmount !== "" && rawAmount !== "0x" && rawAmount !== "0x0" && rawAmount !== "0";
440
+ const mappedPositions = positions.filter((p) => p.protocol_name).map((p) => ({
441
+ protocol: p.protocol_name,
442
+ pool: p.pool ?? void 0,
443
+ tokenSymbol: p.token_symbol ?? void 0,
444
+ amount: mappedAction !== "Withdraw" && typeof p.amount === "string" && p.amount !== "" && p.amount !== "0x" ? String(hexToDecimal(p.amount, tokenDecimals(p.token_symbol))) : void 0
445
+ }));
434
446
  return {
435
447
  agent: "zyfai",
436
448
  action: mappedAction,
437
449
  date: entry.date || "",
438
450
  newApy: newApy === null ? null : String(newApy),
439
451
  oldApy: oldApy === null ? null : String(oldApy),
452
+ ...mappedPositions.length ? { positions: mappedPositions } : {},
440
453
  transactions: [
441
454
  {
442
455
  txHashes: entry.transactionHash ? [entry.transactionHash] : [],
@@ -871,6 +884,7 @@ var WETH_ADDRESS_BY_CHAIN = {
871
884
  var VIEM_CHAIN = { 8453: import_chains.base, 42161: import_chains.arbitrum, 1: import_chains.mainnet };
872
885
  var SPLITTING_ASSETS = ["USDC", "WETH"];
873
886
  var POOL_POLICY_CHAIN_IDS = [8453, 42161, 1];
887
+ var EARNINGS_REFRESH_COOLDOWN_MS = 60 * 60 * 1e3;
874
888
  var ZyfaiAgent = class {
875
889
  id = "zyfai";
876
890
  supportedChainIds = [8453, 42161, 1];
@@ -922,6 +936,7 @@ var ZyfaiAgent = class {
922
936
  // successful decode is cached forever; failures are NOT cached so a transient
923
937
  // RPC error retries on the next fetch.
924
938
  withdrawAmountCache = /* @__PURE__ */ new Map();
939
+ earningsRefreshInFlight = null;
925
940
  constructor(apiKey, rpcUrls, referralSource) {
926
941
  this.rpcUrls = rpcUrls ?? DEFAULT_ZYFAI_RPC_URLS;
927
942
  this.sdk = new import_sdk.ZyfaiSDK({
@@ -1719,6 +1734,22 @@ var ZyfaiAgent = class {
1719
1734
  const raw = await this.sdk.getOnchainEarnings(smartWallet);
1720
1735
  return mapEarnings(raw, smartWallet);
1721
1736
  }
1737
+ async refreshEarnings(state, chainId) {
1738
+ if (this.earningsRefreshInFlight) return this.earningsRefreshInFlight;
1739
+ this.earningsRefreshInFlight = (async () => {
1740
+ const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1741
+ const current = await this.sdk.getOnchainEarnings(smartWallet);
1742
+ const lastCheck = current.data.lastCheckTimestamp ? Date.parse(current.data.lastCheckTimestamp) : Number.NaN;
1743
+ const isFresh = Number.isFinite(lastCheck) && Date.now() - lastCheck < EARNINGS_REFRESH_COOLDOWN_MS;
1744
+ const earnings = isFresh ? current : await this.sdk.calculateOnchainEarnings(smartWallet);
1745
+ return mapEarnings(earnings, smartWallet);
1746
+ })();
1747
+ try {
1748
+ return await this.earningsRefreshInFlight;
1749
+ } finally {
1750
+ this.earningsRefreshInFlight = null;
1751
+ }
1752
+ }
1722
1753
  async getAccountApy(state, chainId, days, tokenSymbol) {
1723
1754
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1724
1755
  const raw = await this.sdk.getDailyApyHistory(smartWallet, days);
@@ -1743,7 +1774,7 @@ var ZyfaiAgent = class {
1743
1774
  toDate
1744
1775
  });
1745
1776
  raw.data.forEach((entry, idx) => {
1746
- if (entry.chainId === validChainId && entry.action !== "Deposit to protocol") {
1777
+ if (entry.chainId === validChainId) {
1747
1778
  matched.push({ entry, rawIdx: offset + idx });
1748
1779
  }
1749
1780
  });
@@ -3419,6 +3450,28 @@ var OwneySDK = class {
3419
3450
  agentEarnings: results
3420
3451
  };
3421
3452
  }
3453
+ /**
3454
+ * Return refreshed earnings while letting each agent own its refresh policy.
3455
+ * Agents without a refresh capability fall back to their current snapshot.
3456
+ */
3457
+ async refreshEarnings(agentId) {
3458
+ const state = this.requireState();
3459
+ const chainId = this.requireChainId();
3460
+ const refreshAgent = (agent) => agent.refreshEarnings?.(state, chainId) ?? agent.getEarnings(state, chainId);
3461
+ if (agentId) return refreshAgent(this.getAgent(agentId));
3462
+ let totalEarnings = 0;
3463
+ const agentEarnings = {};
3464
+ const refreshed = await Promise.all(
3465
+ [...this.getActiveAgents().entries()].map(
3466
+ async ([id, agent]) => [id, await refreshAgent(agent)]
3467
+ )
3468
+ );
3469
+ for (const [id, earnings] of refreshed) {
3470
+ agentEarnings[id] = earnings;
3471
+ totalEarnings += Number(earnings.lifetimeEarnings);
3472
+ }
3473
+ return { totalEarnings: String(totalEarnings), agentEarnings };
3474
+ }
3422
3475
  /**
3423
3476
  * Get the weighted APY for the user's account over a time period.
3424
3477
  * When querying all agents, the total APY is a balance-weighted average.
package/dist/index.d.cts CHANGED
@@ -383,6 +383,11 @@ interface IAgent {
383
383
  withdraw(state: ConnectionState, chainId: number, token: OwneySupportedTokens, amount?: string): Promise<AgentWithdrawResult>;
384
384
  getBalances(state: ConnectionState, chainId: number): Promise<AgentBalance>;
385
385
  getEarnings(state: ConnectionState, chainId: number): Promise<AgentEarnings>;
386
+ /**
387
+ * Refresh the agent's earnings snapshot when its backend supports doing so.
388
+ * Implementations own freshness and request-deduplication policy.
389
+ */
390
+ refreshEarnings?(state: ConnectionState, chainId: number): Promise<AgentEarnings>;
386
391
  getAccountApy(state: ConnectionState, chainId: number, days: DailyApyDays,
387
392
  /**
388
393
  * Optional asset symbol ("USDC" / "WETH") scoping the daily series to a
@@ -581,6 +586,11 @@ declare class OwneySDK {
581
586
  * @returns {AgentEarnings} for a single agent, or {OwneyEarnings} with totalEarnings and per-agent breakdown
582
587
  */
583
588
  getEarnings(agentId?: AgentId): Promise<OwneyEarnings | AgentEarnings>;
589
+ /**
590
+ * Return refreshed earnings while letting each agent own its refresh policy.
591
+ * Agents without a refresh capability fall back to their current snapshot.
592
+ */
593
+ refreshEarnings(agentId?: AgentId): Promise<OwneyEarnings | AgentEarnings>;
584
594
  /**
585
595
  * Get the weighted APY for the user's account over a time period.
586
596
  * When querying all agents, the total APY is a balance-weighted average.
package/dist/index.d.ts CHANGED
@@ -383,6 +383,11 @@ interface IAgent {
383
383
  withdraw(state: ConnectionState, chainId: number, token: OwneySupportedTokens, amount?: string): Promise<AgentWithdrawResult>;
384
384
  getBalances(state: ConnectionState, chainId: number): Promise<AgentBalance>;
385
385
  getEarnings(state: ConnectionState, chainId: number): Promise<AgentEarnings>;
386
+ /**
387
+ * Refresh the agent's earnings snapshot when its backend supports doing so.
388
+ * Implementations own freshness and request-deduplication policy.
389
+ */
390
+ refreshEarnings?(state: ConnectionState, chainId: number): Promise<AgentEarnings>;
386
391
  getAccountApy(state: ConnectionState, chainId: number, days: DailyApyDays,
387
392
  /**
388
393
  * Optional asset symbol ("USDC" / "WETH") scoping the daily series to a
@@ -581,6 +586,11 @@ declare class OwneySDK {
581
586
  * @returns {AgentEarnings} for a single agent, or {OwneyEarnings} with totalEarnings and per-agent breakdown
582
587
  */
583
588
  getEarnings(agentId?: AgentId): Promise<OwneyEarnings | AgentEarnings>;
589
+ /**
590
+ * Return refreshed earnings while letting each agent own its refresh policy.
591
+ * Agents without a refresh capability fall back to their current snapshot.
592
+ */
593
+ refreshEarnings(agentId?: AgentId): Promise<OwneyEarnings | AgentEarnings>;
584
594
  /**
585
595
  * Get the weighted APY for the user's account over a time period.
586
596
  * When querying all agents, the total APY is a balance-weighted average.
package/dist/index.js CHANGED
@@ -217,9 +217,10 @@ function mapBalances(raw, _chainId, smartWallet) {
217
217
  const tokens = [];
218
218
  for (const [chainIdKey, tokensBySymbol] of Object.entries(portfolioByChain)) {
219
219
  const positionChainId = Number(chainIdKey);
220
- for (const [symbol, { balance, balanceWithFee, decimals }] of Object.entries(
221
- tokensBySymbol ?? {}
222
- )) {
220
+ for (const [
221
+ symbol,
222
+ { balance, balanceWithFee, decimals }
223
+ ] of Object.entries(tokensBySymbol ?? {})) {
223
224
  const amount = hexToDecimal(balanceWithFee ?? balance ?? "0", decimals);
224
225
  totalBalance += amount;
225
226
  tokens.push({
@@ -358,6 +359,11 @@ function mapEntries(rawEntries, chainId) {
358
359
  "Top up wallet": "Top up",
359
360
  "Partial withdraw from protocol": "Withdraw",
360
361
  "Withdraw from protocol": "Withdraw",
362
+ // The agent's own leg of a top up: the funds the user sent moving into the
363
+ // pool that earns the APY. Zyfai emits it as a separate entry seconds after
364
+ // "Top up wallet" and for slightly less (gas). `Deposit` already exists in
365
+ // the public HistoryAction union, so no consumer type change is needed.
366
+ "Deposit to protocol": "Deposit",
361
367
  Rebalance: "Rebalance"
362
368
  };
363
369
  void chainId;
@@ -398,12 +404,19 @@ function mapEntries(rawEntries, chainId) {
398
404
  const mappedAction = (entry.action ? actionMap[entry.action] : void 0) ?? entry.action;
399
405
  const rawAmount = entry?.positions?.[0]?.amount;
400
406
  const hasAmount = mappedAction !== "Withdraw" && typeof rawAmount === "string" && rawAmount !== "" && rawAmount !== "0x" && rawAmount !== "0x0" && rawAmount !== "0";
407
+ const mappedPositions = positions.filter((p) => p.protocol_name).map((p) => ({
408
+ protocol: p.protocol_name,
409
+ pool: p.pool ?? void 0,
410
+ tokenSymbol: p.token_symbol ?? void 0,
411
+ amount: mappedAction !== "Withdraw" && typeof p.amount === "string" && p.amount !== "" && p.amount !== "0x" ? String(hexToDecimal(p.amount, tokenDecimals(p.token_symbol))) : void 0
412
+ }));
401
413
  return {
402
414
  agent: "zyfai",
403
415
  action: mappedAction,
404
416
  date: entry.date || "",
405
417
  newApy: newApy === null ? null : String(newApy),
406
418
  oldApy: oldApy === null ? null : String(oldApy),
419
+ ...mappedPositions.length ? { positions: mappedPositions } : {},
407
420
  transactions: [
408
421
  {
409
422
  txHashes: entry.transactionHash ? [entry.transactionHash] : [],
@@ -838,6 +851,7 @@ var WETH_ADDRESS_BY_CHAIN = {
838
851
  var VIEM_CHAIN = { 8453: base, 42161: arbitrum, 1: mainnet };
839
852
  var SPLITTING_ASSETS = ["USDC", "WETH"];
840
853
  var POOL_POLICY_CHAIN_IDS = [8453, 42161, 1];
854
+ var EARNINGS_REFRESH_COOLDOWN_MS = 60 * 60 * 1e3;
841
855
  var ZyfaiAgent = class {
842
856
  id = "zyfai";
843
857
  supportedChainIds = [8453, 42161, 1];
@@ -889,6 +903,7 @@ var ZyfaiAgent = class {
889
903
  // successful decode is cached forever; failures are NOT cached so a transient
890
904
  // RPC error retries on the next fetch.
891
905
  withdrawAmountCache = /* @__PURE__ */ new Map();
906
+ earningsRefreshInFlight = null;
892
907
  constructor(apiKey, rpcUrls, referralSource) {
893
908
  this.rpcUrls = rpcUrls ?? DEFAULT_ZYFAI_RPC_URLS;
894
909
  this.sdk = new ZyfaiSDK({
@@ -1686,6 +1701,22 @@ var ZyfaiAgent = class {
1686
1701
  const raw = await this.sdk.getOnchainEarnings(smartWallet);
1687
1702
  return mapEarnings(raw, smartWallet);
1688
1703
  }
1704
+ async refreshEarnings(state, chainId) {
1705
+ if (this.earningsRefreshInFlight) return this.earningsRefreshInFlight;
1706
+ this.earningsRefreshInFlight = (async () => {
1707
+ const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1708
+ const current = await this.sdk.getOnchainEarnings(smartWallet);
1709
+ const lastCheck = current.data.lastCheckTimestamp ? Date.parse(current.data.lastCheckTimestamp) : Number.NaN;
1710
+ const isFresh = Number.isFinite(lastCheck) && Date.now() - lastCheck < EARNINGS_REFRESH_COOLDOWN_MS;
1711
+ const earnings = isFresh ? current : await this.sdk.calculateOnchainEarnings(smartWallet);
1712
+ return mapEarnings(earnings, smartWallet);
1713
+ })();
1714
+ try {
1715
+ return await this.earningsRefreshInFlight;
1716
+ } finally {
1717
+ this.earningsRefreshInFlight = null;
1718
+ }
1719
+ }
1689
1720
  async getAccountApy(state, chainId, days, tokenSymbol) {
1690
1721
  const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1691
1722
  const raw = await this.sdk.getDailyApyHistory(smartWallet, days);
@@ -1710,7 +1741,7 @@ var ZyfaiAgent = class {
1710
1741
  toDate
1711
1742
  });
1712
1743
  raw.data.forEach((entry, idx) => {
1713
- if (entry.chainId === validChainId && entry.action !== "Deposit to protocol") {
1744
+ if (entry.chainId === validChainId) {
1714
1745
  matched.push({ entry, rawIdx: offset + idx });
1715
1746
  }
1716
1747
  });
@@ -3390,6 +3421,28 @@ var OwneySDK = class {
3390
3421
  agentEarnings: results
3391
3422
  };
3392
3423
  }
3424
+ /**
3425
+ * Return refreshed earnings while letting each agent own its refresh policy.
3426
+ * Agents without a refresh capability fall back to their current snapshot.
3427
+ */
3428
+ async refreshEarnings(agentId) {
3429
+ const state = this.requireState();
3430
+ const chainId = this.requireChainId();
3431
+ const refreshAgent = (agent) => agent.refreshEarnings?.(state, chainId) ?? agent.getEarnings(state, chainId);
3432
+ if (agentId) return refreshAgent(this.getAgent(agentId));
3433
+ let totalEarnings = 0;
3434
+ const agentEarnings = {};
3435
+ const refreshed = await Promise.all(
3436
+ [...this.getActiveAgents().entries()].map(
3437
+ async ([id, agent]) => [id, await refreshAgent(agent)]
3438
+ )
3439
+ );
3440
+ for (const [id, earnings] of refreshed) {
3441
+ agentEarnings[id] = earnings;
3442
+ totalEarnings += Number(earnings.lifetimeEarnings);
3443
+ }
3444
+ return { totalEarnings: String(totalEarnings), agentEarnings };
3445
+ }
3393
3446
  /**
3394
3447
  * Get the weighted APY for the user's account over a time period.
3395
3448
  * When querying all agents, the total APY is a balance-weighted average.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owney/sdk",
3
- "version": "0.7.7",
3
+ "version": "0.7.9",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",