@owney/sdk 0.7.26-beta.2 → 0.7.26-beta.4
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 +371 -59
- package/dist/index.d.cts +56 -2
- package/dist/index.d.ts +56 -2
- package/dist/index.js +341 -24
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2309,6 +2309,21 @@ function createSwapApi(baseUrl, apiKey) {
|
|
|
2309
2309
|
* Already filtered server-side to what a quote will accept, so anything
|
|
2310
2310
|
* returned here can be paid with.
|
|
2311
2311
|
*/
|
|
2312
|
+
/**
|
|
2313
|
+
* Tokens the wallet holds on one chain.
|
|
2314
|
+
*
|
|
2315
|
+
* Already filtered upstream to non-zero balances of tokens the routing API
|
|
2316
|
+
* would quote — 1inch answers with every token it knows about, zeros
|
|
2317
|
+
* included, and the curation that decides what is offerable lives there.
|
|
2318
|
+
*/
|
|
2319
|
+
walletBalances: (params) => request(
|
|
2320
|
+
baseUrl,
|
|
2321
|
+
apiKey,
|
|
2322
|
+
`/balances?${new URLSearchParams({
|
|
2323
|
+
chainId: String(params.chainId),
|
|
2324
|
+
walletAddress: params.walletAddress
|
|
2325
|
+
})}`
|
|
2326
|
+
),
|
|
2312
2327
|
searchTokens: (params) => request(
|
|
2313
2328
|
baseUrl,
|
|
2314
2329
|
apiKey,
|
|
@@ -2366,9 +2381,10 @@ function createSwapApi(baseUrl, apiKey) {
|
|
|
2366
2381
|
dstSymbol: params.to.symbol,
|
|
2367
2382
|
amount: params.from.amount,
|
|
2368
2383
|
walletAddress: params.walletAddress,
|
|
2369
|
-
secretHashes: params.secretHashes,
|
|
2384
|
+
...params.secretHashes ? { secretHashes: params.secretHashes } : {},
|
|
2370
2385
|
...params.direction ? { direction: params.direction } : {},
|
|
2371
|
-
...params.receiver ? { receiver: params.receiver } : {}
|
|
2386
|
+
...params.receiver ? { receiver: params.receiver } : {},
|
|
2387
|
+
...params.permit ? { permit: params.permit } : {}
|
|
2372
2388
|
}
|
|
2373
2389
|
}),
|
|
2374
2390
|
submitOrder: (body) => request(baseUrl, apiKey, "/order", { method: "POST", body }),
|
|
@@ -2381,7 +2397,16 @@ function createSwapApi(baseUrl, apiKey) {
|
|
|
2381
2397
|
method: "POST",
|
|
2382
2398
|
body: { orderHash, secret }
|
|
2383
2399
|
}),
|
|
2384
|
-
|
|
2400
|
+
/**
|
|
2401
|
+
* @param chainId present only for a SAME-CHAIN order. That product keys its
|
|
2402
|
+
* orders per chain and the Fusion+ endpoint does not know them, so asking
|
|
2403
|
+
* without it answers 404 — which reads as an order that vanished.
|
|
2404
|
+
*/
|
|
2405
|
+
orderStatus: (orderHash, chainId) => request(
|
|
2406
|
+
baseUrl,
|
|
2407
|
+
apiKey,
|
|
2408
|
+
chainId === void 0 ? `/order/${orderHash}` : `/order/${orderHash}?chainId=${chainId}`
|
|
2409
|
+
),
|
|
2385
2410
|
readyForSecrets: (orderHash) => request(
|
|
2386
2411
|
baseUrl,
|
|
2387
2412
|
apiKey,
|
|
@@ -2524,7 +2549,12 @@ var SWAP_TERMINAL_STATUSES = [
|
|
|
2524
2549
|
"executed",
|
|
2525
2550
|
"expired",
|
|
2526
2551
|
"cancelled",
|
|
2527
|
-
"refunded"
|
|
2552
|
+
"refunded",
|
|
2553
|
+
"filled",
|
|
2554
|
+
"false-predicate",
|
|
2555
|
+
"not-enough-balance-or-allowance",
|
|
2556
|
+
"wrong-permit",
|
|
2557
|
+
"invalid-signature"
|
|
2528
2558
|
];
|
|
2529
2559
|
var isSwapTerminal = (status) => SWAP_TERMINAL_STATUSES.includes(status);
|
|
2530
2560
|
|
|
@@ -2602,6 +2632,71 @@ async function runFusionOrder(deps, options) {
|
|
|
2602
2632
|
}
|
|
2603
2633
|
}
|
|
2604
2634
|
|
|
2635
|
+
// src/lib/swap/swap.same-chain-runner.ts
|
|
2636
|
+
var DEFAULT_POLL_MS2 = 5e3;
|
|
2637
|
+
var MAX_BACKOFF_MS2 = 3e4;
|
|
2638
|
+
var backoffFor2 = (failures, base3) => Math.min(base3 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS2);
|
|
2639
|
+
var DEFAULT_TIMEOUT_MS2 = 3 * 60 * 1e3;
|
|
2640
|
+
function explain(status) {
|
|
2641
|
+
switch (status) {
|
|
2642
|
+
case "expired":
|
|
2643
|
+
return "No one filled the swap in time, so nothing was exchanged. Your funds never left your wallet.";
|
|
2644
|
+
case "cancelled":
|
|
2645
|
+
return "The swap was cancelled before anyone filled it. Your funds never left your wallet.";
|
|
2646
|
+
case "not-enough-balance-or-allowance":
|
|
2647
|
+
return "The swap could not be filled because the balance or approval had changed since it was signed. Nothing was exchanged.";
|
|
2648
|
+
case "wrong-permit":
|
|
2649
|
+
return "The approval signed with this swap was not accepted. Nothing was exchanged \u2014 try again, approving in your wallet if you are asked to.";
|
|
2650
|
+
case "invalid-signature":
|
|
2651
|
+
return "The swap signature was rejected. Nothing was exchanged.";
|
|
2652
|
+
case "false-predicate":
|
|
2653
|
+
return "The swap's conditions no longer held when it was filled. Nothing was exchanged.";
|
|
2654
|
+
default:
|
|
2655
|
+
return "The swap did not complete. Nothing was exchanged.";
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
async function runSameChainOrder(deps, options) {
|
|
2659
|
+
const {
|
|
2660
|
+
orderHash,
|
|
2661
|
+
onStage,
|
|
2662
|
+
pollIntervalMs = DEFAULT_POLL_MS2,
|
|
2663
|
+
timeoutMs = DEFAULT_TIMEOUT_MS2
|
|
2664
|
+
} = options;
|
|
2665
|
+
const deadline = deps.now() + timeoutMs;
|
|
2666
|
+
let failures = 0;
|
|
2667
|
+
onStage?.("swapping");
|
|
2668
|
+
for (; ; ) {
|
|
2669
|
+
if (deps.now() >= deadline) {
|
|
2670
|
+
throw new OwneyError(
|
|
2671
|
+
"SWAP_ORDER_EXPIRED",
|
|
2672
|
+
"No one filled the swap in time. Nothing was exchanged and your funds are still in your wallet.",
|
|
2673
|
+
{ orderHash }
|
|
2674
|
+
);
|
|
2675
|
+
}
|
|
2676
|
+
let status;
|
|
2677
|
+
try {
|
|
2678
|
+
({ status } = await deps.orderStatus(orderHash));
|
|
2679
|
+
failures = 0;
|
|
2680
|
+
} catch {
|
|
2681
|
+
failures += 1;
|
|
2682
|
+
await deps.sleep(backoffFor2(failures, pollIntervalMs));
|
|
2683
|
+
continue;
|
|
2684
|
+
}
|
|
2685
|
+
if (isSwapTerminal(status)) {
|
|
2686
|
+
if (status === "filled" || status === "executed") {
|
|
2687
|
+
onStage?.("swapped");
|
|
2688
|
+
return { status, filled: true };
|
|
2689
|
+
}
|
|
2690
|
+
throw new OwneyError(
|
|
2691
|
+
status === "cancelled" ? "SWAP_ORDER_CANCELLED" : "SWAP_ORDER_EXPIRED",
|
|
2692
|
+
explain(status),
|
|
2693
|
+
{ orderHash, status }
|
|
2694
|
+
);
|
|
2695
|
+
}
|
|
2696
|
+
await deps.sleep(pollIntervalMs);
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2605
2700
|
// src/lib/swap/swap.secret-store.ts
|
|
2606
2701
|
var KEY_PREFIX2 = "owney.swap.order";
|
|
2607
2702
|
var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -2685,7 +2780,7 @@ async function executeSwap(deps, options) {
|
|
|
2685
2780
|
});
|
|
2686
2781
|
const before = await deps.readTargetBalance();
|
|
2687
2782
|
debugLog("owney-sdk", "swap: target balance before", before.toString());
|
|
2688
|
-
const result = quote.rail === "classic" ? await runClassic(deps, options) : await runFusion(deps, options, walletAddress);
|
|
2783
|
+
const result = quote.rail === "classic" ? await runClassic(deps, options) : quote.rail === "fusion" ? await runSameChainFusion(deps, options, walletAddress) : await runFusion(deps, options, walletAddress);
|
|
2689
2784
|
const after = await deps.readTargetBalance();
|
|
2690
2785
|
const received = after - before;
|
|
2691
2786
|
debugLog("owney-sdk", "swap: target balance after", {
|
|
@@ -2756,25 +2851,75 @@ async function runClassic(deps, options) {
|
|
|
2756
2851
|
onStage?.("swapped");
|
|
2757
2852
|
return { txHash };
|
|
2758
2853
|
}
|
|
2759
|
-
async function
|
|
2854
|
+
async function runSameChainFusion(deps, options, walletAddress) {
|
|
2760
2855
|
const { quote, direction, onStage } = options;
|
|
2761
2856
|
const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
|
|
2762
2857
|
const amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount));
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2858
|
+
const permit = await authoriseSpend(deps, options, amount, isNativeSource);
|
|
2859
|
+
onStage?.("quoting");
|
|
2860
|
+
debugLog("owney-sdk", "swap: building same-chain fusion order");
|
|
2861
|
+
const built = await deps.api.buildOrder({
|
|
2862
|
+
from: {
|
|
2863
|
+
chainId: quote.src.chainId,
|
|
2864
|
+
symbol: quote.src.symbol,
|
|
2865
|
+
amount: amount.toString()
|
|
2866
|
+
},
|
|
2867
|
+
to: { chainId: quote.dst.chainId, symbol: quote.dst.symbol },
|
|
2868
|
+
walletAddress,
|
|
2869
|
+
...direction ? { direction } : {},
|
|
2870
|
+
...permit ? { permit } : {}
|
|
2871
|
+
});
|
|
2872
|
+
debugLog("owney-sdk", "swap: same-chain order built", {
|
|
2873
|
+
orderHash: built.orderHash
|
|
2874
|
+
});
|
|
2875
|
+
onStage?.("signing");
|
|
2876
|
+
await deps.ensureChain(quote.src.chainId);
|
|
2877
|
+
const signature = await deps.signTypedData(built.typedData);
|
|
2878
|
+
debugLog("owney-sdk", "swap: signed, submitting same-chain order");
|
|
2879
|
+
await deps.api.submitOrder({
|
|
2880
|
+
// Without this the routing API hands the order to the Fusion+ relayer,
|
|
2881
|
+
// which does not know it and answers with an error that names nothing.
|
|
2882
|
+
rail: "fusion",
|
|
2883
|
+
srcChainId: quote.src.chainId,
|
|
2884
|
+
order: built.order,
|
|
2885
|
+
signature,
|
|
2886
|
+
quoteId: built.quoteId,
|
|
2887
|
+
...built.extension ? { extension: built.extension } : {}
|
|
2888
|
+
});
|
|
2889
|
+
await runSameChainOrder(deps.sameChainRunner ?? deps.runner, {
|
|
2890
|
+
orderHash: built.orderHash,
|
|
2891
|
+
...onStage ? { onStage } : {}
|
|
2892
|
+
});
|
|
2893
|
+
return { orderHash: built.orderHash };
|
|
2894
|
+
}
|
|
2895
|
+
async function authoriseSpend(deps, options, amount, isNativeSource) {
|
|
2896
|
+
const { quote, onStage } = options;
|
|
2897
|
+
if (!quote.spender || isNativeSource) return void 0;
|
|
2898
|
+
const current = await deps.readAllowance(quote.spender);
|
|
2899
|
+
debugLog("owney-sdk", "swap: fusion allowance", {
|
|
2900
|
+
spender: quote.spender,
|
|
2901
|
+
current: current.toString(),
|
|
2902
|
+
needed: amount.toString()
|
|
2903
|
+
});
|
|
2904
|
+
if (current >= amount) return void 0;
|
|
2905
|
+
onStage?.("approving");
|
|
2906
|
+
await deps.ensureChain(quote.src.chainId);
|
|
2907
|
+
const permit = quote.permitSupported ? await deps.buildPermit?.(quote.spender, MAX_UINT256) ?? void 0 : void 0;
|
|
2908
|
+
if (permit) {
|
|
2909
|
+
debugLog("owney-sdk", "swap: permitting limit order protocol", {
|
|
2910
|
+
bytes: (permit.length - 2) / 2
|
|
2770
2911
|
});
|
|
2771
|
-
|
|
2772
|
-
onStage?.("approving");
|
|
2773
|
-
await deps.ensureChain(quote.src.chainId);
|
|
2774
|
-
await deps.approve(quote.spender, MAX_UINT256);
|
|
2775
|
-
debugLog("owney-sdk", "swap: approved limit order protocol");
|
|
2776
|
-
}
|
|
2912
|
+
return permit;
|
|
2777
2913
|
}
|
|
2914
|
+
await deps.approve(quote.spender, MAX_UINT256);
|
|
2915
|
+
debugLog("owney-sdk", "swap: approved limit order protocol");
|
|
2916
|
+
return void 0;
|
|
2917
|
+
}
|
|
2918
|
+
async function runFusion(deps, options, walletAddress) {
|
|
2919
|
+
const { quote, direction, onStage } = options;
|
|
2920
|
+
const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
|
|
2921
|
+
const amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount));
|
|
2922
|
+
const permit = await authoriseSpend(deps, options, amount, isNativeSource);
|
|
2778
2923
|
const { secrets, secretHashes } = mintSecrets(quote.secretsCount ?? 1);
|
|
2779
2924
|
onStage?.("quoting");
|
|
2780
2925
|
debugLog("owney-sdk", "swap: building fusion order", {
|
|
@@ -2790,7 +2935,8 @@ async function runFusion(deps, options, walletAddress) {
|
|
|
2790
2935
|
to: { chainId: quote.dst.chainId, symbol: quote.dst.symbol },
|
|
2791
2936
|
walletAddress,
|
|
2792
2937
|
secretHashes,
|
|
2793
|
-
...direction ? { direction } : {}
|
|
2938
|
+
...direction ? { direction } : {},
|
|
2939
|
+
...permit ? { permit } : {}
|
|
2794
2940
|
});
|
|
2795
2941
|
saveOrder({
|
|
2796
2942
|
orderHash: built.orderHash,
|
|
@@ -2841,16 +2987,114 @@ async function runFusion(deps, options, walletAddress) {
|
|
|
2841
2987
|
return { orderHash: built.orderHash };
|
|
2842
2988
|
}
|
|
2843
2989
|
|
|
2990
|
+
// src/lib/swap/swap.permit.ts
|
|
2991
|
+
var import_viem5 = require("viem");
|
|
2992
|
+
var PERMIT_ABI = (0, import_viem5.parseAbi)([
|
|
2993
|
+
"function nonces(address owner) view returns (uint256)",
|
|
2994
|
+
"function name() view returns (string)",
|
|
2995
|
+
"function version() view returns (string)",
|
|
2996
|
+
"function DOMAIN_SEPARATOR() view returns (bytes32)"
|
|
2997
|
+
]);
|
|
2998
|
+
var PERMIT_TYPES = {
|
|
2999
|
+
Permit: [
|
|
3000
|
+
{ name: "owner", type: "address" },
|
|
3001
|
+
{ name: "spender", type: "address" },
|
|
3002
|
+
{ name: "value", type: "uint256" },
|
|
3003
|
+
{ name: "nonce", type: "uint256" },
|
|
3004
|
+
{ name: "deadline", type: "uint256" }
|
|
3005
|
+
]
|
|
3006
|
+
};
|
|
3007
|
+
var VERSION_CANDIDATES = ["1", "2"];
|
|
3008
|
+
var PERMIT_TTL_SECONDS = 3600;
|
|
3009
|
+
async function tryRead(read) {
|
|
3010
|
+
try {
|
|
3011
|
+
return await read();
|
|
3012
|
+
} catch {
|
|
3013
|
+
return null;
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
function matchDomain(name, versions, chainId, token, onChainSeparator) {
|
|
3017
|
+
for (const version of versions) {
|
|
3018
|
+
const domain = { name, version, chainId, verifyingContract: token };
|
|
3019
|
+
if ((0, import_viem5.domainSeparator)({ domain }) === onChainSeparator) return domain;
|
|
3020
|
+
}
|
|
3021
|
+
return null;
|
|
3022
|
+
}
|
|
3023
|
+
async function buildErc2612Permit(reads, input) {
|
|
3024
|
+
const { token, owner, spender, value, chainId } = input;
|
|
3025
|
+
const call = (functionName, args) => reads.readContract({
|
|
3026
|
+
address: token,
|
|
3027
|
+
abi: PERMIT_ABI,
|
|
3028
|
+
functionName,
|
|
3029
|
+
...args ? { args } : {}
|
|
3030
|
+
});
|
|
3031
|
+
const nonce = await tryRead(() => call("nonces", [owner]));
|
|
3032
|
+
if (nonce === null) {
|
|
3033
|
+
debugLog("owney-sdk", "permit: token has no nonces(), using approval", {
|
|
3034
|
+
token
|
|
3035
|
+
});
|
|
3036
|
+
return null;
|
|
3037
|
+
}
|
|
3038
|
+
const separator = await tryRead(
|
|
3039
|
+
() => call("DOMAIN_SEPARATOR")
|
|
3040
|
+
);
|
|
3041
|
+
const name = await tryRead(() => call("name"));
|
|
3042
|
+
if (!separator || !name) {
|
|
3043
|
+
debugLog("owney-sdk", "permit: domain unverifiable, using approval", {
|
|
3044
|
+
token,
|
|
3045
|
+
hasSeparator: Boolean(separator),
|
|
3046
|
+
hasName: Boolean(name)
|
|
3047
|
+
});
|
|
3048
|
+
return null;
|
|
3049
|
+
}
|
|
3050
|
+
const declared = await tryRead(() => call("version"));
|
|
3051
|
+
const domain = matchDomain(
|
|
3052
|
+
name,
|
|
3053
|
+
declared ? [declared, ...VERSION_CANDIDATES] : VERSION_CANDIDATES,
|
|
3054
|
+
chainId,
|
|
3055
|
+
token,
|
|
3056
|
+
separator
|
|
3057
|
+
);
|
|
3058
|
+
if (!domain) {
|
|
3059
|
+
debugLog("owney-sdk", "permit: no domain matched, using approval", {
|
|
3060
|
+
token,
|
|
3061
|
+
declared
|
|
3062
|
+
});
|
|
3063
|
+
return null;
|
|
3064
|
+
}
|
|
3065
|
+
const deadline = BigInt(Math.floor((input.now?.() ?? Date.now()) / 1e3)) + BigInt(PERMIT_TTL_SECONDS);
|
|
3066
|
+
const signature = await input.signTypedData({
|
|
3067
|
+
domain,
|
|
3068
|
+
types: PERMIT_TYPES,
|
|
3069
|
+
primaryType: "Permit",
|
|
3070
|
+
message: { owner, spender, value, nonce, deadline }
|
|
3071
|
+
});
|
|
3072
|
+
const { r, s, v, yParity } = (0, import_viem5.parseSignature)(signature);
|
|
3073
|
+
const recoveryV = v ?? BigInt(yParity + 27);
|
|
3074
|
+
return (0, import_viem5.encodeAbiParameters)(
|
|
3075
|
+
[
|
|
3076
|
+
{ type: "address" },
|
|
3077
|
+
{ type: "address" },
|
|
3078
|
+
{ type: "uint256" },
|
|
3079
|
+
{ type: "uint256" },
|
|
3080
|
+
{ type: "uint8" },
|
|
3081
|
+
{ type: "bytes32" },
|
|
3082
|
+
{ type: "bytes32" }
|
|
3083
|
+
],
|
|
3084
|
+
[owner, spender, value, deadline, Number(recoveryV), r, s]
|
|
3085
|
+
);
|
|
3086
|
+
}
|
|
3087
|
+
|
|
2844
3088
|
// src/lib/swap/swap.arrival.ts
|
|
2845
|
-
var
|
|
2846
|
-
var
|
|
3089
|
+
var DEFAULT_TIMEOUT_MS3 = 18e4;
|
|
3090
|
+
var DEFAULT_POLL_MS3 = 4e3;
|
|
2847
3091
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2848
3092
|
async function awaitWithdrawalArrival(options) {
|
|
2849
3093
|
const {
|
|
2850
3094
|
readBalance,
|
|
2851
3095
|
baseline,
|
|
2852
|
-
timeoutMs =
|
|
2853
|
-
pollMs =
|
|
3096
|
+
timeoutMs = DEFAULT_TIMEOUT_MS3,
|
|
3097
|
+
pollMs = DEFAULT_POLL_MS3
|
|
2854
3098
|
} = options;
|
|
2855
3099
|
const deadline = Date.now() + timeoutMs;
|
|
2856
3100
|
debugLog("owney-sdk", "withdraw: waiting for funds to land", {
|
|
@@ -2925,7 +3169,7 @@ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
|
|
|
2925
3169
|
}
|
|
2926
3170
|
|
|
2927
3171
|
// src/lib/helpers/withdraw-helper.ts
|
|
2928
|
-
var
|
|
3172
|
+
var import_viem6 = require("viem");
|
|
2929
3173
|
function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
|
|
2930
3174
|
const target = asset.toUpperCase();
|
|
2931
3175
|
return agents.map((agent) => {
|
|
@@ -2934,7 +3178,7 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
|
|
|
2934
3178
|
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
2935
3179
|
);
|
|
2936
3180
|
if (!tokenBalance) return { agent, balance: 0n };
|
|
2937
|
-
return { agent, balance: (0,
|
|
3181
|
+
return { agent, balance: (0, import_viem6.parseUnits)(tokenBalance.amount, decimals) };
|
|
2938
3182
|
});
|
|
2939
3183
|
}
|
|
2940
3184
|
function planProportionalShares(balances, requested, totalAvailable) {
|
|
@@ -3081,11 +3325,11 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
|
3081
3325
|
}
|
|
3082
3326
|
|
|
3083
3327
|
// src/client.ts
|
|
3084
|
-
var
|
|
3328
|
+
var import_viem9 = require("viem");
|
|
3085
3329
|
var import_chains2 = require("viem/chains");
|
|
3086
3330
|
|
|
3087
3331
|
// src/lib/transfer-auth.ts
|
|
3088
|
-
var
|
|
3332
|
+
var import_viem7 = require("viem");
|
|
3089
3333
|
var ERC20_META_ABI = [
|
|
3090
3334
|
{ type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
|
|
3091
3335
|
{ type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
|
|
@@ -3117,7 +3361,7 @@ async function readTokenMeta(publicClient, token) {
|
|
|
3117
3361
|
function randomAuthNonce() {
|
|
3118
3362
|
const bytes = new Uint8Array(32);
|
|
3119
3363
|
globalThis.crypto.getRandomValues(bytes);
|
|
3120
|
-
return (0,
|
|
3364
|
+
return (0, import_viem7.bytesToHex)(bytes);
|
|
3121
3365
|
}
|
|
3122
3366
|
|
|
3123
3367
|
// src/lib/sponsor-client.ts
|
|
@@ -3399,7 +3643,7 @@ function makeSponsoredWethCallback(deps) {
|
|
|
3399
3643
|
}
|
|
3400
3644
|
|
|
3401
3645
|
// src/lib/sponsored-calls-deposit.ts
|
|
3402
|
-
var
|
|
3646
|
+
var import_viem8 = require("viem");
|
|
3403
3647
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
3404
3648
|
var DEFAULT_MAX_POLLS = 30;
|
|
3405
3649
|
async function paymasterSupported(provider, owner, chainId) {
|
|
@@ -3407,7 +3651,7 @@ async function paymasterSupported(provider, owner, chainId) {
|
|
|
3407
3651
|
method: "wallet_getCapabilities",
|
|
3408
3652
|
params: [owner]
|
|
3409
3653
|
});
|
|
3410
|
-
const forChain = caps?.[(0,
|
|
3654
|
+
const forChain = caps?.[(0, import_viem8.toHex)(chainId)] ?? caps?.[String(chainId)];
|
|
3411
3655
|
return Boolean(forChain?.paymasterService?.supported);
|
|
3412
3656
|
}
|
|
3413
3657
|
function makeSponsoredCallsCallback(deps) {
|
|
@@ -3441,8 +3685,8 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
3441
3685
|
{ chainId }
|
|
3442
3686
|
);
|
|
3443
3687
|
}
|
|
3444
|
-
const data = (0,
|
|
3445
|
-
abi:
|
|
3688
|
+
const data = (0, import_viem8.encodeFunctionData)({
|
|
3689
|
+
abi: import_viem8.erc20Abi,
|
|
3446
3690
|
functionName: "transfer",
|
|
3447
3691
|
args: [smartWallet, BigInt(amount)]
|
|
3448
3692
|
});
|
|
@@ -3452,7 +3696,7 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
3452
3696
|
{
|
|
3453
3697
|
version: "2.0.0",
|
|
3454
3698
|
from: deps.ownerAddress,
|
|
3455
|
-
chainId: (0,
|
|
3699
|
+
chainId: (0, import_viem8.toHex)(chainId),
|
|
3456
3700
|
atomicRequired: false,
|
|
3457
3701
|
calls: [{ to: token, value: "0x0", data }],
|
|
3458
3702
|
capabilities: {
|
|
@@ -3674,14 +3918,14 @@ var OwneySDK = class {
|
|
|
3674
3918
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
3675
3919
|
// PublicClient/WalletClient param types — structurally identical at
|
|
3676
3920
|
// runtime, but the two share a name TS treats as unrelated.
|
|
3677
|
-
getPublicClient: (cid) => (0,
|
|
3921
|
+
getPublicClient: (cid) => (0, import_viem9.createPublicClient)({
|
|
3678
3922
|
chain: VIEM_CHAIN2[cid],
|
|
3679
|
-
transport: (0,
|
|
3923
|
+
transport: (0, import_viem9.custom)(provider)
|
|
3680
3924
|
}),
|
|
3681
|
-
getWalletClient: (cid) => (0,
|
|
3925
|
+
getWalletClient: (cid) => (0, import_viem9.createWalletClient)({
|
|
3682
3926
|
account: owner,
|
|
3683
3927
|
chain: VIEM_CHAIN2[cid],
|
|
3684
|
-
transport: (0,
|
|
3928
|
+
transport: (0, import_viem9.custom)(provider)
|
|
3685
3929
|
})
|
|
3686
3930
|
});
|
|
3687
3931
|
if (!onApproved) this.cachedSponsoredCallback = callback;
|
|
@@ -3727,14 +3971,14 @@ var OwneySDK = class {
|
|
|
3727
3971
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
3728
3972
|
// PublicClient/WalletClient param types — structurally identical at
|
|
3729
3973
|
// runtime, but the two share a name TS treats as unrelated.
|
|
3730
|
-
getPublicClient: (cid) => (0,
|
|
3974
|
+
getPublicClient: (cid) => (0, import_viem9.createPublicClient)({
|
|
3731
3975
|
chain: VIEM_CHAIN2[cid],
|
|
3732
|
-
transport: (0,
|
|
3976
|
+
transport: (0, import_viem9.custom)(provider)
|
|
3733
3977
|
}),
|
|
3734
|
-
getWalletClient: (cid) => (0,
|
|
3978
|
+
getWalletClient: (cid) => (0, import_viem9.createWalletClient)({
|
|
3735
3979
|
account: owner,
|
|
3736
3980
|
chain: VIEM_CHAIN2[cid],
|
|
3737
|
-
transport: (0,
|
|
3981
|
+
transport: (0, import_viem9.custom)(provider)
|
|
3738
3982
|
})
|
|
3739
3983
|
});
|
|
3740
3984
|
if (!onApproved) this.cachedWethSponsoredCallback = callback;
|
|
@@ -4316,11 +4560,11 @@ var OwneySDK = class {
|
|
|
4316
4560
|
);
|
|
4317
4561
|
}
|
|
4318
4562
|
await ensureWalletOnChain(
|
|
4319
|
-
(0,
|
|
4320
|
-
(0,
|
|
4563
|
+
(0, import_viem9.createPublicClient)({ chain, transport: (0, import_viem9.custom)(provider) }),
|
|
4564
|
+
(0, import_viem9.createWalletClient)({
|
|
4321
4565
|
account: state.walletAddress,
|
|
4322
4566
|
chain,
|
|
4323
|
-
transport: (0,
|
|
4567
|
+
transport: (0, import_viem9.custom)(provider)
|
|
4324
4568
|
}),
|
|
4325
4569
|
chainId
|
|
4326
4570
|
);
|
|
@@ -4336,16 +4580,16 @@ var OwneySDK = class {
|
|
|
4336
4580
|
const provider = this.requireConnectedProvider();
|
|
4337
4581
|
const srcChain = VIEM_CHAIN2[quote.src.chainId];
|
|
4338
4582
|
const dstChain = VIEM_CHAIN2[quote.dst.chainId];
|
|
4339
|
-
const wallet = (0,
|
|
4583
|
+
const wallet = (0, import_viem9.createWalletClient)({
|
|
4340
4584
|
account: state.walletAddress,
|
|
4341
4585
|
chain: srcChain,
|
|
4342
|
-
transport: (0,
|
|
4586
|
+
transport: (0, import_viem9.custom)(provider)
|
|
4343
4587
|
});
|
|
4344
|
-
const srcPublic = (0,
|
|
4588
|
+
const srcPublic = (0, import_viem9.createPublicClient)({
|
|
4345
4589
|
chain: srcChain,
|
|
4346
4590
|
transport: swapReadTransport(quote.src.chainId, this.zyfaiRpcUrls)
|
|
4347
4591
|
});
|
|
4348
|
-
const dstPublic = (0,
|
|
4592
|
+
const dstPublic = (0, import_viem9.createPublicClient)({
|
|
4349
4593
|
chain: dstChain,
|
|
4350
4594
|
transport: swapReadTransport(quote.dst.chainId, this.zyfaiRpcUrls)
|
|
4351
4595
|
});
|
|
@@ -4362,7 +4606,7 @@ var OwneySDK = class {
|
|
|
4362
4606
|
}
|
|
4363
4607
|
return dstPublic.readContract({
|
|
4364
4608
|
address: dst,
|
|
4365
|
-
abi:
|
|
4609
|
+
abi: import_viem9.erc20Abi,
|
|
4366
4610
|
functionName: "balanceOf",
|
|
4367
4611
|
args: [state.walletAddress]
|
|
4368
4612
|
});
|
|
@@ -4430,6 +4674,41 @@ var OwneySDK = class {
|
|
|
4430
4674
|
return hash;
|
|
4431
4675
|
},
|
|
4432
4676
|
ensureChain: (chainId) => this.ensureSwapChain(chainId),
|
|
4677
|
+
/**
|
|
4678
|
+
* Sign the approval instead of paying for it. Null means the token
|
|
4679
|
+
* cannot be permitted (WETH has no `permit`) or its domain could not be
|
|
4680
|
+
* verified, and the executor sends an ordinary approval instead.
|
|
4681
|
+
*
|
|
4682
|
+
* Reads go through srcPublic like every other read here — the domain is
|
|
4683
|
+
* chain-scoped, and reading a nonce off whatever chain the wallet
|
|
4684
|
+
* happens to sit on would sign a permit the token rejects.
|
|
4685
|
+
*/
|
|
4686
|
+
buildPermit: (spender, amount) => buildErc2612Permit(
|
|
4687
|
+
{
|
|
4688
|
+
readContract: (args) => srcPublic.readContract(args)
|
|
4689
|
+
},
|
|
4690
|
+
{
|
|
4691
|
+
token: quote.src.address,
|
|
4692
|
+
owner: state.walletAddress,
|
|
4693
|
+
spender,
|
|
4694
|
+
value: amount,
|
|
4695
|
+
chainId: quote.src.chainId,
|
|
4696
|
+
signTypedData: (payload) => wallet.signTypedData({
|
|
4697
|
+
account: state.walletAddress,
|
|
4698
|
+
...payload
|
|
4699
|
+
})
|
|
4700
|
+
}
|
|
4701
|
+
),
|
|
4702
|
+
/**
|
|
4703
|
+
* Same-chain polling, with the chain bound in. That product keys its
|
|
4704
|
+
* orders per chain; asking the Fusion+ endpoint for one answers 404,
|
|
4705
|
+
* which would read as an order that vanished mid-swap.
|
|
4706
|
+
*/
|
|
4707
|
+
sameChainRunner: {
|
|
4708
|
+
orderStatus: (h) => this.swapApi().orderStatus(h, quote.src.chainId),
|
|
4709
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
4710
|
+
now: () => Date.now()
|
|
4711
|
+
},
|
|
4433
4712
|
runner: {
|
|
4434
4713
|
readyForSecrets: (h) => this.swapApi().readyForSecrets(h),
|
|
4435
4714
|
submitSecret: (h, secret) => this.swapApi().submitSecret(h, secret),
|
|
@@ -4448,6 +4727,39 @@ var OwneySDK = class {
|
|
|
4448
4727
|
async getSwapTokens() {
|
|
4449
4728
|
return this.swapApi().listTokens();
|
|
4450
4729
|
}
|
|
4730
|
+
/**
|
|
4731
|
+
* What the wallet actually holds, across every supported chain, restricted
|
|
4732
|
+
* to tokens a swap could use.
|
|
4733
|
+
*
|
|
4734
|
+
* `getSwapTokens` answers "what may someone pay with in principle" from a
|
|
4735
|
+
* fixed list. This answers "what has this wallet got", which is a different
|
|
4736
|
+
* question and the one a picker needs: a user can hold something routable —
|
|
4737
|
+
* tBTC, say — that no fixed list of ours would ever mention.
|
|
4738
|
+
*
|
|
4739
|
+
* Each chain is asked separately because that is how the balance product is
|
|
4740
|
+
* shaped, and a chain that fails contributes nothing rather than failing the
|
|
4741
|
+
* lot. A picker with two chains in it beats an error. (ROUT-242)
|
|
4742
|
+
*/
|
|
4743
|
+
async getWalletBalances(chainIds) {
|
|
4744
|
+
const state = this.requireState();
|
|
4745
|
+
const chains = chainIds ?? SUPPORTED_CHAIN_IDS;
|
|
4746
|
+
const failedChainIds = [];
|
|
4747
|
+
const results = await Promise.all(
|
|
4748
|
+
chains.map(async (chainId) => {
|
|
4749
|
+
try {
|
|
4750
|
+
const { tokens } = await this.swapApi().walletBalances({
|
|
4751
|
+
chainId,
|
|
4752
|
+
walletAddress: state.walletAddress
|
|
4753
|
+
});
|
|
4754
|
+
return tokens.map((token) => ({ ...token, chainId }));
|
|
4755
|
+
} catch {
|
|
4756
|
+
failedChainIds.push(chainId);
|
|
4757
|
+
return [];
|
|
4758
|
+
}
|
|
4759
|
+
})
|
|
4760
|
+
);
|
|
4761
|
+
return { tokens: results.flat(), failedChainIds };
|
|
4762
|
+
}
|
|
4451
4763
|
/**
|
|
4452
4764
|
* Search the assets a user may pay with, across every supported chain.
|
|
4453
4765
|
*
|
|
@@ -4575,13 +4887,13 @@ var OwneySDK = class {
|
|
|
4575
4887
|
);
|
|
4576
4888
|
}
|
|
4577
4889
|
const srcChain = VIEM_CHAIN2[options.from.chainId];
|
|
4578
|
-
const srcPublic = (0,
|
|
4890
|
+
const srcPublic = (0, import_viem9.createPublicClient)({
|
|
4579
4891
|
chain: srcChain,
|
|
4580
4892
|
transport: swapReadTransport(options.from.chainId, this.zyfaiRpcUrls)
|
|
4581
4893
|
});
|
|
4582
4894
|
const readWalletBalance = () => srcPublic.readContract({
|
|
4583
4895
|
address: asset.address,
|
|
4584
|
-
abi:
|
|
4896
|
+
abi: import_viem9.erc20Abi,
|
|
4585
4897
|
functionName: "balanceOf",
|
|
4586
4898
|
args: [state.walletAddress]
|
|
4587
4899
|
});
|
|
@@ -4593,7 +4905,7 @@ var OwneySDK = class {
|
|
|
4593
4905
|
options.onSwapProgress?.("withdrawing");
|
|
4594
4906
|
const withdraw = await this.withdraw({
|
|
4595
4907
|
asset: options.from.symbol,
|
|
4596
|
-
...options.amount === void 0 ? {} : { amount: (0,
|
|
4908
|
+
...options.amount === void 0 ? {} : { amount: (0, import_viem9.parseUnits)(options.amount, asset.decimals).toString() },
|
|
4597
4909
|
...options.agentId ? { agentId: options.agentId } : {}
|
|
4598
4910
|
});
|
|
4599
4911
|
const arrived = await awaitWithdrawalArrival({
|
|
@@ -5229,10 +5541,10 @@ var OwneySDK = class {
|
|
|
5229
5541
|
);
|
|
5230
5542
|
}
|
|
5231
5543
|
const provider = this.requireConnectedProvider();
|
|
5232
|
-
const wallet = (0,
|
|
5544
|
+
const wallet = (0, import_viem9.createWalletClient)({
|
|
5233
5545
|
account: state.walletAddress,
|
|
5234
5546
|
chain: VIEM_CHAIN2[chainId],
|
|
5235
|
-
transport: (0,
|
|
5547
|
+
transport: (0, import_viem9.custom)(provider)
|
|
5236
5548
|
});
|
|
5237
5549
|
const hash = await wallet.writeContract({
|
|
5238
5550
|
address: token,
|
|
@@ -5242,9 +5554,9 @@ var OwneySDK = class {
|
|
|
5242
5554
|
account: state.walletAddress,
|
|
5243
5555
|
chain: VIEM_CHAIN2[chainId]
|
|
5244
5556
|
});
|
|
5245
|
-
const publicClient = (0,
|
|
5557
|
+
const publicClient = (0, import_viem9.createPublicClient)({
|
|
5246
5558
|
chain: VIEM_CHAIN2[chainId],
|
|
5247
|
-
transport: (0,
|
|
5559
|
+
transport: (0, import_viem9.custom)(provider)
|
|
5248
5560
|
});
|
|
5249
5561
|
const receipt = await publicClient.waitForTransactionReceipt({
|
|
5250
5562
|
hash,
|
|
@@ -5370,7 +5682,7 @@ var OwneySDK = class {
|
|
|
5370
5682
|
};
|
|
5371
5683
|
|
|
5372
5684
|
// src/agents/zyfai/zyfai.siwx.ts
|
|
5373
|
-
var
|
|
5685
|
+
var import_viem10 = require("viem");
|
|
5374
5686
|
var import_siwe = require("siwe");
|
|
5375
5687
|
var import_sdk2 = require("@zyfai/sdk");
|
|
5376
5688
|
|
|
@@ -5497,7 +5809,7 @@ function buildSIWXConfig(deps) {
|
|
|
5497
5809
|
issuedAt,
|
|
5498
5810
|
toString() {
|
|
5499
5811
|
return new import_siwe.SiweMessage({
|
|
5500
|
-
address: (0,
|
|
5812
|
+
address: (0, import_viem10.getAddress)(accountAddress),
|
|
5501
5813
|
chainId: numericChainId(chainId),
|
|
5502
5814
|
domain,
|
|
5503
5815
|
uri,
|
package/dist/index.d.cts
CHANGED
|
@@ -13,11 +13,16 @@ import { SIWXConfig } from '@reown/appkit-controllers';
|
|
|
13
13
|
* `classic` — same chain. One atomic transaction: it either completes or
|
|
14
14
|
* nothing moved.
|
|
15
15
|
*
|
|
16
|
+
* `fusion` — same chain, filled by a resolver against a signed intent. No
|
|
17
|
+
* transaction from the user, so no native gas: that is the entire reason it
|
|
18
|
+
* exists. The trade is certainty — the order sits in an auction and can expire
|
|
19
|
+
* unfilled, where a classic swap resolves immediately either way.
|
|
20
|
+
*
|
|
16
21
|
* `fusion-plus` — crossing chains. The user's funds sit in an escrow while a
|
|
17
22
|
* resolver fills the other side, so the order has a lifecycle and can end in
|
|
18
23
|
* `expired` → `refunding` → `refunded` without ever depositing.
|
|
19
24
|
*/
|
|
20
|
-
type SwapRail = "classic" | "fusion-plus";
|
|
25
|
+
type SwapRail = "classic" | "fusion" | "fusion-plus";
|
|
21
26
|
type SwapTokenInfo = {
|
|
22
27
|
readonly symbol: string;
|
|
23
28
|
readonly address: string;
|
|
@@ -49,6 +54,26 @@ type SwapChainTokens = {
|
|
|
49
54
|
* the bulk list does, and the extra fields are the ones a picker needs to let
|
|
50
55
|
* someone choose safely between three tokens all called PEPE.
|
|
51
56
|
*/
|
|
57
|
+
/**
|
|
58
|
+
* A token the wallet actually holds, as the Pay with picker needs it.
|
|
59
|
+
*
|
|
60
|
+
* The picker used to read a fixed list of (token, chain) pairs, so it could
|
|
61
|
+
* only ever offer those. This is the other direction — what is really there —
|
|
62
|
+
* already intersected server-side with what the routing API would quote, so
|
|
63
|
+
* every row can be acted on. (ROUT-242)
|
|
64
|
+
*/
|
|
65
|
+
type SwapWalletBalance = {
|
|
66
|
+
readonly symbol: string;
|
|
67
|
+
readonly address: `0x${string}`;
|
|
68
|
+
readonly decimals: number;
|
|
69
|
+
readonly chainId: number;
|
|
70
|
+
readonly name?: string;
|
|
71
|
+
readonly logoURI?: string;
|
|
72
|
+
readonly tags?: readonly string[];
|
|
73
|
+
readonly isNative?: true;
|
|
74
|
+
/** Smallest unit, as a decimal string. */
|
|
75
|
+
readonly balance: string;
|
|
76
|
+
};
|
|
52
77
|
type SwapTokenSearchResult = SwapTokenInfo & {
|
|
53
78
|
readonly chainId: number;
|
|
54
79
|
readonly name?: string;
|
|
@@ -110,6 +135,17 @@ type SwapQuote = {
|
|
|
110
135
|
* calldata instead.
|
|
111
136
|
*/
|
|
112
137
|
spender?: string;
|
|
138
|
+
/**
|
|
139
|
+
* Cross-chain only. True when the routing API will accept an EIP-2612 permit
|
|
140
|
+
* on the order, so the approval is performed by the resolver inside the fill
|
|
141
|
+
* and the user needs no native ETH. False or absent means send an approval
|
|
142
|
+
* transaction, which is what every swap did before ROUT-242.
|
|
143
|
+
*
|
|
144
|
+
* It has to arrive on the QUOTE rather than being tried and recovered from:
|
|
145
|
+
* the client decides whether to spend the user's gas before the order it
|
|
146
|
+
* would be attached to exists.
|
|
147
|
+
*/
|
|
148
|
+
permitSupported?: boolean;
|
|
113
149
|
/**
|
|
114
150
|
* True only for a cross-chain swap FROM native ETH, which needs an on-chain
|
|
115
151
|
* order creation carrying the full amount as msg.value. The user's funds
|
|
@@ -155,7 +191,7 @@ type SwapQuote = {
|
|
|
155
191
|
* `refunding` is the window the returning-funds screen renders: the order has
|
|
156
192
|
* failed and the money is on its way back, but is not back yet.
|
|
157
193
|
*/
|
|
158
|
-
type SwapOrderStatus = "pending" | "executed" | "expired" | "cancelled" | "refunding" | "refunded" | "unpublished";
|
|
194
|
+
type SwapOrderStatus = "pending" | "executed" | "expired" | "cancelled" | "refunding" | "refunded" | "unpublished" | "filled" | "partially-filled" | "false-predicate" | "not-enough-balance-or-allowance" | "wrong-permit" | "invalid-signature";
|
|
159
195
|
/**
|
|
160
196
|
* Stage reported to the UI while a swap runs, in either direction.
|
|
161
197
|
*
|
|
@@ -909,6 +945,24 @@ declare class OwneySDK {
|
|
|
909
945
|
getSwapTokens(): Promise<{
|
|
910
946
|
chains: SwapChainTokens[];
|
|
911
947
|
}>;
|
|
948
|
+
/**
|
|
949
|
+
* What the wallet actually holds, across every supported chain, restricted
|
|
950
|
+
* to tokens a swap could use.
|
|
951
|
+
*
|
|
952
|
+
* `getSwapTokens` answers "what may someone pay with in principle" from a
|
|
953
|
+
* fixed list. This answers "what has this wallet got", which is a different
|
|
954
|
+
* question and the one a picker needs: a user can hold something routable —
|
|
955
|
+
* tBTC, say — that no fixed list of ours would ever mention.
|
|
956
|
+
*
|
|
957
|
+
* Each chain is asked separately because that is how the balance product is
|
|
958
|
+
* shaped, and a chain that fails contributes nothing rather than failing the
|
|
959
|
+
* lot. A picker with two chains in it beats an error. (ROUT-242)
|
|
960
|
+
*/
|
|
961
|
+
getWalletBalances(chainIds?: readonly number[]): Promise<{
|
|
962
|
+
tokens: SwapWalletBalance[];
|
|
963
|
+
/** Chains that could not be read, so the caller can say so or retry. */
|
|
964
|
+
failedChainIds: number[];
|
|
965
|
+
}>;
|
|
912
966
|
/**
|
|
913
967
|
* Search the assets a user may pay with, across every supported chain.
|
|
914
968
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -13,11 +13,16 @@ import { SIWXConfig } from '@reown/appkit-controllers';
|
|
|
13
13
|
* `classic` — same chain. One atomic transaction: it either completes or
|
|
14
14
|
* nothing moved.
|
|
15
15
|
*
|
|
16
|
+
* `fusion` — same chain, filled by a resolver against a signed intent. No
|
|
17
|
+
* transaction from the user, so no native gas: that is the entire reason it
|
|
18
|
+
* exists. The trade is certainty — the order sits in an auction and can expire
|
|
19
|
+
* unfilled, where a classic swap resolves immediately either way.
|
|
20
|
+
*
|
|
16
21
|
* `fusion-plus` — crossing chains. The user's funds sit in an escrow while a
|
|
17
22
|
* resolver fills the other side, so the order has a lifecycle and can end in
|
|
18
23
|
* `expired` → `refunding` → `refunded` without ever depositing.
|
|
19
24
|
*/
|
|
20
|
-
type SwapRail = "classic" | "fusion-plus";
|
|
25
|
+
type SwapRail = "classic" | "fusion" | "fusion-plus";
|
|
21
26
|
type SwapTokenInfo = {
|
|
22
27
|
readonly symbol: string;
|
|
23
28
|
readonly address: string;
|
|
@@ -49,6 +54,26 @@ type SwapChainTokens = {
|
|
|
49
54
|
* the bulk list does, and the extra fields are the ones a picker needs to let
|
|
50
55
|
* someone choose safely between three tokens all called PEPE.
|
|
51
56
|
*/
|
|
57
|
+
/**
|
|
58
|
+
* A token the wallet actually holds, as the Pay with picker needs it.
|
|
59
|
+
*
|
|
60
|
+
* The picker used to read a fixed list of (token, chain) pairs, so it could
|
|
61
|
+
* only ever offer those. This is the other direction — what is really there —
|
|
62
|
+
* already intersected server-side with what the routing API would quote, so
|
|
63
|
+
* every row can be acted on. (ROUT-242)
|
|
64
|
+
*/
|
|
65
|
+
type SwapWalletBalance = {
|
|
66
|
+
readonly symbol: string;
|
|
67
|
+
readonly address: `0x${string}`;
|
|
68
|
+
readonly decimals: number;
|
|
69
|
+
readonly chainId: number;
|
|
70
|
+
readonly name?: string;
|
|
71
|
+
readonly logoURI?: string;
|
|
72
|
+
readonly tags?: readonly string[];
|
|
73
|
+
readonly isNative?: true;
|
|
74
|
+
/** Smallest unit, as a decimal string. */
|
|
75
|
+
readonly balance: string;
|
|
76
|
+
};
|
|
52
77
|
type SwapTokenSearchResult = SwapTokenInfo & {
|
|
53
78
|
readonly chainId: number;
|
|
54
79
|
readonly name?: string;
|
|
@@ -110,6 +135,17 @@ type SwapQuote = {
|
|
|
110
135
|
* calldata instead.
|
|
111
136
|
*/
|
|
112
137
|
spender?: string;
|
|
138
|
+
/**
|
|
139
|
+
* Cross-chain only. True when the routing API will accept an EIP-2612 permit
|
|
140
|
+
* on the order, so the approval is performed by the resolver inside the fill
|
|
141
|
+
* and the user needs no native ETH. False or absent means send an approval
|
|
142
|
+
* transaction, which is what every swap did before ROUT-242.
|
|
143
|
+
*
|
|
144
|
+
* It has to arrive on the QUOTE rather than being tried and recovered from:
|
|
145
|
+
* the client decides whether to spend the user's gas before the order it
|
|
146
|
+
* would be attached to exists.
|
|
147
|
+
*/
|
|
148
|
+
permitSupported?: boolean;
|
|
113
149
|
/**
|
|
114
150
|
* True only for a cross-chain swap FROM native ETH, which needs an on-chain
|
|
115
151
|
* order creation carrying the full amount as msg.value. The user's funds
|
|
@@ -155,7 +191,7 @@ type SwapQuote = {
|
|
|
155
191
|
* `refunding` is the window the returning-funds screen renders: the order has
|
|
156
192
|
* failed and the money is on its way back, but is not back yet.
|
|
157
193
|
*/
|
|
158
|
-
type SwapOrderStatus = "pending" | "executed" | "expired" | "cancelled" | "refunding" | "refunded" | "unpublished";
|
|
194
|
+
type SwapOrderStatus = "pending" | "executed" | "expired" | "cancelled" | "refunding" | "refunded" | "unpublished" | "filled" | "partially-filled" | "false-predicate" | "not-enough-balance-or-allowance" | "wrong-permit" | "invalid-signature";
|
|
159
195
|
/**
|
|
160
196
|
* Stage reported to the UI while a swap runs, in either direction.
|
|
161
197
|
*
|
|
@@ -909,6 +945,24 @@ declare class OwneySDK {
|
|
|
909
945
|
getSwapTokens(): Promise<{
|
|
910
946
|
chains: SwapChainTokens[];
|
|
911
947
|
}>;
|
|
948
|
+
/**
|
|
949
|
+
* What the wallet actually holds, across every supported chain, restricted
|
|
950
|
+
* to tokens a swap could use.
|
|
951
|
+
*
|
|
952
|
+
* `getSwapTokens` answers "what may someone pay with in principle" from a
|
|
953
|
+
* fixed list. This answers "what has this wallet got", which is a different
|
|
954
|
+
* question and the one a picker needs: a user can hold something routable —
|
|
955
|
+
* tBTC, say — that no fixed list of ours would ever mention.
|
|
956
|
+
*
|
|
957
|
+
* Each chain is asked separately because that is how the balance product is
|
|
958
|
+
* shaped, and a chain that fails contributes nothing rather than failing the
|
|
959
|
+
* lot. A picker with two chains in it beats an error. (ROUT-242)
|
|
960
|
+
*/
|
|
961
|
+
getWalletBalances(chainIds?: readonly number[]): Promise<{
|
|
962
|
+
tokens: SwapWalletBalance[];
|
|
963
|
+
/** Chains that could not be read, so the caller can say so or retry. */
|
|
964
|
+
failedChainIds: number[];
|
|
965
|
+
}>;
|
|
912
966
|
/**
|
|
913
967
|
* Search the assets a user may pay with, across every supported chain.
|
|
914
968
|
*
|
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,
|
|
@@ -2332,9 +2347,10 @@ function createSwapApi(baseUrl, apiKey) {
|
|
|
2332
2347
|
dstSymbol: params.to.symbol,
|
|
2333
2348
|
amount: params.from.amount,
|
|
2334
2349
|
walletAddress: params.walletAddress,
|
|
2335
|
-
secretHashes: params.secretHashes,
|
|
2350
|
+
...params.secretHashes ? { secretHashes: params.secretHashes } : {},
|
|
2336
2351
|
...params.direction ? { direction: params.direction } : {},
|
|
2337
|
-
...params.receiver ? { receiver: params.receiver } : {}
|
|
2352
|
+
...params.receiver ? { receiver: params.receiver } : {},
|
|
2353
|
+
...params.permit ? { permit: params.permit } : {}
|
|
2338
2354
|
}
|
|
2339
2355
|
}),
|
|
2340
2356
|
submitOrder: (body) => request(baseUrl, apiKey, "/order", { method: "POST", body }),
|
|
@@ -2347,7 +2363,16 @@ function createSwapApi(baseUrl, apiKey) {
|
|
|
2347
2363
|
method: "POST",
|
|
2348
2364
|
body: { orderHash, secret }
|
|
2349
2365
|
}),
|
|
2350
|
-
|
|
2366
|
+
/**
|
|
2367
|
+
* @param chainId present only for a SAME-CHAIN order. That product keys its
|
|
2368
|
+
* orders per chain and the Fusion+ endpoint does not know them, so asking
|
|
2369
|
+
* without it answers 404 — which reads as an order that vanished.
|
|
2370
|
+
*/
|
|
2371
|
+
orderStatus: (orderHash, chainId) => request(
|
|
2372
|
+
baseUrl,
|
|
2373
|
+
apiKey,
|
|
2374
|
+
chainId === void 0 ? `/order/${orderHash}` : `/order/${orderHash}?chainId=${chainId}`
|
|
2375
|
+
),
|
|
2351
2376
|
readyForSecrets: (orderHash) => request(
|
|
2352
2377
|
baseUrl,
|
|
2353
2378
|
apiKey,
|
|
@@ -2490,7 +2515,12 @@ var SWAP_TERMINAL_STATUSES = [
|
|
|
2490
2515
|
"executed",
|
|
2491
2516
|
"expired",
|
|
2492
2517
|
"cancelled",
|
|
2493
|
-
"refunded"
|
|
2518
|
+
"refunded",
|
|
2519
|
+
"filled",
|
|
2520
|
+
"false-predicate",
|
|
2521
|
+
"not-enough-balance-or-allowance",
|
|
2522
|
+
"wrong-permit",
|
|
2523
|
+
"invalid-signature"
|
|
2494
2524
|
];
|
|
2495
2525
|
var isSwapTerminal = (status) => SWAP_TERMINAL_STATUSES.includes(status);
|
|
2496
2526
|
|
|
@@ -2568,6 +2598,71 @@ async function runFusionOrder(deps, options) {
|
|
|
2568
2598
|
}
|
|
2569
2599
|
}
|
|
2570
2600
|
|
|
2601
|
+
// src/lib/swap/swap.same-chain-runner.ts
|
|
2602
|
+
var DEFAULT_POLL_MS2 = 5e3;
|
|
2603
|
+
var MAX_BACKOFF_MS2 = 3e4;
|
|
2604
|
+
var backoffFor2 = (failures, base3) => Math.min(base3 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS2);
|
|
2605
|
+
var DEFAULT_TIMEOUT_MS2 = 3 * 60 * 1e3;
|
|
2606
|
+
function explain(status) {
|
|
2607
|
+
switch (status) {
|
|
2608
|
+
case "expired":
|
|
2609
|
+
return "No one filled the swap in time, so nothing was exchanged. Your funds never left your wallet.";
|
|
2610
|
+
case "cancelled":
|
|
2611
|
+
return "The swap was cancelled before anyone filled it. Your funds never left your wallet.";
|
|
2612
|
+
case "not-enough-balance-or-allowance":
|
|
2613
|
+
return "The swap could not be filled because the balance or approval had changed since it was signed. Nothing was exchanged.";
|
|
2614
|
+
case "wrong-permit":
|
|
2615
|
+
return "The approval signed with this swap was not accepted. Nothing was exchanged \u2014 try again, approving in your wallet if you are asked to.";
|
|
2616
|
+
case "invalid-signature":
|
|
2617
|
+
return "The swap signature was rejected. Nothing was exchanged.";
|
|
2618
|
+
case "false-predicate":
|
|
2619
|
+
return "The swap's conditions no longer held when it was filled. Nothing was exchanged.";
|
|
2620
|
+
default:
|
|
2621
|
+
return "The swap did not complete. Nothing was exchanged.";
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
async function runSameChainOrder(deps, options) {
|
|
2625
|
+
const {
|
|
2626
|
+
orderHash,
|
|
2627
|
+
onStage,
|
|
2628
|
+
pollIntervalMs = DEFAULT_POLL_MS2,
|
|
2629
|
+
timeoutMs = DEFAULT_TIMEOUT_MS2
|
|
2630
|
+
} = options;
|
|
2631
|
+
const deadline = deps.now() + timeoutMs;
|
|
2632
|
+
let failures = 0;
|
|
2633
|
+
onStage?.("swapping");
|
|
2634
|
+
for (; ; ) {
|
|
2635
|
+
if (deps.now() >= deadline) {
|
|
2636
|
+
throw new OwneyError(
|
|
2637
|
+
"SWAP_ORDER_EXPIRED",
|
|
2638
|
+
"No one filled the swap in time. Nothing was exchanged and your funds are still in your wallet.",
|
|
2639
|
+
{ orderHash }
|
|
2640
|
+
);
|
|
2641
|
+
}
|
|
2642
|
+
let status;
|
|
2643
|
+
try {
|
|
2644
|
+
({ status } = await deps.orderStatus(orderHash));
|
|
2645
|
+
failures = 0;
|
|
2646
|
+
} catch {
|
|
2647
|
+
failures += 1;
|
|
2648
|
+
await deps.sleep(backoffFor2(failures, pollIntervalMs));
|
|
2649
|
+
continue;
|
|
2650
|
+
}
|
|
2651
|
+
if (isSwapTerminal(status)) {
|
|
2652
|
+
if (status === "filled" || status === "executed") {
|
|
2653
|
+
onStage?.("swapped");
|
|
2654
|
+
return { status, filled: true };
|
|
2655
|
+
}
|
|
2656
|
+
throw new OwneyError(
|
|
2657
|
+
status === "cancelled" ? "SWAP_ORDER_CANCELLED" : "SWAP_ORDER_EXPIRED",
|
|
2658
|
+
explain(status),
|
|
2659
|
+
{ orderHash, status }
|
|
2660
|
+
);
|
|
2661
|
+
}
|
|
2662
|
+
await deps.sleep(pollIntervalMs);
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2571
2666
|
// src/lib/swap/swap.secret-store.ts
|
|
2572
2667
|
var KEY_PREFIX2 = "owney.swap.order";
|
|
2573
2668
|
var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -2651,7 +2746,7 @@ async function executeSwap(deps, options) {
|
|
|
2651
2746
|
});
|
|
2652
2747
|
const before = await deps.readTargetBalance();
|
|
2653
2748
|
debugLog("owney-sdk", "swap: target balance before", before.toString());
|
|
2654
|
-
const result = quote.rail === "classic" ? await runClassic(deps, options) : await runFusion(deps, options, walletAddress);
|
|
2749
|
+
const result = quote.rail === "classic" ? await runClassic(deps, options) : quote.rail === "fusion" ? await runSameChainFusion(deps, options, walletAddress) : await runFusion(deps, options, walletAddress);
|
|
2655
2750
|
const after = await deps.readTargetBalance();
|
|
2656
2751
|
const received = after - before;
|
|
2657
2752
|
debugLog("owney-sdk", "swap: target balance after", {
|
|
@@ -2722,25 +2817,75 @@ async function runClassic(deps, options) {
|
|
|
2722
2817
|
onStage?.("swapped");
|
|
2723
2818
|
return { txHash };
|
|
2724
2819
|
}
|
|
2725
|
-
async function
|
|
2820
|
+
async function runSameChainFusion(deps, options, walletAddress) {
|
|
2726
2821
|
const { quote, direction, onStage } = options;
|
|
2727
2822
|
const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
|
|
2728
2823
|
const amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount));
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2824
|
+
const permit = await authoriseSpend(deps, options, amount, isNativeSource);
|
|
2825
|
+
onStage?.("quoting");
|
|
2826
|
+
debugLog("owney-sdk", "swap: building same-chain fusion order");
|
|
2827
|
+
const built = await deps.api.buildOrder({
|
|
2828
|
+
from: {
|
|
2829
|
+
chainId: quote.src.chainId,
|
|
2830
|
+
symbol: quote.src.symbol,
|
|
2831
|
+
amount: amount.toString()
|
|
2832
|
+
},
|
|
2833
|
+
to: { chainId: quote.dst.chainId, symbol: quote.dst.symbol },
|
|
2834
|
+
walletAddress,
|
|
2835
|
+
...direction ? { direction } : {},
|
|
2836
|
+
...permit ? { permit } : {}
|
|
2837
|
+
});
|
|
2838
|
+
debugLog("owney-sdk", "swap: same-chain order built", {
|
|
2839
|
+
orderHash: built.orderHash
|
|
2840
|
+
});
|
|
2841
|
+
onStage?.("signing");
|
|
2842
|
+
await deps.ensureChain(quote.src.chainId);
|
|
2843
|
+
const signature = await deps.signTypedData(built.typedData);
|
|
2844
|
+
debugLog("owney-sdk", "swap: signed, submitting same-chain order");
|
|
2845
|
+
await deps.api.submitOrder({
|
|
2846
|
+
// Without this the routing API hands the order to the Fusion+ relayer,
|
|
2847
|
+
// which does not know it and answers with an error that names nothing.
|
|
2848
|
+
rail: "fusion",
|
|
2849
|
+
srcChainId: quote.src.chainId,
|
|
2850
|
+
order: built.order,
|
|
2851
|
+
signature,
|
|
2852
|
+
quoteId: built.quoteId,
|
|
2853
|
+
...built.extension ? { extension: built.extension } : {}
|
|
2854
|
+
});
|
|
2855
|
+
await runSameChainOrder(deps.sameChainRunner ?? deps.runner, {
|
|
2856
|
+
orderHash: built.orderHash,
|
|
2857
|
+
...onStage ? { onStage } : {}
|
|
2858
|
+
});
|
|
2859
|
+
return { orderHash: built.orderHash };
|
|
2860
|
+
}
|
|
2861
|
+
async function authoriseSpend(deps, options, amount, isNativeSource) {
|
|
2862
|
+
const { quote, onStage } = options;
|
|
2863
|
+
if (!quote.spender || isNativeSource) return void 0;
|
|
2864
|
+
const current = await deps.readAllowance(quote.spender);
|
|
2865
|
+
debugLog("owney-sdk", "swap: fusion allowance", {
|
|
2866
|
+
spender: quote.spender,
|
|
2867
|
+
current: current.toString(),
|
|
2868
|
+
needed: amount.toString()
|
|
2869
|
+
});
|
|
2870
|
+
if (current >= amount) return void 0;
|
|
2871
|
+
onStage?.("approving");
|
|
2872
|
+
await deps.ensureChain(quote.src.chainId);
|
|
2873
|
+
const permit = quote.permitSupported ? await deps.buildPermit?.(quote.spender, MAX_UINT256) ?? void 0 : void 0;
|
|
2874
|
+
if (permit) {
|
|
2875
|
+
debugLog("owney-sdk", "swap: permitting limit order protocol", {
|
|
2876
|
+
bytes: (permit.length - 2) / 2
|
|
2736
2877
|
});
|
|
2737
|
-
|
|
2738
|
-
onStage?.("approving");
|
|
2739
|
-
await deps.ensureChain(quote.src.chainId);
|
|
2740
|
-
await deps.approve(quote.spender, MAX_UINT256);
|
|
2741
|
-
debugLog("owney-sdk", "swap: approved limit order protocol");
|
|
2742
|
-
}
|
|
2878
|
+
return permit;
|
|
2743
2879
|
}
|
|
2880
|
+
await deps.approve(quote.spender, MAX_UINT256);
|
|
2881
|
+
debugLog("owney-sdk", "swap: approved limit order protocol");
|
|
2882
|
+
return void 0;
|
|
2883
|
+
}
|
|
2884
|
+
async function runFusion(deps, options, walletAddress) {
|
|
2885
|
+
const { quote, direction, onStage } = options;
|
|
2886
|
+
const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
|
|
2887
|
+
const amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount));
|
|
2888
|
+
const permit = await authoriseSpend(deps, options, amount, isNativeSource);
|
|
2744
2889
|
const { secrets, secretHashes } = mintSecrets(quote.secretsCount ?? 1);
|
|
2745
2890
|
onStage?.("quoting");
|
|
2746
2891
|
debugLog("owney-sdk", "swap: building fusion order", {
|
|
@@ -2756,7 +2901,8 @@ async function runFusion(deps, options, walletAddress) {
|
|
|
2756
2901
|
to: { chainId: quote.dst.chainId, symbol: quote.dst.symbol },
|
|
2757
2902
|
walletAddress,
|
|
2758
2903
|
secretHashes,
|
|
2759
|
-
...direction ? { direction } : {}
|
|
2904
|
+
...direction ? { direction } : {},
|
|
2905
|
+
...permit ? { permit } : {}
|
|
2760
2906
|
});
|
|
2761
2907
|
saveOrder({
|
|
2762
2908
|
orderHash: built.orderHash,
|
|
@@ -2807,16 +2953,119 @@ async function runFusion(deps, options, walletAddress) {
|
|
|
2807
2953
|
return { orderHash: built.orderHash };
|
|
2808
2954
|
}
|
|
2809
2955
|
|
|
2956
|
+
// src/lib/swap/swap.permit.ts
|
|
2957
|
+
import {
|
|
2958
|
+
domainSeparator,
|
|
2959
|
+
encodeAbiParameters,
|
|
2960
|
+
parseAbi as parseAbi2,
|
|
2961
|
+
parseSignature
|
|
2962
|
+
} from "viem";
|
|
2963
|
+
var PERMIT_ABI = parseAbi2([
|
|
2964
|
+
"function nonces(address owner) view returns (uint256)",
|
|
2965
|
+
"function name() view returns (string)",
|
|
2966
|
+
"function version() view returns (string)",
|
|
2967
|
+
"function DOMAIN_SEPARATOR() view returns (bytes32)"
|
|
2968
|
+
]);
|
|
2969
|
+
var PERMIT_TYPES = {
|
|
2970
|
+
Permit: [
|
|
2971
|
+
{ name: "owner", type: "address" },
|
|
2972
|
+
{ name: "spender", type: "address" },
|
|
2973
|
+
{ name: "value", type: "uint256" },
|
|
2974
|
+
{ name: "nonce", type: "uint256" },
|
|
2975
|
+
{ name: "deadline", type: "uint256" }
|
|
2976
|
+
]
|
|
2977
|
+
};
|
|
2978
|
+
var VERSION_CANDIDATES = ["1", "2"];
|
|
2979
|
+
var PERMIT_TTL_SECONDS = 3600;
|
|
2980
|
+
async function tryRead(read) {
|
|
2981
|
+
try {
|
|
2982
|
+
return await read();
|
|
2983
|
+
} catch {
|
|
2984
|
+
return null;
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
function matchDomain(name, versions, chainId, token, onChainSeparator) {
|
|
2988
|
+
for (const version of versions) {
|
|
2989
|
+
const domain = { name, version, chainId, verifyingContract: token };
|
|
2990
|
+
if (domainSeparator({ domain }) === onChainSeparator) return domain;
|
|
2991
|
+
}
|
|
2992
|
+
return null;
|
|
2993
|
+
}
|
|
2994
|
+
async function buildErc2612Permit(reads, input) {
|
|
2995
|
+
const { token, owner, spender, value, chainId } = input;
|
|
2996
|
+
const call = (functionName, args) => reads.readContract({
|
|
2997
|
+
address: token,
|
|
2998
|
+
abi: PERMIT_ABI,
|
|
2999
|
+
functionName,
|
|
3000
|
+
...args ? { args } : {}
|
|
3001
|
+
});
|
|
3002
|
+
const nonce = await tryRead(() => call("nonces", [owner]));
|
|
3003
|
+
if (nonce === null) {
|
|
3004
|
+
debugLog("owney-sdk", "permit: token has no nonces(), using approval", {
|
|
3005
|
+
token
|
|
3006
|
+
});
|
|
3007
|
+
return null;
|
|
3008
|
+
}
|
|
3009
|
+
const separator = await tryRead(
|
|
3010
|
+
() => call("DOMAIN_SEPARATOR")
|
|
3011
|
+
);
|
|
3012
|
+
const name = await tryRead(() => call("name"));
|
|
3013
|
+
if (!separator || !name) {
|
|
3014
|
+
debugLog("owney-sdk", "permit: domain unverifiable, using approval", {
|
|
3015
|
+
token,
|
|
3016
|
+
hasSeparator: Boolean(separator),
|
|
3017
|
+
hasName: Boolean(name)
|
|
3018
|
+
});
|
|
3019
|
+
return null;
|
|
3020
|
+
}
|
|
3021
|
+
const declared = await tryRead(() => call("version"));
|
|
3022
|
+
const domain = matchDomain(
|
|
3023
|
+
name,
|
|
3024
|
+
declared ? [declared, ...VERSION_CANDIDATES] : VERSION_CANDIDATES,
|
|
3025
|
+
chainId,
|
|
3026
|
+
token,
|
|
3027
|
+
separator
|
|
3028
|
+
);
|
|
3029
|
+
if (!domain) {
|
|
3030
|
+
debugLog("owney-sdk", "permit: no domain matched, using approval", {
|
|
3031
|
+
token,
|
|
3032
|
+
declared
|
|
3033
|
+
});
|
|
3034
|
+
return null;
|
|
3035
|
+
}
|
|
3036
|
+
const deadline = BigInt(Math.floor((input.now?.() ?? Date.now()) / 1e3)) + BigInt(PERMIT_TTL_SECONDS);
|
|
3037
|
+
const signature = await input.signTypedData({
|
|
3038
|
+
domain,
|
|
3039
|
+
types: PERMIT_TYPES,
|
|
3040
|
+
primaryType: "Permit",
|
|
3041
|
+
message: { owner, spender, value, nonce, deadline }
|
|
3042
|
+
});
|
|
3043
|
+
const { r, s, v, yParity } = parseSignature(signature);
|
|
3044
|
+
const recoveryV = v ?? BigInt(yParity + 27);
|
|
3045
|
+
return encodeAbiParameters(
|
|
3046
|
+
[
|
|
3047
|
+
{ type: "address" },
|
|
3048
|
+
{ type: "address" },
|
|
3049
|
+
{ type: "uint256" },
|
|
3050
|
+
{ type: "uint256" },
|
|
3051
|
+
{ type: "uint8" },
|
|
3052
|
+
{ type: "bytes32" },
|
|
3053
|
+
{ type: "bytes32" }
|
|
3054
|
+
],
|
|
3055
|
+
[owner, spender, value, deadline, Number(recoveryV), r, s]
|
|
3056
|
+
);
|
|
3057
|
+
}
|
|
3058
|
+
|
|
2810
3059
|
// src/lib/swap/swap.arrival.ts
|
|
2811
|
-
var
|
|
2812
|
-
var
|
|
3060
|
+
var DEFAULT_TIMEOUT_MS3 = 18e4;
|
|
3061
|
+
var DEFAULT_POLL_MS3 = 4e3;
|
|
2813
3062
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2814
3063
|
async function awaitWithdrawalArrival(options) {
|
|
2815
3064
|
const {
|
|
2816
3065
|
readBalance,
|
|
2817
3066
|
baseline,
|
|
2818
|
-
timeoutMs =
|
|
2819
|
-
pollMs =
|
|
3067
|
+
timeoutMs = DEFAULT_TIMEOUT_MS3,
|
|
3068
|
+
pollMs = DEFAULT_POLL_MS3
|
|
2820
3069
|
} = options;
|
|
2821
3070
|
const deadline = Date.now() + timeoutMs;
|
|
2822
3071
|
debugLog("owney-sdk", "withdraw: waiting for funds to land", {
|
|
@@ -4402,6 +4651,41 @@ var OwneySDK = class {
|
|
|
4402
4651
|
return hash;
|
|
4403
4652
|
},
|
|
4404
4653
|
ensureChain: (chainId) => this.ensureSwapChain(chainId),
|
|
4654
|
+
/**
|
|
4655
|
+
* Sign the approval instead of paying for it. Null means the token
|
|
4656
|
+
* cannot be permitted (WETH has no `permit`) or its domain could not be
|
|
4657
|
+
* verified, and the executor sends an ordinary approval instead.
|
|
4658
|
+
*
|
|
4659
|
+
* Reads go through srcPublic like every other read here — the domain is
|
|
4660
|
+
* chain-scoped, and reading a nonce off whatever chain the wallet
|
|
4661
|
+
* happens to sit on would sign a permit the token rejects.
|
|
4662
|
+
*/
|
|
4663
|
+
buildPermit: (spender, amount) => buildErc2612Permit(
|
|
4664
|
+
{
|
|
4665
|
+
readContract: (args) => srcPublic.readContract(args)
|
|
4666
|
+
},
|
|
4667
|
+
{
|
|
4668
|
+
token: quote.src.address,
|
|
4669
|
+
owner: state.walletAddress,
|
|
4670
|
+
spender,
|
|
4671
|
+
value: amount,
|
|
4672
|
+
chainId: quote.src.chainId,
|
|
4673
|
+
signTypedData: (payload) => wallet.signTypedData({
|
|
4674
|
+
account: state.walletAddress,
|
|
4675
|
+
...payload
|
|
4676
|
+
})
|
|
4677
|
+
}
|
|
4678
|
+
),
|
|
4679
|
+
/**
|
|
4680
|
+
* Same-chain polling, with the chain bound in. That product keys its
|
|
4681
|
+
* orders per chain; asking the Fusion+ endpoint for one answers 404,
|
|
4682
|
+
* which would read as an order that vanished mid-swap.
|
|
4683
|
+
*/
|
|
4684
|
+
sameChainRunner: {
|
|
4685
|
+
orderStatus: (h) => this.swapApi().orderStatus(h, quote.src.chainId),
|
|
4686
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
4687
|
+
now: () => Date.now()
|
|
4688
|
+
},
|
|
4405
4689
|
runner: {
|
|
4406
4690
|
readyForSecrets: (h) => this.swapApi().readyForSecrets(h),
|
|
4407
4691
|
submitSecret: (h, secret) => this.swapApi().submitSecret(h, secret),
|
|
@@ -4420,6 +4704,39 @@ var OwneySDK = class {
|
|
|
4420
4704
|
async getSwapTokens() {
|
|
4421
4705
|
return this.swapApi().listTokens();
|
|
4422
4706
|
}
|
|
4707
|
+
/**
|
|
4708
|
+
* What the wallet actually holds, across every supported chain, restricted
|
|
4709
|
+
* to tokens a swap could use.
|
|
4710
|
+
*
|
|
4711
|
+
* `getSwapTokens` answers "what may someone pay with in principle" from a
|
|
4712
|
+
* fixed list. This answers "what has this wallet got", which is a different
|
|
4713
|
+
* question and the one a picker needs: a user can hold something routable —
|
|
4714
|
+
* tBTC, say — that no fixed list of ours would ever mention.
|
|
4715
|
+
*
|
|
4716
|
+
* Each chain is asked separately because that is how the balance product is
|
|
4717
|
+
* shaped, and a chain that fails contributes nothing rather than failing the
|
|
4718
|
+
* lot. A picker with two chains in it beats an error. (ROUT-242)
|
|
4719
|
+
*/
|
|
4720
|
+
async getWalletBalances(chainIds) {
|
|
4721
|
+
const state = this.requireState();
|
|
4722
|
+
const chains = chainIds ?? SUPPORTED_CHAIN_IDS;
|
|
4723
|
+
const failedChainIds = [];
|
|
4724
|
+
const results = await Promise.all(
|
|
4725
|
+
chains.map(async (chainId) => {
|
|
4726
|
+
try {
|
|
4727
|
+
const { tokens } = await this.swapApi().walletBalances({
|
|
4728
|
+
chainId,
|
|
4729
|
+
walletAddress: state.walletAddress
|
|
4730
|
+
});
|
|
4731
|
+
return tokens.map((token) => ({ ...token, chainId }));
|
|
4732
|
+
} catch {
|
|
4733
|
+
failedChainIds.push(chainId);
|
|
4734
|
+
return [];
|
|
4735
|
+
}
|
|
4736
|
+
})
|
|
4737
|
+
);
|
|
4738
|
+
return { tokens: results.flat(), failedChainIds };
|
|
4739
|
+
}
|
|
4423
4740
|
/**
|
|
4424
4741
|
* Search the assets a user may pay with, across every supported chain.
|
|
4425
4742
|
*
|