@owney/sdk 0.7.23-beta.1 → 0.7.24-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -27,7 +27,6 @@ __export(index_exports, {
27
27
  OwneyError: () => OwneyError,
28
28
  OwneySDK: () => OwneySDK,
29
29
  createOwneySIWX: () => createOwneySIWX,
30
- listPendingSwaps: () => listOrders,
31
30
  setOwneyDebug: () => setOwneyDebug
32
31
  });
33
32
  module.exports = __toCommonJS(index_exports);
@@ -698,6 +697,75 @@ function computeAllocationApy(positions) {
698
697
  const totalApy = totalValue > 0 ? String(weightedSum / totalValue) : "0";
699
698
  return { totalApy, apyByChainAndAsset };
700
699
  }
700
+ function earningsForToken(bucket, chainId, asset) {
701
+ const tokens = bucket?.[String(chainId)];
702
+ if (!tokens) return null;
703
+ const wanted = asset.toUpperCase();
704
+ for (const [symbol, value] of Object.entries(tokens)) {
705
+ if (symbol.toUpperCase() !== wanted) continue;
706
+ const amount = Number(value);
707
+ return Number.isFinite(amount) ? amount : null;
708
+ }
709
+ return null;
710
+ }
711
+ function assetsInSnapshot(entry, chainId) {
712
+ const key2 = String(chainId);
713
+ const seen = /* @__PURE__ */ new Set();
714
+ for (const bucket of [
715
+ entry.daily_total_delta_by_token_withoutFee,
716
+ entry.daily_total_delta_by_token,
717
+ entry.lifetime_earnings_by_token,
718
+ entry.unrealized_earnings_by_token,
719
+ entry.current_earnings_by_token,
720
+ entry.total_earnings_by_token
721
+ ]) {
722
+ for (const symbol of Object.keys(bucket?.[key2] ?? {})) {
723
+ seen.add(symbol.toUpperCase());
724
+ }
725
+ }
726
+ return [...seen];
727
+ }
728
+ function netDeltaForSnapshot(entry, chainId, asset) {
729
+ const net = earningsForToken(
730
+ entry.daily_total_delta_by_token_withoutFee,
731
+ chainId,
732
+ asset
733
+ );
734
+ if (net !== null) return net;
735
+ const gross = earningsForToken(
736
+ entry.daily_total_delta_by_token,
737
+ chainId,
738
+ asset
739
+ );
740
+ if (gross === null) return 0;
741
+ if (!warnedGrossApyFallbacks.has("daily_earnings_delta_without_fee")) {
742
+ warnedGrossApyFallbacks.add("daily_earnings_delta_without_fee");
743
+ console.warn(
744
+ `[owney] @zyfai/sdk did not supply daily_total_delta_by_token_withoutFee; falling back to the gross daily delta, which does not deduct Zyfai's performance fee and so reads high.`
745
+ );
746
+ }
747
+ debugLog("zyfai:earnings", "gross fallback for daily earnings", { gross });
748
+ return gross;
749
+ }
750
+ function mapDailyEarnings(raw, chainId, tokenSymbol) {
751
+ const wanted = tokenSymbol?.toUpperCase();
752
+ const snapshots = [...raw.data ?? []].sort(
753
+ (a, b) => a.snapshot_date.localeCompare(b.snapshot_date)
754
+ );
755
+ const byAsset = /* @__PURE__ */ new Map();
756
+ for (const entry of snapshots) {
757
+ for (const asset of assetsInSnapshot(entry, chainId)) {
758
+ if (wanted && asset !== wanted) continue;
759
+ const delta = netDeltaForSnapshot(entry, chainId, asset);
760
+ const series = byAsset.get(asset) ?? { points: [], total: 0 };
761
+ series.total += delta;
762
+ series.points.push({ date: entry.snapshot_date, amount: series.total });
763
+ byAsset.set(asset, series);
764
+ }
765
+ }
766
+ const assets = [...byAsset.entries()].map(([asset, series]) => ({ asset, points: series.points })).sort((a, b) => a.asset.localeCompare(b.asset));
767
+ return { walletAddress: raw.walletAddress, chainId, assets };
768
+ }
701
769
 
702
770
  // src/agents/zyfai/zyfai.withdraw-amount.ts
703
771
  var TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
@@ -1944,6 +2012,15 @@ var ZyfaiAgent = class _ZyfaiAgent {
1944
2012
  const raw = await this.sdk.getDailyApyHistory(smartWallet, days);
1945
2013
  return mapApyHistory(raw, chainId, tokenSymbol);
1946
2014
  }
2015
+ async getDailyEarnings(state, chainId, days, tokenSymbol) {
2016
+ const { smartWallet } = await this.resolveSmartWallet(state, chainId);
2017
+ const start = new Date(Date.now() - (DayFilterMapping[days] + 1) * 864e5);
2018
+ const raw = await this.sdk.getDailyEarnings(
2019
+ smartWallet,
2020
+ start.toISOString().slice(0, 10)
2021
+ );
2022
+ return mapDailyEarnings(raw, chainId, tokenSymbol);
2023
+ }
1947
2024
  /**
1948
2025
  * Owney speaks asset symbols ("USDC" / "WETH"); Zyfai's history endpoint
1949
2026
  * takes lowercase `assetType` and denominates WETH as "eth" (the same
@@ -2121,597 +2198,6 @@ async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL) {
2121
2198
  return json.data;
2122
2199
  }
2123
2200
 
2124
- // src/lib/chain-guard.ts
2125
- var CHAIN_NAMES = {
2126
- 1: "Ethereum",
2127
- 8453: "Base",
2128
- 42161: "Arbitrum"
2129
- };
2130
- function chainName(chainId) {
2131
- return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
2132
- }
2133
- async function ensureWalletOnChain(pub, wallet, expected) {
2134
- const actual = await pub.getChainId();
2135
- if (actual === expected) return;
2136
- try {
2137
- await wallet.switchChain({ id: expected });
2138
- } catch (error) {
2139
- throw new OwneyError(
2140
- "CHAIN_MISMATCH",
2141
- `Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
2142
- {
2143
- expectedChainId: expected,
2144
- actualChainId: actual,
2145
- cause: error instanceof Error ? error.message : String(error)
2146
- }
2147
- );
2148
- }
2149
- const after = await pub.getChainId();
2150
- if (after !== expected) {
2151
- throw new OwneyError(
2152
- "CHAIN_MISMATCH",
2153
- `Your wallet is still on ${chainName(after)}. Switch it to ${chainName(expected)} and try again.`,
2154
- { expectedChainId: expected, actualChainId: after }
2155
- );
2156
- }
2157
- }
2158
-
2159
- // src/lib/swap/swap-api.ts
2160
- async function request(baseUrl, apiKey, path, init) {
2161
- const url = `${baseUrl}/api/v1/swap${path}`;
2162
- const res = await fetch(url, {
2163
- method: init?.method ?? "GET",
2164
- headers: {
2165
- "Content-Type": "application/json",
2166
- "x-owney-api-key": apiKey
2167
- },
2168
- ...init ? { body: JSON.stringify(init.body) } : {}
2169
- });
2170
- if (!res.ok) {
2171
- const text = await res.text().catch(() => "");
2172
- if (res.status === 429) {
2173
- throw new OwneyError(
2174
- "SWAP_RATE_LIMITED",
2175
- "Swap provider is rate limiting, retry shortly",
2176
- { statusCode: res.status }
2177
- );
2178
- }
2179
- if (res.status === 403) {
2180
- throw new OwneyError(
2181
- "SWAP_DISABLED",
2182
- "Swap is not enabled for this organization",
2183
- { statusCode: res.status }
2184
- );
2185
- }
2186
- throw new OwneyError(
2187
- "SWAP_REQUEST_FAILED",
2188
- `Swap API error ${res.status}: ${text}`,
2189
- { statusCode: res.status, responseBody: text }
2190
- );
2191
- }
2192
- const json = await res.json();
2193
- if (!json.success) {
2194
- throw new OwneyError(
2195
- "SWAP_REQUEST_FAILED",
2196
- `Swap API request failed: ${json.message ?? "unknown error"}`,
2197
- { message: json.message }
2198
- );
2199
- }
2200
- return json.data;
2201
- }
2202
- function createSwapApi(baseUrl, apiKey) {
2203
- return {
2204
- /** Source assets the user may pay with, and each chain's deposit targets. */
2205
- listTokens: () => request(baseUrl, apiKey, "/tokens"),
2206
- /**
2207
- * `walletAddress` is required even though the routing API could not infer
2208
- * it: the Fusion+ quoter binds a quote to whoever will sign the order and
2209
- * rejects the request without it.
2210
- */
2211
- quote: (params) => request(baseUrl, apiKey, "/quote", {
2212
- method: "POST",
2213
- body: {
2214
- srcChainId: params.from.chainId,
2215
- srcSymbol: params.from.symbol,
2216
- dstChainId: params.to.chainId,
2217
- dstSymbol: params.to.symbol,
2218
- amount: params.from.amount,
2219
- walletAddress: params.walletAddress
2220
- }
2221
- }),
2222
- /** Ready-to-send calldata for a same-chain swap. */
2223
- swapTx: (params) => request(baseUrl, apiKey, "/tx", {
2224
- method: "POST",
2225
- body: {
2226
- srcChainId: params.from.chainId,
2227
- srcSymbol: params.from.symbol,
2228
- dstChainId: params.to.chainId,
2229
- dstSymbol: params.to.symbol,
2230
- amount: params.from.amount,
2231
- walletAddress: params.walletAddress,
2232
- slippage: params.slippage
2233
- }
2234
- }),
2235
- /**
2236
- * Builds a Fusion+ order server-side and returns EIP-712 typed data.
2237
- *
2238
- * Only HASHES go over the wire. The preimages never leave the browser —
2239
- * see swap.secrets.
2240
- */
2241
- buildOrder: (params) => request(baseUrl, apiKey, "/order/build", {
2242
- method: "POST",
2243
- body: {
2244
- srcChainId: params.from.chainId,
2245
- srcSymbol: params.from.symbol,
2246
- dstChainId: params.to.chainId,
2247
- dstSymbol: params.to.symbol,
2248
- amount: params.from.amount,
2249
- walletAddress: params.walletAddress,
2250
- secretHashes: params.secretHashes,
2251
- ...params.receiver ? { receiver: params.receiver } : {}
2252
- }
2253
- }),
2254
- submitOrder: (body) => request(baseUrl, apiKey, "/order", { method: "POST", body }),
2255
- /**
2256
- * Only call once `readyForSecrets` reports the escrow deployed. Publishing
2257
- * earlier hands a resolver the preimage while the user's funds are locked
2258
- * and nothing has been posted on the destination chain.
2259
- */
2260
- submitSecret: (orderHash, secret) => request(baseUrl, apiKey, "/order/secret", {
2261
- method: "POST",
2262
- body: { orderHash, secret }
2263
- }),
2264
- orderStatus: (orderHash) => request(baseUrl, apiKey, `/order/${orderHash}`),
2265
- readyForSecrets: (orderHash) => request(
2266
- baseUrl,
2267
- apiKey,
2268
- `/order/${orderHash}/ready-for-secrets`
2269
- )
2270
- };
2271
- }
2272
-
2273
- // src/lib/swap/swap.rpc.ts
2274
- var import_viem2 = require("viem");
2275
- var DEFAULT_RPC_URLS = {
2276
- 1: ["https://cloudflare-eth.com", "https://ethereum-rpc.publicnode.com"],
2277
- 8453: ["https://mainnet.base.org", "https://base-rpc.publicnode.com"],
2278
- 42161: [
2279
- "https://arb1.arbitrum.io/rpc",
2280
- "https://arbitrum-one-rpc.publicnode.com"
2281
- ]
2282
- };
2283
- function swapReadTransport(chainId, overrides) {
2284
- const override = overrides?.[chainId];
2285
- if (override) return (0, import_viem2.http)(override);
2286
- const urls = DEFAULT_RPC_URLS[chainId];
2287
- if (!urls || urls.length === 0) return (0, import_viem2.http)();
2288
- return (0, import_viem2.fallback)(urls.map((url) => (0, import_viem2.http)(url)));
2289
- }
2290
- function receiptTimeoutMs(chainId) {
2291
- return chainId === 1 ? 6e5 : 18e4;
2292
- }
2293
-
2294
- // src/lib/permit2.ts
2295
- var import_viem3 = require("viem");
2296
- var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
2297
- var MAX_UINT256 = 2n ** 256n - 1n;
2298
- var ERC20_ALLOWANCE_ABI = [
2299
- {
2300
- type: "function",
2301
- name: "allowance",
2302
- stateMutability: "view",
2303
- inputs: [
2304
- { name: "owner", type: "address" },
2305
- { name: "spender", type: "address" }
2306
- ],
2307
- outputs: [{ name: "", type: "uint256" }]
2308
- },
2309
- {
2310
- type: "function",
2311
- name: "approve",
2312
- stateMutability: "nonpayable",
2313
- inputs: [
2314
- { name: "spender", type: "address" },
2315
- { name: "amount", type: "uint256" }
2316
- ],
2317
- outputs: [{ name: "", type: "bool" }]
2318
- },
2319
- {
2320
- type: "function",
2321
- name: "balanceOf",
2322
- stateMutability: "view",
2323
- inputs: [{ name: "account", type: "address" }],
2324
- outputs: [{ name: "", type: "uint256" }]
2325
- }
2326
- ];
2327
- function buildPermitTransferFromTypedData(input) {
2328
- return {
2329
- domain: {
2330
- name: "Permit2",
2331
- chainId: input.chainId,
2332
- verifyingContract: PERMIT2_ADDRESS
2333
- },
2334
- types: {
2335
- PermitTransferFrom: [
2336
- { name: "permitted", type: "TokenPermissions" },
2337
- { name: "spender", type: "address" },
2338
- { name: "nonce", type: "uint256" },
2339
- { name: "deadline", type: "uint256" }
2340
- ],
2341
- TokenPermissions: [
2342
- { name: "token", type: "address" },
2343
- { name: "amount", type: "uint256" }
2344
- ]
2345
- },
2346
- primaryType: "PermitTransferFrom",
2347
- message: input.message
2348
- };
2349
- }
2350
- function randomPermit2Nonce() {
2351
- const bytes = new Uint8Array(32);
2352
- globalThis.crypto.getRandomValues(bytes);
2353
- return BigInt((0, import_viem3.bytesToHex)(bytes));
2354
- }
2355
- async function readPermit2Allowance(publicClient, token, owner) {
2356
- return publicClient.readContract({
2357
- address: token,
2358
- abi: ERC20_ALLOWANCE_ABI,
2359
- functionName: "allowance",
2360
- args: [owner, PERMIT2_ADDRESS]
2361
- });
2362
- }
2363
- async function readErc20Balance(publicClient, token, owner) {
2364
- return publicClient.readContract({
2365
- address: token,
2366
- abi: ERC20_ALLOWANCE_ABI,
2367
- functionName: "balanceOf",
2368
- args: [owner]
2369
- });
2370
- }
2371
-
2372
- // src/lib/swap/swap.secrets.ts
2373
- var import_viem4 = require("viem");
2374
- var SECRET_BYTES = 32;
2375
- function randomBytes(length) {
2376
- const bytes = new Uint8Array(length);
2377
- const cryptoObj = typeof globalThis !== "undefined" ? globalThis.crypto : void 0;
2378
- if (!cryptoObj?.getRandomValues) {
2379
- throw new Error(
2380
- "[owney-sdk] Secure randomness is unavailable, so a swap secret cannot be generated safely."
2381
- );
2382
- }
2383
- cryptoObj.getRandomValues(bytes);
2384
- return bytes;
2385
- }
2386
- function mintSecrets(count) {
2387
- if (!Number.isInteger(count) || count < 1) {
2388
- throw new Error(
2389
- `[owney-sdk] A swap needs at least one secret, got ${String(count)}.`
2390
- );
2391
- }
2392
- const secrets = [];
2393
- const secretHashes = [];
2394
- for (let i = 0; i < count; i++) {
2395
- const secret = (0, import_viem4.toHex)(randomBytes(SECRET_BYTES));
2396
- secrets.push(secret);
2397
- secretHashes.push((0, import_viem4.keccak256)(secret));
2398
- }
2399
- return { secrets, secretHashes };
2400
- }
2401
-
2402
- // src/lib/swap/swap.types.ts
2403
- var SWAP_TERMINAL_STATUSES = [
2404
- "executed",
2405
- "expired",
2406
- "cancelled",
2407
- "refunded"
2408
- ];
2409
- var isSwapTerminal = (status) => SWAP_TERMINAL_STATUSES.includes(status);
2410
-
2411
- // src/lib/swap/swap.order-runner.ts
2412
- var DEFAULT_POLL_MS = 5e3;
2413
- var MAX_BACKOFF_MS = 3e4;
2414
- var backoffFor = (failures, base3) => Math.min(base3 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS);
2415
- var DEFAULT_TIMEOUT_MS = 15 * 60 * 1e3;
2416
- async function runFusionOrder(deps, options) {
2417
- const {
2418
- orderHash,
2419
- secrets,
2420
- onStage,
2421
- pollIntervalMs = DEFAULT_POLL_MS,
2422
- timeoutMs = DEFAULT_TIMEOUT_MS
2423
- } = options;
2424
- const deadline = deps.now() + timeoutMs;
2425
- let failures = 0;
2426
- const published = /* @__PURE__ */ new Set();
2427
- onStage?.("swapping");
2428
- for (; ; ) {
2429
- if (deps.now() >= deadline) {
2430
- throw new OwneyError(
2431
- "SWAP_REQUEST_FAILED",
2432
- "Timed out waiting for the swap to settle. It may still complete \u2014 check the order status before retrying.",
2433
- { orderHash }
2434
- );
2435
- }
2436
- let ready;
2437
- try {
2438
- ready = await deps.readyForSecrets(orderHash);
2439
- } catch {
2440
- ready = {};
2441
- }
2442
- for (const fill of ready.fills ?? []) {
2443
- if (published.has(fill.idx)) continue;
2444
- const secret = secrets[fill.idx];
2445
- if (secret === void 0) {
2446
- throw new OwneyError(
2447
- "SWAP_REQUEST_FAILED",
2448
- `Swap needs a secret for fill ${fill.idx} that this session does not have. The order will refund once its timelock expires.`,
2449
- { orderHash, fillIndex: fill.idx }
2450
- );
2451
- }
2452
- try {
2453
- await deps.submitSecret(orderHash, secret);
2454
- published.add(fill.idx);
2455
- } catch {
2456
- failures += 1;
2457
- }
2458
- }
2459
- let status;
2460
- try {
2461
- ({ status } = await deps.orderStatus(orderHash));
2462
- failures = 0;
2463
- } catch {
2464
- failures += 1;
2465
- await deps.sleep(backoffFor(failures, pollIntervalMs));
2466
- continue;
2467
- }
2468
- if (status === "refunding") onStage?.("refunding");
2469
- if (isSwapTerminal(status)) {
2470
- if (status === "executed") {
2471
- onStage?.("swapped");
2472
- return { status, filled: true };
2473
- }
2474
- if (status === "refunded") onStage?.("refunded");
2475
- throw new OwneyError(
2476
- status === "refunded" ? "SWAP_ORDER_REFUNDED" : status === "cancelled" ? "SWAP_ORDER_CANCELLED" : "SWAP_ORDER_EXPIRED",
2477
- status === "refunded" ? "The swap did not complete and your funds have been returned." : "The swap did not complete in time. Your funds will be returned once the timelock expires.",
2478
- { orderHash, status }
2479
- );
2480
- }
2481
- await deps.sleep(pollIntervalMs);
2482
- }
2483
- }
2484
-
2485
- // src/lib/swap/swap.secret-store.ts
2486
- var KEY_PREFIX2 = "owney.swap.order";
2487
- var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
2488
- var storage2 = () => {
2489
- if (typeof window === "undefined") return null;
2490
- try {
2491
- return window.localStorage;
2492
- } catch {
2493
- return null;
2494
- }
2495
- };
2496
- var keyFor = (orderHash) => `${KEY_PREFIX2}.${orderHash}`;
2497
- function saveOrder(order) {
2498
- const store = storage2();
2499
- if (!store) return;
2500
- try {
2501
- store.setItem(keyFor(order.orderHash), JSON.stringify(order));
2502
- } catch {
2503
- }
2504
- }
2505
- function clearOrder(orderHash) {
2506
- const store = storage2();
2507
- if (!store) return;
2508
- try {
2509
- store.removeItem(keyFor(orderHash));
2510
- } catch {
2511
- }
2512
- }
2513
- function listOrders(now = Date.now()) {
2514
- const store = storage2();
2515
- if (!store) return [];
2516
- const out = [];
2517
- try {
2518
- const keys = [];
2519
- for (let i = 0; i < store.length; i++) {
2520
- const key2 = store.key(i);
2521
- if (key2?.startsWith(`${KEY_PREFIX2}.`)) keys.push(key2);
2522
- }
2523
- for (const key2 of keys) {
2524
- const raw = store.getItem(key2);
2525
- if (!raw) continue;
2526
- try {
2527
- const parsed = JSON.parse(raw);
2528
- if (now - parsed.createdAt > MAX_AGE_MS) {
2529
- store.removeItem(key2);
2530
- continue;
2531
- }
2532
- if (Array.isArray(parsed.secrets) && parsed.secrets.length > 0) {
2533
- out.push(parsed);
2534
- }
2535
- } catch {
2536
- store.removeItem(key2);
2537
- }
2538
- }
2539
- } catch {
2540
- return out;
2541
- }
2542
- return out.sort((a, b) => b.createdAt - a.createdAt);
2543
- }
2544
-
2545
- // src/lib/swap/swap.executor.ts
2546
- var DEFAULT_SLIPPAGE = 1;
2547
- async function affordableAmount(deps, quoted) {
2548
- const balance = await deps.readSourceBalance();
2549
- if (balance >= quoted) return quoted;
2550
- debugLog("owney-sdk", "swap: trimming to the current source balance", {
2551
- quoted: quoted.toString(),
2552
- balance: balance.toString(),
2553
- short: (quoted - balance).toString()
2554
- });
2555
- return balance;
2556
- }
2557
- async function executeSwap(deps, options) {
2558
- const { quote, walletAddress, onStage } = options;
2559
- debugLog("owney-sdk", "swap: start", {
2560
- rail: quote.rail,
2561
- from: `${quote.src.amount} ${quote.src.symbol} on ${quote.src.chainId}`,
2562
- to: `${quote.dst.symbol} on ${quote.dst.chainId}`,
2563
- expected: quote.dst.amount,
2564
- floor: quote.dstAmountMin
2565
- });
2566
- const before = await deps.readTargetBalance();
2567
- debugLog("owney-sdk", "swap: target balance before", before.toString());
2568
- const result = quote.rail === "classic" ? await runClassic(deps, options) : await runFusion(deps, options, walletAddress);
2569
- const after = await deps.readTargetBalance();
2570
- const received = after - before;
2571
- debugLog("owney-sdk", "swap: target balance after", {
2572
- after: after.toString(),
2573
- received: received.toString()
2574
- });
2575
- if (received <= 0n) {
2576
- throw new OwneyError(
2577
- "SWAP_REQUEST_FAILED",
2578
- "The swap completed but no funds arrived in the wallet. Check the transaction before retrying.",
2579
- { rail: quote.rail, ...result }
2580
- );
2581
- }
2582
- return { received: received.toString(), ...result };
2583
- }
2584
- async function runClassic(deps, options) {
2585
- const {
2586
- quote,
2587
- walletAddress,
2588
- slippage = DEFAULT_SLIPPAGE,
2589
- onStage
2590
- } = options;
2591
- const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
2592
- const amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount));
2593
- onStage?.("quoting");
2594
- debugLog("owney-sdk", "swap: fetching classic calldata");
2595
- const { tx } = await deps.api.swapTx({
2596
- from: {
2597
- chainId: quote.src.chainId,
2598
- symbol: quote.src.symbol,
2599
- amount: amount.toString()
2600
- },
2601
- to: { chainId: quote.dst.chainId, symbol: quote.dst.symbol },
2602
- walletAddress,
2603
- slippage
2604
- });
2605
- const isNative = BigInt(tx.value ?? "0") > 0n;
2606
- if (!isNative) {
2607
- const needed = amount;
2608
- const current = await deps.readAllowance(tx.to);
2609
- debugLog("owney-sdk", "swap: allowance", {
2610
- spender: tx.to,
2611
- current: current.toString(),
2612
- needed: needed.toString()
2613
- });
2614
- if (current < needed) {
2615
- onStage?.("approving");
2616
- await deps.ensureChain(quote.src.chainId);
2617
- await deps.approve(tx.to, MAX_UINT256);
2618
- }
2619
- }
2620
- onStage?.("signing");
2621
- await deps.ensureChain(quote.src.chainId);
2622
- debugLog("owney-sdk", "swap: sending classic swap tx", { to: tx.to });
2623
- const txHash = await deps.sendTransaction({
2624
- to: tx.to,
2625
- data: tx.data,
2626
- value: tx.value ?? "0"
2627
- });
2628
- onStage?.("swapped");
2629
- return { txHash };
2630
- }
2631
- async function runFusion(deps, options, walletAddress) {
2632
- const { quote, onStage } = options;
2633
- const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
2634
- const amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount));
2635
- if (quote.spender && !isNativeSource) {
2636
- const needed = amount;
2637
- const current = await deps.readAllowance(quote.spender);
2638
- debugLog("owney-sdk", "swap: fusion allowance", {
2639
- spender: quote.spender,
2640
- current: current.toString(),
2641
- needed: needed.toString()
2642
- });
2643
- if (current < needed) {
2644
- onStage?.("approving");
2645
- await deps.ensureChain(quote.src.chainId);
2646
- await deps.approve(quote.spender, MAX_UINT256);
2647
- debugLog("owney-sdk", "swap: approved limit order protocol");
2648
- }
2649
- }
2650
- const { secrets, secretHashes } = mintSecrets(quote.secretsCount ?? 1);
2651
- onStage?.("quoting");
2652
- debugLog("owney-sdk", "swap: building fusion order", {
2653
- secrets: secretHashes.length
2654
- });
2655
- const built = await deps.api.buildOrder({
2656
- from: {
2657
- chainId: quote.src.chainId,
2658
- symbol: quote.src.symbol,
2659
- // The trimmed amount — the order is re-quoted at this size server-side.
2660
- amount: amount.toString()
2661
- },
2662
- to: { chainId: quote.dst.chainId, symbol: quote.dst.symbol },
2663
- walletAddress,
2664
- secretHashes
2665
- });
2666
- saveOrder({
2667
- orderHash: built.orderHash,
2668
- secrets,
2669
- srcChainId: quote.src.chainId,
2670
- srcSymbol: quote.src.symbol,
2671
- dstChainId: quote.dst.chainId,
2672
- dstSymbol: quote.dst.symbol,
2673
- amount: amount.toString(),
2674
- createdAt: Date.now()
2675
- });
2676
- debugLog("owney-sdk", "swap: order built", { orderHash: built.orderHash });
2677
- onStage?.("signing");
2678
- await deps.ensureChain(quote.src.chainId);
2679
- debugLog("owney-sdk", "swap: awaiting signature in wallet", {
2680
- signingOnChain: quote.src.chainId
2681
- });
2682
- const signature = await deps.signTypedData(built.typedData);
2683
- debugLog("owney-sdk", "swap: signed, submitting to relayer");
2684
- await deps.api.submitOrder({
2685
- srcChainId: quote.src.chainId,
2686
- // The ORDER STRUCT, not the typed-data envelope we just signed. Sending
2687
- // the envelope here gets a bare 500 from the relayer.
2688
- order: built.order,
2689
- signature,
2690
- quoteId: built.quoteId,
2691
- // Single-fill orders must NOT carry secretHashes — the relayer rejects
2692
- // them with SECRET_HASHES_NOT_REQUIRED. The one hash is already inside the
2693
- // order's hashlock, so repeating it here is redundant, and only a
2694
- // multi-fill order (a Merkle tree of hashes) needs them listed.
2695
- ...secretHashes.length > 1 ? { secretHashes } : {},
2696
- ...built.extension ? { extension: built.extension } : {}
2697
- });
2698
- debugLog("owney-sdk", "swap: order submitted, polling escrows");
2699
- try {
2700
- await runFusionOrder(deps.runner, {
2701
- orderHash: built.orderHash,
2702
- secrets,
2703
- ...onStage ? { onStage } : {}
2704
- });
2705
- } catch (error) {
2706
- if (error instanceof OwneyError && (error.code === "SWAP_ORDER_REFUNDED" || error.code === "SWAP_ORDER_CANCELLED")) {
2707
- clearOrder(built.orderHash);
2708
- }
2709
- throw error;
2710
- }
2711
- clearOrder(built.orderHash);
2712
- return { orderHash: built.orderHash };
2713
- }
2714
-
2715
2201
  // src/lib/health-report.ts
2716
2202
  var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
2717
2203
  async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL2) {
@@ -2746,7 +2232,7 @@ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
2746
2232
  }
2747
2233
 
2748
2234
  // src/lib/helpers/withdraw-helper.ts
2749
- var import_viem5 = require("viem");
2235
+ var import_viem2 = require("viem");
2750
2236
  function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
2751
2237
  const target = asset.toUpperCase();
2752
2238
  return agents.map((agent) => {
@@ -2755,7 +2241,7 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
2755
2241
  (t) => t.chainId === chainId && t.asset.toUpperCase() === target
2756
2242
  );
2757
2243
  if (!tokenBalance) return { agent, balance: 0n };
2758
- return { agent, balance: (0, import_viem5.parseUnits)(tokenBalance.amount, decimals) };
2244
+ return { agent, balance: (0, import_viem2.parseUnits)(tokenBalance.amount, decimals) };
2759
2245
  });
2760
2246
  }
2761
2247
  function planProportionalShares(balances, requested, totalAvailable) {
@@ -2902,11 +2388,11 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
2902
2388
  }
2903
2389
 
2904
2390
  // src/client.ts
2905
- var import_viem8 = require("viem");
2391
+ var import_viem6 = require("viem");
2906
2392
  var import_chains2 = require("viem/chains");
2907
2393
 
2908
2394
  // src/lib/transfer-auth.ts
2909
- var import_viem6 = require("viem");
2395
+ var import_viem3 = require("viem");
2910
2396
  var ERC20_META_ABI = [
2911
2397
  { type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
2912
2398
  { type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
@@ -2938,7 +2424,7 @@ async function readTokenMeta(publicClient, token) {
2938
2424
  function randomAuthNonce() {
2939
2425
  const bytes = new Uint8Array(32);
2940
2426
  globalThis.crypto.getRandomValues(bytes);
2941
- return (0, import_viem6.bytesToHex)(bytes);
2427
+ return (0, import_viem3.bytesToHex)(bytes);
2942
2428
  }
2943
2429
 
2944
2430
  // src/lib/sponsor-client.ts
@@ -3058,6 +2544,119 @@ async function getSponsorRelayerAddress(input) {
3058
2544
  return parsed.data.relayer;
3059
2545
  }
3060
2546
 
2547
+ // src/lib/permit2.ts
2548
+ var import_viem4 = require("viem");
2549
+ var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
2550
+ var MAX_UINT256 = 2n ** 256n - 1n;
2551
+ var ERC20_ALLOWANCE_ABI = [
2552
+ {
2553
+ type: "function",
2554
+ name: "allowance",
2555
+ stateMutability: "view",
2556
+ inputs: [
2557
+ { name: "owner", type: "address" },
2558
+ { name: "spender", type: "address" }
2559
+ ],
2560
+ outputs: [{ name: "", type: "uint256" }]
2561
+ },
2562
+ {
2563
+ type: "function",
2564
+ name: "approve",
2565
+ stateMutability: "nonpayable",
2566
+ inputs: [
2567
+ { name: "spender", type: "address" },
2568
+ { name: "amount", type: "uint256" }
2569
+ ],
2570
+ outputs: [{ name: "", type: "bool" }]
2571
+ },
2572
+ {
2573
+ type: "function",
2574
+ name: "balanceOf",
2575
+ stateMutability: "view",
2576
+ inputs: [{ name: "account", type: "address" }],
2577
+ outputs: [{ name: "", type: "uint256" }]
2578
+ }
2579
+ ];
2580
+ function buildPermitTransferFromTypedData(input) {
2581
+ return {
2582
+ domain: {
2583
+ name: "Permit2",
2584
+ chainId: input.chainId,
2585
+ verifyingContract: PERMIT2_ADDRESS
2586
+ },
2587
+ types: {
2588
+ PermitTransferFrom: [
2589
+ { name: "permitted", type: "TokenPermissions" },
2590
+ { name: "spender", type: "address" },
2591
+ { name: "nonce", type: "uint256" },
2592
+ { name: "deadline", type: "uint256" }
2593
+ ],
2594
+ TokenPermissions: [
2595
+ { name: "token", type: "address" },
2596
+ { name: "amount", type: "uint256" }
2597
+ ]
2598
+ },
2599
+ primaryType: "PermitTransferFrom",
2600
+ message: input.message
2601
+ };
2602
+ }
2603
+ function randomPermit2Nonce() {
2604
+ const bytes = new Uint8Array(32);
2605
+ globalThis.crypto.getRandomValues(bytes);
2606
+ return BigInt((0, import_viem4.bytesToHex)(bytes));
2607
+ }
2608
+ async function readPermit2Allowance(publicClient, token, owner) {
2609
+ return publicClient.readContract({
2610
+ address: token,
2611
+ abi: ERC20_ALLOWANCE_ABI,
2612
+ functionName: "allowance",
2613
+ args: [owner, PERMIT2_ADDRESS]
2614
+ });
2615
+ }
2616
+ async function readErc20Balance(publicClient, token, owner) {
2617
+ return publicClient.readContract({
2618
+ address: token,
2619
+ abi: ERC20_ALLOWANCE_ABI,
2620
+ functionName: "balanceOf",
2621
+ args: [owner]
2622
+ });
2623
+ }
2624
+
2625
+ // src/lib/chain-guard.ts
2626
+ var CHAIN_NAMES = {
2627
+ 1: "Ethereum",
2628
+ 8453: "Base",
2629
+ 42161: "Arbitrum"
2630
+ };
2631
+ function chainName(chainId) {
2632
+ return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
2633
+ }
2634
+ async function ensureWalletOnChain(pub, wallet, expected) {
2635
+ const actual = await pub.getChainId();
2636
+ if (actual === expected) return;
2637
+ try {
2638
+ await wallet.switchChain({ id: expected });
2639
+ } catch (error) {
2640
+ throw new OwneyError(
2641
+ "CHAIN_MISMATCH",
2642
+ `Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
2643
+ {
2644
+ expectedChainId: expected,
2645
+ actualChainId: actual,
2646
+ cause: error instanceof Error ? error.message : String(error)
2647
+ }
2648
+ );
2649
+ }
2650
+ const after = await pub.getChainId();
2651
+ if (after !== expected) {
2652
+ throw new OwneyError(
2653
+ "CHAIN_MISMATCH",
2654
+ `Your wallet is still on ${chainName(after)}. Switch it to ${chainName(expected)} and try again.`,
2655
+ { expectedChainId: expected, actualChainId: after }
2656
+ );
2657
+ }
2658
+ }
2659
+
3061
2660
  // src/lib/sponsored-deposit.ts
3062
2661
  var AUTH_WINDOW_SECONDS = 15 * 60;
3063
2662
  function makeSponsoredDepositCallback(deps) {
@@ -3220,7 +2819,7 @@ function makeSponsoredWethCallback(deps) {
3220
2819
  }
3221
2820
 
3222
2821
  // src/lib/sponsored-calls-deposit.ts
3223
- var import_viem7 = require("viem");
2822
+ var import_viem5 = require("viem");
3224
2823
  var DEFAULT_POLL_INTERVAL_MS = 1500;
3225
2824
  var DEFAULT_MAX_POLLS = 30;
3226
2825
  async function paymasterSupported(provider, owner, chainId) {
@@ -3228,7 +2827,7 @@ async function paymasterSupported(provider, owner, chainId) {
3228
2827
  method: "wallet_getCapabilities",
3229
2828
  params: [owner]
3230
2829
  });
3231
- const forChain = caps?.[(0, import_viem7.toHex)(chainId)] ?? caps?.[String(chainId)];
2830
+ const forChain = caps?.[(0, import_viem5.toHex)(chainId)] ?? caps?.[String(chainId)];
3232
2831
  return Boolean(forChain?.paymasterService?.supported);
3233
2832
  }
3234
2833
  function makeSponsoredCallsCallback(deps) {
@@ -3262,8 +2861,8 @@ function makeSponsoredCallsCallback(deps) {
3262
2861
  { chainId }
3263
2862
  );
3264
2863
  }
3265
- const data = (0, import_viem7.encodeFunctionData)({
3266
- abi: import_viem7.erc20Abi,
2864
+ const data = (0, import_viem5.encodeFunctionData)({
2865
+ abi: import_viem5.erc20Abi,
3267
2866
  functionName: "transfer",
3268
2867
  args: [smartWallet, BigInt(amount)]
3269
2868
  });
@@ -3273,7 +2872,7 @@ function makeSponsoredCallsCallback(deps) {
3273
2872
  {
3274
2873
  version: "2.0.0",
3275
2874
  from: deps.ownerAddress,
3276
- chainId: (0, import_viem7.toHex)(chainId),
2875
+ chainId: (0, import_viem5.toHex)(chainId),
3277
2876
  atomicRequired: false,
3278
2877
  calls: [{ to: token, value: "0x0", data }],
3279
2878
  capabilities: {
@@ -3495,14 +3094,14 @@ var OwneySDK = class {
3495
3094
  // Casts work around viem's chain-narrowed Client vs the generic
3496
3095
  // PublicClient/WalletClient param types — structurally identical at
3497
3096
  // runtime, but the two share a name TS treats as unrelated.
3498
- getPublicClient: (cid) => (0, import_viem8.createPublicClient)({
3097
+ getPublicClient: (cid) => (0, import_viem6.createPublicClient)({
3499
3098
  chain: VIEM_CHAIN2[cid],
3500
- transport: (0, import_viem8.custom)(provider)
3099
+ transport: (0, import_viem6.custom)(provider)
3501
3100
  }),
3502
- getWalletClient: (cid) => (0, import_viem8.createWalletClient)({
3101
+ getWalletClient: (cid) => (0, import_viem6.createWalletClient)({
3503
3102
  account: owner,
3504
3103
  chain: VIEM_CHAIN2[cid],
3505
- transport: (0, import_viem8.custom)(provider)
3104
+ transport: (0, import_viem6.custom)(provider)
3506
3105
  })
3507
3106
  });
3508
3107
  if (!onApproved) this.cachedSponsoredCallback = callback;
@@ -3548,14 +3147,14 @@ var OwneySDK = class {
3548
3147
  // Casts work around viem's chain-narrowed Client vs the generic
3549
3148
  // PublicClient/WalletClient param types — structurally identical at
3550
3149
  // runtime, but the two share a name TS treats as unrelated.
3551
- getPublicClient: (cid) => (0, import_viem8.createPublicClient)({
3150
+ getPublicClient: (cid) => (0, import_viem6.createPublicClient)({
3552
3151
  chain: VIEM_CHAIN2[cid],
3553
- transport: (0, import_viem8.custom)(provider)
3152
+ transport: (0, import_viem6.custom)(provider)
3554
3153
  }),
3555
- getWalletClient: (cid) => (0, import_viem8.createWalletClient)({
3154
+ getWalletClient: (cid) => (0, import_viem6.createWalletClient)({
3556
3155
  account: owner,
3557
3156
  chain: VIEM_CHAIN2[cid],
3558
- transport: (0, import_viem8.custom)(provider)
3157
+ transport: (0, import_viem6.custom)(provider)
3559
3158
  })
3560
3159
  });
3561
3160
  if (!onApproved) this.cachedWethSponsoredCallback = callback;
@@ -4110,211 +3709,6 @@ var OwneySDK = class {
4110
3709
  return eligible;
4111
3710
  }
4112
3711
  // --- Fund operations ---
4113
- // --- Swap to yield (ROUT-242) ---
4114
- /** Lazily built so an app that never swaps pays nothing for it. */
4115
- swapApiClient;
4116
- swapApi() {
4117
- this.swapApiClient ??= createSwapApi(
4118
- this.routingApiBaseUrl ?? ROUTING_API_BASE_URL,
4119
- this.apiKey
4120
- );
4121
- return this.swapApiClient;
4122
- }
4123
- /**
4124
- * Put the wallet on `chainId`, or fail with something actionable.
4125
- *
4126
- * Reuses the same guard the deposit rail uses, which re-reads the chain after
4127
- * switching — some wallets resolve wallet_switchEthereumChain before the
4128
- * network has actually changed.
4129
- */
4130
- async ensureSwapChain(chainId) {
4131
- const provider = this.requireConnectedProvider();
4132
- const state = this.requireState();
4133
- const chain = VIEM_CHAIN2[chainId];
4134
- if (!chain) {
4135
- throw new OwneyError(
4136
- "CHAIN_UNSUPPORTED",
4137
- `Chain ${chainId} is not supported`,
4138
- { chainId }
4139
- );
4140
- }
4141
- await ensureWalletOnChain(
4142
- (0, import_viem8.createPublicClient)({ chain, transport: (0, import_viem8.custom)(provider) }),
4143
- (0, import_viem8.createWalletClient)({
4144
- account: state.walletAddress,
4145
- chain,
4146
- transport: (0, import_viem8.custom)(provider)
4147
- }),
4148
- chainId
4149
- );
4150
- }
4151
- /**
4152
- * Binds the executor's abstract deps to this client's wallet.
4153
- *
4154
- * Kept as a builder rather than baked into the executor so the whole swap
4155
- * flow stays testable without a provider — the executor never imports viem.
4156
- */
4157
- buildSwapDeps(quote) {
4158
- const state = this.requireState();
4159
- const provider = this.requireConnectedProvider();
4160
- const srcChain = VIEM_CHAIN2[quote.src.chainId];
4161
- const dstChain = VIEM_CHAIN2[quote.dst.chainId];
4162
- const wallet = (0, import_viem8.createWalletClient)({
4163
- account: state.walletAddress,
4164
- chain: srcChain,
4165
- transport: (0, import_viem8.custom)(provider)
4166
- });
4167
- const srcPublic = (0, import_viem8.createPublicClient)({
4168
- chain: srcChain,
4169
- transport: swapReadTransport(quote.src.chainId, this.zyfaiRpcUrls)
4170
- });
4171
- const dstPublic = (0, import_viem8.createPublicClient)({
4172
- chain: dstChain,
4173
- transport: swapReadTransport(quote.dst.chainId, this.zyfaiRpcUrls)
4174
- });
4175
- return {
4176
- api: this.swapApi(),
4177
- readTargetBalance: () => dstPublic.readContract({
4178
- address: quote.dst.address,
4179
- abi: import_viem8.erc20Abi,
4180
- functionName: "balanceOf",
4181
- args: [state.walletAddress]
4182
- }),
4183
- sendTransaction: async (tx) => {
4184
- const hash = await wallet.sendTransaction({
4185
- to: tx.to,
4186
- data: tx.data,
4187
- value: BigInt(tx.value || "0"),
4188
- account: state.walletAddress,
4189
- chain: srcChain
4190
- });
4191
- const receipt = await srcPublic.waitForTransactionReceipt({
4192
- timeout: receiptTimeoutMs(quote.src.chainId),
4193
- hash,
4194
- confirmations: 1
4195
- });
4196
- if (receipt.status !== "success") {
4197
- throw new OwneyError(
4198
- "SWAP_REQUEST_FAILED",
4199
- `Swap transaction reverted (tx ${hash})`,
4200
- { hash }
4201
- );
4202
- }
4203
- return hash;
4204
- },
4205
- signTypedData: (typedData) => wallet.signTypedData({
4206
- account: state.walletAddress,
4207
- ...typedData
4208
- }),
4209
- // Chain-bound like every other read here: the wallet provider's chain is
4210
- // not ours to rely on mid-swap.
4211
- readSourceBalance: async () => {
4212
- const src = quote.src.address;
4213
- if (src.toLowerCase().startsWith("0xeeee")) {
4214
- return srcPublic.getBalance({ address: state.walletAddress });
4215
- }
4216
- return srcPublic.readContract({
4217
- address: src,
4218
- abi: ERC20_ALLOWANCE_ABI,
4219
- functionName: "balanceOf",
4220
- args: [state.walletAddress]
4221
- });
4222
- },
4223
- readAllowance: (spender) => srcPublic.readContract({
4224
- address: quote.src.address,
4225
- abi: ERC20_ALLOWANCE_ABI,
4226
- functionName: "allowance",
4227
- args: [state.walletAddress, spender]
4228
- }),
4229
- approve: async (spender, amount) => {
4230
- const hash = await wallet.writeContract({
4231
- address: quote.src.address,
4232
- abi: ERC20_ALLOWANCE_ABI,
4233
- functionName: "approve",
4234
- args: [spender, amount],
4235
- account: state.walletAddress,
4236
- chain: srcChain
4237
- });
4238
- await srcPublic.waitForTransactionReceipt({
4239
- hash,
4240
- confirmations: 1,
4241
- timeout: receiptTimeoutMs(quote.src.chainId)
4242
- });
4243
- return hash;
4244
- },
4245
- ensureChain: (chainId) => this.ensureSwapChain(chainId),
4246
- runner: {
4247
- readyForSecrets: (h) => this.swapApi().readyForSecrets(h),
4248
- submitSecret: (h, secret) => this.swapApi().submitSecret(h, secret),
4249
- orderStatus: (h) => this.swapApi().orderStatus(h),
4250
- sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
4251
- now: () => Date.now()
4252
- }
4253
- };
4254
- }
4255
- /**
4256
- * Assets the user may pay with, and what each chain deposits into.
4257
- *
4258
- * The source list is deliberately wider than the deposit list: it includes
4259
- * native ETH and USDT, which Owney never holds but users often do.
4260
- */
4261
- async getSwapTokens() {
4262
- return this.swapApi().listTokens();
4263
- }
4264
- /**
4265
- * Price a swap without committing to it.
4266
- *
4267
- * `dstAmountMin` is the number to validate against a deposit minimum —
4268
- * `dst.amount` is an estimate that a decaying auction or slippage can undercut,
4269
- * and a swap landing below the floor leaves the user swapped but not
4270
- * deposited.
4271
- */
4272
- async getSwapQuote(params) {
4273
- const state = this.requireState();
4274
- return this.swapApi().quote({ ...params, walletAddress: state.walletAddress });
4275
- }
4276
- /**
4277
- * Swap an asset the user holds into a deposit asset, then deposit it.
4278
- *
4279
- * Kept separate from `deposit()` rather than bolted on as an option: the
4280
- * return shape differs, the staging callback is meaningless on the plain
4281
- * path, and integrators who never swap should not have to reason about any
4282
- * of it.
4283
- *
4284
- * The deposit runs on the MEASURED arrival, not the quote. A quote is an
4285
- * estimate, so depositing the quoted figure would either strand dust or try
4286
- * to move funds that never came.
4287
- *
4288
- * Failure modes differ in a way callers must respect. A same-chain swap is
4289
- * atomic — if it fails, nothing moved. A cross-chain swap escrows the user's
4290
- * funds first, so SWAP_ORDER_EXPIRED / REFUNDED / CANCELLED all mean the
4291
- * money left the wallet. Only the former can honestly say "nothing has left
4292
- * your wallet".
4293
- */
4294
- async swapAndDeposit(options) {
4295
- const state = this.requireState();
4296
- const api = this.swapApi();
4297
- const quote = await api.quote({
4298
- from: options.from,
4299
- to: options.to,
4300
- walletAddress: state.walletAddress
4301
- });
4302
- await this.ensureSwapChain(quote.src.chainId);
4303
- const swap = await executeSwap(this.buildSwapDeps(quote), {
4304
- quote,
4305
- walletAddress: state.walletAddress,
4306
- ...options.slippage === void 0 ? {} : { slippage: options.slippage },
4307
- ...options.onSwapProgress ? { onStage: options.onSwapProgress } : {}
4308
- });
4309
- options.onSwapProgress?.("depositing");
4310
- await this.ensureSwapChain(quote.dst.chainId);
4311
- const deposit = await this.deposit({
4312
- amount: swap.received,
4313
- asset: options.to.symbol,
4314
- ...options.agentId ? { agentId: options.agentId } : {}
4315
- });
4316
- return { swap, deposit };
4317
- }
4318
3712
  /**
4319
3713
  * Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
4320
3714
  * Validates that the asset is supported by the target agent(s) on the active chain.
@@ -4595,6 +3989,72 @@ var OwneySDK = class {
4595
3989
  * @param options.days - Lookback period: "7D", "14D", or "30D"
4596
3990
  * @returns {AccountAgentApy} for a single agent, or {OwneyAccountApy} with totalApy and per-agent breakdown
4597
3991
  */
3992
+ /**
3993
+ * Daily cumulative NET earnings for the selected chain/asset, backing the
3994
+ * "recent earnings" subline. Net is computed as Zyfai's own
3995
+ * `lifetime + unrealized + current x 0.9`, so the figure reconciles with the
3996
+ * balance headline rather than reading ~11% high. (ROUT-452)
3997
+ *
3998
+ * Unlike getAccountApy this does NOT blend across agents: earnings are
3999
+ * summed, not weighted, and an agent that fails to report must not silently
4000
+ * subtract from the total. Without an agentId the series is the sum of the
4001
+ * agents that answered.
4002
+ */
4003
+ async getDailyEarnings({
4004
+ agentId,
4005
+ days,
4006
+ tokenSymbol
4007
+ }) {
4008
+ const state = this.requireState();
4009
+ const chainId = this.requireChainId();
4010
+ if (agentId) {
4011
+ const agent = this.getAgent(agentId);
4012
+ if (!agent.getDailyEarnings) {
4013
+ return {
4014
+ walletAddress: state.walletAddress ?? "",
4015
+ chainId,
4016
+ assets: []
4017
+ };
4018
+ }
4019
+ return this.readAgent(
4020
+ agent,
4021
+ "dailyEarnings",
4022
+ () => agent.getDailyEarnings(state, chainId, days, tokenSymbol),
4023
+ { days, tokenSymbol }
4024
+ );
4025
+ }
4026
+ const entries = [...this.getActiveAgents().entries()].filter(
4027
+ ([, agent]) => agent.getDailyEarnings
4028
+ );
4029
+ const series = await Promise.all(
4030
+ entries.map(
4031
+ ([, agent]) => this.readAgent(
4032
+ agent,
4033
+ "dailyEarnings",
4034
+ () => agent.getDailyEarnings(state, chainId, days, tokenSymbol),
4035
+ { days, tokenSymbol }
4036
+ )
4037
+ )
4038
+ );
4039
+ const byAsset = /* @__PURE__ */ new Map();
4040
+ for (const s of series) {
4041
+ for (const { asset, points } of s.assets) {
4042
+ const byDate = byAsset.get(asset) ?? /* @__PURE__ */ new Map();
4043
+ for (const point of points) {
4044
+ byDate.set(point.date, (byDate.get(point.date) ?? 0) + point.amount);
4045
+ }
4046
+ byAsset.set(asset, byDate);
4047
+ }
4048
+ }
4049
+ return {
4050
+ walletAddress: series[0]?.walletAddress ?? state.walletAddress ?? "",
4051
+ chainId,
4052
+ assets: [...byAsset.entries()].map(([asset, byDate]) => ({
4053
+ asset,
4054
+ points: [...byDate.entries()].map(([date, amount]) => ({ date, amount })).sort((a, b) => a.date.localeCompare(b.date))
4055
+ })).sort((a, b) => a.asset.localeCompare(b.asset))
4056
+ };
4057
+ }
4598
4058
  async getAccountApy({
4599
4059
  agentId,
4600
4060
  days,
@@ -4809,10 +4269,10 @@ var OwneySDK = class {
4809
4269
  );
4810
4270
  }
4811
4271
  const provider = this.requireConnectedProvider();
4812
- const wallet = (0, import_viem8.createWalletClient)({
4272
+ const wallet = (0, import_viem6.createWalletClient)({
4813
4273
  account: state.walletAddress,
4814
4274
  chain: VIEM_CHAIN2[chainId],
4815
- transport: (0, import_viem8.custom)(provider)
4275
+ transport: (0, import_viem6.custom)(provider)
4816
4276
  });
4817
4277
  const hash = await wallet.writeContract({
4818
4278
  address: token,
@@ -4822,9 +4282,9 @@ var OwneySDK = class {
4822
4282
  account: state.walletAddress,
4823
4283
  chain: VIEM_CHAIN2[chainId]
4824
4284
  });
4825
- const publicClient = (0, import_viem8.createPublicClient)({
4285
+ const publicClient = (0, import_viem6.createPublicClient)({
4826
4286
  chain: VIEM_CHAIN2[chainId],
4827
- transport: (0, import_viem8.custom)(provider)
4287
+ transport: (0, import_viem6.custom)(provider)
4828
4288
  });
4829
4289
  const receipt = await publicClient.waitForTransactionReceipt({
4830
4290
  hash,
@@ -4936,13 +4396,13 @@ var OwneySDK = class {
4936
4396
  };
4937
4397
 
4938
4398
  // src/agents/zyfai/zyfai.siwx.ts
4939
- var import_viem9 = require("viem");
4399
+ var import_viem7 = require("viem");
4940
4400
  var import_siwe = require("siwe");
4941
4401
  var import_sdk2 = require("@zyfai/sdk");
4942
4402
 
4943
4403
  // src/agents/zyfai/zyfai.siwx-cache.ts
4944
- var KEY_PREFIX3 = "owney.siwx.session";
4945
- var storage3 = () => {
4404
+ var KEY_PREFIX2 = "owney.siwx.session";
4405
+ var storage2 = () => {
4946
4406
  if (typeof window === "undefined") return null;
4947
4407
  try {
4948
4408
  return window.localStorage;
@@ -4950,8 +4410,8 @@ var storage3 = () => {
4950
4410
  return null;
4951
4411
  }
4952
4412
  };
4953
- var buildKey2 = (address) => `${KEY_PREFIX3}:${address.toLowerCase()}`;
4954
- var legacyKeyPrefix2 = (address) => `${KEY_PREFIX3}:${address.toLowerCase()}:`;
4413
+ var buildKey2 = (address) => `${KEY_PREFIX2}:${address.toLowerCase()}`;
4414
+ var legacyKeyPrefix2 = (address) => `${KEY_PREFIX2}:${address.toLowerCase()}:`;
4955
4415
  var memorySiwxSessions = /* @__PURE__ */ new Map();
4956
4416
  var readLegacySiwxSession = (store, address) => {
4957
4417
  if (!store) return null;
@@ -4983,7 +4443,7 @@ var readLegacySiwxSession = (store, address) => {
4983
4443
  var readSiwxSession = (address, chainId) => {
4984
4444
  if (typeof window === "undefined") return null;
4985
4445
  const key2 = buildKey2(address);
4986
- const store = storage3();
4446
+ const store = storage2();
4987
4447
  let raw = null;
4988
4448
  try {
4989
4449
  raw = store?.getItem(key2) ?? null;
@@ -5013,7 +4473,7 @@ var writeSiwxSession = (address, _chainId, session) => {
5013
4473
  if (typeof window === "undefined") return;
5014
4474
  const key2 = buildKey2(address);
5015
4475
  memorySiwxSessions.set(key2, session);
5016
- const store = storage3();
4476
+ const store = storage2();
5017
4477
  try {
5018
4478
  store?.setItem(key2, JSON.stringify(session));
5019
4479
  } catch {
@@ -5022,7 +4482,7 @@ var writeSiwxSession = (address, _chainId, session) => {
5022
4482
  var clearSiwxSession = (address, _chainId) => {
5023
4483
  const key2 = buildKey2(address);
5024
4484
  memorySiwxSessions.delete(key2);
5025
- const store = storage3();
4485
+ const store = storage2();
5026
4486
  try {
5027
4487
  store?.removeItem(key2);
5028
4488
  } catch {
@@ -5063,7 +4523,7 @@ function buildSIWXConfig(deps) {
5063
4523
  issuedAt,
5064
4524
  toString() {
5065
4525
  return new import_siwe.SiweMessage({
5066
- address: (0, import_viem9.getAddress)(accountAddress),
4526
+ address: (0, import_viem7.getAddress)(accountAddress),
5067
4527
  chainId: numericChainId(chainId),
5068
4528
  domain,
5069
4529
  uri,
@@ -5141,9 +4601,9 @@ function buildSIWXConfig(deps) {
5141
4601
  }
5142
4602
  function createOwneySIWX(config) {
5143
4603
  const zyfai = new import_sdk2.ZyfaiSDK({ apiKey: config.apiKey });
5144
- const http4 = zyfai.httpClient;
4604
+ const http2 = zyfai.httpClient;
5145
4605
  return buildSIWXConfig({
5146
- post: (url, data) => http4.post(url, data),
4606
+ post: (url, data) => http2.post(url, data),
5147
4607
  referralSource: config.referralSource
5148
4608
  });
5149
4609
  }
@@ -5156,6 +4616,5 @@ function createOwneySIWX(config) {
5156
4616
  OwneyError,
5157
4617
  OwneySDK,
5158
4618
  createOwneySIWX,
5159
- listPendingSwaps,
5160
4619
  setOwneyDebug
5161
4620
  });