@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.cjs
CHANGED
|
@@ -27,6 +27,7 @@ __export(index_exports, {
|
|
|
27
27
|
OwneyError: () => OwneyError,
|
|
28
28
|
OwneySDK: () => OwneySDK,
|
|
29
29
|
createOwneySIWX: () => createOwneySIWX,
|
|
30
|
+
listPendingDeposits: () => listPendingDeposits,
|
|
30
31
|
listPendingSwaps: () => listOrders,
|
|
31
32
|
setOwneyDebug: () => setOwneyDebug
|
|
32
33
|
});
|
|
@@ -2309,6 +2310,21 @@ function createSwapApi(baseUrl, apiKey) {
|
|
|
2309
2310
|
* Already filtered server-side to what a quote will accept, so anything
|
|
2310
2311
|
* returned here can be paid with.
|
|
2311
2312
|
*/
|
|
2313
|
+
/**
|
|
2314
|
+
* Tokens the wallet holds on one chain.
|
|
2315
|
+
*
|
|
2316
|
+
* Already filtered upstream to non-zero balances of tokens the routing API
|
|
2317
|
+
* would quote — 1inch answers with every token it knows about, zeros
|
|
2318
|
+
* included, and the curation that decides what is offerable lives there.
|
|
2319
|
+
*/
|
|
2320
|
+
walletBalances: (params) => request(
|
|
2321
|
+
baseUrl,
|
|
2322
|
+
apiKey,
|
|
2323
|
+
`/balances?${new URLSearchParams({
|
|
2324
|
+
chainId: String(params.chainId),
|
|
2325
|
+
walletAddress: params.walletAddress
|
|
2326
|
+
})}`
|
|
2327
|
+
),
|
|
2312
2328
|
searchTokens: (params) => request(
|
|
2313
2329
|
baseUrl,
|
|
2314
2330
|
apiKey,
|
|
@@ -2317,7 +2333,8 @@ function createSwapApi(baseUrl, apiKey) {
|
|
|
2317
2333
|
// Omitted, the routing API searches every supported chain — which is
|
|
2318
2334
|
// what a picker wants, since its rows already span all three.
|
|
2319
2335
|
...params.chainId === void 0 ? {} : { chainId: String(params.chainId) },
|
|
2320
|
-
...params.limit === void 0 ? {} : { limit: String(params.limit) }
|
|
2336
|
+
...params.limit === void 0 ? {} : { limit: String(params.limit) },
|
|
2337
|
+
...params.direction === void 0 ? {} : { direction: params.direction }
|
|
2321
2338
|
}).toString()}`
|
|
2322
2339
|
),
|
|
2323
2340
|
/**
|
|
@@ -2403,12 +2420,41 @@ function createSwapApi(baseUrl, apiKey) {
|
|
|
2403
2420
|
// src/lib/swap/swap.rpc.ts
|
|
2404
2421
|
var import_viem2 = require("viem");
|
|
2405
2422
|
var DEFAULT_RPC_URLS = {
|
|
2406
|
-
|
|
2423
|
+
// NOT cloudflare-eth.com, which used to lead this list. It answers
|
|
2424
|
+
// eth_chainId happily, errors on eth_blockNumber, and — the part that
|
|
2425
|
+
// actually broke things — returns `result: null` for the receipt of a
|
|
2426
|
+
// transaction that is demonstrably mined. Null is a VALID answer meaning
|
|
2427
|
+
// "not found", so viem's fallback has no reason to try the next endpoint:
|
|
2428
|
+
// waitForTransactionReceipt simply polls until it times out, which on this
|
|
2429
|
+
// chain is ten minutes of a spinner (ROUT-242). An endpoint that ERRORS is
|
|
2430
|
+
// safe; an endpoint that lies quietly is not.
|
|
2431
|
+
1: ["https://ethereum-rpc.publicnode.com", "https://eth.drpc.org"],
|
|
2407
2432
|
8453: ["https://mainnet.base.org", "https://base-rpc.publicnode.com"],
|
|
2408
2433
|
42161: [
|
|
2409
2434
|
"https://arb1.arbitrum.io/rpc",
|
|
2410
2435
|
"https://arbitrum-one-rpc.publicnode.com"
|
|
2411
|
-
]
|
|
2436
|
+
],
|
|
2437
|
+
// Added with the wider swap chain set (ROUT-242, comment 15982). Every one
|
|
2438
|
+
// of these answered with `access-control-allow-origin: *` on 2026-09-29;
|
|
2439
|
+
// a chain without two verified endpoints does not belong in the list.
|
|
2440
|
+
10: ["https://mainnet.optimism.io", "https://optimism-rpc.publicnode.com"],
|
|
2441
|
+
// polygon-rpc.com led this list until it started answering every call with
|
|
2442
|
+
// "API key disabled, reason: tenant disabled". It errors rather than lying,
|
|
2443
|
+
// so the fallback did cover it — at the cost of a failed request in front of
|
|
2444
|
+
// every Polygon read.
|
|
2445
|
+
137: ["https://polygon-bor-rpc.publicnode.com", "https://polygon.drpc.org"],
|
|
2446
|
+
56: ["https://bsc-dataseed.bnbchain.org", "https://bsc-rpc.publicnode.com"],
|
|
2447
|
+
43114: [
|
|
2448
|
+
"https://api.avax.network/ext/bc/C/rpc",
|
|
2449
|
+
"https://avalanche-c-chain-rpc.publicnode.com"
|
|
2450
|
+
],
|
|
2451
|
+
143: ["https://rpc.monad.xyz", "https://rpc1.monad.xyz"],
|
|
2452
|
+
// ONE endpoint, not two: of the six the public chain registry lists for Arc,
|
|
2453
|
+
// this is the only one that answers a browser at all. viem ships none. It
|
|
2454
|
+
// was checked with a real eth_chainId call (0x13b2), not just a CORS header,
|
|
2455
|
+
// but a single provider is a single point of failure - add a second when one
|
|
2456
|
+
// exists.
|
|
2457
|
+
5042: ["https://rpc.beamrpc.com"]
|
|
2412
2458
|
};
|
|
2413
2459
|
function swapReadTransport(chainId, overrides) {
|
|
2414
2460
|
const override = overrides?.[chainId];
|
|
@@ -2421,6 +2467,157 @@ function receiptTimeoutMs(chainId) {
|
|
|
2421
2467
|
return chainId === 1 ? 6e5 : 18e4;
|
|
2422
2468
|
}
|
|
2423
2469
|
|
|
2470
|
+
// src/lib/swap/swap.wrap.ts
|
|
2471
|
+
var WRAP_SELECTOR = "0xd0e30db0";
|
|
2472
|
+
var FALLBACK_WRAP_GAS = 50000n;
|
|
2473
|
+
var APPROVE_GAS = 60000n;
|
|
2474
|
+
var GAS_SAFETY = 2n;
|
|
2475
|
+
var MIN_WALLET_GAS_PRICE = 3000000000n;
|
|
2476
|
+
async function wrapNative(deps, options) {
|
|
2477
|
+
const { chainId, wrapper, nativeSymbol, requested } = options;
|
|
2478
|
+
const balance = await deps.readNativeBalance();
|
|
2479
|
+
let wrapGas;
|
|
2480
|
+
try {
|
|
2481
|
+
wrapGas = await deps.estimateWrapGas(wrapper, requested);
|
|
2482
|
+
} catch (error) {
|
|
2483
|
+
wrapGas = FALLBACK_WRAP_GAS;
|
|
2484
|
+
debugLog("owney-sdk", "wrap: gas estimate failed, using the fallback", {
|
|
2485
|
+
fallback: FALLBACK_WRAP_GAS.toString(),
|
|
2486
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2487
|
+
});
|
|
2488
|
+
}
|
|
2489
|
+
const measured = await deps.getMaxFeePerGas();
|
|
2490
|
+
const gasPrice = measured > MIN_WALLET_GAS_PRICE ? measured : MIN_WALLET_GAS_PRICE;
|
|
2491
|
+
const reserve = (wrapGas + APPROVE_GAS) * gasPrice * GAS_SAFETY;
|
|
2492
|
+
const spendable = balance > reserve ? balance - reserve : 0n;
|
|
2493
|
+
const wrapped = requested < spendable ? requested : spendable;
|
|
2494
|
+
debugLog("owney-sdk", "wrap: sizing", {
|
|
2495
|
+
balance: balance.toString(),
|
|
2496
|
+
requested: requested.toString(),
|
|
2497
|
+
measuredGasPrice: measured.toString(),
|
|
2498
|
+
reservedAtGasPrice: gasPrice.toString(),
|
|
2499
|
+
reserve: reserve.toString(),
|
|
2500
|
+
wrapped: wrapped.toString()
|
|
2501
|
+
});
|
|
2502
|
+
if (wrapped <= 0n) {
|
|
2503
|
+
throw new OwneyError(
|
|
2504
|
+
"SWAP_INSUFFICIENT_GAS",
|
|
2505
|
+
`Not enough ${nativeSymbol} to cover both the swap and its network fees. Leave a little ${nativeSymbol} for gas, or swap a smaller amount.`,
|
|
2506
|
+
{
|
|
2507
|
+
chainId,
|
|
2508
|
+
// Named in the details as well as the message so the app's own copy
|
|
2509
|
+
// can say "leave a little POL" rather than re-deriving it.
|
|
2510
|
+
symbol: nativeSymbol,
|
|
2511
|
+
balance: balance.toString(),
|
|
2512
|
+
requested: requested.toString(),
|
|
2513
|
+
reserve: reserve.toString()
|
|
2514
|
+
}
|
|
2515
|
+
);
|
|
2516
|
+
}
|
|
2517
|
+
await deps.ensureChain(chainId);
|
|
2518
|
+
debugLog("owney-sdk", "wrap: sending deposit()", {
|
|
2519
|
+
wrapper,
|
|
2520
|
+
value: wrapped.toString()
|
|
2521
|
+
});
|
|
2522
|
+
const txHash = await deps.sendTransaction(
|
|
2523
|
+
{ to: wrapper, data: WRAP_SELECTOR, value: wrapped.toString() },
|
|
2524
|
+
options.onSubmitted
|
|
2525
|
+
);
|
|
2526
|
+
return { wrapped, txHash };
|
|
2527
|
+
}
|
|
2528
|
+
function requireWrapper(quote) {
|
|
2529
|
+
const wrapper = quote.wrappedNative;
|
|
2530
|
+
if (!wrapper?.address) {
|
|
2531
|
+
throw new OwneyError(
|
|
2532
|
+
"SWAP_NATIVE_UNSUPPORTED",
|
|
2533
|
+
`Swapping ${quote.src.symbol} from this chain to another isn't supported yet. Swap a token instead, or move to the destination chain first.`,
|
|
2534
|
+
{ chainId: quote.src.chainId, symbol: quote.src.symbol }
|
|
2535
|
+
);
|
|
2536
|
+
}
|
|
2537
|
+
return { symbol: wrapper.symbol, address: wrapper.address };
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
// src/lib/swap/swap.chains.ts
|
|
2541
|
+
var import_chains2 = require("viem/chains");
|
|
2542
|
+
var CHAINS = {
|
|
2543
|
+
8453: import_chains2.base,
|
|
2544
|
+
42161: import_chains2.arbitrum,
|
|
2545
|
+
1: import_chains2.mainnet,
|
|
2546
|
+
10: import_chains2.optimism,
|
|
2547
|
+
137: import_chains2.polygon,
|
|
2548
|
+
56: import_chains2.bsc,
|
|
2549
|
+
43114: import_chains2.avalanche,
|
|
2550
|
+
143: import_chains2.monad,
|
|
2551
|
+
5042: import_chains2.arc
|
|
2552
|
+
};
|
|
2553
|
+
var SWAP_CHAIN_IDS = Object.keys(CHAINS).map(Number);
|
|
2554
|
+
function swapChain(chainId) {
|
|
2555
|
+
return CHAINS[chainId];
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2558
|
+
// src/lib/swap/swap.arrival.ts
|
|
2559
|
+
var DEFAULT_TIMEOUT_MS = 18e4;
|
|
2560
|
+
var DEFAULT_POLL_MS = 4e3;
|
|
2561
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2562
|
+
async function awaitWithdrawalArrival(options) {
|
|
2563
|
+
const { arrived, lastReadError } = await awaitBalanceIncrease(options);
|
|
2564
|
+
if (arrived !== null) return arrived;
|
|
2565
|
+
throw new OwneyError(
|
|
2566
|
+
"WITHDRAW_ARRIVAL_TIMEOUT",
|
|
2567
|
+
"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.",
|
|
2568
|
+
{
|
|
2569
|
+
baseline: options.baseline.toString(),
|
|
2570
|
+
waitedMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
2571
|
+
...lastReadError ? { lastReadError } : {}
|
|
2572
|
+
}
|
|
2573
|
+
);
|
|
2574
|
+
}
|
|
2575
|
+
async function awaitBalanceIncrease(options) {
|
|
2576
|
+
const {
|
|
2577
|
+
readBalance,
|
|
2578
|
+
baseline,
|
|
2579
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
2580
|
+
pollMs = DEFAULT_POLL_MS
|
|
2581
|
+
} = options;
|
|
2582
|
+
const deadline = Date.now() + timeoutMs;
|
|
2583
|
+
debugLog("owney-sdk", "arrival: waiting for funds to land", {
|
|
2584
|
+
baseline: baseline.toString(),
|
|
2585
|
+
timeoutMs
|
|
2586
|
+
});
|
|
2587
|
+
let lastError;
|
|
2588
|
+
for (; ; ) {
|
|
2589
|
+
try {
|
|
2590
|
+
const balance = await readBalance();
|
|
2591
|
+
if (balance > baseline) {
|
|
2592
|
+
const arrived = balance - baseline;
|
|
2593
|
+
debugLog("owney-sdk", "arrival: funds landed", {
|
|
2594
|
+
arrived: arrived.toString()
|
|
2595
|
+
});
|
|
2596
|
+
return { arrived };
|
|
2597
|
+
}
|
|
2598
|
+
} catch (error) {
|
|
2599
|
+
lastError = error;
|
|
2600
|
+
debugLog("owney-sdk", "arrival: balance read failed, retrying", {
|
|
2601
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2602
|
+
});
|
|
2603
|
+
}
|
|
2604
|
+
if (Date.now() >= deadline) {
|
|
2605
|
+
debugLog("owney-sdk", "arrival: nothing landed before the deadline", {
|
|
2606
|
+
baseline: baseline.toString(),
|
|
2607
|
+
waitedMs: timeoutMs,
|
|
2608
|
+
...lastError ? {
|
|
2609
|
+
lastReadError: lastError instanceof Error ? lastError.message : String(lastError)
|
|
2610
|
+
} : {}
|
|
2611
|
+
});
|
|
2612
|
+
return lastError ? {
|
|
2613
|
+
arrived: null,
|
|
2614
|
+
lastReadError: lastError instanceof Error ? lastError.message : String(lastError)
|
|
2615
|
+
} : { arrived: null };
|
|
2616
|
+
}
|
|
2617
|
+
await sleep(pollMs);
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2424
2621
|
// src/lib/permit2.ts
|
|
2425
2622
|
var import_viem3 = require("viem");
|
|
2426
2623
|
var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
@@ -2544,17 +2741,17 @@ var SWAP_TERMINAL_STATUSES = [
|
|
|
2544
2741
|
var isSwapTerminal = (status) => SWAP_TERMINAL_STATUSES.includes(status);
|
|
2545
2742
|
|
|
2546
2743
|
// src/lib/swap/swap.order-runner.ts
|
|
2547
|
-
var
|
|
2744
|
+
var DEFAULT_POLL_MS2 = 5e3;
|
|
2548
2745
|
var MAX_BACKOFF_MS = 3e4;
|
|
2549
|
-
var backoffFor = (failures,
|
|
2550
|
-
var
|
|
2746
|
+
var backoffFor = (failures, base4) => Math.min(base4 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS);
|
|
2747
|
+
var DEFAULT_TIMEOUT_MS2 = 15 * 60 * 1e3;
|
|
2551
2748
|
async function runFusionOrder(deps, options) {
|
|
2552
2749
|
const {
|
|
2553
2750
|
orderHash,
|
|
2554
2751
|
secrets,
|
|
2555
2752
|
onStage,
|
|
2556
|
-
pollIntervalMs =
|
|
2557
|
-
timeoutMs =
|
|
2753
|
+
pollIntervalMs = DEFAULT_POLL_MS2,
|
|
2754
|
+
timeoutMs = DEFAULT_TIMEOUT_MS2
|
|
2558
2755
|
} = options;
|
|
2559
2756
|
const deadline = deps.now() + timeoutMs;
|
|
2560
2757
|
let failures = 0;
|
|
@@ -2565,7 +2762,11 @@ async function runFusionOrder(deps, options) {
|
|
|
2565
2762
|
throw new OwneyError(
|
|
2566
2763
|
"SWAP_REQUEST_FAILED",
|
|
2567
2764
|
"Timed out waiting for the swap to settle. It may still complete \u2014 check the order status before retrying.",
|
|
2568
|
-
|
|
2765
|
+
// Marks the one failure that means "we stopped watching a live order",
|
|
2766
|
+
// as opposed to a verdict or a prompt nobody answered. The executor
|
|
2767
|
+
// checks the wallet before believing it: resolvers do not stop filling
|
|
2768
|
+
// because we stopped looking (ROUT-242, comment 15980).
|
|
2769
|
+
{ orderHash, settlementUnconfirmed: true }
|
|
2569
2770
|
);
|
|
2570
2771
|
}
|
|
2571
2772
|
let ready;
|
|
@@ -2618,10 +2819,10 @@ async function runFusionOrder(deps, options) {
|
|
|
2618
2819
|
}
|
|
2619
2820
|
|
|
2620
2821
|
// src/lib/swap/swap.same-chain-runner.ts
|
|
2621
|
-
var
|
|
2822
|
+
var DEFAULT_POLL_MS3 = 5e3;
|
|
2622
2823
|
var MAX_BACKOFF_MS2 = 3e4;
|
|
2623
|
-
var backoffFor2 = (failures,
|
|
2624
|
-
var
|
|
2824
|
+
var backoffFor2 = (failures, base4) => Math.min(base4 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS2);
|
|
2825
|
+
var DEFAULT_TIMEOUT_MS3 = 3 * 60 * 1e3;
|
|
2625
2826
|
function explain(status) {
|
|
2626
2827
|
switch (status) {
|
|
2627
2828
|
case "expired":
|
|
@@ -2644,8 +2845,8 @@ async function runSameChainOrder(deps, options) {
|
|
|
2644
2845
|
const {
|
|
2645
2846
|
orderHash,
|
|
2646
2847
|
onStage,
|
|
2647
|
-
pollIntervalMs =
|
|
2648
|
-
timeoutMs =
|
|
2848
|
+
pollIntervalMs = DEFAULT_POLL_MS3,
|
|
2849
|
+
timeoutMs = DEFAULT_TIMEOUT_MS3
|
|
2649
2850
|
} = options;
|
|
2650
2851
|
const deadline = deps.now() + timeoutMs;
|
|
2651
2852
|
let failures = 0;
|
|
@@ -2743,6 +2944,35 @@ function listOrders(now = Date.now()) {
|
|
|
2743
2944
|
}
|
|
2744
2945
|
|
|
2745
2946
|
// src/lib/swap/swap.executor.ts
|
|
2947
|
+
var WALLET_PROMPT_TIMEOUT_MS = 3 * 60 * 1e3;
|
|
2948
|
+
async function awaitWalletPrompt(prompt, what) {
|
|
2949
|
+
let timer;
|
|
2950
|
+
try {
|
|
2951
|
+
return await Promise.race([
|
|
2952
|
+
prompt(),
|
|
2953
|
+
new Promise((_, reject) => {
|
|
2954
|
+
timer = setTimeout(() => {
|
|
2955
|
+
reject(
|
|
2956
|
+
new OwneyError(
|
|
2957
|
+
"SWAP_WALLET_PROMPT_TIMEOUT",
|
|
2958
|
+
// Naming the stale request matters: the wallet is still holding
|
|
2959
|
+
// it, which is why the next connection attempt is refused too
|
|
2960
|
+
// ("Connection can be declined if a previous request is still
|
|
2961
|
+
// active"). Dismissing it is what actually unblocks them.
|
|
2962
|
+
`Your wallet didn't answer the request to ${what}. Open your wallet, dismiss any request still waiting there, and try again.`,
|
|
2963
|
+
{ prompt: what, timeoutMs: WALLET_PROMPT_TIMEOUT_MS }
|
|
2964
|
+
)
|
|
2965
|
+
);
|
|
2966
|
+
}, WALLET_PROMPT_TIMEOUT_MS);
|
|
2967
|
+
})
|
|
2968
|
+
]);
|
|
2969
|
+
} finally {
|
|
2970
|
+
if (timer) clearTimeout(timer);
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
function stoppedWatching(error) {
|
|
2974
|
+
return error instanceof OwneyError && error.details?.settlementUnconfirmed === true;
|
|
2975
|
+
}
|
|
2746
2976
|
var DEFAULT_SLIPPAGE = 1;
|
|
2747
2977
|
async function affordableAmount(deps, quoted) {
|
|
2748
2978
|
const balance = await deps.readSourceBalance();
|
|
@@ -2765,21 +2995,39 @@ async function executeSwap(deps, options) {
|
|
|
2765
2995
|
});
|
|
2766
2996
|
const before = await deps.readTargetBalance();
|
|
2767
2997
|
debugLog("owney-sdk", "swap: target balance before", before.toString());
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2998
|
+
let result;
|
|
2999
|
+
let railError;
|
|
3000
|
+
try {
|
|
3001
|
+
result = quote.rail === "classic" ? await runClassic(deps, options) : quote.rail === "fusion" ? await runSameChainFusion(deps, options, walletAddress) : await runFusion(deps, options, walletAddress);
|
|
3002
|
+
} catch (error) {
|
|
3003
|
+
if (!stoppedWatching(error)) throw error;
|
|
3004
|
+
railError = error;
|
|
3005
|
+
debugLog("owney-sdk", "swap: rail failed, checking whether funds landed", {
|
|
3006
|
+
message: error instanceof Error ? error.message : String(error)
|
|
3007
|
+
});
|
|
3008
|
+
}
|
|
3009
|
+
const { arrived } = await awaitBalanceIncrease({
|
|
3010
|
+
readBalance: () => deps.readTargetBalance(),
|
|
3011
|
+
baseline: before,
|
|
3012
|
+
...options.arrivalTimeoutMs === void 0 ? {} : { timeoutMs: options.arrivalTimeoutMs },
|
|
3013
|
+
...options.arrivalPollMs === void 0 ? {} : { pollMs: options.arrivalPollMs }
|
|
2774
3014
|
});
|
|
2775
|
-
if (
|
|
3015
|
+
if (arrived === null) {
|
|
3016
|
+
if (railError) throw railError;
|
|
2776
3017
|
throw new OwneyError(
|
|
2777
3018
|
"SWAP_REQUEST_FAILED",
|
|
2778
3019
|
"The swap completed but no funds arrived in the wallet. Check the transaction before retrying.",
|
|
2779
|
-
{ rail: quote.rail, ...result }
|
|
3020
|
+
{ rail: quote.rail, ...result ?? {} }
|
|
2780
3021
|
);
|
|
2781
3022
|
}
|
|
2782
|
-
|
|
3023
|
+
if (railError) {
|
|
3024
|
+
debugLog("owney-sdk", "swap: rail gave up but the funds are here", {
|
|
3025
|
+
received: arrived.toString()
|
|
3026
|
+
});
|
|
3027
|
+
const orderHash = railError.details?.orderHash;
|
|
3028
|
+
if (orderHash) clearOrder(orderHash);
|
|
3029
|
+
}
|
|
3030
|
+
return { received: arrived.toString(), ...result ?? {} };
|
|
2783
3031
|
}
|
|
2784
3032
|
async function runClassic(deps, options) {
|
|
2785
3033
|
const {
|
|
@@ -2859,7 +3107,10 @@ async function runSameChainFusion(deps, options, walletAddress) {
|
|
|
2859
3107
|
});
|
|
2860
3108
|
onStage?.("signing");
|
|
2861
3109
|
await deps.ensureChain(quote.src.chainId);
|
|
2862
|
-
const signature = await
|
|
3110
|
+
const signature = await awaitWalletPrompt(
|
|
3111
|
+
() => deps.signTypedData(built.typedData),
|
|
3112
|
+
"sign the swap"
|
|
3113
|
+
);
|
|
2863
3114
|
debugLog("owney-sdk", "swap: signed, submitting same-chain order");
|
|
2864
3115
|
await deps.api.submitOrder({
|
|
2865
3116
|
// Without this the routing API hands the order to the Fusion+ relayer,
|
|
@@ -2889,7 +3140,10 @@ async function authoriseSpend(deps, options, amount, isNativeSource) {
|
|
|
2889
3140
|
if (current >= amount) return void 0;
|
|
2890
3141
|
onStage?.("approving");
|
|
2891
3142
|
await deps.ensureChain(quote.src.chainId);
|
|
2892
|
-
const permit = quote.permitSupported ? await
|
|
3143
|
+
const permit = quote.permitSupported ? await awaitWalletPrompt(
|
|
3144
|
+
async () => deps.buildPermit?.(quote.spender, MAX_UINT256),
|
|
3145
|
+
`approve ${quote.src.symbol}`
|
|
3146
|
+
) ?? void 0 : void 0;
|
|
2893
3147
|
if (permit) {
|
|
2894
3148
|
debugLog("owney-sdk", "swap: permitting limit order protocol", {
|
|
2895
3149
|
bytes: (permit.length - 2) / 2
|
|
@@ -2903,8 +3157,15 @@ async function authoriseSpend(deps, options, amount, isNativeSource) {
|
|
|
2903
3157
|
async function runFusion(deps, options, walletAddress) {
|
|
2904
3158
|
const { quote, direction, onStage } = options;
|
|
2905
3159
|
const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
|
|
2906
|
-
|
|
2907
|
-
|
|
3160
|
+
if (isNativeSource) {
|
|
3161
|
+
throw new OwneyError(
|
|
3162
|
+
"SWAP_NATIVE_UNSUPPORTED",
|
|
3163
|
+
`${quote.src.symbol} has to be wrapped before it can be swapped to another chain. Nothing has left your wallet.`,
|
|
3164
|
+
{ chainId: quote.src.chainId, symbol: quote.src.symbol }
|
|
3165
|
+
);
|
|
3166
|
+
}
|
|
3167
|
+
const amount = await affordableAmount(deps, BigInt(quote.src.amount));
|
|
3168
|
+
const permit = await authoriseSpend(deps, options, amount, false);
|
|
2908
3169
|
const { secrets, secretHashes } = mintSecrets(quote.secretsCount ?? 1);
|
|
2909
3170
|
onStage?.("quoting");
|
|
2910
3171
|
debugLog("owney-sdk", "swap: building fusion order", {
|
|
@@ -2939,7 +3200,10 @@ async function runFusion(deps, options, walletAddress) {
|
|
|
2939
3200
|
debugLog("owney-sdk", "swap: awaiting signature in wallet", {
|
|
2940
3201
|
signingOnChain: quote.src.chainId
|
|
2941
3202
|
});
|
|
2942
|
-
const signature = await
|
|
3203
|
+
const signature = await awaitWalletPrompt(
|
|
3204
|
+
() => deps.signTypedData(built.typedData),
|
|
3205
|
+
"sign the swap"
|
|
3206
|
+
);
|
|
2943
3207
|
debugLog("owney-sdk", "swap: signed, submitting to relayer");
|
|
2944
3208
|
await deps.api.submitOrder({
|
|
2945
3209
|
srcChainId: quote.src.chainId,
|
|
@@ -3070,54 +3334,64 @@ async function buildErc2612Permit(reads, input) {
|
|
|
3070
3334
|
);
|
|
3071
3335
|
}
|
|
3072
3336
|
|
|
3073
|
-
// src/lib/swap/swap.
|
|
3074
|
-
var
|
|
3075
|
-
var
|
|
3076
|
-
var
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3337
|
+
// src/lib/swap/swap.pending-deposit.ts
|
|
3338
|
+
var KEY_PREFIX3 = "owney.swap.pending-deposit";
|
|
3339
|
+
var MAX_AGE_MS2 = 24 * 60 * 60 * 1e3;
|
|
3340
|
+
var storage3 = () => {
|
|
3341
|
+
if (typeof window === "undefined") return null;
|
|
3342
|
+
try {
|
|
3343
|
+
return window.localStorage;
|
|
3344
|
+
} catch {
|
|
3345
|
+
return null;
|
|
3346
|
+
}
|
|
3347
|
+
};
|
|
3348
|
+
var keyFor2 = (id) => `${KEY_PREFIX3}.${id}`;
|
|
3349
|
+
function savePendingDeposit(entry) {
|
|
3350
|
+
const store = storage3();
|
|
3351
|
+
if (!store) return;
|
|
3352
|
+
try {
|
|
3353
|
+
store.setItem(keyFor2(entry.id), JSON.stringify(entry));
|
|
3354
|
+
} catch {
|
|
3355
|
+
return;
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
function clearPendingDeposit(id) {
|
|
3359
|
+
const store = storage3();
|
|
3360
|
+
if (!store) return;
|
|
3361
|
+
try {
|
|
3362
|
+
store.removeItem(keyFor2(id));
|
|
3363
|
+
} catch {
|
|
3364
|
+
return;
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
function listPendingDeposits(now = Date.now()) {
|
|
3368
|
+
const store = storage3();
|
|
3369
|
+
if (!store) return [];
|
|
3370
|
+
const out = [];
|
|
3371
|
+
try {
|
|
3372
|
+
const keys = [];
|
|
3373
|
+
for (let i = 0; i < store.length; i++) {
|
|
3374
|
+
const key2 = store.key(i);
|
|
3375
|
+
if (key2?.startsWith(`${KEY_PREFIX3}.`)) keys.push(key2);
|
|
3105
3376
|
}
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
lastReadError: lastError instanceof Error ? lastError.message : String(lastError)
|
|
3115
|
-
} : {}
|
|
3377
|
+
for (const key2 of keys) {
|
|
3378
|
+
const raw = store.getItem(key2);
|
|
3379
|
+
if (!raw) continue;
|
|
3380
|
+
try {
|
|
3381
|
+
const parsed = JSON.parse(raw);
|
|
3382
|
+
if (now - parsed.createdAt > MAX_AGE_MS2) {
|
|
3383
|
+
store.removeItem(key2);
|
|
3384
|
+
continue;
|
|
3116
3385
|
}
|
|
3117
|
-
|
|
3386
|
+
out.push(parsed);
|
|
3387
|
+
} catch {
|
|
3388
|
+
store.removeItem(key2);
|
|
3389
|
+
}
|
|
3118
3390
|
}
|
|
3119
|
-
|
|
3391
|
+
} catch {
|
|
3392
|
+
return out;
|
|
3120
3393
|
}
|
|
3394
|
+
return out.sort((a, b) => b.createdAt - a.createdAt);
|
|
3121
3395
|
}
|
|
3122
3396
|
|
|
3123
3397
|
// src/lib/health-report.ts
|
|
@@ -3311,7 +3585,7 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
|
3311
3585
|
|
|
3312
3586
|
// src/client.ts
|
|
3313
3587
|
var import_viem9 = require("viem");
|
|
3314
|
-
var
|
|
3588
|
+
var import_chains3 = require("viem/chains");
|
|
3315
3589
|
|
|
3316
3590
|
// src/lib/transfer-auth.ts
|
|
3317
3591
|
var import_viem7 = require("viem");
|
|
@@ -3352,10 +3626,10 @@ function randomAuthNonce() {
|
|
|
3352
3626
|
// src/lib/sponsor-client.ts
|
|
3353
3627
|
var ROUTING_API_BASE_URL3 = "https://owney-routing-api-243946518160.europe-west4.run.app";
|
|
3354
3628
|
async function postSponsorTransferAuth(input) {
|
|
3355
|
-
const
|
|
3629
|
+
const base4 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
3356
3630
|
let res;
|
|
3357
3631
|
try {
|
|
3358
|
-
res = await fetch(`${
|
|
3632
|
+
res = await fetch(`${base4}/api/v1/sponsor/erc20-transfer-auth`, {
|
|
3359
3633
|
method: "POST",
|
|
3360
3634
|
headers: {
|
|
3361
3635
|
"content-type": "application/json",
|
|
@@ -3392,10 +3666,10 @@ async function postSponsorTransferAuth(input) {
|
|
|
3392
3666
|
return parsed.data;
|
|
3393
3667
|
}
|
|
3394
3668
|
async function postSponsorPermit2Transfer(input) {
|
|
3395
|
-
const
|
|
3669
|
+
const base4 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
3396
3670
|
let res;
|
|
3397
3671
|
try {
|
|
3398
|
-
res = await fetch(`${
|
|
3672
|
+
res = await fetch(`${base4}/api/v1/sponsor/permit2-transfer`, {
|
|
3399
3673
|
method: "POST",
|
|
3400
3674
|
headers: {
|
|
3401
3675
|
"content-type": "application/json",
|
|
@@ -3430,11 +3704,11 @@ async function postSponsorPermit2Transfer(input) {
|
|
|
3430
3704
|
return parsed.data;
|
|
3431
3705
|
}
|
|
3432
3706
|
async function getSponsorRelayerAddress(input) {
|
|
3433
|
-
const
|
|
3707
|
+
const base4 = input.baseUrl ?? ROUTING_API_BASE_URL3;
|
|
3434
3708
|
let res;
|
|
3435
3709
|
try {
|
|
3436
3710
|
res = await fetch(
|
|
3437
|
-
`${
|
|
3711
|
+
`${base4}/api/v1/sponsor/relayer-address?chainId=${input.chainId}`,
|
|
3438
3712
|
{
|
|
3439
3713
|
headers: { "x-owney-api-key": input.apiKey }
|
|
3440
3714
|
}
|
|
@@ -3719,6 +3993,7 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
3719
3993
|
}
|
|
3720
3994
|
|
|
3721
3995
|
// src/client.ts
|
|
3996
|
+
var WALLET_CATCHUP_TIMEOUT_MS = 3e4;
|
|
3722
3997
|
function encodeMultiAgentCursor(map) {
|
|
3723
3998
|
return Buffer.from(JSON.stringify(map), "utf8").toString("base64");
|
|
3724
3999
|
}
|
|
@@ -3745,9 +4020,9 @@ var SPONSORED_USDC_BY_CHAIN = {
|
|
|
3745
4020
|
1: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
|
3746
4021
|
};
|
|
3747
4022
|
var VIEM_CHAIN2 = {
|
|
3748
|
-
8453:
|
|
3749
|
-
42161:
|
|
3750
|
-
1:
|
|
4023
|
+
8453: import_chains3.base,
|
|
4024
|
+
42161: import_chains3.arbitrum,
|
|
4025
|
+
1: import_chains3.mainnet
|
|
3751
4026
|
};
|
|
3752
4027
|
var SPONSORED_WETH_BY_CHAIN = {
|
|
3753
4028
|
8453: "0x4200000000000000000000000000000000000006",
|
|
@@ -4536,7 +4811,7 @@ var OwneySDK = class {
|
|
|
4536
4811
|
async ensureSwapChain(chainId) {
|
|
4537
4812
|
const provider = this.requireConnectedProvider();
|
|
4538
4813
|
const state = this.requireState();
|
|
4539
|
-
const chain =
|
|
4814
|
+
const chain = swapChain(chainId);
|
|
4540
4815
|
if (!chain) {
|
|
4541
4816
|
throw new OwneyError(
|
|
4542
4817
|
"CHAIN_UNSUPPORTED",
|
|
@@ -4554,6 +4829,111 @@ var OwneySDK = class {
|
|
|
4554
4829
|
chainId
|
|
4555
4830
|
);
|
|
4556
4831
|
}
|
|
4832
|
+
/**
|
|
4833
|
+
* Turn a native-source cross-chain quote into an ERC-20 one.
|
|
4834
|
+
*
|
|
4835
|
+
* Fusion+ cannot escrow native value, so the quote arrives with
|
|
4836
|
+
* `requiresOnchainOrder` set and the relayer would refuse the order after
|
|
4837
|
+
* the user signed it (ROUT-242). Wrapping first costs one transaction and
|
|
4838
|
+
* puts the swap back on the ERC-20 rail that already works, rather than
|
|
4839
|
+
* adding a second order type that would need its own escrow handling.
|
|
4840
|
+
*
|
|
4841
|
+
* Re-quoted afterwards rather than patched: the amount wrapped may be less
|
|
4842
|
+
* than the amount quoted (gas has to come out of the same balance), and
|
|
4843
|
+
* every executor dep — allowance, permit, balances — is derived from the
|
|
4844
|
+
* quote's source token. A quote naming ETH would have all of them reading
|
|
4845
|
+
* the wrong asset.
|
|
4846
|
+
*/
|
|
4847
|
+
async wrapNativeSource(quote, walletAddress, onStage, direction) {
|
|
4848
|
+
const wrapper = requireWrapper(quote);
|
|
4849
|
+
const chainId = quote.src.chainId;
|
|
4850
|
+
const chain = swapChain(chainId);
|
|
4851
|
+
const provider = this.requireConnectedProvider();
|
|
4852
|
+
const publicClient = (0, import_viem9.createPublicClient)({
|
|
4853
|
+
chain,
|
|
4854
|
+
transport: swapReadTransport(chainId, this.zyfaiRpcUrls)
|
|
4855
|
+
});
|
|
4856
|
+
const wallet = (0, import_viem9.createWalletClient)({
|
|
4857
|
+
account: walletAddress,
|
|
4858
|
+
chain,
|
|
4859
|
+
transport: (0, import_viem9.custom)(provider)
|
|
4860
|
+
});
|
|
4861
|
+
onStage?.("wrapping");
|
|
4862
|
+
const { wrapped } = await wrapNative(
|
|
4863
|
+
{
|
|
4864
|
+
readNativeBalance: () => publicClient.getBalance({
|
|
4865
|
+
address: walletAddress
|
|
4866
|
+
}),
|
|
4867
|
+
estimateWrapGas: (to, value) => publicClient.estimateGas({
|
|
4868
|
+
account: walletAddress,
|
|
4869
|
+
to,
|
|
4870
|
+
data: WRAP_SELECTOR,
|
|
4871
|
+
value
|
|
4872
|
+
}),
|
|
4873
|
+
// The 1559 ceiling, not the spot price: a wallet bills against the
|
|
4874
|
+
// former and the two differ wildly on a quiet chain.
|
|
4875
|
+
getMaxFeePerGas: async () => {
|
|
4876
|
+
const fees = await publicClient.estimateFeesPerGas();
|
|
4877
|
+
return fees.maxFeePerGas ?? publicClient.getGasPrice();
|
|
4878
|
+
},
|
|
4879
|
+
sendTransaction: async (tx, onSubmitted) => {
|
|
4880
|
+
const hash = await wallet.sendTransaction({
|
|
4881
|
+
to: tx.to,
|
|
4882
|
+
data: tx.data,
|
|
4883
|
+
value: BigInt(tx.value),
|
|
4884
|
+
account: walletAddress,
|
|
4885
|
+
chain
|
|
4886
|
+
});
|
|
4887
|
+
onSubmitted?.(hash);
|
|
4888
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
4889
|
+
hash,
|
|
4890
|
+
confirmations: 1,
|
|
4891
|
+
timeout: receiptTimeoutMs(chainId)
|
|
4892
|
+
});
|
|
4893
|
+
if (receipt.status !== "success") {
|
|
4894
|
+
throw new OwneyError(
|
|
4895
|
+
"SWAP_REQUEST_FAILED",
|
|
4896
|
+
`Wrapping your ${quote.src.symbol} failed (tx ${hash}). Nothing was swapped.`,
|
|
4897
|
+
{ hash }
|
|
4898
|
+
);
|
|
4899
|
+
}
|
|
4900
|
+
return hash;
|
|
4901
|
+
},
|
|
4902
|
+
ensureChain: (id) => this.ensureSwapChain(id)
|
|
4903
|
+
},
|
|
4904
|
+
{
|
|
4905
|
+
chainId,
|
|
4906
|
+
wrapper: wrapper.address,
|
|
4907
|
+
nativeSymbol: quote.src.symbol,
|
|
4908
|
+
requested: BigInt(quote.src.amount),
|
|
4909
|
+
onSubmitted: () => onStage?.("wrapped")
|
|
4910
|
+
}
|
|
4911
|
+
);
|
|
4912
|
+
debugLog("owney-sdk", "swap: wrapped native source", {
|
|
4913
|
+
symbol: wrapper.symbol,
|
|
4914
|
+
amount: wrapped.toString()
|
|
4915
|
+
});
|
|
4916
|
+
try {
|
|
4917
|
+
return await this.swapApi().quote({
|
|
4918
|
+
from: { chainId, symbol: wrapper.symbol, amount: wrapped.toString() },
|
|
4919
|
+
to: { chainId: quote.dst.chainId, symbol: quote.dst.symbol },
|
|
4920
|
+
walletAddress,
|
|
4921
|
+
...direction ? { direction } : {}
|
|
4922
|
+
});
|
|
4923
|
+
} catch (error) {
|
|
4924
|
+
throw new OwneyError(
|
|
4925
|
+
"SWAP_REQUEST_FAILED",
|
|
4926
|
+
`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.`,
|
|
4927
|
+
{
|
|
4928
|
+
wrappedSymbol: wrapper.symbol,
|
|
4929
|
+
wrappedAmount: wrapped.toString(),
|
|
4930
|
+
chainId,
|
|
4931
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
4932
|
+
...error instanceof OwneyError ? { causeCode: error.code } : {}
|
|
4933
|
+
}
|
|
4934
|
+
);
|
|
4935
|
+
}
|
|
4936
|
+
}
|
|
4557
4937
|
/**
|
|
4558
4938
|
* Binds the executor's abstract deps to this client's wallet.
|
|
4559
4939
|
*
|
|
@@ -4563,8 +4943,8 @@ var OwneySDK = class {
|
|
|
4563
4943
|
buildSwapDeps(quote) {
|
|
4564
4944
|
const state = this.requireState();
|
|
4565
4945
|
const provider = this.requireConnectedProvider();
|
|
4566
|
-
const srcChain =
|
|
4567
|
-
const dstChain =
|
|
4946
|
+
const srcChain = swapChain(quote.src.chainId);
|
|
4947
|
+
const dstChain = swapChain(quote.dst.chainId);
|
|
4568
4948
|
const wallet = (0, import_viem9.createWalletClient)({
|
|
4569
4949
|
account: state.walletAddress,
|
|
4570
4950
|
chain: srcChain,
|
|
@@ -4694,13 +5074,59 @@ var OwneySDK = class {
|
|
|
4694
5074
|
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
4695
5075
|
now: () => Date.now()
|
|
4696
5076
|
},
|
|
4697
|
-
runner:
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
5077
|
+
runner: this.swapRunnerDeps()
|
|
5078
|
+
};
|
|
5079
|
+
}
|
|
5080
|
+
/**
|
|
5081
|
+
* Waits until the WALLET'S OWN node reports at least `amount` of `token`.
|
|
5082
|
+
*
|
|
5083
|
+
* Separate from the swap's arrival measurement on purpose: that one runs on
|
|
5084
|
+
* a pinned HTTP RPC, and what matters here is the view the deposit will
|
|
5085
|
+
* actually use. Returns whether it got there — a courtesy, not a gate, since
|
|
5086
|
+
* the deposit's own check is the authority and refusing to even attempt
|
|
5087
|
+
* would strand funds we might have been able to move.
|
|
5088
|
+
*/
|
|
5089
|
+
async walletSeesBalance(chainId, token, amount) {
|
|
5090
|
+
const provider = this.requireConnectedProvider();
|
|
5091
|
+
const state = this.requireState();
|
|
5092
|
+
const pub = (0, import_viem9.createPublicClient)({
|
|
5093
|
+
chain: swapChain(chainId),
|
|
5094
|
+
transport: (0, import_viem9.custom)(provider)
|
|
5095
|
+
});
|
|
5096
|
+
const { arrived } = await awaitBalanceIncrease({
|
|
5097
|
+
readBalance: () => pub.readContract({
|
|
5098
|
+
address: token,
|
|
5099
|
+
abi: ERC20_ALLOWANCE_ABI,
|
|
5100
|
+
functionName: "balanceOf",
|
|
5101
|
+
args: [state.walletAddress]
|
|
5102
|
+
}),
|
|
5103
|
+
// `awaitBalanceIncrease` resolves on the first read STRICTLY above the
|
|
5104
|
+
// baseline, so one below the target means "at least the target".
|
|
5105
|
+
baseline: amount - 1n,
|
|
5106
|
+
timeoutMs: WALLET_CATCHUP_TIMEOUT_MS
|
|
5107
|
+
});
|
|
5108
|
+
return arrived !== null;
|
|
5109
|
+
}
|
|
5110
|
+
/**
|
|
5111
|
+
* Runs a quoted swap. A named seam rather than an inline `executeSwap` call
|
|
5112
|
+
* so the deposit half of `swapAndDeposit` can be exercised on its own.
|
|
5113
|
+
*/
|
|
5114
|
+
runSwap(quote, options) {
|
|
5115
|
+
return executeSwap(this.buildSwapDeps(quote), { quote, ...options });
|
|
5116
|
+
}
|
|
5117
|
+
/**
|
|
5118
|
+
* The half of the swap deps that only ever talks to the routing API.
|
|
5119
|
+
*
|
|
5120
|
+
* Split out because resuming an order needs exactly this and nothing else —
|
|
5121
|
+
* no provider, no wallet client, no chain. See `resumePendingSwaps`.
|
|
5122
|
+
*/
|
|
5123
|
+
swapRunnerDeps() {
|
|
5124
|
+
return {
|
|
5125
|
+
readyForSecrets: (h) => this.swapApi().readyForSecrets(h),
|
|
5126
|
+
submitSecret: (h, secret) => this.swapApi().submitSecret(h, secret),
|
|
5127
|
+
orderStatus: (h) => this.swapApi().orderStatus(h),
|
|
5128
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
5129
|
+
now: () => Date.now()
|
|
4704
5130
|
};
|
|
4705
5131
|
}
|
|
4706
5132
|
/**
|
|
@@ -4712,6 +5138,126 @@ var OwneySDK = class {
|
|
|
4712
5138
|
async getSwapTokens() {
|
|
4713
5139
|
return this.swapApi().listTokens();
|
|
4714
5140
|
}
|
|
5141
|
+
/**
|
|
5142
|
+
* Swaps that landed but were never deposited, newest first.
|
|
5143
|
+
*
|
|
5144
|
+
* The money is already in the wallet in the right asset — what is missing is
|
|
5145
|
+
* an ordinary deposit, which is why this needs no order and no signature to
|
|
5146
|
+
* describe. See `completePendingDeposit`.
|
|
5147
|
+
*/
|
|
5148
|
+
listPendingDeposits() {
|
|
5149
|
+
return listPendingDeposits();
|
|
5150
|
+
}
|
|
5151
|
+
/**
|
|
5152
|
+
* Deposit a swap that already landed.
|
|
5153
|
+
*
|
|
5154
|
+
* Nothing is swapped again: the funds are in the wallet, so this is the
|
|
5155
|
+
* second half of `swapAndDeposit` on its own. Cleared only once the deposit
|
|
5156
|
+
* has actually gone through — a failure here leaves the record in place so it
|
|
5157
|
+
* can be offered again rather than silently dropped.
|
|
5158
|
+
*/
|
|
5159
|
+
async completePendingDeposit(id) {
|
|
5160
|
+
const entry = listPendingDeposits().find((p) => p.id === id);
|
|
5161
|
+
if (!entry) {
|
|
5162
|
+
throw new OwneyError(
|
|
5163
|
+
"SWAP_REQUEST_FAILED",
|
|
5164
|
+
"That swap is no longer waiting to be added.",
|
|
5165
|
+
{ id }
|
|
5166
|
+
);
|
|
5167
|
+
}
|
|
5168
|
+
await this.ensureSwapChain(entry.chainId);
|
|
5169
|
+
const deposit = await this.deposit({
|
|
5170
|
+
amount: entry.received,
|
|
5171
|
+
asset: entry.symbol,
|
|
5172
|
+
...entry.agentId ? { agentId: entry.agentId } : {}
|
|
5173
|
+
});
|
|
5174
|
+
clearPendingDeposit(entry.id);
|
|
5175
|
+
return deposit;
|
|
5176
|
+
}
|
|
5177
|
+
/**
|
|
5178
|
+
* Pick up cross-chain swaps a previous session left in flight.
|
|
5179
|
+
*
|
|
5180
|
+
* A Fusion+ order is only finished when the client publishes a secret for
|
|
5181
|
+
* each fill, once the escrows exist — so a user who closed the tab mid-swap
|
|
5182
|
+
* leaves an order nobody will ever complete, and it expires and refunds
|
|
5183
|
+
* instead of arriving. The preimages are stored for exactly this (24h sweep,
|
|
5184
|
+
* see the secret store); this is the other half.
|
|
5185
|
+
*
|
|
5186
|
+
* Needs NO wallet, deliberately. The signature already happened and the funds
|
|
5187
|
+
* are already escrowed, so finishing is pure API work — demanding a reconnect
|
|
5188
|
+
* first would strand the people it exists for.
|
|
5189
|
+
*
|
|
5190
|
+
* Every order is attempted even if an earlier one throws: they are unrelated
|
|
5191
|
+
* escrows, and one ending badly must not abandon the next one's secrets.
|
|
5192
|
+
* `unfinished` is not a verdict — the order may still settle, so unless the
|
|
5193
|
+
* chain has said refunded or cancelled its preimages are kept.
|
|
5194
|
+
*/
|
|
5195
|
+
async resumePendingSwaps(options = {}) {
|
|
5196
|
+
const pending = listOrders();
|
|
5197
|
+
if (pending.length === 0) return [];
|
|
5198
|
+
const runner = this.swapRunnerDeps();
|
|
5199
|
+
const results = [];
|
|
5200
|
+
for (const order of pending) {
|
|
5201
|
+
try {
|
|
5202
|
+
await runFusionOrder(runner, {
|
|
5203
|
+
orderHash: order.orderHash,
|
|
5204
|
+
secrets: order.secrets,
|
|
5205
|
+
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
|
|
5206
|
+
...options.onStage ? {
|
|
5207
|
+
onStage: (stage) => options.onStage(order.orderHash, stage)
|
|
5208
|
+
} : {}
|
|
5209
|
+
});
|
|
5210
|
+
clearOrder(order.orderHash);
|
|
5211
|
+
results.push({
|
|
5212
|
+
orderHash: order.orderHash,
|
|
5213
|
+
status: "completed"
|
|
5214
|
+
});
|
|
5215
|
+
} catch (error) {
|
|
5216
|
+
if (error instanceof OwneyError && (error.code === "SWAP_ORDER_REFUNDED" || error.code === "SWAP_ORDER_CANCELLED")) {
|
|
5217
|
+
clearOrder(order.orderHash);
|
|
5218
|
+
}
|
|
5219
|
+
results.push({
|
|
5220
|
+
orderHash: order.orderHash,
|
|
5221
|
+
status: "unfinished",
|
|
5222
|
+
...error instanceof OwneyError ? { error } : {}
|
|
5223
|
+
});
|
|
5224
|
+
}
|
|
5225
|
+
}
|
|
5226
|
+
return results;
|
|
5227
|
+
}
|
|
5228
|
+
/**
|
|
5229
|
+
* What the wallet actually holds, across every supported chain, restricted
|
|
5230
|
+
* to tokens a swap could use.
|
|
5231
|
+
*
|
|
5232
|
+
* `getSwapTokens` answers "what may someone pay with in principle" from a
|
|
5233
|
+
* fixed list. This answers "what has this wallet got", which is a different
|
|
5234
|
+
* question and the one a picker needs: a user can hold something routable —
|
|
5235
|
+
* tBTC, say — that no fixed list of ours would ever mention.
|
|
5236
|
+
*
|
|
5237
|
+
* Each chain is asked separately because that is how the balance product is
|
|
5238
|
+
* shaped, and a chain that fails contributes nothing rather than failing the
|
|
5239
|
+
* lot. A picker with two chains in it beats an error. (ROUT-242)
|
|
5240
|
+
*/
|
|
5241
|
+
async getWalletBalances(chainIds) {
|
|
5242
|
+
const state = this.requireState();
|
|
5243
|
+
const chains = chainIds ?? SUPPORTED_CHAIN_IDS;
|
|
5244
|
+
const failedChainIds = [];
|
|
5245
|
+
const results = await Promise.all(
|
|
5246
|
+
chains.map(async (chainId) => {
|
|
5247
|
+
try {
|
|
5248
|
+
const { tokens } = await this.swapApi().walletBalances({
|
|
5249
|
+
chainId,
|
|
5250
|
+
walletAddress: state.walletAddress
|
|
5251
|
+
});
|
|
5252
|
+
return tokens.map((token) => ({ ...token, chainId }));
|
|
5253
|
+
} catch {
|
|
5254
|
+
failedChainIds.push(chainId);
|
|
5255
|
+
return [];
|
|
5256
|
+
}
|
|
5257
|
+
})
|
|
5258
|
+
);
|
|
5259
|
+
return { tokens: results.flat(), failedChainIds };
|
|
5260
|
+
}
|
|
4715
5261
|
/**
|
|
4716
5262
|
* Search the assets a user may pay with, across every supported chain.
|
|
4717
5263
|
*
|
|
@@ -4733,7 +5279,8 @@ var OwneySDK = class {
|
|
|
4733
5279
|
return this.swapApi().searchTokens({
|
|
4734
5280
|
query,
|
|
4735
5281
|
...params.chainId === void 0 ? {} : { chainId: params.chainId },
|
|
4736
|
-
...params.limit === void 0 ? {} : { limit: params.limit }
|
|
5282
|
+
...params.limit === void 0 ? {} : { limit: params.limit },
|
|
5283
|
+
...params.direction === void 0 ? {} : { direction: params.direction }
|
|
4737
5284
|
});
|
|
4738
5285
|
}
|
|
4739
5286
|
/**
|
|
@@ -4772,26 +5319,56 @@ var OwneySDK = class {
|
|
|
4772
5319
|
async swapAndDeposit(options) {
|
|
4773
5320
|
const state = this.requireState();
|
|
4774
5321
|
const api = this.swapApi();
|
|
4775
|
-
const
|
|
5322
|
+
const quoted = await api.quote({
|
|
4776
5323
|
from: options.from,
|
|
4777
5324
|
to: options.to,
|
|
4778
5325
|
walletAddress: state.walletAddress
|
|
4779
5326
|
});
|
|
5327
|
+
const quote = quoted.requiresOnchainOrder ? await this.wrapNativeSource(
|
|
5328
|
+
quoted,
|
|
5329
|
+
state.walletAddress,
|
|
5330
|
+
options.onSwapProgress
|
|
5331
|
+
) : quoted;
|
|
4780
5332
|
await this.ensureSwapChain(quote.src.chainId);
|
|
4781
|
-
const swap = await
|
|
4782
|
-
quote,
|
|
5333
|
+
const swap = await this.runSwap(quote, {
|
|
4783
5334
|
walletAddress: state.walletAddress,
|
|
4784
5335
|
...options.slippage === void 0 ? {} : { slippage: options.slippage },
|
|
4785
5336
|
...options.onSwapProgress ? { onStage: options.onSwapProgress } : {}
|
|
4786
5337
|
});
|
|
4787
5338
|
options.onSwapProgress?.("depositing");
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
5339
|
+
try {
|
|
5340
|
+
await this.ensureSwapChain(quote.dst.chainId);
|
|
5341
|
+
await this.walletSeesBalance(
|
|
5342
|
+
quote.dst.chainId,
|
|
5343
|
+
quote.dst.address,
|
|
5344
|
+
BigInt(swap.received)
|
|
5345
|
+
);
|
|
5346
|
+
const deposit = await this.deposit({
|
|
5347
|
+
amount: swap.received,
|
|
5348
|
+
asset: options.to.symbol,
|
|
5349
|
+
...options.agentId ? { agentId: options.agentId } : {}
|
|
5350
|
+
});
|
|
5351
|
+
return { swap, deposit };
|
|
5352
|
+
} catch (error) {
|
|
5353
|
+
const pending = {
|
|
5354
|
+
id: `${quote.dst.chainId}-${options.to.symbol}-${Date.now()}`,
|
|
5355
|
+
received: swap.received,
|
|
5356
|
+
symbol: options.to.symbol,
|
|
5357
|
+
chainId: quote.dst.chainId,
|
|
5358
|
+
...options.agentId ? { agentId: options.agentId } : {},
|
|
5359
|
+
createdAt: Date.now()
|
|
5360
|
+
};
|
|
5361
|
+
savePendingDeposit(pending);
|
|
5362
|
+
throw new OwneyError(
|
|
5363
|
+
"SWAP_DEPOSIT_PENDING",
|
|
5364
|
+
`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.`,
|
|
5365
|
+
{
|
|
5366
|
+
...pending,
|
|
5367
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
5368
|
+
...error instanceof OwneyError ? { causeCode: error.code } : {}
|
|
5369
|
+
}
|
|
5370
|
+
);
|
|
5371
|
+
}
|
|
4795
5372
|
}
|
|
4796
5373
|
/**
|
|
4797
5374
|
* Withdraw from an agent and swap the proceeds into whatever the user wants
|
|
@@ -5639,8 +6216,8 @@ var import_siwe = require("siwe");
|
|
|
5639
6216
|
var import_sdk2 = require("@zyfai/sdk");
|
|
5640
6217
|
|
|
5641
6218
|
// src/agents/zyfai/zyfai.siwx-cache.ts
|
|
5642
|
-
var
|
|
5643
|
-
var
|
|
6219
|
+
var KEY_PREFIX4 = "owney.siwx.session";
|
|
6220
|
+
var storage4 = () => {
|
|
5644
6221
|
if (typeof window === "undefined") return null;
|
|
5645
6222
|
try {
|
|
5646
6223
|
return window.localStorage;
|
|
@@ -5648,8 +6225,8 @@ var storage3 = () => {
|
|
|
5648
6225
|
return null;
|
|
5649
6226
|
}
|
|
5650
6227
|
};
|
|
5651
|
-
var buildKey2 = (address) => `${
|
|
5652
|
-
var legacyKeyPrefix2 = (address) => `${
|
|
6228
|
+
var buildKey2 = (address) => `${KEY_PREFIX4}:${address.toLowerCase()}`;
|
|
6229
|
+
var legacyKeyPrefix2 = (address) => `${KEY_PREFIX4}:${address.toLowerCase()}:`;
|
|
5653
6230
|
var memorySiwxSessions = /* @__PURE__ */ new Map();
|
|
5654
6231
|
var readLegacySiwxSession = (store, address) => {
|
|
5655
6232
|
if (!store) return null;
|
|
@@ -5681,7 +6258,7 @@ var readLegacySiwxSession = (store, address) => {
|
|
|
5681
6258
|
var readSiwxSession = (address, chainId) => {
|
|
5682
6259
|
if (typeof window === "undefined") return null;
|
|
5683
6260
|
const key2 = buildKey2(address);
|
|
5684
|
-
const store =
|
|
6261
|
+
const store = storage4();
|
|
5685
6262
|
let raw = null;
|
|
5686
6263
|
try {
|
|
5687
6264
|
raw = store?.getItem(key2) ?? null;
|
|
@@ -5711,7 +6288,7 @@ var writeSiwxSession = (address, _chainId, session) => {
|
|
|
5711
6288
|
if (typeof window === "undefined") return;
|
|
5712
6289
|
const key2 = buildKey2(address);
|
|
5713
6290
|
memorySiwxSessions.set(key2, session);
|
|
5714
|
-
const store =
|
|
6291
|
+
const store = storage4();
|
|
5715
6292
|
try {
|
|
5716
6293
|
store?.setItem(key2, JSON.stringify(session));
|
|
5717
6294
|
} catch {
|
|
@@ -5720,7 +6297,7 @@ var writeSiwxSession = (address, _chainId, session) => {
|
|
|
5720
6297
|
var clearSiwxSession = (address, _chainId) => {
|
|
5721
6298
|
const key2 = buildKey2(address);
|
|
5722
6299
|
memorySiwxSessions.delete(key2);
|
|
5723
|
-
const store =
|
|
6300
|
+
const store = storage4();
|
|
5724
6301
|
try {
|
|
5725
6302
|
store?.removeItem(key2);
|
|
5726
6303
|
} catch {
|
|
@@ -5854,6 +6431,7 @@ function createOwneySIWX(config) {
|
|
|
5854
6431
|
OwneyError,
|
|
5855
6432
|
OwneySDK,
|
|
5856
6433
|
createOwneySIWX,
|
|
6434
|
+
listPendingDeposits,
|
|
5857
6435
|
listPendingSwaps,
|
|
5858
6436
|
setOwneyDebug
|
|
5859
6437
|
});
|