@owney/sdk 0.7.26-beta.2 → 0.7.26-beta.3
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 +323 -59
- package/dist/index.d.cts +18 -2
- package/dist/index.d.ts +18 -2
- package/dist/index.js +293 -24
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2366,9 +2366,10 @@ function createSwapApi(baseUrl, apiKey) {
|
|
|
2366
2366
|
dstSymbol: params.to.symbol,
|
|
2367
2367
|
amount: params.from.amount,
|
|
2368
2368
|
walletAddress: params.walletAddress,
|
|
2369
|
-
secretHashes: params.secretHashes,
|
|
2369
|
+
...params.secretHashes ? { secretHashes: params.secretHashes } : {},
|
|
2370
2370
|
...params.direction ? { direction: params.direction } : {},
|
|
2371
|
-
...params.receiver ? { receiver: params.receiver } : {}
|
|
2371
|
+
...params.receiver ? { receiver: params.receiver } : {},
|
|
2372
|
+
...params.permit ? { permit: params.permit } : {}
|
|
2372
2373
|
}
|
|
2373
2374
|
}),
|
|
2374
2375
|
submitOrder: (body) => request(baseUrl, apiKey, "/order", { method: "POST", body }),
|
|
@@ -2381,7 +2382,16 @@ function createSwapApi(baseUrl, apiKey) {
|
|
|
2381
2382
|
method: "POST",
|
|
2382
2383
|
body: { orderHash, secret }
|
|
2383
2384
|
}),
|
|
2384
|
-
|
|
2385
|
+
/**
|
|
2386
|
+
* @param chainId present only for a SAME-CHAIN order. That product keys its
|
|
2387
|
+
* orders per chain and the Fusion+ endpoint does not know them, so asking
|
|
2388
|
+
* without it answers 404 — which reads as an order that vanished.
|
|
2389
|
+
*/
|
|
2390
|
+
orderStatus: (orderHash, chainId) => request(
|
|
2391
|
+
baseUrl,
|
|
2392
|
+
apiKey,
|
|
2393
|
+
chainId === void 0 ? `/order/${orderHash}` : `/order/${orderHash}?chainId=${chainId}`
|
|
2394
|
+
),
|
|
2385
2395
|
readyForSecrets: (orderHash) => request(
|
|
2386
2396
|
baseUrl,
|
|
2387
2397
|
apiKey,
|
|
@@ -2524,7 +2534,12 @@ var SWAP_TERMINAL_STATUSES = [
|
|
|
2524
2534
|
"executed",
|
|
2525
2535
|
"expired",
|
|
2526
2536
|
"cancelled",
|
|
2527
|
-
"refunded"
|
|
2537
|
+
"refunded",
|
|
2538
|
+
"filled",
|
|
2539
|
+
"false-predicate",
|
|
2540
|
+
"not-enough-balance-or-allowance",
|
|
2541
|
+
"wrong-permit",
|
|
2542
|
+
"invalid-signature"
|
|
2528
2543
|
];
|
|
2529
2544
|
var isSwapTerminal = (status) => SWAP_TERMINAL_STATUSES.includes(status);
|
|
2530
2545
|
|
|
@@ -2602,6 +2617,71 @@ async function runFusionOrder(deps, options) {
|
|
|
2602
2617
|
}
|
|
2603
2618
|
}
|
|
2604
2619
|
|
|
2620
|
+
// src/lib/swap/swap.same-chain-runner.ts
|
|
2621
|
+
var DEFAULT_POLL_MS2 = 5e3;
|
|
2622
|
+
var MAX_BACKOFF_MS2 = 3e4;
|
|
2623
|
+
var backoffFor2 = (failures, base3) => Math.min(base3 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS2);
|
|
2624
|
+
var DEFAULT_TIMEOUT_MS2 = 3 * 60 * 1e3;
|
|
2625
|
+
function explain(status) {
|
|
2626
|
+
switch (status) {
|
|
2627
|
+
case "expired":
|
|
2628
|
+
return "No one filled the swap in time, so nothing was exchanged. Your funds never left your wallet.";
|
|
2629
|
+
case "cancelled":
|
|
2630
|
+
return "The swap was cancelled before anyone filled it. Your funds never left your wallet.";
|
|
2631
|
+
case "not-enough-balance-or-allowance":
|
|
2632
|
+
return "The swap could not be filled because the balance or approval had changed since it was signed. Nothing was exchanged.";
|
|
2633
|
+
case "wrong-permit":
|
|
2634
|
+
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.";
|
|
2635
|
+
case "invalid-signature":
|
|
2636
|
+
return "The swap signature was rejected. Nothing was exchanged.";
|
|
2637
|
+
case "false-predicate":
|
|
2638
|
+
return "The swap's conditions no longer held when it was filled. Nothing was exchanged.";
|
|
2639
|
+
default:
|
|
2640
|
+
return "The swap did not complete. Nothing was exchanged.";
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
async function runSameChainOrder(deps, options) {
|
|
2644
|
+
const {
|
|
2645
|
+
orderHash,
|
|
2646
|
+
onStage,
|
|
2647
|
+
pollIntervalMs = DEFAULT_POLL_MS2,
|
|
2648
|
+
timeoutMs = DEFAULT_TIMEOUT_MS2
|
|
2649
|
+
} = options;
|
|
2650
|
+
const deadline = deps.now() + timeoutMs;
|
|
2651
|
+
let failures = 0;
|
|
2652
|
+
onStage?.("swapping");
|
|
2653
|
+
for (; ; ) {
|
|
2654
|
+
if (deps.now() >= deadline) {
|
|
2655
|
+
throw new OwneyError(
|
|
2656
|
+
"SWAP_ORDER_EXPIRED",
|
|
2657
|
+
"No one filled the swap in time. Nothing was exchanged and your funds are still in your wallet.",
|
|
2658
|
+
{ orderHash }
|
|
2659
|
+
);
|
|
2660
|
+
}
|
|
2661
|
+
let status;
|
|
2662
|
+
try {
|
|
2663
|
+
({ status } = await deps.orderStatus(orderHash));
|
|
2664
|
+
failures = 0;
|
|
2665
|
+
} catch {
|
|
2666
|
+
failures += 1;
|
|
2667
|
+
await deps.sleep(backoffFor2(failures, pollIntervalMs));
|
|
2668
|
+
continue;
|
|
2669
|
+
}
|
|
2670
|
+
if (isSwapTerminal(status)) {
|
|
2671
|
+
if (status === "filled" || status === "executed") {
|
|
2672
|
+
onStage?.("swapped");
|
|
2673
|
+
return { status, filled: true };
|
|
2674
|
+
}
|
|
2675
|
+
throw new OwneyError(
|
|
2676
|
+
status === "cancelled" ? "SWAP_ORDER_CANCELLED" : "SWAP_ORDER_EXPIRED",
|
|
2677
|
+
explain(status),
|
|
2678
|
+
{ orderHash, status }
|
|
2679
|
+
);
|
|
2680
|
+
}
|
|
2681
|
+
await deps.sleep(pollIntervalMs);
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2605
2685
|
// src/lib/swap/swap.secret-store.ts
|
|
2606
2686
|
var KEY_PREFIX2 = "owney.swap.order";
|
|
2607
2687
|
var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -2685,7 +2765,7 @@ async function executeSwap(deps, options) {
|
|
|
2685
2765
|
});
|
|
2686
2766
|
const before = await deps.readTargetBalance();
|
|
2687
2767
|
debugLog("owney-sdk", "swap: target balance before", before.toString());
|
|
2688
|
-
const result = quote.rail === "classic" ? await runClassic(deps, options) : await runFusion(deps, options, walletAddress);
|
|
2768
|
+
const result = quote.rail === "classic" ? await runClassic(deps, options) : quote.rail === "fusion" ? await runSameChainFusion(deps, options, walletAddress) : await runFusion(deps, options, walletAddress);
|
|
2689
2769
|
const after = await deps.readTargetBalance();
|
|
2690
2770
|
const received = after - before;
|
|
2691
2771
|
debugLog("owney-sdk", "swap: target balance after", {
|
|
@@ -2756,25 +2836,75 @@ async function runClassic(deps, options) {
|
|
|
2756
2836
|
onStage?.("swapped");
|
|
2757
2837
|
return { txHash };
|
|
2758
2838
|
}
|
|
2759
|
-
async function
|
|
2839
|
+
async function runSameChainFusion(deps, options, walletAddress) {
|
|
2760
2840
|
const { quote, direction, onStage } = options;
|
|
2761
2841
|
const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
|
|
2762
2842
|
const amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount));
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2843
|
+
const permit = await authoriseSpend(deps, options, amount, isNativeSource);
|
|
2844
|
+
onStage?.("quoting");
|
|
2845
|
+
debugLog("owney-sdk", "swap: building same-chain fusion order");
|
|
2846
|
+
const built = await deps.api.buildOrder({
|
|
2847
|
+
from: {
|
|
2848
|
+
chainId: quote.src.chainId,
|
|
2849
|
+
symbol: quote.src.symbol,
|
|
2850
|
+
amount: amount.toString()
|
|
2851
|
+
},
|
|
2852
|
+
to: { chainId: quote.dst.chainId, symbol: quote.dst.symbol },
|
|
2853
|
+
walletAddress,
|
|
2854
|
+
...direction ? { direction } : {},
|
|
2855
|
+
...permit ? { permit } : {}
|
|
2856
|
+
});
|
|
2857
|
+
debugLog("owney-sdk", "swap: same-chain order built", {
|
|
2858
|
+
orderHash: built.orderHash
|
|
2859
|
+
});
|
|
2860
|
+
onStage?.("signing");
|
|
2861
|
+
await deps.ensureChain(quote.src.chainId);
|
|
2862
|
+
const signature = await deps.signTypedData(built.typedData);
|
|
2863
|
+
debugLog("owney-sdk", "swap: signed, submitting same-chain order");
|
|
2864
|
+
await deps.api.submitOrder({
|
|
2865
|
+
// Without this the routing API hands the order to the Fusion+ relayer,
|
|
2866
|
+
// which does not know it and answers with an error that names nothing.
|
|
2867
|
+
rail: "fusion",
|
|
2868
|
+
srcChainId: quote.src.chainId,
|
|
2869
|
+
order: built.order,
|
|
2870
|
+
signature,
|
|
2871
|
+
quoteId: built.quoteId,
|
|
2872
|
+
...built.extension ? { extension: built.extension } : {}
|
|
2873
|
+
});
|
|
2874
|
+
await runSameChainOrder(deps.sameChainRunner ?? deps.runner, {
|
|
2875
|
+
orderHash: built.orderHash,
|
|
2876
|
+
...onStage ? { onStage } : {}
|
|
2877
|
+
});
|
|
2878
|
+
return { orderHash: built.orderHash };
|
|
2879
|
+
}
|
|
2880
|
+
async function authoriseSpend(deps, options, amount, isNativeSource) {
|
|
2881
|
+
const { quote, onStage } = options;
|
|
2882
|
+
if (!quote.spender || isNativeSource) return void 0;
|
|
2883
|
+
const current = await deps.readAllowance(quote.spender);
|
|
2884
|
+
debugLog("owney-sdk", "swap: fusion allowance", {
|
|
2885
|
+
spender: quote.spender,
|
|
2886
|
+
current: current.toString(),
|
|
2887
|
+
needed: amount.toString()
|
|
2888
|
+
});
|
|
2889
|
+
if (current >= amount) return void 0;
|
|
2890
|
+
onStage?.("approving");
|
|
2891
|
+
await deps.ensureChain(quote.src.chainId);
|
|
2892
|
+
const permit = quote.permitSupported ? await deps.buildPermit?.(quote.spender, MAX_UINT256) ?? void 0 : void 0;
|
|
2893
|
+
if (permit) {
|
|
2894
|
+
debugLog("owney-sdk", "swap: permitting limit order protocol", {
|
|
2895
|
+
bytes: (permit.length - 2) / 2
|
|
2770
2896
|
});
|
|
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
|
-
}
|
|
2897
|
+
return permit;
|
|
2777
2898
|
}
|
|
2899
|
+
await deps.approve(quote.spender, MAX_UINT256);
|
|
2900
|
+
debugLog("owney-sdk", "swap: approved limit order protocol");
|
|
2901
|
+
return void 0;
|
|
2902
|
+
}
|
|
2903
|
+
async function runFusion(deps, options, walletAddress) {
|
|
2904
|
+
const { quote, direction, onStage } = options;
|
|
2905
|
+
const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
|
|
2906
|
+
const amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount));
|
|
2907
|
+
const permit = await authoriseSpend(deps, options, amount, isNativeSource);
|
|
2778
2908
|
const { secrets, secretHashes } = mintSecrets(quote.secretsCount ?? 1);
|
|
2779
2909
|
onStage?.("quoting");
|
|
2780
2910
|
debugLog("owney-sdk", "swap: building fusion order", {
|
|
@@ -2790,7 +2920,8 @@ async function runFusion(deps, options, walletAddress) {
|
|
|
2790
2920
|
to: { chainId: quote.dst.chainId, symbol: quote.dst.symbol },
|
|
2791
2921
|
walletAddress,
|
|
2792
2922
|
secretHashes,
|
|
2793
|
-
...direction ? { direction } : {}
|
|
2923
|
+
...direction ? { direction } : {},
|
|
2924
|
+
...permit ? { permit } : {}
|
|
2794
2925
|
});
|
|
2795
2926
|
saveOrder({
|
|
2796
2927
|
orderHash: built.orderHash,
|
|
@@ -2841,16 +2972,114 @@ async function runFusion(deps, options, walletAddress) {
|
|
|
2841
2972
|
return { orderHash: built.orderHash };
|
|
2842
2973
|
}
|
|
2843
2974
|
|
|
2975
|
+
// src/lib/swap/swap.permit.ts
|
|
2976
|
+
var import_viem5 = require("viem");
|
|
2977
|
+
var PERMIT_ABI = (0, import_viem5.parseAbi)([
|
|
2978
|
+
"function nonces(address owner) view returns (uint256)",
|
|
2979
|
+
"function name() view returns (string)",
|
|
2980
|
+
"function version() view returns (string)",
|
|
2981
|
+
"function DOMAIN_SEPARATOR() view returns (bytes32)"
|
|
2982
|
+
]);
|
|
2983
|
+
var PERMIT_TYPES = {
|
|
2984
|
+
Permit: [
|
|
2985
|
+
{ name: "owner", type: "address" },
|
|
2986
|
+
{ name: "spender", type: "address" },
|
|
2987
|
+
{ name: "value", type: "uint256" },
|
|
2988
|
+
{ name: "nonce", type: "uint256" },
|
|
2989
|
+
{ name: "deadline", type: "uint256" }
|
|
2990
|
+
]
|
|
2991
|
+
};
|
|
2992
|
+
var VERSION_CANDIDATES = ["1", "2"];
|
|
2993
|
+
var PERMIT_TTL_SECONDS = 3600;
|
|
2994
|
+
async function tryRead(read) {
|
|
2995
|
+
try {
|
|
2996
|
+
return await read();
|
|
2997
|
+
} catch {
|
|
2998
|
+
return null;
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
function matchDomain(name, versions, chainId, token, onChainSeparator) {
|
|
3002
|
+
for (const version of versions) {
|
|
3003
|
+
const domain = { name, version, chainId, verifyingContract: token };
|
|
3004
|
+
if ((0, import_viem5.domainSeparator)({ domain }) === onChainSeparator) return domain;
|
|
3005
|
+
}
|
|
3006
|
+
return null;
|
|
3007
|
+
}
|
|
3008
|
+
async function buildErc2612Permit(reads, input) {
|
|
3009
|
+
const { token, owner, spender, value, chainId } = input;
|
|
3010
|
+
const call = (functionName, args) => reads.readContract({
|
|
3011
|
+
address: token,
|
|
3012
|
+
abi: PERMIT_ABI,
|
|
3013
|
+
functionName,
|
|
3014
|
+
...args ? { args } : {}
|
|
3015
|
+
});
|
|
3016
|
+
const nonce = await tryRead(() => call("nonces", [owner]));
|
|
3017
|
+
if (nonce === null) {
|
|
3018
|
+
debugLog("owney-sdk", "permit: token has no nonces(), using approval", {
|
|
3019
|
+
token
|
|
3020
|
+
});
|
|
3021
|
+
return null;
|
|
3022
|
+
}
|
|
3023
|
+
const separator = await tryRead(
|
|
3024
|
+
() => call("DOMAIN_SEPARATOR")
|
|
3025
|
+
);
|
|
3026
|
+
const name = await tryRead(() => call("name"));
|
|
3027
|
+
if (!separator || !name) {
|
|
3028
|
+
debugLog("owney-sdk", "permit: domain unverifiable, using approval", {
|
|
3029
|
+
token,
|
|
3030
|
+
hasSeparator: Boolean(separator),
|
|
3031
|
+
hasName: Boolean(name)
|
|
3032
|
+
});
|
|
3033
|
+
return null;
|
|
3034
|
+
}
|
|
3035
|
+
const declared = await tryRead(() => call("version"));
|
|
3036
|
+
const domain = matchDomain(
|
|
3037
|
+
name,
|
|
3038
|
+
declared ? [declared, ...VERSION_CANDIDATES] : VERSION_CANDIDATES,
|
|
3039
|
+
chainId,
|
|
3040
|
+
token,
|
|
3041
|
+
separator
|
|
3042
|
+
);
|
|
3043
|
+
if (!domain) {
|
|
3044
|
+
debugLog("owney-sdk", "permit: no domain matched, using approval", {
|
|
3045
|
+
token,
|
|
3046
|
+
declared
|
|
3047
|
+
});
|
|
3048
|
+
return null;
|
|
3049
|
+
}
|
|
3050
|
+
const deadline = BigInt(Math.floor((input.now?.() ?? Date.now()) / 1e3)) + BigInt(PERMIT_TTL_SECONDS);
|
|
3051
|
+
const signature = await input.signTypedData({
|
|
3052
|
+
domain,
|
|
3053
|
+
types: PERMIT_TYPES,
|
|
3054
|
+
primaryType: "Permit",
|
|
3055
|
+
message: { owner, spender, value, nonce, deadline }
|
|
3056
|
+
});
|
|
3057
|
+
const { r, s, v, yParity } = (0, import_viem5.parseSignature)(signature);
|
|
3058
|
+
const recoveryV = v ?? BigInt(yParity + 27);
|
|
3059
|
+
return (0, import_viem5.encodeAbiParameters)(
|
|
3060
|
+
[
|
|
3061
|
+
{ type: "address" },
|
|
3062
|
+
{ type: "address" },
|
|
3063
|
+
{ type: "uint256" },
|
|
3064
|
+
{ type: "uint256" },
|
|
3065
|
+
{ type: "uint8" },
|
|
3066
|
+
{ type: "bytes32" },
|
|
3067
|
+
{ type: "bytes32" }
|
|
3068
|
+
],
|
|
3069
|
+
[owner, spender, value, deadline, Number(recoveryV), r, s]
|
|
3070
|
+
);
|
|
3071
|
+
}
|
|
3072
|
+
|
|
2844
3073
|
// src/lib/swap/swap.arrival.ts
|
|
2845
|
-
var
|
|
2846
|
-
var
|
|
3074
|
+
var DEFAULT_TIMEOUT_MS3 = 18e4;
|
|
3075
|
+
var DEFAULT_POLL_MS3 = 4e3;
|
|
2847
3076
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2848
3077
|
async function awaitWithdrawalArrival(options) {
|
|
2849
3078
|
const {
|
|
2850
3079
|
readBalance,
|
|
2851
3080
|
baseline,
|
|
2852
|
-
timeoutMs =
|
|
2853
|
-
pollMs =
|
|
3081
|
+
timeoutMs = DEFAULT_TIMEOUT_MS3,
|
|
3082
|
+
pollMs = DEFAULT_POLL_MS3
|
|
2854
3083
|
} = options;
|
|
2855
3084
|
const deadline = Date.now() + timeoutMs;
|
|
2856
3085
|
debugLog("owney-sdk", "withdraw: waiting for funds to land", {
|
|
@@ -2925,7 +3154,7 @@ async function withFailureReporting(apiKey, agentType, fn, baseUrl) {
|
|
|
2925
3154
|
}
|
|
2926
3155
|
|
|
2927
3156
|
// src/lib/helpers/withdraw-helper.ts
|
|
2928
|
-
var
|
|
3157
|
+
var import_viem6 = require("viem");
|
|
2929
3158
|
function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decimals) {
|
|
2930
3159
|
const target = asset.toUpperCase();
|
|
2931
3160
|
return agents.map((agent) => {
|
|
@@ -2934,7 +3163,7 @@ function projectAgentBalancesForAsset(agents, aggregated, chainId, asset, decima
|
|
|
2934
3163
|
(t) => t.chainId === chainId && t.asset.toUpperCase() === target
|
|
2935
3164
|
);
|
|
2936
3165
|
if (!tokenBalance) return { agent, balance: 0n };
|
|
2937
|
-
return { agent, balance: (0,
|
|
3166
|
+
return { agent, balance: (0, import_viem6.parseUnits)(tokenBalance.amount, decimals) };
|
|
2938
3167
|
});
|
|
2939
3168
|
}
|
|
2940
3169
|
function planProportionalShares(balances, requested, totalAvailable) {
|
|
@@ -3081,11 +3310,11 @@ function aggregateApyByChainAndAsset(agentApys, agentBalances) {
|
|
|
3081
3310
|
}
|
|
3082
3311
|
|
|
3083
3312
|
// src/client.ts
|
|
3084
|
-
var
|
|
3313
|
+
var import_viem9 = require("viem");
|
|
3085
3314
|
var import_chains2 = require("viem/chains");
|
|
3086
3315
|
|
|
3087
3316
|
// src/lib/transfer-auth.ts
|
|
3088
|
-
var
|
|
3317
|
+
var import_viem7 = require("viem");
|
|
3089
3318
|
var ERC20_META_ABI = [
|
|
3090
3319
|
{ type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] },
|
|
3091
3320
|
{ type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }
|
|
@@ -3117,7 +3346,7 @@ async function readTokenMeta(publicClient, token) {
|
|
|
3117
3346
|
function randomAuthNonce() {
|
|
3118
3347
|
const bytes = new Uint8Array(32);
|
|
3119
3348
|
globalThis.crypto.getRandomValues(bytes);
|
|
3120
|
-
return (0,
|
|
3349
|
+
return (0, import_viem7.bytesToHex)(bytes);
|
|
3121
3350
|
}
|
|
3122
3351
|
|
|
3123
3352
|
// src/lib/sponsor-client.ts
|
|
@@ -3399,7 +3628,7 @@ function makeSponsoredWethCallback(deps) {
|
|
|
3399
3628
|
}
|
|
3400
3629
|
|
|
3401
3630
|
// src/lib/sponsored-calls-deposit.ts
|
|
3402
|
-
var
|
|
3631
|
+
var import_viem8 = require("viem");
|
|
3403
3632
|
var DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
3404
3633
|
var DEFAULT_MAX_POLLS = 30;
|
|
3405
3634
|
async function paymasterSupported(provider, owner, chainId) {
|
|
@@ -3407,7 +3636,7 @@ async function paymasterSupported(provider, owner, chainId) {
|
|
|
3407
3636
|
method: "wallet_getCapabilities",
|
|
3408
3637
|
params: [owner]
|
|
3409
3638
|
});
|
|
3410
|
-
const forChain = caps?.[(0,
|
|
3639
|
+
const forChain = caps?.[(0, import_viem8.toHex)(chainId)] ?? caps?.[String(chainId)];
|
|
3411
3640
|
return Boolean(forChain?.paymasterService?.supported);
|
|
3412
3641
|
}
|
|
3413
3642
|
function makeSponsoredCallsCallback(deps) {
|
|
@@ -3441,8 +3670,8 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
3441
3670
|
{ chainId }
|
|
3442
3671
|
);
|
|
3443
3672
|
}
|
|
3444
|
-
const data = (0,
|
|
3445
|
-
abi:
|
|
3673
|
+
const data = (0, import_viem8.encodeFunctionData)({
|
|
3674
|
+
abi: import_viem8.erc20Abi,
|
|
3446
3675
|
functionName: "transfer",
|
|
3447
3676
|
args: [smartWallet, BigInt(amount)]
|
|
3448
3677
|
});
|
|
@@ -3452,7 +3681,7 @@ function makeSponsoredCallsCallback(deps) {
|
|
|
3452
3681
|
{
|
|
3453
3682
|
version: "2.0.0",
|
|
3454
3683
|
from: deps.ownerAddress,
|
|
3455
|
-
chainId: (0,
|
|
3684
|
+
chainId: (0, import_viem8.toHex)(chainId),
|
|
3456
3685
|
atomicRequired: false,
|
|
3457
3686
|
calls: [{ to: token, value: "0x0", data }],
|
|
3458
3687
|
capabilities: {
|
|
@@ -3674,14 +3903,14 @@ var OwneySDK = class {
|
|
|
3674
3903
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
3675
3904
|
// PublicClient/WalletClient param types — structurally identical at
|
|
3676
3905
|
// runtime, but the two share a name TS treats as unrelated.
|
|
3677
|
-
getPublicClient: (cid) => (0,
|
|
3906
|
+
getPublicClient: (cid) => (0, import_viem9.createPublicClient)({
|
|
3678
3907
|
chain: VIEM_CHAIN2[cid],
|
|
3679
|
-
transport: (0,
|
|
3908
|
+
transport: (0, import_viem9.custom)(provider)
|
|
3680
3909
|
}),
|
|
3681
|
-
getWalletClient: (cid) => (0,
|
|
3910
|
+
getWalletClient: (cid) => (0, import_viem9.createWalletClient)({
|
|
3682
3911
|
account: owner,
|
|
3683
3912
|
chain: VIEM_CHAIN2[cid],
|
|
3684
|
-
transport: (0,
|
|
3913
|
+
transport: (0, import_viem9.custom)(provider)
|
|
3685
3914
|
})
|
|
3686
3915
|
});
|
|
3687
3916
|
if (!onApproved) this.cachedSponsoredCallback = callback;
|
|
@@ -3727,14 +3956,14 @@ var OwneySDK = class {
|
|
|
3727
3956
|
// Casts work around viem's chain-narrowed Client vs the generic
|
|
3728
3957
|
// PublicClient/WalletClient param types — structurally identical at
|
|
3729
3958
|
// runtime, but the two share a name TS treats as unrelated.
|
|
3730
|
-
getPublicClient: (cid) => (0,
|
|
3959
|
+
getPublicClient: (cid) => (0, import_viem9.createPublicClient)({
|
|
3731
3960
|
chain: VIEM_CHAIN2[cid],
|
|
3732
|
-
transport: (0,
|
|
3961
|
+
transport: (0, import_viem9.custom)(provider)
|
|
3733
3962
|
}),
|
|
3734
|
-
getWalletClient: (cid) => (0,
|
|
3963
|
+
getWalletClient: (cid) => (0, import_viem9.createWalletClient)({
|
|
3735
3964
|
account: owner,
|
|
3736
3965
|
chain: VIEM_CHAIN2[cid],
|
|
3737
|
-
transport: (0,
|
|
3966
|
+
transport: (0, import_viem9.custom)(provider)
|
|
3738
3967
|
})
|
|
3739
3968
|
});
|
|
3740
3969
|
if (!onApproved) this.cachedWethSponsoredCallback = callback;
|
|
@@ -4316,11 +4545,11 @@ var OwneySDK = class {
|
|
|
4316
4545
|
);
|
|
4317
4546
|
}
|
|
4318
4547
|
await ensureWalletOnChain(
|
|
4319
|
-
(0,
|
|
4320
|
-
(0,
|
|
4548
|
+
(0, import_viem9.createPublicClient)({ chain, transport: (0, import_viem9.custom)(provider) }),
|
|
4549
|
+
(0, import_viem9.createWalletClient)({
|
|
4321
4550
|
account: state.walletAddress,
|
|
4322
4551
|
chain,
|
|
4323
|
-
transport: (0,
|
|
4552
|
+
transport: (0, import_viem9.custom)(provider)
|
|
4324
4553
|
}),
|
|
4325
4554
|
chainId
|
|
4326
4555
|
);
|
|
@@ -4336,16 +4565,16 @@ var OwneySDK = class {
|
|
|
4336
4565
|
const provider = this.requireConnectedProvider();
|
|
4337
4566
|
const srcChain = VIEM_CHAIN2[quote.src.chainId];
|
|
4338
4567
|
const dstChain = VIEM_CHAIN2[quote.dst.chainId];
|
|
4339
|
-
const wallet = (0,
|
|
4568
|
+
const wallet = (0, import_viem9.createWalletClient)({
|
|
4340
4569
|
account: state.walletAddress,
|
|
4341
4570
|
chain: srcChain,
|
|
4342
|
-
transport: (0,
|
|
4571
|
+
transport: (0, import_viem9.custom)(provider)
|
|
4343
4572
|
});
|
|
4344
|
-
const srcPublic = (0,
|
|
4573
|
+
const srcPublic = (0, import_viem9.createPublicClient)({
|
|
4345
4574
|
chain: srcChain,
|
|
4346
4575
|
transport: swapReadTransport(quote.src.chainId, this.zyfaiRpcUrls)
|
|
4347
4576
|
});
|
|
4348
|
-
const dstPublic = (0,
|
|
4577
|
+
const dstPublic = (0, import_viem9.createPublicClient)({
|
|
4349
4578
|
chain: dstChain,
|
|
4350
4579
|
transport: swapReadTransport(quote.dst.chainId, this.zyfaiRpcUrls)
|
|
4351
4580
|
});
|
|
@@ -4362,7 +4591,7 @@ var OwneySDK = class {
|
|
|
4362
4591
|
}
|
|
4363
4592
|
return dstPublic.readContract({
|
|
4364
4593
|
address: dst,
|
|
4365
|
-
abi:
|
|
4594
|
+
abi: import_viem9.erc20Abi,
|
|
4366
4595
|
functionName: "balanceOf",
|
|
4367
4596
|
args: [state.walletAddress]
|
|
4368
4597
|
});
|
|
@@ -4430,6 +4659,41 @@ var OwneySDK = class {
|
|
|
4430
4659
|
return hash;
|
|
4431
4660
|
},
|
|
4432
4661
|
ensureChain: (chainId) => this.ensureSwapChain(chainId),
|
|
4662
|
+
/**
|
|
4663
|
+
* Sign the approval instead of paying for it. Null means the token
|
|
4664
|
+
* cannot be permitted (WETH has no `permit`) or its domain could not be
|
|
4665
|
+
* verified, and the executor sends an ordinary approval instead.
|
|
4666
|
+
*
|
|
4667
|
+
* Reads go through srcPublic like every other read here — the domain is
|
|
4668
|
+
* chain-scoped, and reading a nonce off whatever chain the wallet
|
|
4669
|
+
* happens to sit on would sign a permit the token rejects.
|
|
4670
|
+
*/
|
|
4671
|
+
buildPermit: (spender, amount) => buildErc2612Permit(
|
|
4672
|
+
{
|
|
4673
|
+
readContract: (args) => srcPublic.readContract(args)
|
|
4674
|
+
},
|
|
4675
|
+
{
|
|
4676
|
+
token: quote.src.address,
|
|
4677
|
+
owner: state.walletAddress,
|
|
4678
|
+
spender,
|
|
4679
|
+
value: amount,
|
|
4680
|
+
chainId: quote.src.chainId,
|
|
4681
|
+
signTypedData: (payload) => wallet.signTypedData({
|
|
4682
|
+
account: state.walletAddress,
|
|
4683
|
+
...payload
|
|
4684
|
+
})
|
|
4685
|
+
}
|
|
4686
|
+
),
|
|
4687
|
+
/**
|
|
4688
|
+
* Same-chain polling, with the chain bound in. That product keys its
|
|
4689
|
+
* orders per chain; asking the Fusion+ endpoint for one answers 404,
|
|
4690
|
+
* which would read as an order that vanished mid-swap.
|
|
4691
|
+
*/
|
|
4692
|
+
sameChainRunner: {
|
|
4693
|
+
orderStatus: (h) => this.swapApi().orderStatus(h, quote.src.chainId),
|
|
4694
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
4695
|
+
now: () => Date.now()
|
|
4696
|
+
},
|
|
4433
4697
|
runner: {
|
|
4434
4698
|
readyForSecrets: (h) => this.swapApi().readyForSecrets(h),
|
|
4435
4699
|
submitSecret: (h, secret) => this.swapApi().submitSecret(h, secret),
|
|
@@ -4575,13 +4839,13 @@ var OwneySDK = class {
|
|
|
4575
4839
|
);
|
|
4576
4840
|
}
|
|
4577
4841
|
const srcChain = VIEM_CHAIN2[options.from.chainId];
|
|
4578
|
-
const srcPublic = (0,
|
|
4842
|
+
const srcPublic = (0, import_viem9.createPublicClient)({
|
|
4579
4843
|
chain: srcChain,
|
|
4580
4844
|
transport: swapReadTransport(options.from.chainId, this.zyfaiRpcUrls)
|
|
4581
4845
|
});
|
|
4582
4846
|
const readWalletBalance = () => srcPublic.readContract({
|
|
4583
4847
|
address: asset.address,
|
|
4584
|
-
abi:
|
|
4848
|
+
abi: import_viem9.erc20Abi,
|
|
4585
4849
|
functionName: "balanceOf",
|
|
4586
4850
|
args: [state.walletAddress]
|
|
4587
4851
|
});
|
|
@@ -4593,7 +4857,7 @@ var OwneySDK = class {
|
|
|
4593
4857
|
options.onSwapProgress?.("withdrawing");
|
|
4594
4858
|
const withdraw = await this.withdraw({
|
|
4595
4859
|
asset: options.from.symbol,
|
|
4596
|
-
...options.amount === void 0 ? {} : { amount: (0,
|
|
4860
|
+
...options.amount === void 0 ? {} : { amount: (0, import_viem9.parseUnits)(options.amount, asset.decimals).toString() },
|
|
4597
4861
|
...options.agentId ? { agentId: options.agentId } : {}
|
|
4598
4862
|
});
|
|
4599
4863
|
const arrived = await awaitWithdrawalArrival({
|
|
@@ -5229,10 +5493,10 @@ var OwneySDK = class {
|
|
|
5229
5493
|
);
|
|
5230
5494
|
}
|
|
5231
5495
|
const provider = this.requireConnectedProvider();
|
|
5232
|
-
const wallet = (0,
|
|
5496
|
+
const wallet = (0, import_viem9.createWalletClient)({
|
|
5233
5497
|
account: state.walletAddress,
|
|
5234
5498
|
chain: VIEM_CHAIN2[chainId],
|
|
5235
|
-
transport: (0,
|
|
5499
|
+
transport: (0, import_viem9.custom)(provider)
|
|
5236
5500
|
});
|
|
5237
5501
|
const hash = await wallet.writeContract({
|
|
5238
5502
|
address: token,
|
|
@@ -5242,9 +5506,9 @@ var OwneySDK = class {
|
|
|
5242
5506
|
account: state.walletAddress,
|
|
5243
5507
|
chain: VIEM_CHAIN2[chainId]
|
|
5244
5508
|
});
|
|
5245
|
-
const publicClient = (0,
|
|
5509
|
+
const publicClient = (0, import_viem9.createPublicClient)({
|
|
5246
5510
|
chain: VIEM_CHAIN2[chainId],
|
|
5247
|
-
transport: (0,
|
|
5511
|
+
transport: (0, import_viem9.custom)(provider)
|
|
5248
5512
|
});
|
|
5249
5513
|
const receipt = await publicClient.waitForTransactionReceipt({
|
|
5250
5514
|
hash,
|
|
@@ -5370,7 +5634,7 @@ var OwneySDK = class {
|
|
|
5370
5634
|
};
|
|
5371
5635
|
|
|
5372
5636
|
// src/agents/zyfai/zyfai.siwx.ts
|
|
5373
|
-
var
|
|
5637
|
+
var import_viem10 = require("viem");
|
|
5374
5638
|
var import_siwe = require("siwe");
|
|
5375
5639
|
var import_sdk2 = require("@zyfai/sdk");
|
|
5376
5640
|
|
|
@@ -5497,7 +5761,7 @@ function buildSIWXConfig(deps) {
|
|
|
5497
5761
|
issuedAt,
|
|
5498
5762
|
toString() {
|
|
5499
5763
|
return new import_siwe.SiweMessage({
|
|
5500
|
-
address: (0,
|
|
5764
|
+
address: (0, import_viem10.getAddress)(accountAddress),
|
|
5501
5765
|
chainId: numericChainId(chainId),
|
|
5502
5766
|
domain,
|
|
5503
5767
|
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;
|
|
@@ -110,6 +115,17 @@ type SwapQuote = {
|
|
|
110
115
|
* calldata instead.
|
|
111
116
|
*/
|
|
112
117
|
spender?: string;
|
|
118
|
+
/**
|
|
119
|
+
* Cross-chain only. True when the routing API will accept an EIP-2612 permit
|
|
120
|
+
* on the order, so the approval is performed by the resolver inside the fill
|
|
121
|
+
* and the user needs no native ETH. False or absent means send an approval
|
|
122
|
+
* transaction, which is what every swap did before ROUT-242.
|
|
123
|
+
*
|
|
124
|
+
* It has to arrive on the QUOTE rather than being tried and recovered from:
|
|
125
|
+
* the client decides whether to spend the user's gas before the order it
|
|
126
|
+
* would be attached to exists.
|
|
127
|
+
*/
|
|
128
|
+
permitSupported?: boolean;
|
|
113
129
|
/**
|
|
114
130
|
* True only for a cross-chain swap FROM native ETH, which needs an on-chain
|
|
115
131
|
* order creation carrying the full amount as msg.value. The user's funds
|
|
@@ -155,7 +171,7 @@ type SwapQuote = {
|
|
|
155
171
|
* `refunding` is the window the returning-funds screen renders: the order has
|
|
156
172
|
* failed and the money is on its way back, but is not back yet.
|
|
157
173
|
*/
|
|
158
|
-
type SwapOrderStatus = "pending" | "executed" | "expired" | "cancelled" | "refunding" | "refunded" | "unpublished";
|
|
174
|
+
type SwapOrderStatus = "pending" | "executed" | "expired" | "cancelled" | "refunding" | "refunded" | "unpublished" | "filled" | "partially-filled" | "false-predicate" | "not-enough-balance-or-allowance" | "wrong-permit" | "invalid-signature";
|
|
159
175
|
/**
|
|
160
176
|
* Stage reported to the UI while a swap runs, in either direction.
|
|
161
177
|
*
|
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;
|
|
@@ -110,6 +115,17 @@ type SwapQuote = {
|
|
|
110
115
|
* calldata instead.
|
|
111
116
|
*/
|
|
112
117
|
spender?: string;
|
|
118
|
+
/**
|
|
119
|
+
* Cross-chain only. True when the routing API will accept an EIP-2612 permit
|
|
120
|
+
* on the order, so the approval is performed by the resolver inside the fill
|
|
121
|
+
* and the user needs no native ETH. False or absent means send an approval
|
|
122
|
+
* transaction, which is what every swap did before ROUT-242.
|
|
123
|
+
*
|
|
124
|
+
* It has to arrive on the QUOTE rather than being tried and recovered from:
|
|
125
|
+
* the client decides whether to spend the user's gas before the order it
|
|
126
|
+
* would be attached to exists.
|
|
127
|
+
*/
|
|
128
|
+
permitSupported?: boolean;
|
|
113
129
|
/**
|
|
114
130
|
* True only for a cross-chain swap FROM native ETH, which needs an on-chain
|
|
115
131
|
* order creation carrying the full amount as msg.value. The user's funds
|
|
@@ -155,7 +171,7 @@ type SwapQuote = {
|
|
|
155
171
|
* `refunding` is the window the returning-funds screen renders: the order has
|
|
156
172
|
* failed and the money is on its way back, but is not back yet.
|
|
157
173
|
*/
|
|
158
|
-
type SwapOrderStatus = "pending" | "executed" | "expired" | "cancelled" | "refunding" | "refunded" | "unpublished";
|
|
174
|
+
type SwapOrderStatus = "pending" | "executed" | "expired" | "cancelled" | "refunding" | "refunded" | "unpublished" | "filled" | "partially-filled" | "false-predicate" | "not-enough-balance-or-allowance" | "wrong-permit" | "invalid-signature";
|
|
159
175
|
/**
|
|
160
176
|
* Stage reported to the UI while a swap runs, in either direction.
|
|
161
177
|
*
|
package/dist/index.js
CHANGED
|
@@ -2332,9 +2332,10 @@ function createSwapApi(baseUrl, apiKey) {
|
|
|
2332
2332
|
dstSymbol: params.to.symbol,
|
|
2333
2333
|
amount: params.from.amount,
|
|
2334
2334
|
walletAddress: params.walletAddress,
|
|
2335
|
-
secretHashes: params.secretHashes,
|
|
2335
|
+
...params.secretHashes ? { secretHashes: params.secretHashes } : {},
|
|
2336
2336
|
...params.direction ? { direction: params.direction } : {},
|
|
2337
|
-
...params.receiver ? { receiver: params.receiver } : {}
|
|
2337
|
+
...params.receiver ? { receiver: params.receiver } : {},
|
|
2338
|
+
...params.permit ? { permit: params.permit } : {}
|
|
2338
2339
|
}
|
|
2339
2340
|
}),
|
|
2340
2341
|
submitOrder: (body) => request(baseUrl, apiKey, "/order", { method: "POST", body }),
|
|
@@ -2347,7 +2348,16 @@ function createSwapApi(baseUrl, apiKey) {
|
|
|
2347
2348
|
method: "POST",
|
|
2348
2349
|
body: { orderHash, secret }
|
|
2349
2350
|
}),
|
|
2350
|
-
|
|
2351
|
+
/**
|
|
2352
|
+
* @param chainId present only for a SAME-CHAIN order. That product keys its
|
|
2353
|
+
* orders per chain and the Fusion+ endpoint does not know them, so asking
|
|
2354
|
+
* without it answers 404 — which reads as an order that vanished.
|
|
2355
|
+
*/
|
|
2356
|
+
orderStatus: (orderHash, chainId) => request(
|
|
2357
|
+
baseUrl,
|
|
2358
|
+
apiKey,
|
|
2359
|
+
chainId === void 0 ? `/order/${orderHash}` : `/order/${orderHash}?chainId=${chainId}`
|
|
2360
|
+
),
|
|
2351
2361
|
readyForSecrets: (orderHash) => request(
|
|
2352
2362
|
baseUrl,
|
|
2353
2363
|
apiKey,
|
|
@@ -2490,7 +2500,12 @@ var SWAP_TERMINAL_STATUSES = [
|
|
|
2490
2500
|
"executed",
|
|
2491
2501
|
"expired",
|
|
2492
2502
|
"cancelled",
|
|
2493
|
-
"refunded"
|
|
2503
|
+
"refunded",
|
|
2504
|
+
"filled",
|
|
2505
|
+
"false-predicate",
|
|
2506
|
+
"not-enough-balance-or-allowance",
|
|
2507
|
+
"wrong-permit",
|
|
2508
|
+
"invalid-signature"
|
|
2494
2509
|
];
|
|
2495
2510
|
var isSwapTerminal = (status) => SWAP_TERMINAL_STATUSES.includes(status);
|
|
2496
2511
|
|
|
@@ -2568,6 +2583,71 @@ async function runFusionOrder(deps, options) {
|
|
|
2568
2583
|
}
|
|
2569
2584
|
}
|
|
2570
2585
|
|
|
2586
|
+
// src/lib/swap/swap.same-chain-runner.ts
|
|
2587
|
+
var DEFAULT_POLL_MS2 = 5e3;
|
|
2588
|
+
var MAX_BACKOFF_MS2 = 3e4;
|
|
2589
|
+
var backoffFor2 = (failures, base3) => Math.min(base3 * 2 ** Math.min(failures, 5), MAX_BACKOFF_MS2);
|
|
2590
|
+
var DEFAULT_TIMEOUT_MS2 = 3 * 60 * 1e3;
|
|
2591
|
+
function explain(status) {
|
|
2592
|
+
switch (status) {
|
|
2593
|
+
case "expired":
|
|
2594
|
+
return "No one filled the swap in time, so nothing was exchanged. Your funds never left your wallet.";
|
|
2595
|
+
case "cancelled":
|
|
2596
|
+
return "The swap was cancelled before anyone filled it. Your funds never left your wallet.";
|
|
2597
|
+
case "not-enough-balance-or-allowance":
|
|
2598
|
+
return "The swap could not be filled because the balance or approval had changed since it was signed. Nothing was exchanged.";
|
|
2599
|
+
case "wrong-permit":
|
|
2600
|
+
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.";
|
|
2601
|
+
case "invalid-signature":
|
|
2602
|
+
return "The swap signature was rejected. Nothing was exchanged.";
|
|
2603
|
+
case "false-predicate":
|
|
2604
|
+
return "The swap's conditions no longer held when it was filled. Nothing was exchanged.";
|
|
2605
|
+
default:
|
|
2606
|
+
return "The swap did not complete. Nothing was exchanged.";
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
async function runSameChainOrder(deps, options) {
|
|
2610
|
+
const {
|
|
2611
|
+
orderHash,
|
|
2612
|
+
onStage,
|
|
2613
|
+
pollIntervalMs = DEFAULT_POLL_MS2,
|
|
2614
|
+
timeoutMs = DEFAULT_TIMEOUT_MS2
|
|
2615
|
+
} = options;
|
|
2616
|
+
const deadline = deps.now() + timeoutMs;
|
|
2617
|
+
let failures = 0;
|
|
2618
|
+
onStage?.("swapping");
|
|
2619
|
+
for (; ; ) {
|
|
2620
|
+
if (deps.now() >= deadline) {
|
|
2621
|
+
throw new OwneyError(
|
|
2622
|
+
"SWAP_ORDER_EXPIRED",
|
|
2623
|
+
"No one filled the swap in time. Nothing was exchanged and your funds are still in your wallet.",
|
|
2624
|
+
{ orderHash }
|
|
2625
|
+
);
|
|
2626
|
+
}
|
|
2627
|
+
let status;
|
|
2628
|
+
try {
|
|
2629
|
+
({ status } = await deps.orderStatus(orderHash));
|
|
2630
|
+
failures = 0;
|
|
2631
|
+
} catch {
|
|
2632
|
+
failures += 1;
|
|
2633
|
+
await deps.sleep(backoffFor2(failures, pollIntervalMs));
|
|
2634
|
+
continue;
|
|
2635
|
+
}
|
|
2636
|
+
if (isSwapTerminal(status)) {
|
|
2637
|
+
if (status === "filled" || status === "executed") {
|
|
2638
|
+
onStage?.("swapped");
|
|
2639
|
+
return { status, filled: true };
|
|
2640
|
+
}
|
|
2641
|
+
throw new OwneyError(
|
|
2642
|
+
status === "cancelled" ? "SWAP_ORDER_CANCELLED" : "SWAP_ORDER_EXPIRED",
|
|
2643
|
+
explain(status),
|
|
2644
|
+
{ orderHash, status }
|
|
2645
|
+
);
|
|
2646
|
+
}
|
|
2647
|
+
await deps.sleep(pollIntervalMs);
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2571
2651
|
// src/lib/swap/swap.secret-store.ts
|
|
2572
2652
|
var KEY_PREFIX2 = "owney.swap.order";
|
|
2573
2653
|
var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -2651,7 +2731,7 @@ async function executeSwap(deps, options) {
|
|
|
2651
2731
|
});
|
|
2652
2732
|
const before = await deps.readTargetBalance();
|
|
2653
2733
|
debugLog("owney-sdk", "swap: target balance before", before.toString());
|
|
2654
|
-
const result = quote.rail === "classic" ? await runClassic(deps, options) : await runFusion(deps, options, walletAddress);
|
|
2734
|
+
const result = quote.rail === "classic" ? await runClassic(deps, options) : quote.rail === "fusion" ? await runSameChainFusion(deps, options, walletAddress) : await runFusion(deps, options, walletAddress);
|
|
2655
2735
|
const after = await deps.readTargetBalance();
|
|
2656
2736
|
const received = after - before;
|
|
2657
2737
|
debugLog("owney-sdk", "swap: target balance after", {
|
|
@@ -2722,25 +2802,75 @@ async function runClassic(deps, options) {
|
|
|
2722
2802
|
onStage?.("swapped");
|
|
2723
2803
|
return { txHash };
|
|
2724
2804
|
}
|
|
2725
|
-
async function
|
|
2805
|
+
async function runSameChainFusion(deps, options, walletAddress) {
|
|
2726
2806
|
const { quote, direction, onStage } = options;
|
|
2727
2807
|
const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
|
|
2728
2808
|
const amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount));
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2809
|
+
const permit = await authoriseSpend(deps, options, amount, isNativeSource);
|
|
2810
|
+
onStage?.("quoting");
|
|
2811
|
+
debugLog("owney-sdk", "swap: building same-chain fusion order");
|
|
2812
|
+
const built = await deps.api.buildOrder({
|
|
2813
|
+
from: {
|
|
2814
|
+
chainId: quote.src.chainId,
|
|
2815
|
+
symbol: quote.src.symbol,
|
|
2816
|
+
amount: amount.toString()
|
|
2817
|
+
},
|
|
2818
|
+
to: { chainId: quote.dst.chainId, symbol: quote.dst.symbol },
|
|
2819
|
+
walletAddress,
|
|
2820
|
+
...direction ? { direction } : {},
|
|
2821
|
+
...permit ? { permit } : {}
|
|
2822
|
+
});
|
|
2823
|
+
debugLog("owney-sdk", "swap: same-chain order built", {
|
|
2824
|
+
orderHash: built.orderHash
|
|
2825
|
+
});
|
|
2826
|
+
onStage?.("signing");
|
|
2827
|
+
await deps.ensureChain(quote.src.chainId);
|
|
2828
|
+
const signature = await deps.signTypedData(built.typedData);
|
|
2829
|
+
debugLog("owney-sdk", "swap: signed, submitting same-chain order");
|
|
2830
|
+
await deps.api.submitOrder({
|
|
2831
|
+
// Without this the routing API hands the order to the Fusion+ relayer,
|
|
2832
|
+
// which does not know it and answers with an error that names nothing.
|
|
2833
|
+
rail: "fusion",
|
|
2834
|
+
srcChainId: quote.src.chainId,
|
|
2835
|
+
order: built.order,
|
|
2836
|
+
signature,
|
|
2837
|
+
quoteId: built.quoteId,
|
|
2838
|
+
...built.extension ? { extension: built.extension } : {}
|
|
2839
|
+
});
|
|
2840
|
+
await runSameChainOrder(deps.sameChainRunner ?? deps.runner, {
|
|
2841
|
+
orderHash: built.orderHash,
|
|
2842
|
+
...onStage ? { onStage } : {}
|
|
2843
|
+
});
|
|
2844
|
+
return { orderHash: built.orderHash };
|
|
2845
|
+
}
|
|
2846
|
+
async function authoriseSpend(deps, options, amount, isNativeSource) {
|
|
2847
|
+
const { quote, onStage } = options;
|
|
2848
|
+
if (!quote.spender || isNativeSource) return void 0;
|
|
2849
|
+
const current = await deps.readAllowance(quote.spender);
|
|
2850
|
+
debugLog("owney-sdk", "swap: fusion allowance", {
|
|
2851
|
+
spender: quote.spender,
|
|
2852
|
+
current: current.toString(),
|
|
2853
|
+
needed: amount.toString()
|
|
2854
|
+
});
|
|
2855
|
+
if (current >= amount) return void 0;
|
|
2856
|
+
onStage?.("approving");
|
|
2857
|
+
await deps.ensureChain(quote.src.chainId);
|
|
2858
|
+
const permit = quote.permitSupported ? await deps.buildPermit?.(quote.spender, MAX_UINT256) ?? void 0 : void 0;
|
|
2859
|
+
if (permit) {
|
|
2860
|
+
debugLog("owney-sdk", "swap: permitting limit order protocol", {
|
|
2861
|
+
bytes: (permit.length - 2) / 2
|
|
2736
2862
|
});
|
|
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
|
-
}
|
|
2863
|
+
return permit;
|
|
2743
2864
|
}
|
|
2865
|
+
await deps.approve(quote.spender, MAX_UINT256);
|
|
2866
|
+
debugLog("owney-sdk", "swap: approved limit order protocol");
|
|
2867
|
+
return void 0;
|
|
2868
|
+
}
|
|
2869
|
+
async function runFusion(deps, options, walletAddress) {
|
|
2870
|
+
const { quote, direction, onStage } = options;
|
|
2871
|
+
const isNativeSource = quote.src.address.toLowerCase().startsWith("0xeeee");
|
|
2872
|
+
const amount = isNativeSource ? BigInt(quote.src.amount) : await affordableAmount(deps, BigInt(quote.src.amount));
|
|
2873
|
+
const permit = await authoriseSpend(deps, options, amount, isNativeSource);
|
|
2744
2874
|
const { secrets, secretHashes } = mintSecrets(quote.secretsCount ?? 1);
|
|
2745
2875
|
onStage?.("quoting");
|
|
2746
2876
|
debugLog("owney-sdk", "swap: building fusion order", {
|
|
@@ -2756,7 +2886,8 @@ async function runFusion(deps, options, walletAddress) {
|
|
|
2756
2886
|
to: { chainId: quote.dst.chainId, symbol: quote.dst.symbol },
|
|
2757
2887
|
walletAddress,
|
|
2758
2888
|
secretHashes,
|
|
2759
|
-
...direction ? { direction } : {}
|
|
2889
|
+
...direction ? { direction } : {},
|
|
2890
|
+
...permit ? { permit } : {}
|
|
2760
2891
|
});
|
|
2761
2892
|
saveOrder({
|
|
2762
2893
|
orderHash: built.orderHash,
|
|
@@ -2807,16 +2938,119 @@ async function runFusion(deps, options, walletAddress) {
|
|
|
2807
2938
|
return { orderHash: built.orderHash };
|
|
2808
2939
|
}
|
|
2809
2940
|
|
|
2941
|
+
// src/lib/swap/swap.permit.ts
|
|
2942
|
+
import {
|
|
2943
|
+
domainSeparator,
|
|
2944
|
+
encodeAbiParameters,
|
|
2945
|
+
parseAbi as parseAbi2,
|
|
2946
|
+
parseSignature
|
|
2947
|
+
} from "viem";
|
|
2948
|
+
var PERMIT_ABI = parseAbi2([
|
|
2949
|
+
"function nonces(address owner) view returns (uint256)",
|
|
2950
|
+
"function name() view returns (string)",
|
|
2951
|
+
"function version() view returns (string)",
|
|
2952
|
+
"function DOMAIN_SEPARATOR() view returns (bytes32)"
|
|
2953
|
+
]);
|
|
2954
|
+
var PERMIT_TYPES = {
|
|
2955
|
+
Permit: [
|
|
2956
|
+
{ name: "owner", type: "address" },
|
|
2957
|
+
{ name: "spender", type: "address" },
|
|
2958
|
+
{ name: "value", type: "uint256" },
|
|
2959
|
+
{ name: "nonce", type: "uint256" },
|
|
2960
|
+
{ name: "deadline", type: "uint256" }
|
|
2961
|
+
]
|
|
2962
|
+
};
|
|
2963
|
+
var VERSION_CANDIDATES = ["1", "2"];
|
|
2964
|
+
var PERMIT_TTL_SECONDS = 3600;
|
|
2965
|
+
async function tryRead(read) {
|
|
2966
|
+
try {
|
|
2967
|
+
return await read();
|
|
2968
|
+
} catch {
|
|
2969
|
+
return null;
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
function matchDomain(name, versions, chainId, token, onChainSeparator) {
|
|
2973
|
+
for (const version of versions) {
|
|
2974
|
+
const domain = { name, version, chainId, verifyingContract: token };
|
|
2975
|
+
if (domainSeparator({ domain }) === onChainSeparator) return domain;
|
|
2976
|
+
}
|
|
2977
|
+
return null;
|
|
2978
|
+
}
|
|
2979
|
+
async function buildErc2612Permit(reads, input) {
|
|
2980
|
+
const { token, owner, spender, value, chainId } = input;
|
|
2981
|
+
const call = (functionName, args) => reads.readContract({
|
|
2982
|
+
address: token,
|
|
2983
|
+
abi: PERMIT_ABI,
|
|
2984
|
+
functionName,
|
|
2985
|
+
...args ? { args } : {}
|
|
2986
|
+
});
|
|
2987
|
+
const nonce = await tryRead(() => call("nonces", [owner]));
|
|
2988
|
+
if (nonce === null) {
|
|
2989
|
+
debugLog("owney-sdk", "permit: token has no nonces(), using approval", {
|
|
2990
|
+
token
|
|
2991
|
+
});
|
|
2992
|
+
return null;
|
|
2993
|
+
}
|
|
2994
|
+
const separator = await tryRead(
|
|
2995
|
+
() => call("DOMAIN_SEPARATOR")
|
|
2996
|
+
);
|
|
2997
|
+
const name = await tryRead(() => call("name"));
|
|
2998
|
+
if (!separator || !name) {
|
|
2999
|
+
debugLog("owney-sdk", "permit: domain unverifiable, using approval", {
|
|
3000
|
+
token,
|
|
3001
|
+
hasSeparator: Boolean(separator),
|
|
3002
|
+
hasName: Boolean(name)
|
|
3003
|
+
});
|
|
3004
|
+
return null;
|
|
3005
|
+
}
|
|
3006
|
+
const declared = await tryRead(() => call("version"));
|
|
3007
|
+
const domain = matchDomain(
|
|
3008
|
+
name,
|
|
3009
|
+
declared ? [declared, ...VERSION_CANDIDATES] : VERSION_CANDIDATES,
|
|
3010
|
+
chainId,
|
|
3011
|
+
token,
|
|
3012
|
+
separator
|
|
3013
|
+
);
|
|
3014
|
+
if (!domain) {
|
|
3015
|
+
debugLog("owney-sdk", "permit: no domain matched, using approval", {
|
|
3016
|
+
token,
|
|
3017
|
+
declared
|
|
3018
|
+
});
|
|
3019
|
+
return null;
|
|
3020
|
+
}
|
|
3021
|
+
const deadline = BigInt(Math.floor((input.now?.() ?? Date.now()) / 1e3)) + BigInt(PERMIT_TTL_SECONDS);
|
|
3022
|
+
const signature = await input.signTypedData({
|
|
3023
|
+
domain,
|
|
3024
|
+
types: PERMIT_TYPES,
|
|
3025
|
+
primaryType: "Permit",
|
|
3026
|
+
message: { owner, spender, value, nonce, deadline }
|
|
3027
|
+
});
|
|
3028
|
+
const { r, s, v, yParity } = parseSignature(signature);
|
|
3029
|
+
const recoveryV = v ?? BigInt(yParity + 27);
|
|
3030
|
+
return encodeAbiParameters(
|
|
3031
|
+
[
|
|
3032
|
+
{ type: "address" },
|
|
3033
|
+
{ type: "address" },
|
|
3034
|
+
{ type: "uint256" },
|
|
3035
|
+
{ type: "uint256" },
|
|
3036
|
+
{ type: "uint8" },
|
|
3037
|
+
{ type: "bytes32" },
|
|
3038
|
+
{ type: "bytes32" }
|
|
3039
|
+
],
|
|
3040
|
+
[owner, spender, value, deadline, Number(recoveryV), r, s]
|
|
3041
|
+
);
|
|
3042
|
+
}
|
|
3043
|
+
|
|
2810
3044
|
// src/lib/swap/swap.arrival.ts
|
|
2811
|
-
var
|
|
2812
|
-
var
|
|
3045
|
+
var DEFAULT_TIMEOUT_MS3 = 18e4;
|
|
3046
|
+
var DEFAULT_POLL_MS3 = 4e3;
|
|
2813
3047
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2814
3048
|
async function awaitWithdrawalArrival(options) {
|
|
2815
3049
|
const {
|
|
2816
3050
|
readBalance,
|
|
2817
3051
|
baseline,
|
|
2818
|
-
timeoutMs =
|
|
2819
|
-
pollMs =
|
|
3052
|
+
timeoutMs = DEFAULT_TIMEOUT_MS3,
|
|
3053
|
+
pollMs = DEFAULT_POLL_MS3
|
|
2820
3054
|
} = options;
|
|
2821
3055
|
const deadline = Date.now() + timeoutMs;
|
|
2822
3056
|
debugLog("owney-sdk", "withdraw: waiting for funds to land", {
|
|
@@ -4402,6 +4636,41 @@ var OwneySDK = class {
|
|
|
4402
4636
|
return hash;
|
|
4403
4637
|
},
|
|
4404
4638
|
ensureChain: (chainId) => this.ensureSwapChain(chainId),
|
|
4639
|
+
/**
|
|
4640
|
+
* Sign the approval instead of paying for it. Null means the token
|
|
4641
|
+
* cannot be permitted (WETH has no `permit`) or its domain could not be
|
|
4642
|
+
* verified, and the executor sends an ordinary approval instead.
|
|
4643
|
+
*
|
|
4644
|
+
* Reads go through srcPublic like every other read here — the domain is
|
|
4645
|
+
* chain-scoped, and reading a nonce off whatever chain the wallet
|
|
4646
|
+
* happens to sit on would sign a permit the token rejects.
|
|
4647
|
+
*/
|
|
4648
|
+
buildPermit: (spender, amount) => buildErc2612Permit(
|
|
4649
|
+
{
|
|
4650
|
+
readContract: (args) => srcPublic.readContract(args)
|
|
4651
|
+
},
|
|
4652
|
+
{
|
|
4653
|
+
token: quote.src.address,
|
|
4654
|
+
owner: state.walletAddress,
|
|
4655
|
+
spender,
|
|
4656
|
+
value: amount,
|
|
4657
|
+
chainId: quote.src.chainId,
|
|
4658
|
+
signTypedData: (payload) => wallet.signTypedData({
|
|
4659
|
+
account: state.walletAddress,
|
|
4660
|
+
...payload
|
|
4661
|
+
})
|
|
4662
|
+
}
|
|
4663
|
+
),
|
|
4664
|
+
/**
|
|
4665
|
+
* Same-chain polling, with the chain bound in. That product keys its
|
|
4666
|
+
* orders per chain; asking the Fusion+ endpoint for one answers 404,
|
|
4667
|
+
* which would read as an order that vanished mid-swap.
|
|
4668
|
+
*/
|
|
4669
|
+
sameChainRunner: {
|
|
4670
|
+
orderStatus: (h) => this.swapApi().orderStatus(h, quote.src.chainId),
|
|
4671
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
4672
|
+
now: () => Date.now()
|
|
4673
|
+
},
|
|
4405
4674
|
runner: {
|
|
4406
4675
|
readyForSecrets: (h) => this.swapApi().readyForSecrets(h),
|
|
4407
4676
|
submitSecret: (h, secret) => this.swapApi().submitSecret(h, secret),
|