@owney/sdk 0.7.26-beta.3 → 0.7.26-beta.5
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 +688 -110
- package/dist/index.d.cts +212 -31
- package/dist/index.d.ts +212 -31
- package/dist/index.js +697 -110
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2275,6 +2275,21 @@ function createSwapApi(baseUrl, apiKey) {
|
|
|
2275
2275
|
* Already filtered server-side to what a quote will accept, so anything
|
|
2276
2276
|
* returned here can be paid with.
|
|
2277
2277
|
*/
|
|
2278
|
+
/**
|
|
2279
|
+
* Tokens the wallet holds on one chain.
|
|
2280
|
+
*
|
|
2281
|
+
* Already filtered upstream to non-zero balances of tokens the routing API
|
|
2282
|
+
* would quote — 1inch answers with every token it knows about, zeros
|
|
2283
|
+
* included, and the curation that decides what is offerable lives there.
|
|
2284
|
+
*/
|
|
2285
|
+
walletBalances: (params) => request(
|
|
2286
|
+
baseUrl,
|
|
2287
|
+
apiKey,
|
|
2288
|
+
`/balances?${new URLSearchParams({
|
|
2289
|
+
chainId: String(params.chainId),
|
|
2290
|
+
walletAddress: params.walletAddress
|
|
2291
|
+
})}`
|
|
2292
|
+
),
|
|
2278
2293
|
searchTokens: (params) => request(
|
|
2279
2294
|
baseUrl,
|
|
2280
2295
|
apiKey,
|
|
@@ -2283,7 +2298,8 @@ function createSwapApi(baseUrl, apiKey) {
|
|
|
2283
2298
|
// Omitted, the routing API searches every supported chain — which is
|
|
2284
2299
|
// what a picker wants, since its rows already span all three.
|
|
2285
2300
|
...params.chainId === void 0 ? {} : { chainId: String(params.chainId) },
|
|
2286
|
-
...params.limit === void 0 ? {} : { limit: String(params.limit) }
|
|
2301
|
+
...params.limit === void 0 ? {} : { limit: String(params.limit) },
|
|
2302
|
+
...params.direction === void 0 ? {} : { direction: params.direction }
|
|
2287
2303
|
}).toString()}`
|
|
2288
2304
|
),
|
|
2289
2305
|
/**
|
|
@@ -2369,12 +2385,41 @@ function createSwapApi(baseUrl, apiKey) {
|
|
|
2369
2385
|
// src/lib/swap/swap.rpc.ts
|
|
2370
2386
|
import { fallback, http as http2 } from "viem";
|
|
2371
2387
|
var DEFAULT_RPC_URLS = {
|
|
2372
|
-
|
|
2388
|
+
// NOT cloudflare-eth.com, which used to lead this list. It answers
|
|
2389
|
+
// eth_chainId happily, errors on eth_blockNumber, and — the part that
|
|
2390
|
+
// actually broke things — returns `result: null` for the receipt of a
|
|
2391
|
+
// transaction that is demonstrably mined. Null is a VALID answer meaning
|
|
2392
|
+
// "not found", so viem's fallback has no reason to try the next endpoint:
|
|
2393
|
+
// waitForTransactionReceipt simply polls until it times out, which on this
|
|
2394
|
+
// chain is ten minutes of a spinner (ROUT-242). An endpoint that ERRORS is
|
|
2395
|
+
// safe; an endpoint that lies quietly is not.
|
|
2396
|
+
1: ["https://ethereum-rpc.publicnode.com", "https://eth.drpc.org"],
|
|
2373
2397
|
8453: ["https://mainnet.base.org", "https://base-rpc.publicnode.com"],
|
|
2374
2398
|
42161: [
|
|
2375
2399
|
"https://arb1.arbitrum.io/rpc",
|
|
2376
2400
|
"https://arbitrum-one-rpc.publicnode.com"
|
|
2377
|
-
]
|
|
2401
|
+
],
|
|
2402
|
+
// Added with the wider swap chain set (ROUT-242, comment 15982). Every one
|
|
2403
|
+
// of these answered with `access-control-allow-origin: *` on 2026-09-29;
|
|
2404
|
+
// a chain without two verified endpoints does not belong in the list.
|
|
2405
|
+
10: ["https://mainnet.optimism.io", "https://optimism-rpc.publicnode.com"],
|
|
2406
|
+
// polygon-rpc.com led this list until it started answering every call with
|
|
2407
|
+
// "API key disabled, reason: tenant disabled". It errors rather than lying,
|
|
2408
|
+
// so the fallback did cover it — at the cost of a failed request in front of
|
|
2409
|
+
// every Polygon read.
|
|
2410
|
+
137: ["https://polygon-bor-rpc.publicnode.com", "https://polygon.drpc.org"],
|
|
2411
|
+
56: ["https://bsc-dataseed.bnbchain.org", "https://bsc-rpc.publicnode.com"],
|
|
2412
|
+
43114: [
|
|
2413
|
+
"https://api.avax.network/ext/bc/C/rpc",
|
|
2414
|
+
"https://avalanche-c-chain-rpc.publicnode.com"
|
|
2415
|
+
],
|
|
2416
|
+
143: ["https://rpc.monad.xyz", "https://rpc1.monad.xyz"],
|
|
2417
|
+
// ONE endpoint, not two: of the six the public chain registry lists for Arc,
|
|
2418
|
+
// this is the only one that answers a browser at all. viem ships none. It
|
|
2419
|
+
// was checked with a real eth_chainId call (0x13b2), not just a CORS header,
|
|
2420
|
+
// but a single provider is a single point of failure - add a second when one
|
|
2421
|
+
// exists.
|
|
2422
|
+
5042: ["https://rpc.beamrpc.com"]
|
|
2378
2423
|
};
|
|
2379
2424
|
function swapReadTransport(chainId, overrides) {
|
|
2380
2425
|
const override = overrides?.[chainId];
|
|
@@ -2387,6 +2432,167 @@ function receiptTimeoutMs(chainId) {
|
|
|
2387
2432
|
return chainId === 1 ? 6e5 : 18e4;
|
|
2388
2433
|
}
|
|
2389
2434
|
|
|
2435
|
+
// src/lib/swap/swap.wrap.ts
|
|
2436
|
+
var WRAP_SELECTOR = "0xd0e30db0";
|
|
2437
|
+
var FALLBACK_WRAP_GAS = 50000n;
|
|
2438
|
+
var APPROVE_GAS = 60000n;
|
|
2439
|
+
var GAS_SAFETY = 2n;
|
|
2440
|
+
var MIN_WALLET_GAS_PRICE = 3000000000n;
|
|
2441
|
+
async function wrapNative(deps, options) {
|
|
2442
|
+
const { chainId, wrapper, nativeSymbol, requested } = options;
|
|
2443
|
+
const balance = await deps.readNativeBalance();
|
|
2444
|
+
let wrapGas;
|
|
2445
|
+
try {
|
|
2446
|
+
wrapGas = await deps.estimateWrapGas(wrapper, requested);
|
|
2447
|
+
} catch (error) {
|
|
2448
|
+
wrapGas = FALLBACK_WRAP_GAS;
|
|
2449
|
+
debugLog("owney-sdk", "wrap: gas estimate failed, using the fallback", {
|
|
2450
|
+
fallback: FALLBACK_WRAP_GAS.toString(),
|
|
2451
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2452
|
+
});
|
|
2453
|
+
}
|
|
2454
|
+
const measured = await deps.getMaxFeePerGas();
|
|
2455
|
+
const gasPrice = measured > MIN_WALLET_GAS_PRICE ? measured : MIN_WALLET_GAS_PRICE;
|
|
2456
|
+
const reserve = (wrapGas + APPROVE_GAS) * gasPrice * GAS_SAFETY;
|
|
2457
|
+
const spendable = balance > reserve ? balance - reserve : 0n;
|
|
2458
|
+
const wrapped = requested < spendable ? requested : spendable;
|
|
2459
|
+
debugLog("owney-sdk", "wrap: sizing", {
|
|
2460
|
+
balance: balance.toString(),
|
|
2461
|
+
requested: requested.toString(),
|
|
2462
|
+
measuredGasPrice: measured.toString(),
|
|
2463
|
+
reservedAtGasPrice: gasPrice.toString(),
|
|
2464
|
+
reserve: reserve.toString(),
|
|
2465
|
+
wrapped: wrapped.toString()
|
|
2466
|
+
});
|
|
2467
|
+
if (wrapped <= 0n) {
|
|
2468
|
+
throw new OwneyError(
|
|
2469
|
+
"SWAP_INSUFFICIENT_GAS",
|
|
2470
|
+
`Not enough ${nativeSymbol} to cover both the swap and its network fees. Leave a little ${nativeSymbol} for gas, or swap a smaller amount.`,
|
|
2471
|
+
{
|
|
2472
|
+
chainId,
|
|
2473
|
+
// Named in the details as well as the message so the app's own copy
|
|
2474
|
+
// can say "leave a little POL" rather than re-deriving it.
|
|
2475
|
+
symbol: nativeSymbol,
|
|
2476
|
+
balance: balance.toString(),
|
|
2477
|
+
requested: requested.toString(),
|
|
2478
|
+
reserve: reserve.toString()
|
|
2479
|
+
}
|
|
2480
|
+
);
|
|
2481
|
+
}
|
|
2482
|
+
await deps.ensureChain(chainId);
|
|
2483
|
+
debugLog("owney-sdk", "wrap: sending deposit()", {
|
|
2484
|
+
wrapper,
|
|
2485
|
+
value: wrapped.toString()
|
|
2486
|
+
});
|
|
2487
|
+
const txHash = await deps.sendTransaction(
|
|
2488
|
+
{ to: wrapper, data: WRAP_SELECTOR, value: wrapped.toString() },
|
|
2489
|
+
options.onSubmitted
|
|
2490
|
+
);
|
|
2491
|
+
return { wrapped, txHash };
|
|
2492
|
+
}
|
|
2493
|
+
function requireWrapper(quote) {
|
|
2494
|
+
const wrapper = quote.wrappedNative;
|
|
2495
|
+
if (!wrapper?.address) {
|
|
2496
|
+
throw new OwneyError(
|
|
2497
|
+
"SWAP_NATIVE_UNSUPPORTED",
|
|
2498
|
+
`Swapping ${quote.src.symbol} from this chain to another isn't supported yet. Swap a token instead, or move to the destination chain first.`,
|
|
2499
|
+
{ chainId: quote.src.chainId, symbol: quote.src.symbol }
|
|
2500
|
+
);
|
|
2501
|
+
}
|
|
2502
|
+
return { symbol: wrapper.symbol, address: wrapper.address };
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
// src/lib/swap/swap.chains.ts
|
|
2506
|
+
import {
|
|
2507
|
+
arbitrum as arbitrum2,
|
|
2508
|
+
arc,
|
|
2509
|
+
avalanche,
|
|
2510
|
+
base as base2,
|
|
2511
|
+
bsc,
|
|
2512
|
+
mainnet as mainnet2,
|
|
2513
|
+
monad,
|
|
2514
|
+
optimism,
|
|
2515
|
+
polygon
|
|
2516
|
+
} from "viem/chains";
|
|
2517
|
+
var CHAINS = {
|
|
2518
|
+
8453: base2,
|
|
2519
|
+
42161: arbitrum2,
|
|
2520
|
+
1: mainnet2,
|
|
2521
|
+
10: optimism,
|
|
2522
|
+
137: polygon,
|
|
2523
|
+
56: bsc,
|
|
2524
|
+
43114: avalanche,
|
|
2525
|
+
143: monad,
|
|
2526
|
+
5042: arc
|
|
2527
|
+
};
|
|
2528
|
+
var SWAP_CHAIN_IDS = Object.keys(CHAINS).map(Number);
|
|
2529
|
+
function swapChain(chainId) {
|
|
2530
|
+
return CHAINS[chainId];
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
// src/lib/swap/swap.arrival.ts
|
|
2534
|
+
var DEFAULT_TIMEOUT_MS = 18e4;
|
|
2535
|
+
var DEFAULT_POLL_MS = 4e3;
|
|
2536
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2537
|
+
async function awaitWithdrawalArrival(options) {
|
|
2538
|
+
const { arrived, lastReadError } = await awaitBalanceIncrease(options);
|
|
2539
|
+
if (arrived !== null) return arrived;
|
|
2540
|
+
throw new OwneyError(
|
|
2541
|
+
"WITHDRAW_ARRIVAL_TIMEOUT",
|
|
2542
|
+
"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.",
|
|
2543
|
+
{
|
|
2544
|
+
baseline: options.baseline.toString(),
|
|
2545
|
+
waitedMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
2546
|
+
...lastReadError ? { lastReadError } : {}
|
|
2547
|
+
}
|
|
2548
|
+
);
|
|
2549
|
+
}
|
|
2550
|
+
async function awaitBalanceIncrease(options) {
|
|
2551
|
+
const {
|
|
2552
|
+
readBalance,
|
|
2553
|
+
baseline,
|
|
2554
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
2555
|
+
pollMs = DEFAULT_POLL_MS
|
|
2556
|
+
} = options;
|
|
2557
|
+
const deadline = Date.now() + timeoutMs;
|
|
2558
|
+
debugLog("owney-sdk", "arrival: waiting for funds to land", {
|
|
2559
|
+
baseline: baseline.toString(),
|
|
2560
|
+
timeoutMs
|
|
2561
|
+
});
|
|
2562
|
+
let lastError;
|
|
2563
|
+
for (; ; ) {
|
|
2564
|
+
try {
|
|
2565
|
+
const balance = await readBalance();
|
|
2566
|
+
if (balance > baseline) {
|
|
2567
|
+
const arrived = balance - baseline;
|
|
2568
|
+
debugLog("owney-sdk", "arrival: funds landed", {
|
|
2569
|
+
arrived: arrived.toString()
|
|
2570
|
+
});
|
|
2571
|
+
return { arrived };
|
|
2572
|
+
}
|
|
2573
|
+
} catch (error) {
|
|
2574
|
+
lastError = error;
|
|
2575
|
+
debugLog("owney-sdk", "arrival: balance read failed, retrying", {
|
|
2576
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2577
|
+
});
|
|
2578
|
+
}
|
|
2579
|
+
if (Date.now() >= deadline) {
|
|
2580
|
+
debugLog("owney-sdk", "arrival: nothing landed before the deadline", {
|
|
2581
|
+
baseline: baseline.toString(),
|
|
2582
|
+
waitedMs: timeoutMs,
|
|
2583
|
+
...lastError ? {
|
|
2584
|
+
lastReadError: lastError instanceof Error ? lastError.message : String(lastError)
|
|
2585
|
+
} : {}
|
|
2586
|
+
});
|
|
2587
|
+
return lastError ? {
|
|
2588
|
+
arrived: null,
|
|
2589
|
+
lastReadError: lastError instanceof Error ? lastError.message : String(lastError)
|
|
2590
|
+
} : { arrived: null };
|
|
2591
|
+
}
|
|
2592
|
+
await sleep(pollMs);
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2390
2596
|
// src/lib/permit2.ts
|
|
2391
2597
|
import { bytesToHex } from "viem";
|
|
2392
2598
|
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
@@ -2510,17 +2716,17 @@ var SWAP_TERMINAL_STATUSES = [
|
|
|
2510
2716
|
var isSwapTerminal = (status) => SWAP_TERMINAL_STATUSES.includes(status);
|
|
2511
2717
|
|
|
2512
2718
|
// src/lib/swap/swap.order-runner.ts
|
|
2513
|
-
var
|
|
2719
|
+
var DEFAULT_POLL_MS2 = 5e3;
|
|
2514
2720
|
var MAX_BACKOFF_MS = 3e4;
|
|
2515
|
-
var backoffFor = (failures,
|
|
2516
|
-
var
|
|
2721
|
+
var backoffFor = (failures, base4) => Math.min(base4 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS);
|
|
2722
|
+
var DEFAULT_TIMEOUT_MS2 = 15 * 60 * 1e3;
|
|
2517
2723
|
async function runFusionOrder(deps, options) {
|
|
2518
2724
|
const {
|
|
2519
2725
|
orderHash,
|
|
2520
2726
|
secrets,
|
|
2521
2727
|
onStage,
|
|
2522
|
-
pollIntervalMs =
|
|
2523
|
-
timeoutMs =
|
|
2728
|
+
pollIntervalMs = DEFAULT_POLL_MS2,
|
|
2729
|
+
timeoutMs = DEFAULT_TIMEOUT_MS2
|
|
2524
2730
|
} = options;
|
|
2525
2731
|
const deadline = deps.now() + timeoutMs;
|
|
2526
2732
|
let failures = 0;
|
|
@@ -2531,7 +2737,11 @@ async function runFusionOrder(deps, options) {
|
|
|
2531
2737
|
throw new OwneyError(
|
|
2532
2738
|
"SWAP_REQUEST_FAILED",
|
|
2533
2739
|
"Timed out waiting for the swap to settle. It may still complete \u2014 check the order status before retrying.",
|
|
2534
|
-
|
|
2740
|
+
// Marks the one failure that means "we stopped watching a live order",
|
|
2741
|
+
// as opposed to a verdict or a prompt nobody answered. The executor
|
|
2742
|
+
// checks the wallet before believing it: resolvers do not stop filling
|
|
2743
|
+
// because we stopped looking (ROUT-242, comment 15980).
|
|
2744
|
+
{ orderHash, settlementUnconfirmed: true }
|
|
2535
2745
|
);
|
|
2536
2746
|
}
|
|
2537
2747
|
let ready;
|
|
@@ -2584,10 +2794,10 @@ async function runFusionOrder(deps, options) {
|
|
|
2584
2794
|
}
|
|
2585
2795
|
|
|
2586
2796
|
// src/lib/swap/swap.same-chain-runner.ts
|
|
2587
|
-
var
|
|
2797
|
+
var DEFAULT_POLL_MS3 = 5e3;
|
|
2588
2798
|
var MAX_BACKOFF_MS2 = 3e4;
|
|
2589
|
-
var backoffFor2 = (failures,
|
|
2590
|
-
var
|
|
2799
|
+
var backoffFor2 = (failures, base4) => Math.min(base4 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS2);
|
|
2800
|
+
var DEFAULT_TIMEOUT_MS3 = 3 * 60 * 1e3;
|
|
2591
2801
|
function explain(status) {
|
|
2592
2802
|
switch (status) {
|
|
2593
2803
|
case "expired":
|
|
@@ -2610,8 +2820,8 @@ async function runSameChainOrder(deps, options) {
|
|
|
2610
2820
|
const {
|
|
2611
2821
|
orderHash,
|
|
2612
2822
|
onStage,
|
|
2613
|
-
pollIntervalMs =
|
|
2614
|
-
timeoutMs =
|
|
2823
|
+
pollIntervalMs = DEFAULT_POLL_MS3,
|
|
2824
|
+
timeoutMs = DEFAULT_TIMEOUT_MS3
|
|
2615
2825
|
} = options;
|
|
2616
2826
|
const deadline = deps.now() + timeoutMs;
|
|
2617
2827
|
let failures = 0;
|
|
@@ -2709,6 +2919,35 @@ function listOrders(now = Date.now()) {
|
|
|
2709
2919
|
}
|
|
2710
2920
|
|
|
2711
2921
|
// src/lib/swap/swap.executor.ts
|
|
2922
|
+
var WALLET_PROMPT_TIMEOUT_MS = 3 * 60 * 1e3;
|
|
2923
|
+
async function awaitWalletPrompt(prompt, what) {
|
|
2924
|
+
let timer;
|
|
2925
|
+
try {
|
|
2926
|
+
return await Promise.race([
|
|
2927
|
+
prompt(),
|
|
2928
|
+
new Promise((_, reject) => {
|
|
2929
|
+
timer = setTimeout(() => {
|
|
2930
|
+
reject(
|
|
2931
|
+
new OwneyError(
|
|
2932
|
+
"SWAP_WALLET_PROMPT_TIMEOUT",
|
|
2933
|
+
// Naming the stale request matters: the wallet is still holding
|
|
2934
|
+
// it, which is why the next connection attempt is refused too
|
|
2935
|
+
// ("Connection can be declined if a previous request is still
|
|
2936
|
+
// active"). Dismissing it is what actually unblocks them.
|
|
2937
|
+
`Your wallet didn't answer the request to ${what}. Open your wallet, dismiss any request still waiting there, and try again.`,
|
|
2938
|
+
{ prompt: what, timeoutMs: WALLET_PROMPT_TIMEOUT_MS }
|
|
2939
|
+
)
|
|
2940
|
+
);
|
|
2941
|
+
}, WALLET_PROMPT_TIMEOUT_MS);
|
|
2942
|
+
})
|
|
2943
|
+
]);
|
|
2944
|
+
} finally {
|
|
2945
|
+
if (timer) clearTimeout(timer);
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
function stoppedWatching(error) {
|
|
2949
|
+
return error instanceof OwneyError && error.details?.settlementUnconfirmed === true;
|
|
2950
|
+
}
|
|
2712
2951
|
var DEFAULT_SLIPPAGE = 1;
|
|
2713
2952
|
async function affordableAmount(deps, quoted) {
|
|
2714
2953
|
const balance = await deps.readSourceBalance();
|
|
@@ -2731,21 +2970,39 @@ async function executeSwap(deps, options) {
|
|
|
2731
2970
|
});
|
|
2732
2971
|
const before = await deps.readTargetBalance();
|
|
2733
2972
|
debugLog("owney-sdk", "swap: target balance before", before.toString());
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2973
|
+
let result;
|
|
2974
|
+
let railError;
|
|
2975
|
+
try {
|
|
2976
|
+
result = quote.rail === "classic" ? await runClassic(deps, options) : quote.rail === "fusion" ? await runSameChainFusion(deps, options, walletAddress) : await runFusion(deps, options, walletAddress);
|
|
2977
|
+
} catch (error) {
|
|
2978
|
+
if (!stoppedWatching(error)) throw error;
|
|
2979
|
+
railError = error;
|
|
2980
|
+
debugLog("owney-sdk", "swap: rail failed, checking whether funds landed", {
|
|
2981
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2982
|
+
});
|
|
2983
|
+
}
|
|
2984
|
+
const { arrived } = await awaitBalanceIncrease({
|
|
2985
|
+
readBalance: () => deps.readTargetBalance(),
|
|
2986
|
+
baseline: before,
|
|
2987
|
+
...options.arrivalTimeoutMs === void 0 ? {} : { timeoutMs: options.arrivalTimeoutMs },
|
|
2988
|
+
...options.arrivalPollMs === void 0 ? {} : { pollMs: options.arrivalPollMs }
|
|
2740
2989
|
});
|
|
2741
|
-
if (
|
|
2990
|
+
if (arrived === null) {
|
|
2991
|
+
if (railError) throw railError;
|
|
2742
2992
|
throw new OwneyError(
|
|
2743
2993
|
"SWAP_REQUEST_FAILED",
|
|
2744
2994
|
"The swap completed but no funds arrived in the wallet. Check the transaction before retrying.",
|
|
2745
|
-
{ rail: quote.rail, ...result }
|
|
2995
|
+
{ rail: quote.rail, ...result ?? {} }
|
|
2746
2996
|
);
|
|
2747
2997
|
}
|
|
2748
|
-
|
|
2998
|
+
if (railError) {
|
|
2999
|
+
debugLog("owney-sdk", "swap: rail gave up but the funds are here", {
|
|
3000
|
+
received: arrived.toString()
|
|
3001
|
+
});
|
|
3002
|
+
const orderHash = railError.details?.orderHash;
|
|
3003
|
+
if (orderHash) clearOrder(orderHash);
|
|
3004
|
+
}
|
|
3005
|
+
return { received: arrived.toString(), ...result ?? {} };
|
|
2749
3006
|
}
|
|
2750
3007
|
async function runClassic(deps, options) {
|
|
2751
3008
|
const {
|
|
@@ -2825,7 +3082,10 @@ async function runSameChainFusion(deps, options, walletAddress) {
|
|
|
2825
3082
|
});
|
|
2826
3083
|
onStage?.("signing");
|
|
2827
3084
|
await deps.ensureChain(quote.src.chainId);
|
|
2828
|
-
const signature = await
|
|
3085
|
+
const signature = await awaitWalletPrompt(
|
|
3086
|
+
() => deps.signTypedData(built.typedData),
|
|
3087
|
+
"sign the swap"
|
|
3088
|
+
);
|
|
2829
3089
|
debugLog("owney-sdk", "swap: signed, submitting same-chain order");
|
|
2830
3090
|
await deps.api.submitOrder({
|
|
2831
3091
|
// Without this the routing API hands the order to the Fusion+ relayer,
|
|
@@ -2855,7 +3115,10 @@ async function authoriseSpend(deps, options, amount, isNativeSource) {
|
|
|
2855
3115
|
if (current >= amount) return void 0;
|
|
2856
3116
|
onStage?.("approving");
|
|
2857
3117
|
await deps.ensureChain(quote.src.chainId);
|
|
2858
|
-
const permit = quote.permitSupported ? await
|
|
3118
|
+
const permit = quote.permitSupported ? await awaitWalletPrompt(
|
|
3119
|
+
async () => deps.buildPermit?.(quote.spender, MAX_UINT256),
|
|
3120
|
+
`approve ${quote.src.symbol}`
|
|
3121
|
+
) ?? void 0 : void 0;
|
|
2859
3122
|
if (permit) {
|
|
2860
3123
|
debugLog("owney-sdk", "swap: permitting limit order protocol", {
|
|
2861
3124
|
bytes: (permit.length - 2) / 2
|
|
@@ -2869,8 +3132,15 @@ async function authoriseSpend(deps, options, amount, isNativeSource) {
|
|
|
2869
3132
|
async function runFusion(deps, options, walletAddress) {
|
|
2870
3133
|
const { quote, direction, onStage } = options;
|
|
2871
3134
|
const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
|
|
2872
|
-
|
|
2873
|
-
|
|
3135
|
+
if (isNativeSource) {
|
|
3136
|
+
throw new OwneyError(
|
|
3137
|
+
"SWAP_NATIVE_UNSUPPORTED",
|
|
3138
|
+
`${quote.src.symbol} has to be wrapped before it can be swapped to another chain. Nothing has left your wallet.`,
|
|
3139
|
+
{ chainId: quote.src.chainId, symbol: quote.src.symbol }
|
|
3140
|
+
);
|
|
3141
|
+
}
|
|
3142
|
+
const amount = await affordableAmount(deps, BigInt(quote.src.amount));
|
|
3143
|
+
const permit = await authoriseSpend(deps, options, amount, false);
|
|
2874
3144
|
const { secrets, secretHashes } = mintSecrets(quote.secretsCount ?? 1);
|
|
2875
3145
|
onStage?.("quoting");
|
|
2876
3146
|
debugLog("owney-sdk", "swap: building fusion order", {
|
|
@@ -2905,7 +3175,10 @@ async function runFusion(deps, options, walletAddress) {
|
|
|
2905
3175
|
debugLog("owney-sdk", "swap: awaiting signature in wallet", {
|
|
2906
3176
|
signingOnChain: quote.src.chainId
|
|
2907
3177
|
});
|
|
2908
|
-
const signature = await
|
|
3178
|
+
const signature = await awaitWalletPrompt(
|
|
3179
|
+
() => deps.signTypedData(built.typedData),
|
|
3180
|
+
"sign the swap"
|
|
3181
|
+
);
|
|
2909
3182
|
debugLog("owney-sdk", "swap: signed, submitting to relayer");
|
|
2910
3183
|
await deps.api.submitOrder({
|
|
2911
3184
|
srcChainId: quote.src.chainId,
|
|
@@ -3041,54 +3314,64 @@ async function buildErc2612Permit(reads, input) {
|
|
|
3041
3314
|
);
|
|
3042
3315
|
}
|
|
3043
3316
|
|
|
3044
|
-
// src/lib/swap/swap.
|
|
3045
|
-
var
|
|
3046
|
-
var
|
|
3047
|
-
var
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3317
|
+
// src/lib/swap/swap.pending-deposit.ts
|
|
3318
|
+
var KEY_PREFIX3 = "owney.swap.pending-deposit";
|
|
3319
|
+
var MAX_AGE_MS2 = 24 * 60 * 60 * 1e3;
|
|
3320
|
+
var storage3 = () => {
|
|
3321
|
+
if (typeof window === "undefined") return null;
|
|
3322
|
+
try {
|
|
3323
|
+
return window.localStorage;
|
|
3324
|
+
} catch {
|
|
3325
|
+
return null;
|
|
3326
|
+
}
|
|
3327
|
+
};
|
|
3328
|
+
var keyFor2 = (id) => `${KEY_PREFIX3}.${id}`;
|
|
3329
|
+
function savePendingDeposit(entry) {
|
|
3330
|
+
const store = storage3();
|
|
3331
|
+
if (!store) return;
|
|
3332
|
+
try {
|
|
3333
|
+
store.setItem(keyFor2(entry.id), JSON.stringify(entry));
|
|
3334
|
+
} catch {
|
|
3335
|
+
return;
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3338
|
+
function clearPendingDeposit(id) {
|
|
3339
|
+
const store = storage3();
|
|
3340
|
+
if (!store) return;
|
|
3341
|
+
try {
|
|
3342
|
+
store.removeItem(keyFor2(id));
|
|
3343
|
+
} catch {
|
|
3344
|
+
return;
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
function listPendingDeposits(now = Date.now()) {
|
|
3348
|
+
const store = storage3();
|
|
3349
|
+
if (!store) return [];
|
|
3350
|
+
const out = [];
|
|
3351
|
+
try {
|
|
3352
|
+
const keys = [];
|
|
3353
|
+
for (let i = 0; i < store.length; i++) {
|
|
3354
|
+
const key2 = store.key(i);
|
|
3355
|
+
if (key2?.startsWith(`${KEY_PREFIX3}.`)) keys.push(key2);
|
|
3076
3356
|
}
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
lastReadError: lastError instanceof Error ? lastError.message : String(lastError)
|
|
3086
|
-
} : {}
|
|
3357
|
+
for (const key2 of keys) {
|
|
3358
|
+
const raw = store.getItem(key2);
|
|
3359
|
+
if (!raw) continue;
|
|
3360
|
+
try {
|
|
3361
|
+
const parsed = JSON.parse(raw);
|
|
3362
|
+
if (now - parsed.createdAt > MAX_AGE_MS2) {
|
|
3363
|
+
store.removeItem(key2);
|
|
3364
|
+
continue;
|
|
3087
3365
|
}
|
|
3088
|
-
|
|
3366
|
+
out.push(parsed);
|
|
3367
|
+
} catch {
|
|
3368
|
+
store.removeItem(key2);
|
|
3369
|
+
}
|
|
3089
3370
|
}
|
|
3090
|
-
|
|
3371
|
+
} catch {
|
|
3372
|
+
return out;
|
|
3091
3373
|
}
|
|
3374
|
+
return out.sort((a, b) => b.createdAt - a.createdAt);
|
|
3092
3375
|
}
|
|
3093
3376
|
|
|
3094
3377
|
// src/lib/health-report.ts
|
|
@@ -3288,7 +3571,7 @@ import {
|
|
|
3288
3571
|
custom,
|
|
3289
3572
|
erc20Abi as erc20Abi2
|
|
3290
3573
|
} from "viem";
|
|
3291
|
-
import { base as
|
|
3574
|
+
import { base as base3, arbitrum as arbitrum3, mainnet as mainnet3 } from "viem/chains";
|
|
3292
3575
|
|
|
3293
3576
|
// src/lib/transfer-auth.ts
|
|
3294
3577
|
import { bytesToHex as bytesToHex2 } from "viem";
|
|
@@ -3329,10 +3612,10 @@ function randomAuthNonce() {
|
|
|
3329
3612
|
// src/lib/sponsor-client.ts
|
|
3330
3613
|
var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
3331
3614
|
async function postSponsorTransferAuth(input) {
|
|
3332
|
-
const
|
|
3615
|
+
const base4 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
3333
3616
|
let res;
|
|
3334
3617
|
try {
|
|
3335
|
-
res = await fetch(`${
|
|
3618
|
+
res = await fetch(`${base4}/api/v1/sponsor/erc20-transfer-auth`, {
|
|
3336
3619
|
method: "POST",
|
|
3337
3620
|
headers: {
|
|
3338
3621
|
"content-type": "application/json",
|
|
@@ -3369,10 +3652,10 @@ async function postSponsorTransferAuth(input) {
|
|
|
3369
3652
|
return parsed.data;
|
|
3370
3653
|
}
|
|
3371
3654
|
async function postSponsorPermit2Transfer(input) {
|
|
3372
|
-
const
|
|
3655
|
+
const base4 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
3373
3656
|
let res;
|
|
3374
3657
|
try {
|
|
3375
|
-
res = await fetch(`${
|
|
3658
|
+
res = await fetch(`${base4}/api/v1/sponsor/permit2-transfer`, {
|
|
3376
3659
|
method: "POST",
|
|
3377
3660
|
headers: {
|
|
3378
3661
|
"content-type": "application/json",
|
|
@@ -3407,11 +3690,11 @@ async function postSponsorPermit2Transfer(input) {
|
|
|
3407
3690
|
return parsed.data;
|
|
3408
3691
|
}
|
|
3409
3692
|
async function getSponsorRelayerAddress(input) {
|
|
3410
|
-
const
|
|
3693
|
+
const base4 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
3411
3694
|
let res;
|
|
3412
3695
|
try {
|
|
3413
3696
|
res = await fetch(
|
|
3414
|
-
`${
|
|
3697
|
+
`${base4}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
|
|
3415
3698
|
{
|
|
3416
3699
|
headers: { "x-owney-api-key": input.apiKey }
|
|
3417
3700
|
}
|
|
@@ -3696,6 +3979,7 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
3696
3979
|
}
|
|
3697
3980
|
|
|
3698
3981
|
// src/client.ts
|
|
3982
|
+
var WALLET_CATCHUP_TIMEOUT_MS = 3e4;
|
|
3699
3983
|
function encodeMultiAgentCursor(map) {
|
|
3700
3984
|
return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
|
|
3701
3985
|
}
|
|
@@ -3722,9 +4006,9 @@ var SPONSORED_USDC_BY_CHAIN = {
|
|
|
3722
4006
|
1: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
|
3723
4007
|
};
|
|
3724
4008
|
var VIEM_CHAIN2 = {
|
|
3725
|
-
8453:
|
|
3726
|
-
42161:
|
|
3727
|
-
1:
|
|
4009
|
+
8453: base3,
|
|
4010
|
+
42161: arbitrum3,
|
|
4011
|
+
1: mainnet3
|
|
3728
4012
|
};
|
|
3729
4013
|
var SPONSORED_WETH_BY_CHAIN = {
|
|
3730
4014
|
8453: "0x4200000000000000000000000000000000000006",
|
|
@@ -4513,7 +4797,7 @@ var OwneySDK = class {
|
|
|
4513
4797
|
async ensureSwapChain(chainId) {
|
|
4514
4798
|
const provider = this.requireConnectedProvider();
|
|
4515
4799
|
const state = this.requireState();
|
|
4516
|
-
const chain =
|
|
4800
|
+
const chain = swapChain(chainId);
|
|
4517
4801
|
if (!chain) {
|
|
4518
4802
|
throw new OwneyError(
|
|
4519
4803
|
"CHAIN_UNSUPPORTED",
|
|
@@ -4531,6 +4815,111 @@ var OwneySDK = class {
|
|
|
4531
4815
|
chainId
|
|
4532
4816
|
);
|
|
4533
4817
|
}
|
|
4818
|
+
/**
|
|
4819
|
+
* Turn a native-source cross-chain quote into an ERC-20 one.
|
|
4820
|
+
*
|
|
4821
|
+
* Fusion+ cannot escrow native value, so the quote arrives with
|
|
4822
|
+
* `requiresOnchainOrder` set and the relayer would refuse the order after
|
|
4823
|
+
* the user signed it (ROUT-242). Wrapping first costs one transaction and
|
|
4824
|
+
* puts the swap back on the ERC-20 rail that already works, rather than
|
|
4825
|
+
* adding a second order type that would need its own escrow handling.
|
|
4826
|
+
*
|
|
4827
|
+
* Re-quoted afterwards rather than patched: the amount wrapped may be less
|
|
4828
|
+
* than the amount quoted (gas has to come out of the same balance), and
|
|
4829
|
+
* every executor dep — allowance, permit, balances — is derived from the
|
|
4830
|
+
* quote's source token. A quote naming ETH would have all of them reading
|
|
4831
|
+
* the wrong asset.
|
|
4832
|
+
*/
|
|
4833
|
+
async wrapNativeSource(quote, walletAddress, onStage, direction) {
|
|
4834
|
+
const wrapper = requireWrapper(quote);
|
|
4835
|
+
const chainId = quote.src.chainId;
|
|
4836
|
+
const chain = swapChain(chainId);
|
|
4837
|
+
const provider = this.requireConnectedProvider();
|
|
4838
|
+
const publicClient = createPublicClient2({
|
|
4839
|
+
chain,
|
|
4840
|
+
transport: swapReadTransport(chainId, this.zyfaiRpcUrls)
|
|
4841
|
+
});
|
|
4842
|
+
const wallet = createWalletClient({
|
|
4843
|
+
account: walletAddress,
|
|
4844
|
+
chain,
|
|
4845
|
+
transport: custom(provider)
|
|
4846
|
+
});
|
|
4847
|
+
onStage?.("wrapping");
|
|
4848
|
+
const { wrapped } = await wrapNative(
|
|
4849
|
+
{
|
|
4850
|
+
readNativeBalance: () => publicClient.getBalance({
|
|
4851
|
+
address: walletAddress
|
|
4852
|
+
}),
|
|
4853
|
+
estimateWrapGas: (to, value) => publicClient.estimateGas({
|
|
4854
|
+
account: walletAddress,
|
|
4855
|
+
to,
|
|
4856
|
+
data: WRAP_SELECTOR,
|
|
4857
|
+
value
|
|
4858
|
+
}),
|
|
4859
|
+
// The 1559 ceiling, not the spot price: a wallet bills against the
|
|
4860
|
+
// former and the two differ wildly on a quiet chain.
|
|
4861
|
+
getMaxFeePerGas: async () => {
|
|
4862
|
+
const fees = await publicClient.estimateFeesPerGas();
|
|
4863
|
+
return fees.maxFeePerGas ?? publicClient.getGasPrice();
|
|
4864
|
+
},
|
|
4865
|
+
sendTransaction: async (tx, onSubmitted) => {
|
|
4866
|
+
const hash = await wallet.sendTransaction({
|
|
4867
|
+
to: tx.to,
|
|
4868
|
+
data: tx.data,
|
|
4869
|
+
value: BigInt(tx.value),
|
|
4870
|
+
account: walletAddress,
|
|
4871
|
+
chain
|
|
4872
|
+
});
|
|
4873
|
+
onSubmitted?.(hash);
|
|
4874
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
4875
|
+
hash,
|
|
4876
|
+
confirmations: 1,
|
|
4877
|
+
timeout: receiptTimeoutMs(chainId)
|
|
4878
|
+
});
|
|
4879
|
+
if (receipt.status !== "success") {
|
|
4880
|
+
throw new OwneyError(
|
|
4881
|
+
"SWAP_REQUEST_FAILED",
|
|
4882
|
+
`Wrapping your ${quote.src.symbol} failed (tx ${hash}). Nothing was swapped.`,
|
|
4883
|
+
{ hash }
|
|
4884
|
+
);
|
|
4885
|
+
}
|
|
4886
|
+
return hash;
|
|
4887
|
+
},
|
|
4888
|
+
ensureChain: (id) => this.ensureSwapChain(id)
|
|
4889
|
+
},
|
|
4890
|
+
{
|
|
4891
|
+
chainId,
|
|
4892
|
+
wrapper: wrapper.address,
|
|
4893
|
+
nativeSymbol: quote.src.symbol,
|
|
4894
|
+
requested: BigInt(quote.src.amount),
|
|
4895
|
+
onSubmitted: () => onStage?.("wrapped")
|
|
4896
|
+
}
|
|
4897
|
+
);
|
|
4898
|
+
debugLog("owney-sdk", "swap: wrapped native source", {
|
|
4899
|
+
symbol: wrapper.symbol,
|
|
4900
|
+
amount: wrapped.toString()
|
|
4901
|
+
});
|
|
4902
|
+
try {
|
|
4903
|
+
return await this.swapApi().quote({
|
|
4904
|
+
from: { chainId, symbol: wrapper.symbol, amount: wrapped.toString() },
|
|
4905
|
+
to: { chainId: quote.dst.chainId, symbol: quote.dst.symbol },
|
|
4906
|
+
walletAddress,
|
|
4907
|
+
...direction ? { direction } : {}
|
|
4908
|
+
});
|
|
4909
|
+
} catch (error) {
|
|
4910
|
+
throw new OwneyError(
|
|
4911
|
+
"SWAP_REQUEST_FAILED",
|
|
4912
|
+
`Your ${quote.src.symbol} was wrapped to ${wrapper.symbol}, but the swap couldn't be priced. The funds are in your wallet as ${wrapper.symbol} \u2014 swap that instead, no need to wrap again.`,
|
|
4913
|
+
{
|
|
4914
|
+
wrappedSymbol: wrapper.symbol,
|
|
4915
|
+
wrappedAmount: wrapped.toString(),
|
|
4916
|
+
chainId,
|
|
4917
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
4918
|
+
...error instanceof OwneyError ? { causeCode: error.code } : {}
|
|
4919
|
+
}
|
|
4920
|
+
);
|
|
4921
|
+
}
|
|
4922
|
+
}
|
|
4534
4923
|
/**
|
|
4535
4924
|
* Binds the executor's abstract deps to this client's wallet.
|
|
4536
4925
|
*
|
|
@@ -4540,8 +4929,8 @@ var OwneySDK = class {
|
|
|
4540
4929
|
buildSwapDeps(quote) {
|
|
4541
4930
|
const state = this.requireState();
|
|
4542
4931
|
const provider = this.requireConnectedProvider();
|
|
4543
|
-
const srcChain =
|
|
4544
|
-
const dstChain =
|
|
4932
|
+
const srcChain = swapChain(quote.src.chainId);
|
|
4933
|
+
const dstChain = swapChain(quote.dst.chainId);
|
|
4545
4934
|
const wallet = createWalletClient({
|
|
4546
4935
|
account: state.walletAddress,
|
|
4547
4936
|
chain: srcChain,
|
|
@@ -4671,13 +5060,59 @@ var OwneySDK = class {
|
|
|
4671
5060
|
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
4672
5061
|
now: () => Date.now()
|
|
4673
5062
|
},
|
|
4674
|
-
runner:
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
5063
|
+
runner: this.swapRunnerDeps()
|
|
5064
|
+
};
|
|
5065
|
+
}
|
|
5066
|
+
/**
|
|
5067
|
+
* Waits until the WALLET'S OWN node reports at least `amount` of `token`.
|
|
5068
|
+
*
|
|
5069
|
+
* Separate from the swap's arrival measurement on purpose: that one runs on
|
|
5070
|
+
* a pinned HTTP RPC, and what matters here is the view the deposit will
|
|
5071
|
+
* actually use. Returns whether it got there — a courtesy, not a gate, since
|
|
5072
|
+
* the deposit's own check is the authority and refusing to even attempt
|
|
5073
|
+
* would strand funds we might have been able to move.
|
|
5074
|
+
*/
|
|
5075
|
+
async walletSeesBalance(chainId, token, amount) {
|
|
5076
|
+
const provider = this.requireConnectedProvider();
|
|
5077
|
+
const state = this.requireState();
|
|
5078
|
+
const pub = createPublicClient2({
|
|
5079
|
+
chain: swapChain(chainId),
|
|
5080
|
+
transport: custom(provider)
|
|
5081
|
+
});
|
|
5082
|
+
const { arrived } = await awaitBalanceIncrease({
|
|
5083
|
+
readBalance: () => pub.readContract({
|
|
5084
|
+
address: token,
|
|
5085
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
5086
|
+
functionName: "balanceOf",
|
|
5087
|
+
args: [state.walletAddress]
|
|
5088
|
+
}),
|
|
5089
|
+
// `awaitBalanceIncrease` resolves on the first read STRICTLY above the
|
|
5090
|
+
// baseline, so one below the target means "at least the target".
|
|
5091
|
+
baseline: amount - 1n,
|
|
5092
|
+
timeoutMs: WALLET_CATCHUP_TIMEOUT_MS
|
|
5093
|
+
});
|
|
5094
|
+
return arrived !== null;
|
|
5095
|
+
}
|
|
5096
|
+
/**
|
|
5097
|
+
* Runs a quoted swap. A named seam rather than an inline `executeSwap` call
|
|
5098
|
+
* so the deposit half of `swapAndDeposit` can be exercised on its own.
|
|
5099
|
+
*/
|
|
5100
|
+
runSwap(quote, options) {
|
|
5101
|
+
return executeSwap(this.buildSwapDeps(quote), { quote, ...options });
|
|
5102
|
+
}
|
|
5103
|
+
/**
|
|
5104
|
+
* The half of the swap deps that only ever talks to the routing API.
|
|
5105
|
+
*
|
|
5106
|
+
* Split out because resuming an order needs exactly this and nothing else —
|
|
5107
|
+
* no provider, no wallet client, no chain. See `resumePendingSwaps`.
|
|
5108
|
+
*/
|
|
5109
|
+
swapRunnerDeps() {
|
|
5110
|
+
return {
|
|
5111
|
+
readyForSecrets: (h) => this.swapApi().readyForSecrets(h),
|
|
5112
|
+
submitSecret: (h, secret) => this.swapApi().submitSecret(h, secret),
|
|
5113
|
+
orderStatus: (h) => this.swapApi().orderStatus(h),
|
|
5114
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
5115
|
+
now: () => Date.now()
|
|
4681
5116
|
};
|
|
4682
5117
|
}
|
|
4683
5118
|
/**
|
|
@@ -4689,6 +5124,126 @@ var OwneySDK = class {
|
|
|
4689
5124
|
async getSwapTokens() {
|
|
4690
5125
|
return this.swapApi().listTokens();
|
|
4691
5126
|
}
|
|
5127
|
+
/**
|
|
5128
|
+
* Swaps that landed but were never deposited, newest first.
|
|
5129
|
+
*
|
|
5130
|
+
* The money is already in the wallet in the right asset — what is missing is
|
|
5131
|
+
* an ordinary deposit, which is why this needs no order and no signature to
|
|
5132
|
+
* describe. See `completePendingDeposit`.
|
|
5133
|
+
*/
|
|
5134
|
+
listPendingDeposits() {
|
|
5135
|
+
return listPendingDeposits();
|
|
5136
|
+
}
|
|
5137
|
+
/**
|
|
5138
|
+
* Deposit a swap that already landed.
|
|
5139
|
+
*
|
|
5140
|
+
* Nothing is swapped again: the funds are in the wallet, so this is the
|
|
5141
|
+
* second half of `swapAndDeposit` on its own. Cleared only once the deposit
|
|
5142
|
+
* has actually gone through — a failure here leaves the record in place so it
|
|
5143
|
+
* can be offered again rather than silently dropped.
|
|
5144
|
+
*/
|
|
5145
|
+
async completePendingDeposit(id) {
|
|
5146
|
+
const entry = listPendingDeposits().find((p) => p.id === id);
|
|
5147
|
+
if (!entry) {
|
|
5148
|
+
throw new OwneyError(
|
|
5149
|
+
"SWAP_REQUEST_FAILED",
|
|
5150
|
+
"That swap is no longer waiting to be added.",
|
|
5151
|
+
{ id }
|
|
5152
|
+
);
|
|
5153
|
+
}
|
|
5154
|
+
await this.ensureSwapChain(entry.chainId);
|
|
5155
|
+
const deposit = await this.deposit({
|
|
5156
|
+
amount: entry.received,
|
|
5157
|
+
asset: entry.symbol,
|
|
5158
|
+
...entry.agentId ? { agentId: entry.agentId } : {}
|
|
5159
|
+
});
|
|
5160
|
+
clearPendingDeposit(entry.id);
|
|
5161
|
+
return deposit;
|
|
5162
|
+
}
|
|
5163
|
+
/**
|
|
5164
|
+
* Pick up cross-chain swaps a previous session left in flight.
|
|
5165
|
+
*
|
|
5166
|
+
* A Fusion+ order is only finished when the client publishes a secret for
|
|
5167
|
+
* each fill, once the escrows exist — so a user who closed the tab mid-swap
|
|
5168
|
+
* leaves an order nobody will ever complete, and it expires and refunds
|
|
5169
|
+
* instead of arriving. The preimages are stored for exactly this (24h sweep,
|
|
5170
|
+
* see the secret store); this is the other half.
|
|
5171
|
+
*
|
|
5172
|
+
* Needs NO wallet, deliberately. The signature already happened and the funds
|
|
5173
|
+
* are already escrowed, so finishing is pure API work — demanding a reconnect
|
|
5174
|
+
* first would strand the people it exists for.
|
|
5175
|
+
*
|
|
5176
|
+
* Every order is attempted even if an earlier one throws: they are unrelated
|
|
5177
|
+
* escrows, and one ending badly must not abandon the next one's secrets.
|
|
5178
|
+
* `unfinished` is not a verdict — the order may still settle, so unless the
|
|
5179
|
+
* chain has said refunded or cancelled its preimages are kept.
|
|
5180
|
+
*/
|
|
5181
|
+
async resumePendingSwaps(options = {}) {
|
|
5182
|
+
const pending = listOrders();
|
|
5183
|
+
if (pending.length === 0) return [];
|
|
5184
|
+
const runner = this.swapRunnerDeps();
|
|
5185
|
+
const results = [];
|
|
5186
|
+
for (const order of pending) {
|
|
5187
|
+
try {
|
|
5188
|
+
await runFusionOrder(runner, {
|
|
5189
|
+
orderHash: order.orderHash,
|
|
5190
|
+
secrets: order.secrets,
|
|
5191
|
+
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
|
|
5192
|
+
...options.onStage ? {
|
|
5193
|
+
onStage: (stage) => options.onStage(order.orderHash, stage)
|
|
5194
|
+
} : {}
|
|
5195
|
+
});
|
|
5196
|
+
clearOrder(order.orderHash);
|
|
5197
|
+
results.push({
|
|
5198
|
+
orderHash: order.orderHash,
|
|
5199
|
+
status: "completed"
|
|
5200
|
+
});
|
|
5201
|
+
} catch (error) {
|
|
5202
|
+
if (error instanceof OwneyError && (error.code === "SWAP_ORDER_REFUNDED" || error.code === "SWAP_ORDER_CANCELLED")) {
|
|
5203
|
+
clearOrder(order.orderHash);
|
|
5204
|
+
}
|
|
5205
|
+
results.push({
|
|
5206
|
+
orderHash: order.orderHash,
|
|
5207
|
+
status: "unfinished",
|
|
5208
|
+
...error instanceof OwneyError ? { error } : {}
|
|
5209
|
+
});
|
|
5210
|
+
}
|
|
5211
|
+
}
|
|
5212
|
+
return results;
|
|
5213
|
+
}
|
|
5214
|
+
/**
|
|
5215
|
+
* What the wallet actually holds, across every supported chain, restricted
|
|
5216
|
+
* to tokens a swap could use.
|
|
5217
|
+
*
|
|
5218
|
+
* `getSwapTokens` answers "what may someone pay with in principle" from a
|
|
5219
|
+
* fixed list. This answers "what has this wallet got", which is a different
|
|
5220
|
+
* question and the one a picker needs: a user can hold something routable —
|
|
5221
|
+
* tBTC, say — that no fixed list of ours would ever mention.
|
|
5222
|
+
*
|
|
5223
|
+
* Each chain is asked separately because that is how the balance product is
|
|
5224
|
+
* shaped, and a chain that fails contributes nothing rather than failing the
|
|
5225
|
+
* lot. A picker with two chains in it beats an error. (ROUT-242)
|
|
5226
|
+
*/
|
|
5227
|
+
async getWalletBalances(chainIds) {
|
|
5228
|
+
const state = this.requireState();
|
|
5229
|
+
const chains = chainIds ?? SUPPORTED_CHAIN_IDS;
|
|
5230
|
+
const failedChainIds = [];
|
|
5231
|
+
const results = await Promise.all(
|
|
5232
|
+
chains.map(async (chainId) => {
|
|
5233
|
+
try {
|
|
5234
|
+
const { tokens } = await this.swapApi().walletBalances({
|
|
5235
|
+
chainId,
|
|
5236
|
+
walletAddress: state.walletAddress
|
|
5237
|
+
});
|
|
5238
|
+
return tokens.map((token) => ({ ...token, chainId }));
|
|
5239
|
+
} catch {
|
|
5240
|
+
failedChainIds.push(chainId);
|
|
5241
|
+
return [];
|
|
5242
|
+
}
|
|
5243
|
+
})
|
|
5244
|
+
);
|
|
5245
|
+
return { tokens: results.flat(), failedChainIds };
|
|
5246
|
+
}
|
|
4692
5247
|
/**
|
|
4693
5248
|
* Search the assets a user may pay with, across every supported chain.
|
|
4694
5249
|
*
|
|
@@ -4710,7 +5265,8 @@ var OwneySDK = class {
|
|
|
4710
5265
|
return this.swapApi().searchTokens({
|
|
4711
5266
|
query,
|
|
4712
5267
|
...params.chainId === void 0 ? {} : { chainId: params.chainId },
|
|
4713
|
-
...params.limit === void 0 ? {} : { limit: params.limit }
|
|
5268
|
+
...params.limit === void 0 ? {} : { limit: params.limit },
|
|
5269
|
+
...params.direction === void 0 ? {} : { direction: params.direction }
|
|
4714
5270
|
});
|
|
4715
5271
|
}
|
|
4716
5272
|
/**
|
|
@@ -4749,26 +5305,56 @@ var OwneySDK = class {
|
|
|
4749
5305
|
async swapAndDeposit(options) {
|
|
4750
5306
|
const state = this.requireState();
|
|
4751
5307
|
const api = this.swapApi();
|
|
4752
|
-
const
|
|
5308
|
+
const quoted = await api.quote({
|
|
4753
5309
|
from: options.from,
|
|
4754
5310
|
to: options.to,
|
|
4755
5311
|
walletAddress: state.walletAddress
|
|
4756
5312
|
});
|
|
5313
|
+
const quote = quoted.requiresOnchainOrder ? await this.wrapNativeSource(
|
|
5314
|
+
quoted,
|
|
5315
|
+
state.walletAddress,
|
|
5316
|
+
options.onSwapProgress
|
|
5317
|
+
) : quoted;
|
|
4757
5318
|
await this.ensureSwapChain(quote.src.chainId);
|
|
4758
|
-
const swap = await
|
|
4759
|
-
quote,
|
|
5319
|
+
const swap = await this.runSwap(quote, {
|
|
4760
5320
|
walletAddress: state.walletAddress,
|
|
4761
5321
|
...options.slippage === void 0 ? {} : { slippage: options.slippage },
|
|
4762
5322
|
...options.onSwapProgress ? { onStage: options.onSwapProgress } : {}
|
|
4763
5323
|
});
|
|
4764
5324
|
options.onSwapProgress?.("depositing");
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
5325
|
+
try {
|
|
5326
|
+
await this.ensureSwapChain(quote.dst.chainId);
|
|
5327
|
+
await this.walletSeesBalance(
|
|
5328
|
+
quote.dst.chainId,
|
|
5329
|
+
quote.dst.address,
|
|
5330
|
+
BigInt(swap.received)
|
|
5331
|
+
);
|
|
5332
|
+
const deposit = await this.deposit({
|
|
5333
|
+
amount: swap.received,
|
|
5334
|
+
asset: options.to.symbol,
|
|
5335
|
+
...options.agentId ? { agentId: options.agentId } : {}
|
|
5336
|
+
});
|
|
5337
|
+
return { swap, deposit };
|
|
5338
|
+
} catch (error) {
|
|
5339
|
+
const pending = {
|
|
5340
|
+
id: `${quote.dst.chainId}-${options.to.symbol}-${Date.now()}`,
|
|
5341
|
+
received: swap.received,
|
|
5342
|
+
symbol: options.to.symbol,
|
|
5343
|
+
chainId: quote.dst.chainId,
|
|
5344
|
+
...options.agentId ? { agentId: options.agentId } : {},
|
|
5345
|
+
createdAt: Date.now()
|
|
5346
|
+
};
|
|
5347
|
+
savePendingDeposit(pending);
|
|
5348
|
+
throw new OwneyError(
|
|
5349
|
+
"SWAP_DEPOSIT_PENDING",
|
|
5350
|
+
`The swap went through and your ${options.to.symbol} is in your wallet, but adding it to the agent didn't complete. Nothing needs swapping again.`,
|
|
5351
|
+
{
|
|
5352
|
+
...pending,
|
|
5353
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
5354
|
+
...error instanceof OwneyError ? { causeCode: error.code } : {}
|
|
5355
|
+
}
|
|
5356
|
+
);
|
|
5357
|
+
}
|
|
4772
5358
|
}
|
|
4773
5359
|
/**
|
|
4774
5360
|
* Withdraw from an agent and swap the proceeds into whatever the user wants
|
|
@@ -5616,8 +6202,8 @@ import { SiweMessage } from "siwe";
|
|
|
5616
6202
|
import { ZyfaiSDK as ZyfaiSDK2 } from "@zyfai/sdk";
|
|
5617
6203
|
|
|
5618
6204
|
// src/agents/zyfai/zyfai.siwx-cache.ts
|
|
5619
|
-
var
|
|
5620
|
-
var
|
|
6205
|
+
var KEY_PREFIX4 = "owney.siwx.session";
|
|
6206
|
+
var storage4 = () => {
|
|
5621
6207
|
if (typeof window === "undefined") return null;
|
|
5622
6208
|
try {
|
|
5623
6209
|
return window.localStorage;
|
|
@@ -5625,8 +6211,8 @@ var storage3 = () => {
|
|
|
5625
6211
|
return null;
|
|
5626
6212
|
}
|
|
5627
6213
|
};
|
|
5628
|
-
var buildKey2 = (address) => `${
|
|
5629
|
-
var legacyKeyPrefix2 = (address) => `${
|
|
6214
|
+
var buildKey2 = (address) => `${KEY_PREFIX4}:${address.toLowerCase()}`;
|
|
6215
|
+
var legacyKeyPrefix2 = (address) => `${KEY_PREFIX4}:${address.toLowerCase()}:`;
|
|
5630
6216
|
var memorySiwxSessions = /* @__PURE__ */ new Map();
|
|
5631
6217
|
var readLegacySiwxSession = (store, address) => {
|
|
5632
6218
|
if (!store) return null;
|
|
@@ -5658,7 +6244,7 @@ var readLegacySiwxSession = (store, address) => {
|
|
|
5658
6244
|
var readSiwxSession = (address, chainId) => {
|
|
5659
6245
|
if (typeof window === "undefined") return null;
|
|
5660
6246
|
const key2 = buildKey2(address);
|
|
5661
|
-
const store =
|
|
6247
|
+
const store = storage4();
|
|
5662
6248
|
let raw = null;
|
|
5663
6249
|
try {
|
|
5664
6250
|
raw = store?.getItem(key2) ?? null;
|
|
@@ -5688,7 +6274,7 @@ var writeSiwxSession = (address, _chainId, session) => {
|
|
|
5688
6274
|
if (typeof window === "undefined") return;
|
|
5689
6275
|
const key2 = buildKey2(address);
|
|
5690
6276
|
memorySiwxSessions.set(key2, session);
|
|
5691
|
-
const store =
|
|
6277
|
+
const store = storage4();
|
|
5692
6278
|
try {
|
|
5693
6279
|
store?.setItem(key2, JSON.stringify(session));
|
|
5694
6280
|
} catch {
|
|
@@ -5697,7 +6283,7 @@ var writeSiwxSession = (address, _chainId, session) => {
|
|
|
5697
6283
|
var clearSiwxSession = (address, _chainId) => {
|
|
5698
6284
|
const key2 = buildKey2(address);
|
|
5699
6285
|
memorySiwxSessions.delete(key2);
|
|
5700
|
-
const store =
|
|
6286
|
+
const store = storage4();
|
|
5701
6287
|
try {
|
|
5702
6288
|
store?.removeItem(key2);
|
|
5703
6289
|
} catch {
|
|
@@ -5830,6 +6416,7 @@ export {
|
|
|
5830
6416
|
OwneyError,
|
|
5831
6417
|
OwneySDK,
|
|
5832
6418
|
createOwneySIWX,
|
|
6419
|
+
listPendingDeposits,
|
|
5833
6420
|
listOrders as listPendingSwaps,
|
|
5834
6421
|
setOwneyDebug
|
|
5835
6422
|
};
|