@owney/sdk 0.7.22-beta.1 → 0.7.23

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,18 +1,51 @@
1
- import {
2
- AgentChainIncompatibleError,
3
- AgentNotFoundError,
4
- NotConnectedError,
5
- OwneyError,
6
- debugLog,
7
- fetchAgentKeys,
8
- fetchOrgAgentConfig,
9
- setOwneyDebug
10
- } from "./chunk-XBDJWZXY.js";
11
- import {
12
- buildTransferWithAuthorizationTypedData,
13
- randomAuthNonce,
14
- readTokenMeta
15
- } from "./chunk-AURO3C3R.js";
1
+ // src/errors.ts
2
+ var OwneyError = class extends Error {
3
+ code;
4
+ details;
5
+ agentId;
6
+ constructor(code, message, details, agentId) {
7
+ const prefix = agentId ? `[${code}][agent:${agentId}]` : `[${code}]`;
8
+ super(`${prefix} ${message}`);
9
+ this.name = "OwneyError";
10
+ this.code = code;
11
+ this.details = details;
12
+ this.agentId = agentId;
13
+ }
14
+ };
15
+ var AgentNotFoundError = class extends OwneyError {
16
+ constructor(agentId, available) {
17
+ super(
18
+ "AGENT_NOT_FOUND",
19
+ `Unknown agent "${agentId}". Available agents: ${available.join(", ")}`,
20
+ { agentId, available },
21
+ agentId
22
+ );
23
+ this.name = "AgentNotFoundError";
24
+ }
25
+ };
26
+ var NotConnectedError = class extends OwneyError {
27
+ constructor() {
28
+ super("NOT_CONNECTED", "Not connected. Call sdk.connect(provider) first.");
29
+ this.name = "NotConnectedError";
30
+ }
31
+ };
32
+ var AgentChainIncompatibleError = class extends OwneyError {
33
+ incompatibleAgents;
34
+ connectedChainId;
35
+ constructor(incompatibleAgents, connectedChainId) {
36
+ const details = incompatibleAgents.map(
37
+ ({ agentId, supportedChainIds }) => `"${agentId}" supports chains [${supportedChainIds.join(", ")}]`
38
+ ).join("; ");
39
+ super(
40
+ "AGENT_CHAIN_INCOMPATIBLE",
41
+ `Chain ${connectedChainId} is not supported by the following agents: ${details}`,
42
+ { incompatibleAgents, connectedChainId }
43
+ );
44
+ this.name = "AgentChainIncompatibleError";
45
+ this.incompatibleAgents = incompatibleAgents;
46
+ this.connectedChainId = connectedChainId;
47
+ }
48
+ };
16
49
 
17
50
  // src/lib/rate-limit.ts
18
51
  function rateLimitDelay(error, now = Date.now()) {
@@ -179,6 +212,23 @@ var SupportedAssets = [
179
212
  }
180
213
  ];
181
214
 
215
+ // src/lib/debug.ts
216
+ var configuredDebug = false;
217
+ function setOwneyDebug(enabled) {
218
+ configuredDebug = enabled;
219
+ }
220
+ function isOwneyDebug() {
221
+ return globalThis.__OWNEY_DEBUG__ === true || configuredDebug;
222
+ }
223
+ function debugLog(scope, message, data) {
224
+ if (!isOwneyDebug()) return;
225
+ if (data === void 0) {
226
+ console.log(`[${scope}] ${message}`);
227
+ } else {
228
+ console.log(`[${scope}] ${message}`, data);
229
+ }
230
+ }
231
+
182
232
  // src/lib/utils.ts
183
233
  var isValidChainId = (chainId) => {
184
234
  if (!SUPPORTED_CHAIN_IDS.includes(chainId)) {
@@ -190,12 +240,6 @@ var isValidChainId = (chainId) => {
190
240
  }
191
241
  return chainId;
192
242
  };
193
- function isSameAsset(tokenSymbol, asset) {
194
- const token = tokenSymbol.toLowerCase();
195
- const target = asset.toLowerCase();
196
- if (token === target) return true;
197
- return target === "weth" && token === "eth" || target === "eth" && token === "weth";
198
- }
199
243
  function hexToDecimal(hex, decimals = 6) {
200
244
  const normalized = hex.startsWith("0x") || hex.startsWith("0X") ? hex : `0x${hex}`;
201
245
  const parsed = BigInt(normalized);
@@ -620,6 +664,63 @@ function computeAllocationApy(positions) {
620
664
  const totalApy = totalValue > 0 ? String(weightedSum / totalValue) : "0";
621
665
  return { totalApy, apyByChainAndAsset };
622
666
  }
667
+ var ZYFAI_NET_OF_FEE_MULTIPLIER = 0.9;
668
+ function sumEarningsBucket(bucket, chainId, tokenSymbol) {
669
+ const tokens = bucket?.[String(chainId)];
670
+ if (!tokens) return null;
671
+ const wanted = tokenSymbol?.toUpperCase();
672
+ let total = 0;
673
+ let matched = false;
674
+ for (const [symbol, value] of Object.entries(tokens)) {
675
+ if (wanted && symbol.toUpperCase() !== wanted) continue;
676
+ matched = true;
677
+ const amount = Number(value);
678
+ if (!Number.isFinite(amount)) continue;
679
+ total += amount;
680
+ }
681
+ return matched ? total : null;
682
+ }
683
+ function netEarningsForSnapshot(entry, chainId, tokenSymbol) {
684
+ const lifetime = sumEarningsBucket(entry.lifetime_earnings_by_token, chainId, tokenSymbol);
685
+ const unrealized = sumEarningsBucket(entry.unrealized_earnings_by_token, chainId, tokenSymbol);
686
+ const current = sumEarningsBucket(entry.current_earnings_by_token, chainId, tokenSymbol);
687
+ if (lifetime === null && unrealized === null && current === null) {
688
+ const gross = sumEarningsBucket(entry.total_earnings_by_token, chainId, tokenSymbol);
689
+ if (gross === null) return null;
690
+ if (!warnedGrossApyFallbacks.has("daily_earnings_net_components")) {
691
+ warnedGrossApyFallbacks.add("daily_earnings_net_components");
692
+ console.warn(
693
+ `[owney] @zyfai/sdk omitted the daily net-earnings components; falling back to gross totals, which do not deduct Zyfai's performance fee and so read high.`
694
+ );
695
+ }
696
+ debugLog("zyfai:earnings", "gross fallback for daily earnings", { gross });
697
+ return gross;
698
+ }
699
+ return (lifetime ?? 0) + (unrealized ?? 0) + (current ?? 0) * ZYFAI_NET_OF_FEE_MULTIPLIER;
700
+ }
701
+ function mapDailyEarnings(raw, chainId, tokenSymbol) {
702
+ const points = (raw.data ?? []).map((entry) => ({
703
+ date: entry.snapshot_date,
704
+ net: netEarningsForSnapshot(entry, chainId, tokenSymbol)
705
+ })).filter((p) => p.net !== null).sort((a, b) => a.date.localeCompare(b.date));
706
+ return { walletAddress: raw.walletAddress, points };
707
+ }
708
+ var MS_PER_DAY = 24 * 60 * 60 * 1e3;
709
+ function recentEarningsFromPoints(points, requestedDays) {
710
+ if (points.length === 0) return null;
711
+ const first = points[0];
712
+ const last = points[points.length - 1];
713
+ const spanDays = Math.round(
714
+ (Date.parse(last.date) - Date.parse(first.date)) / MS_PER_DAY
715
+ );
716
+ const amount = Math.max(0, last.net - first.net);
717
+ return {
718
+ amount,
719
+ spanDays: Number.isFinite(spanDays) ? spanDays : 0,
720
+ // A single snapshot spans no window at all, so it is always truncated.
721
+ isTruncated: requestedDays === void 0 ? points.length < 2 : spanDays < requestedDays
722
+ };
723
+ }
623
724
 
624
725
  // src/agents/zyfai/zyfai.withdraw-amount.ts
625
726
  var TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
@@ -1449,37 +1550,6 @@ var ZyfaiAgent = class _ZyfaiAgent {
1449
1550
  );
1450
1551
  }
1451
1552
  }
1452
- /**
1453
- * Records the deposit with Zyfai, retrying transient failures.
1454
- *
1455
- * This runs AFTER the transfer has already landed on-chain, so it must never
1456
- * fail the deposit — the funds moved. But it is also the ONLY source of the
1457
- * "Top up wallet" entry the history is built from: Zyfai auto-deploys the
1458
- * balance it detects either way, so when this call is lost the user's deposit
1459
- * never appears in Activity (an earlier withdrawal stays the newest row) and
1460
- * nothing ever backfills it. Retry, then log loudly enough to be recoverable.
1461
- */
1462
- async logDepositWithRetry(chainId, txHash, amount, tokenAddress) {
1463
- const ATTEMPTS = 3;
1464
- const RETRY_DELAY_MS = 1e3;
1465
- for (let attempt = 1; attempt <= ATTEMPTS; attempt++) {
1466
- try {
1467
- await (tokenAddress ? this.sdk.logDeposit(chainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(chainId, txHash, amount));
1468
- return;
1469
- } catch (logError) {
1470
- if (attempt === ATTEMPTS) {
1471
- console.error(
1472
- "[owney-sdk] Deposit landed on-chain but logDeposit failed \u2014 it will be missing from Zyfai history:",
1473
- { txHash, chainId, amount, tokenAddress, error: logError }
1474
- );
1475
- return;
1476
- }
1477
- await new Promise(
1478
- (resolve) => setTimeout(resolve, RETRY_DELAY_MS * attempt)
1479
- );
1480
- }
1481
- }
1482
- }
1483
1553
  /**
1484
1554
  * True when `smartWallet` is the backend-managed pool wallet Zyfai assigned
1485
1555
  * to this EOA — the "new wallet" kind that is predeployed on the chains it
@@ -1789,12 +1859,14 @@ var ZyfaiAgent = class _ZyfaiAgent {
1789
1859
  );
1790
1860
  const txHash = await depositCallback(smartWallet, validChainId, amount);
1791
1861
  const tokenAddress = asset === "WETH" ? WETH_ADDRESS_BY_CHAIN[validChainId] : void 0;
1792
- await this.logDepositWithRetry(
1793
- validChainId,
1794
- txHash,
1795
- amount,
1796
- tokenAddress
1797
- );
1862
+ try {
1863
+ await (tokenAddress ? this.sdk.logDeposit(validChainId, txHash, amount, tokenAddress) : this.sdk.logDeposit(validChainId, txHash, amount));
1864
+ } catch (logError) {
1865
+ console.warn(
1866
+ "[owney-sdk] Deposit landed on-chain but logDeposit failed (non-fatal):",
1867
+ logError
1868
+ );
1869
+ }
1798
1870
  return { txHash, smartWallet, amount };
1799
1871
  }
1800
1872
  await this.ensureWalletDeployed(this.getAddress(), validChainId);
@@ -1895,6 +1967,15 @@ var ZyfaiAgent = class _ZyfaiAgent {
1895
1967
  const raw = await this.sdk.getDailyApyHistory(smartWallet, days);
1896
1968
  return mapApyHistory(raw, chainId, tokenSymbol);
1897
1969
  }
1970
+ async getDailyEarnings(state, chainId, days, tokenSymbol) {
1971
+ const { smartWallet } = await this.resolveSmartWallet(state, chainId);
1972
+ const start = new Date(Date.now() - (DayFilterMapping[days] + 1) * 864e5);
1973
+ const raw = await this.sdk.getDailyEarnings(
1974
+ smartWallet,
1975
+ start.toISOString().slice(0, 10)
1976
+ );
1977
+ return mapDailyEarnings(raw, chainId, tokenSymbol);
1978
+ }
1898
1979
  /**
1899
1980
  * Owney speaks asset symbols ("USDC" / "WETH"); Zyfai's history endpoint
1900
1981
  * takes lowercase `assetType` and denominates WETH as "eth" (the same
@@ -2008,9 +2089,73 @@ var ZyfaiAgent = class _ZyfaiAgent {
2008
2089
  }
2009
2090
  };
2010
2091
 
2011
- // src/lib/health-report.ts
2092
+ // src/lib/routing-api.ts
2012
2093
  var ROUTING_API_BASE_URL = "https://owney-routing-api-243946518160.europe-west4.run.app";
2013
- async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL) {
2094
+ async function fetchOrgAgentConfig(apiKey, baseUrl = ROUTING_API_BASE_URL) {
2095
+ const url = `${baseUrl}/api/v1/agent/org-config`;
2096
+ try {
2097
+ const res = await fetch(url, {
2098
+ method: "GET",
2099
+ headers: {
2100
+ "Content-Type": "application/json",
2101
+ "x-owney-api-key": `${apiKey}`
2102
+ }
2103
+ });
2104
+ if (!res.ok) {
2105
+ if (res.status !== 404) {
2106
+ console.warn(
2107
+ `[owney-sdk] Could not read org agent config (${res.status}); leaving user profiles unchanged.`
2108
+ );
2109
+ }
2110
+ return null;
2111
+ }
2112
+ const json = await res.json();
2113
+ const policy = json.success ? json.data ?? null : null;
2114
+ debugLog(
2115
+ "owney-sdk",
2116
+ policy ? "org agent config: loaded" : "org agent config: none set by this partner \u2014 user profiles will be left as they are",
2117
+ policy ?? void 0
2118
+ );
2119
+ return policy;
2120
+ } catch (error) {
2121
+ console.warn(
2122
+ "[owney-sdk] Could not read org agent config (non-fatal):",
2123
+ error instanceof Error ? error.message : String(error)
2124
+ );
2125
+ return null;
2126
+ }
2127
+ }
2128
+ async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL) {
2129
+ const url = `${baseUrl}/api/v1/agent/keys`;
2130
+ const res = await fetch(url, {
2131
+ method: "GET",
2132
+ headers: {
2133
+ "Content-Type": "application/json",
2134
+ "x-owney-api-key": `${apiKey}`
2135
+ }
2136
+ });
2137
+ if (!res.ok) {
2138
+ const text = await res.text().catch(() => "");
2139
+ throw new OwneyError(
2140
+ "API_ROUTING_ERROR",
2141
+ `Routing API error ${res.status}: ${text}`,
2142
+ { statusCode: res.status, responseBody: text }
2143
+ );
2144
+ }
2145
+ const json = await res.json();
2146
+ if (!json.success) {
2147
+ throw new OwneyError(
2148
+ "API_ROUTING_FAILED",
2149
+ `Routing API request failed: ${json.message}`,
2150
+ { message: json.message }
2151
+ );
2152
+ }
2153
+ return json.data;
2154
+ }
2155
+
2156
+ // src/lib/health-report.ts
2157
+ var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
2158
+ async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL2) {
2014
2159
  try {
2015
2160
  await fetch(`${baseUrl}/api/v1/agent/health-report`, {
2016
2161
  method: "POST",
@@ -2044,10 +2189,11 @@ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
2044
2189
  // src/lib/helpers/withdraw-helper.ts
2045
2190
  import { parseUnits } from "viem";
2046
2191
  function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
2192
+ const target = asset.toUpperCase();
2047
2193
  return agents.map((agent) => {
2048
2194
  const agentBalance = aggregated[agent.id];
2049
2195
  const tokenBalance = agentBalance?.tokens.find(
2050
- (t) => t.chainId === chainId && isSameAsset(t.asset, asset)
2196
+ (t) => t.chainId === chainId && t.asset.toUpperCase() === target
2051
2197
  );
2052
2198
  if (!tokenBalance) return { agent, balance: 0n };
2053
2199
  return { agent, balance: parseUnits(tokenBalance.amount, decimals) };
@@ -2204,10 +2350,46 @@ import {
2204
2350
  } from "viem";
2205
2351
  import { base as base2, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
2206
2352
 
2353
+ // src/lib/transfer-auth.ts
2354
+ import { bytesToHex } from "viem";
2355
+ var ERC20_META_ABI = [
2356
+ { type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
2357
+ { type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
2358
+ ];
2359
+ function buildTransferWithAuthorizationTypedData(input) {
2360
+ return {
2361
+ domain: { name: input.tokenName, version: input.tokenVersion, chainId: input.chainId, verifyingContract: input.token },
2362
+ types: {
2363
+ TransferWithAuthorization: [
2364
+ { name: "from", type: "address" },
2365
+ { name: "to", type: "address" },
2366
+ { name: "value", type: "uint256" },
2367
+ { name: "validAfter", type: "uint256" },
2368
+ { name: "validBefore", type: "uint256" },
2369
+ { name: "nonce", type: "bytes32" }
2370
+ ]
2371
+ },
2372
+ primaryType: "TransferWithAuthorization",
2373
+ message: input.message
2374
+ };
2375
+ }
2376
+ async function readTokenMeta(publicClient, token) {
2377
+ const [tokenName, tokenVersion] = await Promise.all([
2378
+ publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "name" }),
2379
+ publicClient.readContract({ address: token, abi: ERC20_META_ABI, functionName: "version" }).catch(() => "2")
2380
+ ]);
2381
+ return { tokenName, tokenVersion };
2382
+ }
2383
+ function randomAuthNonce() {
2384
+ const bytes = new Uint8Array(32);
2385
+ globalThis.crypto.getRandomValues(bytes);
2386
+ return bytesToHex(bytes);
2387
+ }
2388
+
2207
2389
  // src/lib/sponsor-client.ts
2208
- var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
2390
+ var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
2209
2391
  async function postSponsorTransferAuth(input) {
2210
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL2;
2392
+ const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2211
2393
  let res;
2212
2394
  try {
2213
2395
  res = await fetch(`${base3}/api/v1/sponsor/erc20-transfer-auth`, {
@@ -2247,7 +2429,7 @@ async function postSponsorTransferAuth(input) {
2247
2429
  return parsed.data;
2248
2430
  }
2249
2431
  async function postSponsorPermit2Transfer(input) {
2250
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL2;
2432
+ const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2251
2433
  let res;
2252
2434
  try {
2253
2435
  res = await fetch(`${base3}/api/v1/sponsor/permit2-transfer`, {
@@ -2285,7 +2467,7 @@ async function postSponsorPermit2Transfer(input) {
2285
2467
  return parsed.data;
2286
2468
  }
2287
2469
  async function getSponsorRelayerAddress(input) {
2288
- const base3 = input.baseUrl ?? ROUTING_API_BASE_URL2;
2470
+ const base3 = input.baseUrl ?? ROUTING_API_BASE_URL3;
2289
2471
  let res;
2290
2472
  try {
2291
2473
  res = await fetch(
@@ -2322,7 +2504,7 @@ async function getSponsorRelayerAddress(input) {
2322
2504
  }
2323
2505
 
2324
2506
  // src/lib/permit2.ts
2325
- import { bytesToHex } from "viem";
2507
+ import { bytesToHex as bytesToHex2 } from "viem";
2326
2508
  var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
2327
2509
  var MAX_UINT256 = 2n ** 256n - 1n;
2328
2510
  var ERC20_ALLOWANCE_ABI = [
@@ -2380,7 +2562,7 @@ function buildPermitTransferFromTypedData(input) {
2380
2562
  function randomPermit2Nonce() {
2381
2563
  const bytes = new Uint8Array(32);
2382
2564
  globalThis.crypto.getRandomValues(bytes);
2383
- return BigInt(bytesToHex(bytes));
2565
+ return BigInt(bytesToHex2(bytes));
2384
2566
  }
2385
2567
  async function readPermit2Allowance(publicClient, token, owner) {
2386
2568
  return publicClient.readContract({
@@ -2725,7 +2907,6 @@ var SPONSORED_WETH_BY_CHAIN = {
2725
2907
  function shouldFallbackToUserPaid(error, asset, appCallback) {
2726
2908
  return (asset === "WETH" || asset === "USDC") && appCallback === void 0 && error instanceof OwneyError && error.code === "SPONSOR_REQUEST_FAILED" && error.details?.safeToFallback === true;
2727
2909
  }
2728
- var AGENT_ELIGIBILITY_ORDER = ["surfliquid", "zyfai"];
2729
2910
  var OwneySDK = class {
2730
2911
  agents = /* @__PURE__ */ new Map();
2731
2912
  activeAgents = /* @__PURE__ */ new Set();
@@ -3009,7 +3190,7 @@ var OwneySDK = class {
3009
3190
  );
3010
3191
  this.disabledAgents.clear();
3011
3192
  for (const { key: key2, agent_type, is_enabled } of agentKeys) {
3012
- const agent = await this.createAgent(agent_type, key2);
3193
+ const agent = this.createAgent(agent_type, key2);
3013
3194
  if (!agent) continue;
3014
3195
  this.agents.set(agent_type, agent);
3015
3196
  if (is_enabled === false) {
@@ -3030,18 +3211,10 @@ var OwneySDK = class {
3030
3211
  this.initializingAgentsPromise = null;
3031
3212
  }
3032
3213
  }
3033
- async createAgent(agentId, key2) {
3214
+ createAgent(agentId, key2) {
3034
3215
  if (agentId === "zyfai") {
3035
3216
  return new ZyfaiAgent(key2, this.zyfaiRpcUrls, this.referralSource);
3036
3217
  }
3037
- if (agentId === "surfliquid") {
3038
- if (!key2) return null;
3039
- const { SurfLiquidAgent } = await import("./surfliquid.agent-AE7RZ66F.js");
3040
- return new SurfLiquidAgent({
3041
- apiKey: this.apiKey,
3042
- routingApiBaseUrl: this.routingApiBaseUrl
3043
- });
3044
- }
3045
3218
  return null;
3046
3219
  }
3047
3220
  /**
@@ -3439,14 +3612,15 @@ var OwneySDK = class {
3439
3612
  async hasExistingBalance(agent, state, chainId, asset, requireReliableRead = false) {
3440
3613
  try {
3441
3614
  const balance = await agent.getBalances(state, chainId);
3615
+ const target = asset.toLowerCase();
3442
3616
  const token = balance.tokens.find(
3443
- (t) => t.chainId === chainId && isSameAsset(t.asset, asset)
3617
+ (t) => t.chainId === chainId && t.asset.toLowerCase() === target
3444
3618
  );
3445
3619
  const targetChainName = agent.supportedAssets.find((entry) => entry.chainId === chainId)?.chain?.trim().toUpperCase();
3446
3620
  const position = (balance.positions ?? []).find((p) => {
3447
3621
  const positionChain = p.chain.trim().toUpperCase();
3448
3622
  const matchesChain = positionChain === String(chainId) || targetChainName !== void 0 && positionChain === targetChainName;
3449
- return matchesChain && isSameAsset(p.asset, asset) && Number(p.amount) > 0;
3623
+ return matchesChain && p.asset.toLowerCase() === target && Number(p.amount) > 0;
3450
3624
  });
3451
3625
  return !!token && Number(token.amount) > 0 || !!position;
3452
3626
  } catch (error) {
@@ -3493,34 +3667,6 @@ var OwneySDK = class {
3493
3667
  }
3494
3668
  return eligible;
3495
3669
  }
3496
- /**
3497
- * Agent ids the routing API provisioned for this org that support the given
3498
- * chain + asset, ordered by preference ({@link AGENT_ELIGIBILITY_ORDER},
3499
- * surfliquid first). Returns `[]` when the org has no compatible agent — never
3500
- * throws on an empty org. Loads agent keys on first call (apiKey only, no
3501
- * wallet), so the UI can resolve which agent to use before the user connects.
3502
- *
3503
- * This is the source of truth for agent availability: an agent appears here
3504
- * iff the routing API returned its key. No per-app feature flags.
3505
- */
3506
- async getEligibleAgentIds(chainId, asset) {
3507
- try {
3508
- await this.ensureAgentsInitialized();
3509
- } catch (error) {
3510
- if (error instanceof OwneyError && error.code === "API_NO_AGENTS") {
3511
- return [];
3512
- }
3513
- throw error;
3514
- }
3515
- return [...this.agents.values()].filter((agent) => {
3516
- const chainAssets = agent.supportedAssets.find(
3517
- (sa) => sa.chainId === chainId
3518
- );
3519
- return chainAssets?.assets.some((a) => a.symbol === asset) ?? false;
3520
- }).map((agent) => agent.id).sort(
3521
- (a, b) => AGENT_ELIGIBILITY_ORDER.indexOf(a) - AGENT_ELIGIBILITY_ORDER.indexOf(b)
3522
- );
3523
- }
3524
3670
  // --- Fund operations ---
3525
3671
  /**
3526
3672
  * Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
@@ -3557,19 +3703,9 @@ var OwneySDK = class {
3557
3703
  }
3558
3704
  const eligibleAgents = this.getEligibleAgents(chainId, asset);
3559
3705
  if (!amount) {
3560
- const aggregated2 = await this.getBalances();
3561
- const funded = projectAgentBalancesForAsset(
3562
- eligibleAgents,
3563
- aggregated2.agentBalances,
3564
- chainId,
3565
- asset,
3566
- assetInfo.decimals
3567
- ).filter(
3568
- ({ agent, balance }) => balance > 0n || aggregated2.agentErrors?.[agent.id] !== void 0
3569
- ).map(({ agent }) => agent);
3570
3706
  const results2 = {};
3571
3707
  const agentErrors2 = {};
3572
- for (const agent of funded) {
3708
+ for (const agent of eligibleAgents) {
3573
3709
  try {
3574
3710
  results2[agent.id] = await agent.withdraw(state, chainId, token);
3575
3711
  } catch (err) {
@@ -3812,6 +3948,60 @@ var OwneySDK = class {
3812
3948
  * @param options.days - Lookback period: "7D", "14D", or "30D"
3813
3949
  * @returns {AccountAgentApy} for a single agent, or {OwneyAccountApy} with totalApy and per-agent breakdown
3814
3950
  */
3951
+ /**
3952
+ * Daily cumulative NET earnings for the selected chain/asset, backing the
3953
+ * "recent earnings" subline. Net is computed as Zyfai's own
3954
+ * `lifetime + unrealized + current x 0.9`, so the figure reconciles with the
3955
+ * balance headline rather than reading ~11% high. (ROUT-452)
3956
+ *
3957
+ * Unlike getAccountApy this does NOT blend across agents: earnings are
3958
+ * summed, not weighted, and an agent that fails to report must not silently
3959
+ * subtract from the total. Without an agentId the series is the sum of the
3960
+ * agents that answered.
3961
+ */
3962
+ async getDailyEarnings({
3963
+ agentId,
3964
+ days,
3965
+ tokenSymbol
3966
+ }) {
3967
+ const state = this.requireState();
3968
+ const chainId = this.requireChainId();
3969
+ if (agentId) {
3970
+ const agent = this.getAgent(agentId);
3971
+ if (!agent.getDailyEarnings) {
3972
+ return { walletAddress: state.walletAddress ?? "", points: [] };
3973
+ }
3974
+ return this.readAgent(
3975
+ agent,
3976
+ "dailyEarnings",
3977
+ () => agent.getDailyEarnings(state, chainId, days, tokenSymbol),
3978
+ { days, tokenSymbol }
3979
+ );
3980
+ }
3981
+ const entries = [...this.getActiveAgents().entries()].filter(
3982
+ ([, agent]) => agent.getDailyEarnings
3983
+ );
3984
+ const series = await Promise.all(
3985
+ entries.map(
3986
+ ([, agent]) => this.readAgent(
3987
+ agent,
3988
+ "dailyEarnings",
3989
+ () => agent.getDailyEarnings(state, chainId, days, tokenSymbol),
3990
+ { days, tokenSymbol }
3991
+ )
3992
+ )
3993
+ );
3994
+ const byDate = /* @__PURE__ */ new Map();
3995
+ for (const s of series) {
3996
+ for (const point of s.points) {
3997
+ byDate.set(point.date, (byDate.get(point.date) ?? 0) + point.net);
3998
+ }
3999
+ }
4000
+ return {
4001
+ walletAddress: series[0]?.walletAddress ?? state.walletAddress ?? "",
4002
+ points: [...byDate.entries()].map(([date, net]) => ({ date, net })).sort((a, b) => a.date.localeCompare(b.date))
4003
+ };
4004
+ }
3815
4005
  async getAccountApy({
3816
4006
  agentId,
3817
4007
  days,
@@ -4372,5 +4562,6 @@ export {
4372
4562
  OwneyError,
4373
4563
  OwneySDK,
4374
4564
  createOwneySIWX,
4565
+ recentEarningsFromPoints,
4375
4566
  setOwneyDebug
4376
4567
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owney/sdk",
3
- "version": "0.7.22-beta.1",
3
+ "version": "0.7.23",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",
@@ -20,12 +20,10 @@
20
20
  "build": "tsup",
21
21
  "dev": "tsup --watch",
22
22
  "test": "vitest run",
23
- "test:watch": "vitest",
24
- "typecheck": "tsc -p tsconfig.test.json --noEmit"
23
+ "test:watch": "vitest"
25
24
  },
26
25
  "dependencies": {
27
- "@zyfai/sdk": "0.2.52",
28
- "permissionless": "^0.4.0",
26
+ "@zyfai/sdk": "0.2.55",
29
27
  "siwe": "^3.0.0",
30
28
  "viem": "^2.48.1"
31
29
  },