@owney/sdk 0.7.24-beta.0 → 0.7.25-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 +1070 -164
- package/dist/index.d.cts +319 -2
- package/dist/index.d.ts +319 -2
- package/dist/index.js +1052 -146
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2165,6 +2165,653 @@ async function fetchAgentKeys(apiKey, baseUrl = ROUTING_API_BASE_URL) {
|
|
|
2165
2165
|
return json.data;
|
|
2166
2166
|
}
|
|
2167
2167
|
|
|
2168
|
+
// src/lib/chain-guard.ts
|
|
2169
|
+
var CHAIN_NAMES = {
|
|
2170
|
+
1: "Ethereum",
|
|
2171
|
+
8453: "Base",
|
|
2172
|
+
42161: "Arbitrum"
|
|
2173
|
+
};
|
|
2174
|
+
function chainName(chainId) {
|
|
2175
|
+
return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
|
|
2176
|
+
}
|
|
2177
|
+
async function ensureWalletOnChain(pub, wallet, expected) {
|
|
2178
|
+
const actual = await pub.getChainId();
|
|
2179
|
+
if (actual === expected) return;
|
|
2180
|
+
try {
|
|
2181
|
+
await wallet.switchChain({ id: expected });
|
|
2182
|
+
} catch (error) {
|
|
2183
|
+
throw new OwneyError(
|
|
2184
|
+
"CHAIN_MISMATCH",
|
|
2185
|
+
`Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
|
|
2186
|
+
{
|
|
2187
|
+
expectedChainId: expected,
|
|
2188
|
+
actualChainId: actual,
|
|
2189
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
2190
|
+
}
|
|
2191
|
+
);
|
|
2192
|
+
}
|
|
2193
|
+
const after = await pub.getChainId();
|
|
2194
|
+
if (after !== expected) {
|
|
2195
|
+
throw new OwneyError(
|
|
2196
|
+
"CHAIN_MISMATCH",
|
|
2197
|
+
`Your wallet is still on ${chainName(after)}. Switch it to ${chainName(expected)} and try again.`,
|
|
2198
|
+
{ expectedChainId: expected, actualChainId: after }
|
|
2199
|
+
);
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
// src/lib/swap/swap-api.ts
|
|
2204
|
+
async function request(baseUrl, apiKey, path, init) {
|
|
2205
|
+
const url = `${baseUrl}/api/v1/swap${path}`;
|
|
2206
|
+
const res = await fetch(url, {
|
|
2207
|
+
method: init?.method ?? "GET",
|
|
2208
|
+
headers: {
|
|
2209
|
+
"Content-Type": "application/json",
|
|
2210
|
+
"x-owney-api-key": apiKey
|
|
2211
|
+
},
|
|
2212
|
+
...init ? { body: JSON.stringify(init.body) } : {}
|
|
2213
|
+
});
|
|
2214
|
+
if (!res.ok) {
|
|
2215
|
+
const text = await res.text().catch(() => "");
|
|
2216
|
+
if (res.status === 429) {
|
|
2217
|
+
throw new OwneyError(
|
|
2218
|
+
"SWAP_RATE_LIMITED",
|
|
2219
|
+
"Swap provider is rate limiting, retry shortly",
|
|
2220
|
+
{ statusCode: res.status }
|
|
2221
|
+
);
|
|
2222
|
+
}
|
|
2223
|
+
if (res.status === 403) {
|
|
2224
|
+
throw new OwneyError(
|
|
2225
|
+
"SWAP_DISABLED",
|
|
2226
|
+
"Swap is not enabled for this organization",
|
|
2227
|
+
{ statusCode: res.status }
|
|
2228
|
+
);
|
|
2229
|
+
}
|
|
2230
|
+
throw new OwneyError(
|
|
2231
|
+
"SWAP_REQUEST_FAILED",
|
|
2232
|
+
`Swap API error ${res.status}: ${text}`,
|
|
2233
|
+
{ statusCode: res.status, responseBody: text }
|
|
2234
|
+
);
|
|
2235
|
+
}
|
|
2236
|
+
const json = await res.json();
|
|
2237
|
+
if (!json.success) {
|
|
2238
|
+
throw new OwneyError(
|
|
2239
|
+
"SWAP_REQUEST_FAILED",
|
|
2240
|
+
`Swap API request failed: ${json.message ?? "unknown error"}`,
|
|
2241
|
+
{ message: json.message }
|
|
2242
|
+
);
|
|
2243
|
+
}
|
|
2244
|
+
return json.data;
|
|
2245
|
+
}
|
|
2246
|
+
function createSwapApi(baseUrl, apiKey) {
|
|
2247
|
+
return {
|
|
2248
|
+
/** Source assets the user may pay with, and each chain's deposit targets. */
|
|
2249
|
+
listTokens: () => request(baseUrl, apiKey, "/tokens"),
|
|
2250
|
+
/**
|
|
2251
|
+
* `walletAddress` is required even though the routing API could not infer
|
|
2252
|
+
* it: the Fusion+ quoter binds a quote to whoever will sign the order and
|
|
2253
|
+
* rejects the request without it.
|
|
2254
|
+
*/
|
|
2255
|
+
quote: (params) => request(baseUrl, apiKey, "/quote", {
|
|
2256
|
+
method: "POST",
|
|
2257
|
+
body: {
|
|
2258
|
+
srcChainId: params.from.chainId,
|
|
2259
|
+
srcSymbol: params.from.symbol,
|
|
2260
|
+
dstChainId: params.to.chainId,
|
|
2261
|
+
dstSymbol: params.to.symbol,
|
|
2262
|
+
amount: params.from.amount,
|
|
2263
|
+
walletAddress: params.walletAddress,
|
|
2264
|
+
...params.direction ? { direction: params.direction } : {}
|
|
2265
|
+
}
|
|
2266
|
+
}),
|
|
2267
|
+
/** Ready-to-send calldata for a same-chain swap. */
|
|
2268
|
+
swapTx: (params) => request(baseUrl, apiKey, "/tx", {
|
|
2269
|
+
method: "POST",
|
|
2270
|
+
body: {
|
|
2271
|
+
srcChainId: params.from.chainId,
|
|
2272
|
+
srcSymbol: params.from.symbol,
|
|
2273
|
+
dstChainId: params.to.chainId,
|
|
2274
|
+
dstSymbol: params.to.symbol,
|
|
2275
|
+
amount: params.from.amount,
|
|
2276
|
+
walletAddress: params.walletAddress,
|
|
2277
|
+
slippage: params.slippage,
|
|
2278
|
+
...params.direction ? { direction: params.direction } : {}
|
|
2279
|
+
}
|
|
2280
|
+
}),
|
|
2281
|
+
/**
|
|
2282
|
+
* Builds a Fusion+ order server-side and returns EIP-712 typed data.
|
|
2283
|
+
*
|
|
2284
|
+
* Only HASHES go over the wire. The preimages never leave the browser —
|
|
2285
|
+
* see swap.secrets.
|
|
2286
|
+
*/
|
|
2287
|
+
buildOrder: (params) => request(baseUrl, apiKey, "/order/build", {
|
|
2288
|
+
method: "POST",
|
|
2289
|
+
body: {
|
|
2290
|
+
srcChainId: params.from.chainId,
|
|
2291
|
+
srcSymbol: params.from.symbol,
|
|
2292
|
+
dstChainId: params.to.chainId,
|
|
2293
|
+
dstSymbol: params.to.symbol,
|
|
2294
|
+
amount: params.from.amount,
|
|
2295
|
+
walletAddress: params.walletAddress,
|
|
2296
|
+
secretHashes: params.secretHashes,
|
|
2297
|
+
...params.direction ? { direction: params.direction } : {},
|
|
2298
|
+
...params.receiver ? { receiver: params.receiver } : {}
|
|
2299
|
+
}
|
|
2300
|
+
}),
|
|
2301
|
+
submitOrder: (body) => request(baseUrl, apiKey, "/order", { method: "POST", body }),
|
|
2302
|
+
/**
|
|
2303
|
+
* Only call once `readyForSecrets` reports the escrow deployed. Publishing
|
|
2304
|
+
* earlier hands a resolver the preimage while the user's funds are locked
|
|
2305
|
+
* and nothing has been posted on the destination chain.
|
|
2306
|
+
*/
|
|
2307
|
+
submitSecret: (orderHash, secret) => request(baseUrl, apiKey, "/order/secret", {
|
|
2308
|
+
method: "POST",
|
|
2309
|
+
body: { orderHash, secret }
|
|
2310
|
+
}),
|
|
2311
|
+
orderStatus: (orderHash) => request(baseUrl, apiKey, `/order/${orderHash}`),
|
|
2312
|
+
readyForSecrets: (orderHash) => request(
|
|
2313
|
+
baseUrl,
|
|
2314
|
+
apiKey,
|
|
2315
|
+
`/order/${orderHash}/ready-for-secrets`
|
|
2316
|
+
)
|
|
2317
|
+
};
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
// src/lib/swap/swap.rpc.ts
|
|
2321
|
+
import { fallback, http as http2 } from "viem";
|
|
2322
|
+
var DEFAULT_RPC_URLS = {
|
|
2323
|
+
1: ["https://cloudflare-eth.com", "https://ethereum-rpc.publicnode.com"],
|
|
2324
|
+
8453: ["https://mainnet.base.org", "https://base-rpc.publicnode.com"],
|
|
2325
|
+
42161: [
|
|
2326
|
+
"https://arb1.arbitrum.io/rpc",
|
|
2327
|
+
"https://arbitrum-one-rpc.publicnode.com"
|
|
2328
|
+
]
|
|
2329
|
+
};
|
|
2330
|
+
function swapReadTransport(chainId, overrides) {
|
|
2331
|
+
const override = overrides?.[chainId];
|
|
2332
|
+
if (override) return http2(override);
|
|
2333
|
+
const urls = DEFAULT_RPC_URLS[chainId];
|
|
2334
|
+
if (!urls || urls.length === 0) return http2();
|
|
2335
|
+
return fallback(urls.map((url) => http2(url)));
|
|
2336
|
+
}
|
|
2337
|
+
function receiptTimeoutMs(chainId) {
|
|
2338
|
+
return chainId === 1 ? 6e5 : 18e4;
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
// src/lib/permit2.ts
|
|
2342
|
+
import { bytesToHex } from "viem";
|
|
2343
|
+
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
2344
|
+
var MAX_UINT256 = 2n ** 256n - 1n;
|
|
2345
|
+
var ERC20_ALLOWANCE_ABI = [
|
|
2346
|
+
{
|
|
2347
|
+
type: "function",
|
|
2348
|
+
name: "allowance",
|
|
2349
|
+
stateMutability: "view",
|
|
2350
|
+
inputs: [
|
|
2351
|
+
{ name: "owner", type: "address" },
|
|
2352
|
+
{ name: "spender", type: "address" }
|
|
2353
|
+
],
|
|
2354
|
+
outputs: [{ name: "", type: "uint256" }]
|
|
2355
|
+
},
|
|
2356
|
+
{
|
|
2357
|
+
type: "function",
|
|
2358
|
+
name: "approve",
|
|
2359
|
+
stateMutability: "nonpayable",
|
|
2360
|
+
inputs: [
|
|
2361
|
+
{ name: "spender", type: "address" },
|
|
2362
|
+
{ name: "amount", type: "uint256" }
|
|
2363
|
+
],
|
|
2364
|
+
outputs: [{ name: "", type: "bool" }]
|
|
2365
|
+
},
|
|
2366
|
+
{
|
|
2367
|
+
type: "function",
|
|
2368
|
+
name: "balanceOf",
|
|
2369
|
+
stateMutability: "view",
|
|
2370
|
+
inputs: [{ name: "account", type: "address" }],
|
|
2371
|
+
outputs: [{ name: "", type: "uint256" }]
|
|
2372
|
+
}
|
|
2373
|
+
];
|
|
2374
|
+
function buildPermitTransferFromTypedData(input) {
|
|
2375
|
+
return {
|
|
2376
|
+
domain: {
|
|
2377
|
+
name: "Permit2",
|
|
2378
|
+
chainId: input.chainId,
|
|
2379
|
+
verifyingContract: PERMIT2_ADDRESS
|
|
2380
|
+
},
|
|
2381
|
+
types: {
|
|
2382
|
+
PermitTransferFrom: [
|
|
2383
|
+
{ name: "permitted", type: "TokenPermissions" },
|
|
2384
|
+
{ name: "spender", type: "address" },
|
|
2385
|
+
{ name: "nonce", type: "uint256" },
|
|
2386
|
+
{ name: "deadline", type: "uint256" }
|
|
2387
|
+
],
|
|
2388
|
+
TokenPermissions: [
|
|
2389
|
+
{ name: "token", type: "address" },
|
|
2390
|
+
{ name: "amount", type: "uint256" }
|
|
2391
|
+
]
|
|
2392
|
+
},
|
|
2393
|
+
primaryType: "PermitTransferFrom",
|
|
2394
|
+
message: input.message
|
|
2395
|
+
};
|
|
2396
|
+
}
|
|
2397
|
+
function randomPermit2Nonce() {
|
|
2398
|
+
const bytes = new Uint8Array(32);
|
|
2399
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
2400
|
+
return BigInt(bytesToHex(bytes));
|
|
2401
|
+
}
|
|
2402
|
+
async function readPermit2Allowance(publicClient, token, owner) {
|
|
2403
|
+
return publicClient.readContract({
|
|
2404
|
+
address: token,
|
|
2405
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
2406
|
+
functionName: "allowance",
|
|
2407
|
+
args: [owner, PERMIT2_ADDRESS]
|
|
2408
|
+
});
|
|
2409
|
+
}
|
|
2410
|
+
async function readErc20Balance(publicClient, token, owner) {
|
|
2411
|
+
return publicClient.readContract({
|
|
2412
|
+
address: token,
|
|
2413
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
2414
|
+
functionName: "balanceOf",
|
|
2415
|
+
args: [owner]
|
|
2416
|
+
});
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
// src/lib/swap/swap.secrets.ts
|
|
2420
|
+
import { keccak256, toHex } from "viem";
|
|
2421
|
+
var SECRET_BYTES = 32;
|
|
2422
|
+
function randomBytes(length) {
|
|
2423
|
+
const bytes = new Uint8Array(length);
|
|
2424
|
+
const cryptoObj = typeof globalThis !== "undefined" ? globalThis.crypto : void 0;
|
|
2425
|
+
if (!cryptoObj?.getRandomValues) {
|
|
2426
|
+
throw new Error(
|
|
2427
|
+
"[owney-sdk] Secure randomness is unavailable, so a swap secret cannot be generated safely."
|
|
2428
|
+
);
|
|
2429
|
+
}
|
|
2430
|
+
cryptoObj.getRandomValues(bytes);
|
|
2431
|
+
return bytes;
|
|
2432
|
+
}
|
|
2433
|
+
function mintSecrets(count) {
|
|
2434
|
+
if (!Number.isInteger(count) || count < 1) {
|
|
2435
|
+
throw new Error(
|
|
2436
|
+
`[owney-sdk] A swap needs at least one secret, got ${String(count)}.`
|
|
2437
|
+
);
|
|
2438
|
+
}
|
|
2439
|
+
const secrets = [];
|
|
2440
|
+
const secretHashes = [];
|
|
2441
|
+
for (let i = 0; i < count; i++) {
|
|
2442
|
+
const secret = toHex(randomBytes(SECRET_BYTES));
|
|
2443
|
+
secrets.push(secret);
|
|
2444
|
+
secretHashes.push(keccak256(secret));
|
|
2445
|
+
}
|
|
2446
|
+
return { secrets, secretHashes };
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
// src/lib/swap/swap.types.ts
|
|
2450
|
+
var SWAP_TERMINAL_STATUSES = [
|
|
2451
|
+
"executed",
|
|
2452
|
+
"expired",
|
|
2453
|
+
"cancelled",
|
|
2454
|
+
"refunded"
|
|
2455
|
+
];
|
|
2456
|
+
var isSwapTerminal = (status) => SWAP_TERMINAL_STATUSES.includes(status);
|
|
2457
|
+
|
|
2458
|
+
// src/lib/swap/swap.order-runner.ts
|
|
2459
|
+
var DEFAULT_POLL_MS = 5e3;
|
|
2460
|
+
var MAX_BACKOFF_MS = 3e4;
|
|
2461
|
+
var backoffFor = (failures, base3) => Math.min(base3 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS);
|
|
2462
|
+
var DEFAULT_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
2463
|
+
async function runFusionOrder(deps, options) {
|
|
2464
|
+
const {
|
|
2465
|
+
orderHash,
|
|
2466
|
+
secrets,
|
|
2467
|
+
onStage,
|
|
2468
|
+
pollIntervalMs = DEFAULT_POLL_MS,
|
|
2469
|
+
timeoutMs = DEFAULT_TIMEOUT_MS
|
|
2470
|
+
} = options;
|
|
2471
|
+
const deadline = deps.now() + timeoutMs;
|
|
2472
|
+
let failures = 0;
|
|
2473
|
+
const published = /* @__PURE__ */ new Set();
|
|
2474
|
+
onStage?.("swapping");
|
|
2475
|
+
for (; ; ) {
|
|
2476
|
+
if (deps.now() >= deadline) {
|
|
2477
|
+
throw new OwneyError(
|
|
2478
|
+
"SWAP_REQUEST_FAILED",
|
|
2479
|
+
"Timed out waiting for the swap to settle. It may still complete \u2014 check the order status before retrying.",
|
|
2480
|
+
{ orderHash }
|
|
2481
|
+
);
|
|
2482
|
+
}
|
|
2483
|
+
let ready;
|
|
2484
|
+
try {
|
|
2485
|
+
ready = await deps.readyForSecrets(orderHash);
|
|
2486
|
+
} catch {
|
|
2487
|
+
ready = {};
|
|
2488
|
+
}
|
|
2489
|
+
for (const fill of ready.fills ?? []) {
|
|
2490
|
+
if (published.has(fill.idx)) continue;
|
|
2491
|
+
const secret = secrets[fill.idx];
|
|
2492
|
+
if (secret === void 0) {
|
|
2493
|
+
throw new OwneyError(
|
|
2494
|
+
"SWAP_REQUEST_FAILED",
|
|
2495
|
+
`Swap needs a secret for fill ${fill.idx} that this session does not have. The order will refund once its timelock expires.`,
|
|
2496
|
+
{ orderHash, fillIndex: fill.idx }
|
|
2497
|
+
);
|
|
2498
|
+
}
|
|
2499
|
+
try {
|
|
2500
|
+
await deps.submitSecret(orderHash, secret);
|
|
2501
|
+
published.add(fill.idx);
|
|
2502
|
+
} catch {
|
|
2503
|
+
failures += 1;
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
let status;
|
|
2507
|
+
try {
|
|
2508
|
+
({ status } = await deps.orderStatus(orderHash));
|
|
2509
|
+
failures = 0;
|
|
2510
|
+
} catch {
|
|
2511
|
+
failures += 1;
|
|
2512
|
+
await deps.sleep(backoffFor(failures, pollIntervalMs));
|
|
2513
|
+
continue;
|
|
2514
|
+
}
|
|
2515
|
+
if (status === "refunding") onStage?.("refunding");
|
|
2516
|
+
if (isSwapTerminal(status)) {
|
|
2517
|
+
if (status === "executed") {
|
|
2518
|
+
onStage?.("swapped");
|
|
2519
|
+
return { status, filled: true };
|
|
2520
|
+
}
|
|
2521
|
+
if (status === "refunded") onStage?.("refunded");
|
|
2522
|
+
throw new OwneyError(
|
|
2523
|
+
status === "refunded" ? "SWAP_ORDER_REFUNDED" : status === "cancelled" ? "SWAP_ORDER_CANCELLED" : "SWAP_ORDER_EXPIRED",
|
|
2524
|
+
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.",
|
|
2525
|
+
{ orderHash, status }
|
|
2526
|
+
);
|
|
2527
|
+
}
|
|
2528
|
+
await deps.sleep(pollIntervalMs);
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
// src/lib/swap/swap.secret-store.ts
|
|
2533
|
+
var KEY_PREFIX2 = "owney.swap.order";
|
|
2534
|
+
var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
2535
|
+
var storage2 = () => {
|
|
2536
|
+
if (typeof window === "undefined") return null;
|
|
2537
|
+
try {
|
|
2538
|
+
return window.localStorage;
|
|
2539
|
+
} catch {
|
|
2540
|
+
return null;
|
|
2541
|
+
}
|
|
2542
|
+
};
|
|
2543
|
+
var keyFor = (orderHash) => `${KEY_PREFIX2}.${orderHash}`;
|
|
2544
|
+
function saveOrder(order) {
|
|
2545
|
+
const store = storage2();
|
|
2546
|
+
if (!store) return;
|
|
2547
|
+
try {
|
|
2548
|
+
store.setItem(keyFor(order.orderHash), JSON.stringify(order));
|
|
2549
|
+
} catch {
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
function clearOrder(orderHash) {
|
|
2553
|
+
const store = storage2();
|
|
2554
|
+
if (!store) return;
|
|
2555
|
+
try {
|
|
2556
|
+
store.removeItem(keyFor(orderHash));
|
|
2557
|
+
} catch {
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
function listOrders(now = Date.now()) {
|
|
2561
|
+
const store = storage2();
|
|
2562
|
+
if (!store) return [];
|
|
2563
|
+
const out = [];
|
|
2564
|
+
try {
|
|
2565
|
+
const keys = [];
|
|
2566
|
+
for (let i = 0; i < store.length; i++) {
|
|
2567
|
+
const key2 = store.key(i);
|
|
2568
|
+
if (key2?.startsWith(`${KEY_PREFIX2}.`)) keys.push(key2);
|
|
2569
|
+
}
|
|
2570
|
+
for (const key2 of keys) {
|
|
2571
|
+
const raw = store.getItem(key2);
|
|
2572
|
+
if (!raw) continue;
|
|
2573
|
+
try {
|
|
2574
|
+
const parsed = JSON.parse(raw);
|
|
2575
|
+
if (now - parsed.createdAt > MAX_AGE_MS) {
|
|
2576
|
+
store.removeItem(key2);
|
|
2577
|
+
continue;
|
|
2578
|
+
}
|
|
2579
|
+
if (Array.isArray(parsed.secrets) && parsed.secrets.length > 0) {
|
|
2580
|
+
out.push(parsed);
|
|
2581
|
+
}
|
|
2582
|
+
} catch {
|
|
2583
|
+
store.removeItem(key2);
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
} catch {
|
|
2587
|
+
return out;
|
|
2588
|
+
}
|
|
2589
|
+
return out.sort((a, b) => b.createdAt - a.createdAt);
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
// src/lib/swap/swap.executor.ts
|
|
2593
|
+
var DEFAULT_SLIPPAGE = 1;
|
|
2594
|
+
async function affordableAmount(deps, quoted) {
|
|
2595
|
+
const balance = await deps.readSourceBalance();
|
|
2596
|
+
if (balance >= quoted) return quoted;
|
|
2597
|
+
debugLog("owney-sdk", "swap: trimming to the current source balance", {
|
|
2598
|
+
quoted: quoted.toString(),
|
|
2599
|
+
balance: balance.toString(),
|
|
2600
|
+
short: (quoted - balance).toString()
|
|
2601
|
+
});
|
|
2602
|
+
return balance;
|
|
2603
|
+
}
|
|
2604
|
+
async function executeSwap(deps, options) {
|
|
2605
|
+
const { quote, walletAddress, onStage } = options;
|
|
2606
|
+
debugLog("owney-sdk", "swap: start", {
|
|
2607
|
+
rail: quote.rail,
|
|
2608
|
+
from: `${quote.src.amount} ${quote.src.symbol} on ${quote.src.chainId}`,
|
|
2609
|
+
to: `${quote.dst.symbol} on ${quote.dst.chainId}`,
|
|
2610
|
+
expected: quote.dst.amount,
|
|
2611
|
+
floor: quote.dstAmountMin
|
|
2612
|
+
});
|
|
2613
|
+
const before = await deps.readTargetBalance();
|
|
2614
|
+
debugLog("owney-sdk", "swap: target balance before", before.toString());
|
|
2615
|
+
const result = quote.rail === "classic" ? await runClassic(deps, options) : await runFusion(deps, options, walletAddress);
|
|
2616
|
+
const after = await deps.readTargetBalance();
|
|
2617
|
+
const received = after - before;
|
|
2618
|
+
debugLog("owney-sdk", "swap: target balance after", {
|
|
2619
|
+
after: after.toString(),
|
|
2620
|
+
received: received.toString()
|
|
2621
|
+
});
|
|
2622
|
+
if (received <= 0n) {
|
|
2623
|
+
throw new OwneyError(
|
|
2624
|
+
"SWAP_REQUEST_FAILED",
|
|
2625
|
+
"The swap completed but no funds arrived in the wallet. Check the transaction before retrying.",
|
|
2626
|
+
{ rail: quote.rail, ...result }
|
|
2627
|
+
);
|
|
2628
|
+
}
|
|
2629
|
+
return { received: received.toString(), ...result };
|
|
2630
|
+
}
|
|
2631
|
+
async function runClassic(deps, options) {
|
|
2632
|
+
const {
|
|
2633
|
+
quote,
|
|
2634
|
+
walletAddress,
|
|
2635
|
+
slippage = DEFAULT_SLIPPAGE,
|
|
2636
|
+
direction,
|
|
2637
|
+
onStage
|
|
2638
|
+
} = options;
|
|
2639
|
+
const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
|
|
2640
|
+
const amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount));
|
|
2641
|
+
onStage?.("quoting");
|
|
2642
|
+
debugLog("owney-sdk", "swap: fetching classic calldata");
|
|
2643
|
+
const { tx } = await deps.api.swapTx({
|
|
2644
|
+
from: {
|
|
2645
|
+
chainId: quote.src.chainId,
|
|
2646
|
+
symbol: quote.src.symbol,
|
|
2647
|
+
amount: amount.toString()
|
|
2648
|
+
},
|
|
2649
|
+
to: { chainId: quote.dst.chainId, symbol: quote.dst.symbol },
|
|
2650
|
+
walletAddress,
|
|
2651
|
+
slippage,
|
|
2652
|
+
...direction ? { direction } : {}
|
|
2653
|
+
});
|
|
2654
|
+
const isNative = BigInt(tx.value ?? "0") > 0n;
|
|
2655
|
+
if (!isNative) {
|
|
2656
|
+
const needed = amount;
|
|
2657
|
+
const current = await deps.readAllowance(tx.to);
|
|
2658
|
+
debugLog("owney-sdk", "swap: allowance", {
|
|
2659
|
+
spender: tx.to,
|
|
2660
|
+
current: current.toString(),
|
|
2661
|
+
needed: needed.toString()
|
|
2662
|
+
});
|
|
2663
|
+
if (current < needed) {
|
|
2664
|
+
onStage?.("approving");
|
|
2665
|
+
await deps.ensureChain(quote.src.chainId);
|
|
2666
|
+
await deps.approve(tx.to, MAX_UINT256);
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
onStage?.("signing");
|
|
2670
|
+
await deps.ensureChain(quote.src.chainId);
|
|
2671
|
+
debugLog("owney-sdk", "swap: sending classic swap tx", { to: tx.to });
|
|
2672
|
+
const txHash = await deps.sendTransaction({
|
|
2673
|
+
to: tx.to,
|
|
2674
|
+
data: tx.data,
|
|
2675
|
+
value: tx.value ?? "0"
|
|
2676
|
+
});
|
|
2677
|
+
onStage?.("swapped");
|
|
2678
|
+
return { txHash };
|
|
2679
|
+
}
|
|
2680
|
+
async function runFusion(deps, options, walletAddress) {
|
|
2681
|
+
const { quote, direction, onStage } = options;
|
|
2682
|
+
const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
|
|
2683
|
+
const amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount));
|
|
2684
|
+
if (quote.spender && !isNativeSource) {
|
|
2685
|
+
const needed = amount;
|
|
2686
|
+
const current = await deps.readAllowance(quote.spender);
|
|
2687
|
+
debugLog("owney-sdk", "swap: fusion allowance", {
|
|
2688
|
+
spender: quote.spender,
|
|
2689
|
+
current: current.toString(),
|
|
2690
|
+
needed: needed.toString()
|
|
2691
|
+
});
|
|
2692
|
+
if (current < needed) {
|
|
2693
|
+
onStage?.("approving");
|
|
2694
|
+
await deps.ensureChain(quote.src.chainId);
|
|
2695
|
+
await deps.approve(quote.spender, MAX_UINT256);
|
|
2696
|
+
debugLog("owney-sdk", "swap: approved limit order protocol");
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
const { secrets, secretHashes } = mintSecrets(quote.secretsCount ?? 1);
|
|
2700
|
+
onStage?.("quoting");
|
|
2701
|
+
debugLog("owney-sdk", "swap: building fusion order", {
|
|
2702
|
+
secrets: secretHashes.length
|
|
2703
|
+
});
|
|
2704
|
+
const built = await deps.api.buildOrder({
|
|
2705
|
+
from: {
|
|
2706
|
+
chainId: quote.src.chainId,
|
|
2707
|
+
symbol: quote.src.symbol,
|
|
2708
|
+
// The trimmed amount — the order is re-quoted at this size server-side.
|
|
2709
|
+
amount: amount.toString()
|
|
2710
|
+
},
|
|
2711
|
+
to: { chainId: quote.dst.chainId, symbol: quote.dst.symbol },
|
|
2712
|
+
walletAddress,
|
|
2713
|
+
secretHashes,
|
|
2714
|
+
...direction ? { direction } : {}
|
|
2715
|
+
});
|
|
2716
|
+
saveOrder({
|
|
2717
|
+
orderHash: built.orderHash,
|
|
2718
|
+
secrets,
|
|
2719
|
+
srcChainId: quote.src.chainId,
|
|
2720
|
+
srcSymbol: quote.src.symbol,
|
|
2721
|
+
dstChainId: quote.dst.chainId,
|
|
2722
|
+
dstSymbol: quote.dst.symbol,
|
|
2723
|
+
amount: amount.toString(),
|
|
2724
|
+
createdAt: Date.now()
|
|
2725
|
+
});
|
|
2726
|
+
debugLog("owney-sdk", "swap: order built", { orderHash: built.orderHash });
|
|
2727
|
+
onStage?.("signing");
|
|
2728
|
+
await deps.ensureChain(quote.src.chainId);
|
|
2729
|
+
debugLog("owney-sdk", "swap: awaiting signature in wallet", {
|
|
2730
|
+
signingOnChain: quote.src.chainId
|
|
2731
|
+
});
|
|
2732
|
+
const signature = await deps.signTypedData(built.typedData);
|
|
2733
|
+
debugLog("owney-sdk", "swap: signed, submitting to relayer");
|
|
2734
|
+
await deps.api.submitOrder({
|
|
2735
|
+
srcChainId: quote.src.chainId,
|
|
2736
|
+
// The ORDER STRUCT, not the typed-data envelope we just signed. Sending
|
|
2737
|
+
// the envelope here gets a bare 500 from the relayer.
|
|
2738
|
+
order: built.order,
|
|
2739
|
+
signature,
|
|
2740
|
+
quoteId: built.quoteId,
|
|
2741
|
+
// Single-fill orders must NOT carry secretHashes — the relayer rejects
|
|
2742
|
+
// them with SECRET_HASHES_NOT_REQUIRED. The one hash is already inside the
|
|
2743
|
+
// order's hashlock, so repeating it here is redundant, and only a
|
|
2744
|
+
// multi-fill order (a Merkle tree of hashes) needs them listed.
|
|
2745
|
+
...secretHashes.length > 1 ? { secretHashes } : {},
|
|
2746
|
+
...built.extension ? { extension: built.extension } : {}
|
|
2747
|
+
});
|
|
2748
|
+
debugLog("owney-sdk", "swap: order submitted, polling escrows");
|
|
2749
|
+
try {
|
|
2750
|
+
await runFusionOrder(deps.runner, {
|
|
2751
|
+
orderHash: built.orderHash,
|
|
2752
|
+
secrets,
|
|
2753
|
+
...onStage ? { onStage } : {}
|
|
2754
|
+
});
|
|
2755
|
+
} catch (error) {
|
|
2756
|
+
if (error instanceof OwneyError && (error.code === "SWAP_ORDER_REFUNDED" || error.code === "SWAP_ORDER_CANCELLED")) {
|
|
2757
|
+
clearOrder(built.orderHash);
|
|
2758
|
+
}
|
|
2759
|
+
throw error;
|
|
2760
|
+
}
|
|
2761
|
+
clearOrder(built.orderHash);
|
|
2762
|
+
return { orderHash: built.orderHash };
|
|
2763
|
+
}
|
|
2764
|
+
|
|
2765
|
+
// src/lib/swap/swap.arrival.ts
|
|
2766
|
+
var DEFAULT_TIMEOUT_MS2 = 18e4;
|
|
2767
|
+
var DEFAULT_POLL_MS2 = 4e3;
|
|
2768
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2769
|
+
async function awaitWithdrawalArrival(options) {
|
|
2770
|
+
const {
|
|
2771
|
+
readBalance,
|
|
2772
|
+
baseline,
|
|
2773
|
+
timeoutMs = DEFAULT_TIMEOUT_MS2,
|
|
2774
|
+
pollMs = DEFAULT_POLL_MS2
|
|
2775
|
+
} = options;
|
|
2776
|
+
const deadline = Date.now() + timeoutMs;
|
|
2777
|
+
debugLog("owney-sdk", "withdraw: waiting for funds to land", {
|
|
2778
|
+
baseline: baseline.toString(),
|
|
2779
|
+
timeoutMs
|
|
2780
|
+
});
|
|
2781
|
+
let lastError;
|
|
2782
|
+
for (; ; ) {
|
|
2783
|
+
try {
|
|
2784
|
+
const balance = await readBalance();
|
|
2785
|
+
if (balance > baseline) {
|
|
2786
|
+
const arrived = balance - baseline;
|
|
2787
|
+
debugLog("owney-sdk", "withdraw: funds landed", {
|
|
2788
|
+
arrived: arrived.toString()
|
|
2789
|
+
});
|
|
2790
|
+
return arrived;
|
|
2791
|
+
}
|
|
2792
|
+
} catch (error) {
|
|
2793
|
+
lastError = error;
|
|
2794
|
+
debugLog("owney-sdk", "withdraw: balance read failed, retrying", {
|
|
2795
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2796
|
+
});
|
|
2797
|
+
}
|
|
2798
|
+
if (Date.now() >= deadline) {
|
|
2799
|
+
throw new OwneyError(
|
|
2800
|
+
"WITHDRAW_ARRIVAL_TIMEOUT",
|
|
2801
|
+
"The withdrawal was accepted but the funds had not arrived in time to swap them. They are on their way to your wallet in the original asset.",
|
|
2802
|
+
{
|
|
2803
|
+
baseline: baseline.toString(),
|
|
2804
|
+
waitedMs: timeoutMs,
|
|
2805
|
+
...lastError ? {
|
|
2806
|
+
lastReadError: lastError instanceof Error ? lastError.message : String(lastError)
|
|
2807
|
+
} : {}
|
|
2808
|
+
}
|
|
2809
|
+
);
|
|
2810
|
+
}
|
|
2811
|
+
await sleep(pollMs);
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2168
2815
|
// src/lib/health-report.ts
|
|
2169
2816
|
var ROUTING_API_BASE_URL2 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
2170
2817
|
async function reportAgentFailure(apiKey, agentType, errorCode, baseUrl = ROUTING_API_BASE_URL2) {
|
|
@@ -2358,12 +3005,13 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
|
2358
3005
|
import {
|
|
2359
3006
|
createPublicClient as createPublicClient2,
|
|
2360
3007
|
createWalletClient,
|
|
2361
|
-
custom
|
|
3008
|
+
custom,
|
|
3009
|
+
erc20Abi as erc20Abi2
|
|
2362
3010
|
} from "viem";
|
|
2363
3011
|
import { base as base2, arbitrum as arbitrum2, mainnet as mainnet2 } from "viem/chains";
|
|
2364
3012
|
|
|
2365
3013
|
// src/lib/transfer-auth.ts
|
|
2366
|
-
import { bytesToHex } from "viem";
|
|
3014
|
+
import { bytesToHex as bytesToHex2 } from "viem";
|
|
2367
3015
|
var ERC20_META_ABI = [
|
|
2368
3016
|
{ type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
|
|
2369
3017
|
{ type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
|
|
@@ -2395,7 +3043,7 @@ async function readTokenMeta(publicClient, token) {
|
|
|
2395
3043
|
function randomAuthNonce() {
|
|
2396
3044
|
const bytes = new Uint8Array(32);
|
|
2397
3045
|
globalThis.crypto.getRandomValues(bytes);
|
|
2398
|
-
return
|
|
3046
|
+
return bytesToHex2(bytes);
|
|
2399
3047
|
}
|
|
2400
3048
|
|
|
2401
3049
|
// src/lib/sponsor-client.ts
|
|
@@ -2515,119 +3163,6 @@ async function getSponsorRelayerAddress(input) {
|
|
|
2515
3163
|
return parsed.data.relayer;
|
|
2516
3164
|
}
|
|
2517
3165
|
|
|
2518
|
-
// src/lib/permit2.ts
|
|
2519
|
-
import { bytesToHex as bytesToHex2 } from "viem";
|
|
2520
|
-
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
2521
|
-
var MAX_UINT256 = 2n ** 256n - 1n;
|
|
2522
|
-
var ERC20_ALLOWANCE_ABI = [
|
|
2523
|
-
{
|
|
2524
|
-
type: "function",
|
|
2525
|
-
name: "allowance",
|
|
2526
|
-
stateMutability: "view",
|
|
2527
|
-
inputs: [
|
|
2528
|
-
{ name: "owner", type: "address" },
|
|
2529
|
-
{ name: "spender", type: "address" }
|
|
2530
|
-
],
|
|
2531
|
-
outputs: [{ name: "", type: "uint256" }]
|
|
2532
|
-
},
|
|
2533
|
-
{
|
|
2534
|
-
type: "function",
|
|
2535
|
-
name: "approve",
|
|
2536
|
-
stateMutability: "nonpayable",
|
|
2537
|
-
inputs: [
|
|
2538
|
-
{ name: "spender", type: "address" },
|
|
2539
|
-
{ name: "amount", type: "uint256" }
|
|
2540
|
-
],
|
|
2541
|
-
outputs: [{ name: "", type: "bool" }]
|
|
2542
|
-
},
|
|
2543
|
-
{
|
|
2544
|
-
type: "function",
|
|
2545
|
-
name: "balanceOf",
|
|
2546
|
-
stateMutability: "view",
|
|
2547
|
-
inputs: [{ name: "account", type: "address" }],
|
|
2548
|
-
outputs: [{ name: "", type: "uint256" }]
|
|
2549
|
-
}
|
|
2550
|
-
];
|
|
2551
|
-
function buildPermitTransferFromTypedData(input) {
|
|
2552
|
-
return {
|
|
2553
|
-
domain: {
|
|
2554
|
-
name: "Permit2",
|
|
2555
|
-
chainId: input.chainId,
|
|
2556
|
-
verifyingContract: PERMIT2_ADDRESS
|
|
2557
|
-
},
|
|
2558
|
-
types: {
|
|
2559
|
-
PermitTransferFrom: [
|
|
2560
|
-
{ name: "permitted", type: "TokenPermissions" },
|
|
2561
|
-
{ name: "spender", type: "address" },
|
|
2562
|
-
{ name: "nonce", type: "uint256" },
|
|
2563
|
-
{ name: "deadline", type: "uint256" }
|
|
2564
|
-
],
|
|
2565
|
-
TokenPermissions: [
|
|
2566
|
-
{ name: "token", type: "address" },
|
|
2567
|
-
{ name: "amount", type: "uint256" }
|
|
2568
|
-
]
|
|
2569
|
-
},
|
|
2570
|
-
primaryType: "PermitTransferFrom",
|
|
2571
|
-
message: input.message
|
|
2572
|
-
};
|
|
2573
|
-
}
|
|
2574
|
-
function randomPermit2Nonce() {
|
|
2575
|
-
const bytes = new Uint8Array(32);
|
|
2576
|
-
globalThis.crypto.getRandomValues(bytes);
|
|
2577
|
-
return BigInt(bytesToHex2(bytes));
|
|
2578
|
-
}
|
|
2579
|
-
async function readPermit2Allowance(publicClient, token, owner) {
|
|
2580
|
-
return publicClient.readContract({
|
|
2581
|
-
address: token,
|
|
2582
|
-
abi: ERC20_ALLOWANCE_ABI,
|
|
2583
|
-
functionName: "allowance",
|
|
2584
|
-
args: [owner, PERMIT2_ADDRESS]
|
|
2585
|
-
});
|
|
2586
|
-
}
|
|
2587
|
-
async function readErc20Balance(publicClient, token, owner) {
|
|
2588
|
-
return publicClient.readContract({
|
|
2589
|
-
address: token,
|
|
2590
|
-
abi: ERC20_ALLOWANCE_ABI,
|
|
2591
|
-
functionName: "balanceOf",
|
|
2592
|
-
args: [owner]
|
|
2593
|
-
});
|
|
2594
|
-
}
|
|
2595
|
-
|
|
2596
|
-
// src/lib/chain-guard.ts
|
|
2597
|
-
var CHAIN_NAMES = {
|
|
2598
|
-
1: "Ethereum",
|
|
2599
|
-
8453: "Base",
|
|
2600
|
-
42161: "Arbitrum"
|
|
2601
|
-
};
|
|
2602
|
-
function chainName(chainId) {
|
|
2603
|
-
return CHAIN_NAMES[chainId] ?? `chain ${chainId}`;
|
|
2604
|
-
}
|
|
2605
|
-
async function ensureWalletOnChain(pub, wallet, expected) {
|
|
2606
|
-
const actual = await pub.getChainId();
|
|
2607
|
-
if (actual === expected) return;
|
|
2608
|
-
try {
|
|
2609
|
-
await wallet.switchChain({ id: expected });
|
|
2610
|
-
} catch (error) {
|
|
2611
|
-
throw new OwneyError(
|
|
2612
|
-
"CHAIN_MISMATCH",
|
|
2613
|
-
`Your wallet is on ${chainName(actual)}. Switch it to ${chainName(expected)} and try again.`,
|
|
2614
|
-
{
|
|
2615
|
-
expectedChainId: expected,
|
|
2616
|
-
actualChainId: actual,
|
|
2617
|
-
cause: error instanceof Error ? error.message : String(error)
|
|
2618
|
-
}
|
|
2619
|
-
);
|
|
2620
|
-
}
|
|
2621
|
-
const after = await pub.getChainId();
|
|
2622
|
-
if (after !== expected) {
|
|
2623
|
-
throw new OwneyError(
|
|
2624
|
-
"CHAIN_MISMATCH",
|
|
2625
|
-
`Your wallet is still on ${chainName(after)}. Switch it to ${chainName(expected)} and try again.`,
|
|
2626
|
-
{ expectedChainId: expected, actualChainId: after }
|
|
2627
|
-
);
|
|
2628
|
-
}
|
|
2629
|
-
}
|
|
2630
|
-
|
|
2631
3166
|
// src/lib/sponsored-deposit.ts
|
|
2632
3167
|
var AUTH_WINDOW_SECONDS = 15 * 60;
|
|
2633
3168
|
function makeSponsoredDepositCallback(deps) {
|
|
@@ -2790,7 +3325,7 @@ function makeSponsoredWethCallback(deps) {
|
|
|
2790
3325
|
}
|
|
2791
3326
|
|
|
2792
3327
|
// src/lib/sponsored-calls-deposit.ts
|
|
2793
|
-
import { encodeFunctionData, erc20Abi, toHex } from "viem";
|
|
3328
|
+
import { encodeFunctionData, erc20Abi, toHex as toHex2 } from "viem";
|
|
2794
3329
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
2795
3330
|
var DEFAULT_MAX_POLLS = 30;
|
|
2796
3331
|
async function paymasterSupported(provider, owner, chainId) {
|
|
@@ -2798,7 +3333,7 @@ async function paymasterSupported(provider, owner, chainId) {
|
|
|
2798
3333
|
method: "wallet_getCapabilities",
|
|
2799
3334
|
params: [owner]
|
|
2800
3335
|
});
|
|
2801
|
-
const forChain = caps?.[
|
|
3336
|
+
const forChain = caps?.[toHex2(chainId)] ?? caps?.[String(chainId)];
|
|
2802
3337
|
return Boolean(forChain?.paymasterService?.supported);
|
|
2803
3338
|
}
|
|
2804
3339
|
function makeSponsoredCallsCallback(deps) {
|
|
@@ -2843,7 +3378,7 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
2843
3378
|
{
|
|
2844
3379
|
version: "2.0.0",
|
|
2845
3380
|
from: deps.ownerAddress,
|
|
2846
|
-
chainId:
|
|
3381
|
+
chainId: toHex2(chainId),
|
|
2847
3382
|
atomicRequired: false,
|
|
2848
3383
|
calls: [{ to: token, value: "0x0", data }],
|
|
2849
3384
|
capabilities: {
|
|
@@ -3156,12 +3691,10 @@ var OwneySDK = class {
|
|
|
3156
3691
|
this.orgAgentConfigPromise = fetchOrgAgentConfig(
|
|
3157
3692
|
this.apiKey,
|
|
3158
3693
|
this.routingApiBaseUrl
|
|
3159
|
-
).then(
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
}
|
|
3164
|
-
);
|
|
3694
|
+
).then((config) => {
|
|
3695
|
+
this.orgAgentConfig = config;
|
|
3696
|
+
return config;
|
|
3697
|
+
});
|
|
3165
3698
|
}
|
|
3166
3699
|
return this.orgAgentConfigPromise;
|
|
3167
3700
|
}
|
|
@@ -3680,6 +4213,330 @@ var OwneySDK = class {
|
|
|
3680
4213
|
return eligible;
|
|
3681
4214
|
}
|
|
3682
4215
|
// --- Fund operations ---
|
|
4216
|
+
// --- Swap to yield (ROUT-242) ---
|
|
4217
|
+
/** Lazily built so an app that never swaps pays nothing for it. */
|
|
4218
|
+
swapApiClient;
|
|
4219
|
+
swapApi() {
|
|
4220
|
+
this.swapApiClient ??= createSwapApi(
|
|
4221
|
+
this.routingApiBaseUrl ?? ROUTING_API_BASE_URL,
|
|
4222
|
+
this.apiKey
|
|
4223
|
+
);
|
|
4224
|
+
return this.swapApiClient;
|
|
4225
|
+
}
|
|
4226
|
+
/**
|
|
4227
|
+
* Put the wallet on `chainId`, or fail with something actionable.
|
|
4228
|
+
*
|
|
4229
|
+
* Reuses the same guard the deposit rail uses, which re-reads the chain after
|
|
4230
|
+
* switching — some wallets resolve wallet_switchEthereumChain before the
|
|
4231
|
+
* network has actually changed.
|
|
4232
|
+
*/
|
|
4233
|
+
async ensureSwapChain(chainId) {
|
|
4234
|
+
const provider = this.requireConnectedProvider();
|
|
4235
|
+
const state = this.requireState();
|
|
4236
|
+
const chain = VIEM_CHAIN2[chainId];
|
|
4237
|
+
if (!chain) {
|
|
4238
|
+
throw new OwneyError(
|
|
4239
|
+
"CHAIN_UNSUPPORTED",
|
|
4240
|
+
`Chain ${chainId} is not supported`,
|
|
4241
|
+
{ chainId }
|
|
4242
|
+
);
|
|
4243
|
+
}
|
|
4244
|
+
await ensureWalletOnChain(
|
|
4245
|
+
createPublicClient2({ chain, transport: custom(provider) }),
|
|
4246
|
+
createWalletClient({
|
|
4247
|
+
account: state.walletAddress,
|
|
4248
|
+
chain,
|
|
4249
|
+
transport: custom(provider)
|
|
4250
|
+
}),
|
|
4251
|
+
chainId
|
|
4252
|
+
);
|
|
4253
|
+
}
|
|
4254
|
+
/**
|
|
4255
|
+
* Binds the executor's abstract deps to this client's wallet.
|
|
4256
|
+
*
|
|
4257
|
+
* Kept as a builder rather than baked into the executor so the whole swap
|
|
4258
|
+
* flow stays testable without a provider — the executor never imports viem.
|
|
4259
|
+
*/
|
|
4260
|
+
buildSwapDeps(quote) {
|
|
4261
|
+
const state = this.requireState();
|
|
4262
|
+
const provider = this.requireConnectedProvider();
|
|
4263
|
+
const srcChain = VIEM_CHAIN2[quote.src.chainId];
|
|
4264
|
+
const dstChain = VIEM_CHAIN2[quote.dst.chainId];
|
|
4265
|
+
const wallet = createWalletClient({
|
|
4266
|
+
account: state.walletAddress,
|
|
4267
|
+
chain: srcChain,
|
|
4268
|
+
transport: custom(provider)
|
|
4269
|
+
});
|
|
4270
|
+
const srcPublic = createPublicClient2({
|
|
4271
|
+
chain: srcChain,
|
|
4272
|
+
transport: swapReadTransport(quote.src.chainId, this.zyfaiRpcUrls)
|
|
4273
|
+
});
|
|
4274
|
+
const dstPublic = createPublicClient2({
|
|
4275
|
+
chain: dstChain,
|
|
4276
|
+
transport: swapReadTransport(quote.dst.chainId, this.zyfaiRpcUrls)
|
|
4277
|
+
});
|
|
4278
|
+
return {
|
|
4279
|
+
api: this.swapApi(),
|
|
4280
|
+
// Native-aware, like readSourceBalance below. Native ETH is never a
|
|
4281
|
+
// DEPOSIT target, so this only ever mattered once withdrawal shipped —
|
|
4282
|
+
// and there it is the headline case. balanceOf() on the 0xEeee sentinel
|
|
4283
|
+
// reverts, which would have read as "the swap landed nothing".
|
|
4284
|
+
readTargetBalance: async () => {
|
|
4285
|
+
const dst = quote.dst.address;
|
|
4286
|
+
if (dst.toLowerCase().startsWith("0xeeee")) {
|
|
4287
|
+
return dstPublic.getBalance({ address: state.walletAddress });
|
|
4288
|
+
}
|
|
4289
|
+
return dstPublic.readContract({
|
|
4290
|
+
address: dst,
|
|
4291
|
+
abi: erc20Abi2,
|
|
4292
|
+
functionName: "balanceOf",
|
|
4293
|
+
args: [state.walletAddress]
|
|
4294
|
+
});
|
|
4295
|
+
},
|
|
4296
|
+
sendTransaction: async (tx) => {
|
|
4297
|
+
const hash = await wallet.sendTransaction({
|
|
4298
|
+
to: tx.to,
|
|
4299
|
+
data: tx.data,
|
|
4300
|
+
value: BigInt(tx.value || "0"),
|
|
4301
|
+
account: state.walletAddress,
|
|
4302
|
+
chain: srcChain
|
|
4303
|
+
});
|
|
4304
|
+
const receipt = await srcPublic.waitForTransactionReceipt({
|
|
4305
|
+
timeout: receiptTimeoutMs(quote.src.chainId),
|
|
4306
|
+
hash,
|
|
4307
|
+
confirmations: 1
|
|
4308
|
+
});
|
|
4309
|
+
if (receipt.status !== "success") {
|
|
4310
|
+
throw new OwneyError(
|
|
4311
|
+
"SWAP_REQUEST_FAILED",
|
|
4312
|
+
`Swap transaction reverted (tx ${hash})`,
|
|
4313
|
+
{ hash }
|
|
4314
|
+
);
|
|
4315
|
+
}
|
|
4316
|
+
return hash;
|
|
4317
|
+
},
|
|
4318
|
+
signTypedData: (typedData) => wallet.signTypedData({
|
|
4319
|
+
account: state.walletAddress,
|
|
4320
|
+
...typedData
|
|
4321
|
+
}),
|
|
4322
|
+
// Chain-bound like every other read here: the wallet provider's chain is
|
|
4323
|
+
// not ours to rely on mid-swap.
|
|
4324
|
+
readSourceBalance: async () => {
|
|
4325
|
+
const src = quote.src.address;
|
|
4326
|
+
if (src.toLowerCase().startsWith("0xeeee")) {
|
|
4327
|
+
return srcPublic.getBalance({ address: state.walletAddress });
|
|
4328
|
+
}
|
|
4329
|
+
return srcPublic.readContract({
|
|
4330
|
+
address: src,
|
|
4331
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
4332
|
+
functionName: "balanceOf",
|
|
4333
|
+
args: [state.walletAddress]
|
|
4334
|
+
});
|
|
4335
|
+
},
|
|
4336
|
+
readAllowance: (spender) => srcPublic.readContract({
|
|
4337
|
+
address: quote.src.address,
|
|
4338
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
4339
|
+
functionName: "allowance",
|
|
4340
|
+
args: [state.walletAddress, spender]
|
|
4341
|
+
}),
|
|
4342
|
+
approve: async (spender, amount) => {
|
|
4343
|
+
const hash = await wallet.writeContract({
|
|
4344
|
+
address: quote.src.address,
|
|
4345
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
4346
|
+
functionName: "approve",
|
|
4347
|
+
args: [spender, amount],
|
|
4348
|
+
account: state.walletAddress,
|
|
4349
|
+
chain: srcChain
|
|
4350
|
+
});
|
|
4351
|
+
await srcPublic.waitForTransactionReceipt({
|
|
4352
|
+
hash,
|
|
4353
|
+
confirmations: 1,
|
|
4354
|
+
timeout: receiptTimeoutMs(quote.src.chainId)
|
|
4355
|
+
});
|
|
4356
|
+
return hash;
|
|
4357
|
+
},
|
|
4358
|
+
ensureChain: (chainId) => this.ensureSwapChain(chainId),
|
|
4359
|
+
runner: {
|
|
4360
|
+
readyForSecrets: (h) => this.swapApi().readyForSecrets(h),
|
|
4361
|
+
submitSecret: (h, secret) => this.swapApi().submitSecret(h, secret),
|
|
4362
|
+
orderStatus: (h) => this.swapApi().orderStatus(h),
|
|
4363
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
4364
|
+
now: () => Date.now()
|
|
4365
|
+
}
|
|
4366
|
+
};
|
|
4367
|
+
}
|
|
4368
|
+
/**
|
|
4369
|
+
* Assets the user may pay with, and what each chain deposits into.
|
|
4370
|
+
*
|
|
4371
|
+
* The source list is deliberately wider than the deposit list: it includes
|
|
4372
|
+
* native ETH and USDT, which Owney never holds but users often do.
|
|
4373
|
+
*/
|
|
4374
|
+
async getSwapTokens() {
|
|
4375
|
+
return this.swapApi().listTokens();
|
|
4376
|
+
}
|
|
4377
|
+
/**
|
|
4378
|
+
* Price a swap without committing to it.
|
|
4379
|
+
*
|
|
4380
|
+
* `dstAmountMin` is the number to validate against a deposit minimum —
|
|
4381
|
+
* `dst.amount` is an estimate that a decaying auction or slippage can undercut,
|
|
4382
|
+
* and a swap landing below the floor leaves the user swapped but not
|
|
4383
|
+
* deposited.
|
|
4384
|
+
*/
|
|
4385
|
+
async getSwapQuote(params) {
|
|
4386
|
+
const state = this.requireState();
|
|
4387
|
+
return this.swapApi().quote({
|
|
4388
|
+
...params,
|
|
4389
|
+
walletAddress: state.walletAddress
|
|
4390
|
+
});
|
|
4391
|
+
}
|
|
4392
|
+
/**
|
|
4393
|
+
* Swap an asset the user holds into a deposit asset, then deposit it.
|
|
4394
|
+
*
|
|
4395
|
+
* Kept separate from `deposit()` rather than bolted on as an option: the
|
|
4396
|
+
* return shape differs, the staging callback is meaningless on the plain
|
|
4397
|
+
* path, and integrators who never swap should not have to reason about any
|
|
4398
|
+
* of it.
|
|
4399
|
+
*
|
|
4400
|
+
* The deposit runs on the MEASURED arrival, not the quote. A quote is an
|
|
4401
|
+
* estimate, so depositing the quoted figure would either strand dust or try
|
|
4402
|
+
* to move funds that never came.
|
|
4403
|
+
*
|
|
4404
|
+
* Failure modes differ in a way callers must respect. A same-chain swap is
|
|
4405
|
+
* atomic — if it fails, nothing moved. A cross-chain swap escrows the user's
|
|
4406
|
+
* funds first, so SWAP_ORDER_EXPIRED / REFUNDED / CANCELLED all mean the
|
|
4407
|
+
* money left the wallet. Only the former can honestly say "nothing has left
|
|
4408
|
+
* your wallet".
|
|
4409
|
+
*/
|
|
4410
|
+
async swapAndDeposit(options) {
|
|
4411
|
+
const state = this.requireState();
|
|
4412
|
+
const api = this.swapApi();
|
|
4413
|
+
const quote = await api.quote({
|
|
4414
|
+
from: options.from,
|
|
4415
|
+
to: options.to,
|
|
4416
|
+
walletAddress: state.walletAddress
|
|
4417
|
+
});
|
|
4418
|
+
await this.ensureSwapChain(quote.src.chainId);
|
|
4419
|
+
const swap = await executeSwap(this.buildSwapDeps(quote), {
|
|
4420
|
+
quote,
|
|
4421
|
+
walletAddress: state.walletAddress,
|
|
4422
|
+
...options.slippage === void 0 ? {} : { slippage: options.slippage },
|
|
4423
|
+
...options.onSwapProgress ? { onStage: options.onSwapProgress } : {}
|
|
4424
|
+
});
|
|
4425
|
+
options.onSwapProgress?.("depositing");
|
|
4426
|
+
await this.ensureSwapChain(quote.dst.chainId);
|
|
4427
|
+
const deposit = await this.deposit({
|
|
4428
|
+
amount: swap.received,
|
|
4429
|
+
asset: options.to.symbol,
|
|
4430
|
+
...options.agentId ? { agentId: options.agentId } : {}
|
|
4431
|
+
});
|
|
4432
|
+
return { swap, deposit };
|
|
4433
|
+
}
|
|
4434
|
+
/**
|
|
4435
|
+
* Withdraw from an agent and swap the proceeds into whatever the user wants
|
|
4436
|
+
* to hold, delivered to their own wallet.
|
|
4437
|
+
*
|
|
4438
|
+
* The mirror of `swapAndDeposit()`, with one structural difference that
|
|
4439
|
+
* drives the whole implementation: a deposit swap starts from funds already
|
|
4440
|
+
* sitting in the wallet, but a withdrawal has to wait for them. The agent's
|
|
4441
|
+
* provider acknowledges a withdrawal and *then* queues the on-chain transfer
|
|
4442
|
+
* to the EOA, so `withdraw()` resolving means "accepted", not "arrived".
|
|
4443
|
+
* Quoting before the tokens land would size the swap against a balance that
|
|
4444
|
+
* is not there yet.
|
|
4445
|
+
*
|
|
4446
|
+
* The swap is therefore sized from the MEASURED arrival, exactly as the
|
|
4447
|
+
* deposit path sizes its deposit from the measured swap output. On a full
|
|
4448
|
+
* withdrawal there is no other number available — "MAX" has no figure until
|
|
4449
|
+
* the agent picks one.
|
|
4450
|
+
*
|
|
4451
|
+
* **Failure here is not symmetrical with the deposit path.** A failed
|
|
4452
|
+
* deposit-swap leaves the user holding what they started with. A failed
|
|
4453
|
+
* withdrawal-swap leaves them holding the AGENT'S asset in their own wallet:
|
|
4454
|
+
* the money is out, safe, and in the wrong denomination. Both
|
|
4455
|
+
* `WITHDRAW_ARRIVAL_TIMEOUT` and `WITHDRAW_SWAP_FAILED` carry `withdrawn` for
|
|
4456
|
+
* that reason — the UI has to tell the user where their money actually is,
|
|
4457
|
+
* and must never present either as a lost withdrawal.
|
|
4458
|
+
*/
|
|
4459
|
+
async withdrawAndSwap(options) {
|
|
4460
|
+
const state = this.requireState();
|
|
4461
|
+
const activeChainId = this.requireChainId();
|
|
4462
|
+
if (options.from.chainId !== activeChainId) {
|
|
4463
|
+
throw new OwneyError(
|
|
4464
|
+
"CHAIN_MISMATCH",
|
|
4465
|
+
`Cannot withdraw from chain ${options.from.chainId} while the active chain is ${activeChainId}. Activate on that chain first.`,
|
|
4466
|
+
{ requested: options.from.chainId, active: activeChainId }
|
|
4467
|
+
);
|
|
4468
|
+
}
|
|
4469
|
+
const asset = SupportedAssets.find(
|
|
4470
|
+
(a) => a.chainId === options.from.chainId && a.symbol === options.from.symbol.toUpperCase()
|
|
4471
|
+
);
|
|
4472
|
+
if (!asset) {
|
|
4473
|
+
throw new OwneyError(
|
|
4474
|
+
"WITHDRAW_NO_PERMITTED_TOKENS",
|
|
4475
|
+
`${options.from.symbol} on chain ${options.from.chainId} is not an asset Owney holds`,
|
|
4476
|
+
{ ...options.from }
|
|
4477
|
+
);
|
|
4478
|
+
}
|
|
4479
|
+
const srcChain = VIEM_CHAIN2[options.from.chainId];
|
|
4480
|
+
const srcPublic = createPublicClient2({
|
|
4481
|
+
chain: srcChain,
|
|
4482
|
+
transport: swapReadTransport(options.from.chainId, this.zyfaiRpcUrls)
|
|
4483
|
+
});
|
|
4484
|
+
const readWalletBalance = () => srcPublic.readContract({
|
|
4485
|
+
address: asset.address,
|
|
4486
|
+
abi: erc20Abi2,
|
|
4487
|
+
functionName: "balanceOf",
|
|
4488
|
+
args: [state.walletAddress]
|
|
4489
|
+
});
|
|
4490
|
+
const baseline = await readWalletBalance();
|
|
4491
|
+
debugLog("owney-sdk", "withdrawAndSwap: baseline", {
|
|
4492
|
+
asset: `${asset.symbol}@${asset.chainId}`,
|
|
4493
|
+
baseline: baseline.toString()
|
|
4494
|
+
});
|
|
4495
|
+
options.onSwapProgress?.("withdrawing");
|
|
4496
|
+
const withdraw = await this.withdraw({
|
|
4497
|
+
asset: options.from.symbol,
|
|
4498
|
+
...options.amount === void 0 ? {} : { amount: options.amount },
|
|
4499
|
+
...options.agentId ? { agentId: options.agentId } : {}
|
|
4500
|
+
});
|
|
4501
|
+
const arrived = await awaitWithdrawalArrival({
|
|
4502
|
+
readBalance: readWalletBalance,
|
|
4503
|
+
baseline,
|
|
4504
|
+
...options.arrivalTimeoutMs === void 0 ? {} : { timeoutMs: options.arrivalTimeoutMs }
|
|
4505
|
+
});
|
|
4506
|
+
const withdrawn = arrived.toString();
|
|
4507
|
+
options.onSwapProgress?.("withdrawn");
|
|
4508
|
+
try {
|
|
4509
|
+
const quote = await this.swapApi().quote({
|
|
4510
|
+
from: { ...options.from, amount: withdrawn },
|
|
4511
|
+
to: options.to,
|
|
4512
|
+
direction: "withdraw",
|
|
4513
|
+
walletAddress: state.walletAddress
|
|
4514
|
+
});
|
|
4515
|
+
await this.ensureSwapChain(quote.src.chainId);
|
|
4516
|
+
const swap = await executeSwap(this.buildSwapDeps(quote), {
|
|
4517
|
+
quote,
|
|
4518
|
+
walletAddress: state.walletAddress,
|
|
4519
|
+
direction: "withdraw",
|
|
4520
|
+
...options.slippage === void 0 ? {} : { slippage: options.slippage },
|
|
4521
|
+
...options.onSwapProgress ? { onStage: options.onSwapProgress } : {}
|
|
4522
|
+
});
|
|
4523
|
+
return { withdraw, withdrawn, swap };
|
|
4524
|
+
} catch (error) {
|
|
4525
|
+
throw new OwneyError(
|
|
4526
|
+
"WITHDRAW_SWAP_FAILED",
|
|
4527
|
+
`Withdrew ${withdrawn} ${asset.symbol} to your wallet, but the swap to ${options.to.symbol} did not complete. The funds are in your wallet as ${asset.symbol}.`,
|
|
4528
|
+
{
|
|
4529
|
+
withdrawn,
|
|
4530
|
+
asset: asset.symbol,
|
|
4531
|
+
chainId: asset.chainId,
|
|
4532
|
+
intendedSymbol: options.to.symbol,
|
|
4533
|
+
intendedChainId: options.to.chainId,
|
|
4534
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
4535
|
+
...error instanceof OwneyError ? { causeCode: error.code } : {}
|
|
4536
|
+
}
|
|
4537
|
+
);
|
|
4538
|
+
}
|
|
4539
|
+
}
|
|
3683
4540
|
/**
|
|
3684
4541
|
* Withdraw funds from a specific agent, or all agents that support the active chain+asset if agentId is omitted.
|
|
3685
4542
|
* Validates that the asset is supported by the target agent(s) on the active chain.
|
|
@@ -3850,7 +4707,11 @@ var OwneySDK = class {
|
|
|
3850
4707
|
const chainId = this.requireChainId();
|
|
3851
4708
|
if (agentId) {
|
|
3852
4709
|
const agent = this.getAgent(agentId);
|
|
3853
|
-
const result = await this.readAgent(
|
|
4710
|
+
const result = await this.readAgent(
|
|
4711
|
+
agent,
|
|
4712
|
+
"balances",
|
|
4713
|
+
() => agent.getBalances(state, chainId)
|
|
4714
|
+
);
|
|
3854
4715
|
return result;
|
|
3855
4716
|
}
|
|
3856
4717
|
let totalBalance = 0;
|
|
@@ -3858,7 +4719,11 @@ var OwneySDK = class {
|
|
|
3858
4719
|
const entries = [...this.getActiveAgents().entries()];
|
|
3859
4720
|
const balanceResults = await Promise.allSettled(
|
|
3860
4721
|
entries.map(async ([id, agent]) => {
|
|
3861
|
-
const b = await this.readAgent(
|
|
4722
|
+
const b = await this.readAgent(
|
|
4723
|
+
agent,
|
|
4724
|
+
"balances",
|
|
4725
|
+
() => agent.getBalances(state, chainId)
|
|
4726
|
+
);
|
|
3862
4727
|
return [id, b];
|
|
3863
4728
|
})
|
|
3864
4729
|
);
|
|
@@ -3879,7 +4744,8 @@ var OwneySDK = class {
|
|
|
3879
4744
|
const reason = settledResult.reason;
|
|
3880
4745
|
agentFailures.push(reason);
|
|
3881
4746
|
const retryDelay = rateLimitDelay(reason);
|
|
3882
|
-
if (retryDelay !== void 0)
|
|
4747
|
+
if (retryDelay !== void 0)
|
|
4748
|
+
agentRetryAt[agentId2] = Date.now() + retryDelay;
|
|
3883
4749
|
agentErrors[agentId2] = reason instanceof Error ? reason.message : String(reason);
|
|
3884
4750
|
}
|
|
3885
4751
|
if (successCount === 0) {
|
|
@@ -3906,14 +4772,22 @@ var OwneySDK = class {
|
|
|
3906
4772
|
const chainId = this.requireChainId();
|
|
3907
4773
|
if (agentId) {
|
|
3908
4774
|
const agent = this.getAgent(agentId);
|
|
3909
|
-
return this.readAgent(
|
|
4775
|
+
return this.readAgent(
|
|
4776
|
+
agent,
|
|
4777
|
+
"earnings",
|
|
4778
|
+
() => agent.getEarnings(state, chainId)
|
|
4779
|
+
);
|
|
3910
4780
|
}
|
|
3911
4781
|
let totalEarnings = 0;
|
|
3912
4782
|
const results = {};
|
|
3913
4783
|
const entries = [...this.getActiveAgents().entries()];
|
|
3914
4784
|
const earningsResults = await Promise.all(
|
|
3915
4785
|
entries.map(async ([id, agent]) => {
|
|
3916
|
-
const e = await this.readAgent(
|
|
4786
|
+
const e = await this.readAgent(
|
|
4787
|
+
agent,
|
|
4788
|
+
"earnings",
|
|
4789
|
+
() => agent.getEarnings(state, chainId)
|
|
4790
|
+
);
|
|
3917
4791
|
return [id, e];
|
|
3918
4792
|
})
|
|
3919
4793
|
);
|
|
@@ -4058,7 +4932,11 @@ var OwneySDK = class {
|
|
|
4058
4932
|
),
|
|
4059
4933
|
Promise.all(
|
|
4060
4934
|
entries.map(async ([id, agent]) => {
|
|
4061
|
-
const b = await this.readAgent(
|
|
4935
|
+
const b = await this.readAgent(
|
|
4936
|
+
agent,
|
|
4937
|
+
"balances",
|
|
4938
|
+
() => agent.getBalances(state, chainId)
|
|
4939
|
+
);
|
|
4062
4940
|
return [id, balanceForApyScope(b, chainId, tokenSymbol)];
|
|
4063
4941
|
})
|
|
4064
4942
|
)
|
|
@@ -4118,7 +4996,12 @@ var OwneySDK = class {
|
|
|
4118
4996
|
const { agentId, filters } = options ?? {};
|
|
4119
4997
|
if (agentId) {
|
|
4120
4998
|
const agent = this.getAgent(agentId);
|
|
4121
|
-
return this.readAgent(
|
|
4999
|
+
return this.readAgent(
|
|
5000
|
+
agent,
|
|
5001
|
+
"history",
|
|
5002
|
+
() => agent.getHistory(state, chainId, filters),
|
|
5003
|
+
filters
|
|
5004
|
+
);
|
|
4122
5005
|
}
|
|
4123
5006
|
const activeAgents = [...this.getActiveAgents().values()];
|
|
4124
5007
|
const cursorMap = filters?.cursor ? decodeMultiAgentCursor(filters.cursor) : {};
|
|
@@ -4175,13 +5058,21 @@ var OwneySDK = class {
|
|
|
4175
5058
|
const chainId = this.requireChainId();
|
|
4176
5059
|
if (agentId) {
|
|
4177
5060
|
const agent = this.getAgent(agentId);
|
|
4178
|
-
return this.readAgent(
|
|
5061
|
+
return this.readAgent(
|
|
5062
|
+
agent,
|
|
5063
|
+
"profile",
|
|
5064
|
+
() => agent.getUserProfile(state, chainId)
|
|
5065
|
+
);
|
|
4179
5066
|
}
|
|
4180
5067
|
const results = {};
|
|
4181
5068
|
const entries = [...this.getActiveAgents().entries()];
|
|
4182
5069
|
const profileResults = await Promise.all(
|
|
4183
5070
|
entries.map(async ([id, agent]) => {
|
|
4184
|
-
const p = await this.readAgent(
|
|
5071
|
+
const p = await this.readAgent(
|
|
5072
|
+
agent,
|
|
5073
|
+
"profile",
|
|
5074
|
+
() => agent.getUserProfile(state, chainId)
|
|
5075
|
+
);
|
|
4185
5076
|
return [id, p];
|
|
4186
5077
|
})
|
|
4187
5078
|
);
|
|
@@ -4289,13 +5180,23 @@ var OwneySDK = class {
|
|
|
4289
5180
|
const agentOptions = { tokenSymbol, chainId };
|
|
4290
5181
|
if (agentId) {
|
|
4291
5182
|
const agent = this.getAgent(agentId);
|
|
4292
|
-
return this.readAgent(
|
|
5183
|
+
return this.readAgent(
|
|
5184
|
+
agent,
|
|
5185
|
+
"agentApy",
|
|
5186
|
+
() => agent.getAgentApy(days, agentOptions),
|
|
5187
|
+
{ days, ...agentOptions }
|
|
5188
|
+
);
|
|
4293
5189
|
}
|
|
4294
5190
|
const results = {};
|
|
4295
5191
|
const agentEntries = [...this.agents.entries()];
|
|
4296
5192
|
const apyResults = await Promise.all(
|
|
4297
5193
|
agentEntries.map(async ([id, agent]) => {
|
|
4298
|
-
const apy = await this.readAgent(
|
|
5194
|
+
const apy = await this.readAgent(
|
|
5195
|
+
agent,
|
|
5196
|
+
"agentApy",
|
|
5197
|
+
() => agent.getAgentApy(days, agentOptions),
|
|
5198
|
+
{ days, ...agentOptions }
|
|
5199
|
+
);
|
|
4299
5200
|
return [id, apy];
|
|
4300
5201
|
})
|
|
4301
5202
|
);
|
|
@@ -4322,7 +5223,11 @@ var OwneySDK = class {
|
|
|
4322
5223
|
const entries = [...activeAgents.entries()];
|
|
4323
5224
|
const balanceResults = await Promise.allSettled(
|
|
4324
5225
|
entries.map(async ([id, agent]) => {
|
|
4325
|
-
const b = await this.readAgent(
|
|
5226
|
+
const b = await this.readAgent(
|
|
5227
|
+
agent,
|
|
5228
|
+
"balances",
|
|
5229
|
+
() => agent.getBalances(state, chainId)
|
|
5230
|
+
);
|
|
4326
5231
|
return [id, b.positions ?? []];
|
|
4327
5232
|
})
|
|
4328
5233
|
);
|
|
@@ -4372,8 +5277,8 @@ import { SiweMessage } from "siwe";
|
|
|
4372
5277
|
import { ZyfaiSDK as ZyfaiSDK2 } from "@zyfai/sdk";
|
|
4373
5278
|
|
|
4374
5279
|
// src/agents/zyfai/zyfai.siwx-cache.ts
|
|
4375
|
-
var
|
|
4376
|
-
var
|
|
5280
|
+
var KEY_PREFIX3 = "owney.siwx.session";
|
|
5281
|
+
var storage3 = () => {
|
|
4377
5282
|
if (typeof window === "undefined") return null;
|
|
4378
5283
|
try {
|
|
4379
5284
|
return window.localStorage;
|
|
@@ -4381,8 +5286,8 @@ var storage2 = () => {
|
|
|
4381
5286
|
return null;
|
|
4382
5287
|
}
|
|
4383
5288
|
};
|
|
4384
|
-
var buildKey2 = (address) => `${
|
|
4385
|
-
var legacyKeyPrefix2 = (address) => `${
|
|
5289
|
+
var buildKey2 = (address) => `${KEY_PREFIX3}:${address.toLowerCase()}`;
|
|
5290
|
+
var legacyKeyPrefix2 = (address) => `${KEY_PREFIX3}:${address.toLowerCase()}:`;
|
|
4386
5291
|
var memorySiwxSessions = /* @__PURE__ */ new Map();
|
|
4387
5292
|
var readLegacySiwxSession = (store, address) => {
|
|
4388
5293
|
if (!store) return null;
|
|
@@ -4414,7 +5319,7 @@ var readLegacySiwxSession = (store, address) => {
|
|
|
4414
5319
|
var readSiwxSession = (address, chainId) => {
|
|
4415
5320
|
if (typeof window === "undefined") return null;
|
|
4416
5321
|
const key2 = buildKey2(address);
|
|
4417
|
-
const store =
|
|
5322
|
+
const store = storage3();
|
|
4418
5323
|
let raw = null;
|
|
4419
5324
|
try {
|
|
4420
5325
|
raw = store?.getItem(key2) ?? null;
|
|
@@ -4444,7 +5349,7 @@ var writeSiwxSession = (address, _chainId, session) => {
|
|
|
4444
5349
|
if (typeof window === "undefined") return;
|
|
4445
5350
|
const key2 = buildKey2(address);
|
|
4446
5351
|
memorySiwxSessions.set(key2, session);
|
|
4447
|
-
const store =
|
|
5352
|
+
const store = storage3();
|
|
4448
5353
|
try {
|
|
4449
5354
|
store?.setItem(key2, JSON.stringify(session));
|
|
4450
5355
|
} catch {
|
|
@@ -4453,7 +5358,7 @@ var writeSiwxSession = (address, _chainId, session) => {
|
|
|
4453
5358
|
var clearSiwxSession = (address, _chainId) => {
|
|
4454
5359
|
const key2 = buildKey2(address);
|
|
4455
5360
|
memorySiwxSessions.delete(key2);
|
|
4456
|
-
const store =
|
|
5361
|
+
const store = storage3();
|
|
4457
5362
|
try {
|
|
4458
5363
|
store?.removeItem(key2);
|
|
4459
5364
|
} catch {
|
|
@@ -4572,9 +5477,9 @@ function buildSIWXConfig(deps) {
|
|
|
4572
5477
|
}
|
|
4573
5478
|
function createOwneySIWX(config) {
|
|
4574
5479
|
const zyfai = new ZyfaiSDK2({ apiKey: config.apiKey });
|
|
4575
|
-
const
|
|
5480
|
+
const http4 = zyfai.httpClient;
|
|
4576
5481
|
return buildSIWXConfig({
|
|
4577
|
-
post: (url, data) =>
|
|
5482
|
+
post: (url, data) => http4.post(url, data),
|
|
4578
5483
|
referralSource: config.referralSource
|
|
4579
5484
|
});
|
|
4580
5485
|
}
|
|
@@ -4586,5 +5491,6 @@ export {
|
|
|
4586
5491
|
OwneyError,
|
|
4587
5492
|
OwneySDK,
|
|
4588
5493
|
createOwneySIWX,
|
|
5494
|
+
listOrders as listPendingSwaps,
|
|
4589
5495
|
setOwneyDebug
|
|
4590
5496
|
};
|