@hyperbridge/simplex 0.16.8 → 0.16.12
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/README.md +31 -14
- package/dist/bin/simplex.js +23880 -2112
- package/dist/bin/simplex.js.map +1 -1
- package/dist/config-storage.cjs +186 -0
- package/dist/config-storage.cjs.map +1 -0
- package/dist/config-storage.d.cts +28 -0
- package/dist/config-storage.d.ts +28 -0
- package/dist/config-storage.js +163 -0
- package/dist/config-storage.js.map +1 -0
- package/dist/index.cjs +704 -295
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +246 -100
- package/dist/index.d.ts +246 -100
- package/dist/index.js +579 -175
- package/dist/index.js.map +1 -1
- package/dist/sqlite.cjs +12 -0
- package/dist/sqlite.cjs.map +1 -1
- package/dist/sqlite.d.cts +2 -1
- package/dist/sqlite.d.ts +2 -1
- package/dist/sqlite.js +12 -0
- package/dist/sqlite.js.map +1 -1
- package/dist/{types-C7rnnKWn.d.cts → types-B5zkFhxR.d.cts} +11 -1
- package/dist/{types-C7rnnKWn.d.ts → types-B5zkFhxR.d.ts} +11 -1
- package/dist/ui/assets/{index-BQu6BMxc.js → index-QU1Ik7IX.js} +35 -35
- package/dist/ui/assets/{index-C3T-gubp.css → index-XKHMOpY2.css} +1 -1
- package/dist/ui/index.html +2 -2
- package/package.json +15 -3
package/dist/index.cjs
CHANGED
|
@@ -32,6 +32,8 @@ var src_exports = {};
|
|
|
32
32
|
__export(src_exports, {
|
|
33
33
|
AssetController: () => AssetController,
|
|
34
34
|
ChainController: () => ChainController,
|
|
35
|
+
LimitOrderController: () => LimitOrderController,
|
|
36
|
+
LimitOrderValidationError: () => LimitOrderValidationError,
|
|
35
37
|
LoggerContext: () => LoggerContext,
|
|
36
38
|
MemoryDataStore: () => MemoryDataStore,
|
|
37
39
|
OrderScanner: () => OrderScanner,
|
|
@@ -44,6 +46,7 @@ __export(src_exports, {
|
|
|
44
46
|
WalletController: () => WalletController,
|
|
45
47
|
addLogSink: () => addLogSink,
|
|
46
48
|
assertConfirmationCoverage: () => assertConfirmationCoverage,
|
|
49
|
+
canDerive: () => canDerive,
|
|
47
50
|
configureLogger: () => configureLogger,
|
|
48
51
|
createSigner: () => createSigner,
|
|
49
52
|
digestSigner: () => digestSigner,
|
|
@@ -56,10 +59,12 @@ __export(src_exports, {
|
|
|
56
59
|
pickAnchorStable: () => pickAnchorStable,
|
|
57
60
|
privateKeySigner: () => privateKeySigner,
|
|
58
61
|
registrySymbols: () => registrySymbols,
|
|
62
|
+
secretPhraseSigner: () => secretPhraseSigner,
|
|
59
63
|
turnkeySigner: () => turnkeySigner,
|
|
60
64
|
validateAssetDefinitions: () => validateAssetDefinitions,
|
|
61
65
|
validateConfig: () => validateConfig,
|
|
62
66
|
validatePairConfigs: () => validatePairConfigs,
|
|
67
|
+
validateSecretPhrase: () => validateSecretPhrase,
|
|
63
68
|
validateSignerConfig: () => validateSignerConfig,
|
|
64
69
|
validateVaultToml: () => validateVaultToml,
|
|
65
70
|
viemSigner: () => viemSigner
|
|
@@ -70,7 +75,7 @@ module.exports = __toCommonJS(src_exports);
|
|
|
70
75
|
var import_node_events3 = require("node:events");
|
|
71
76
|
|
|
72
77
|
// src/core/boot.ts
|
|
73
|
-
var
|
|
78
|
+
var import_viem32 = require("viem");
|
|
74
79
|
|
|
75
80
|
// src/core/filler.ts
|
|
76
81
|
var import_util_crypto = require("@polkadot/util-crypto");
|
|
@@ -1160,6 +1165,7 @@ var import_viem2 = require("viem");
|
|
|
1160
1165
|
var import_sdk2 = require("@hyperbridge/sdk");
|
|
1161
1166
|
var DEFAULT_BLOCK_SCAN_INTERVAL_SECONDS = 3;
|
|
1162
1167
|
var MIN_BLOCK_SCAN_INTERVAL_SECONDS = 0.1;
|
|
1168
|
+
var DEFAULT_MIN_ORDER_SIZE_USD = 20;
|
|
1163
1169
|
function validateRpcUrls(rpcUrls) {
|
|
1164
1170
|
if (rpcUrls.length === 0) {
|
|
1165
1171
|
throw new Error("rpcUrls must contain at least one URL");
|
|
@@ -1582,6 +1588,13 @@ var FillerConfigService = class {
|
|
|
1582
1588
|
getBidValiditySeconds() {
|
|
1583
1589
|
return this.fillerConfig?.bidValiditySeconds ?? 300;
|
|
1584
1590
|
}
|
|
1591
|
+
/**
|
|
1592
|
+
* The order size, in USD, below which `order.fees` must cover the execution cost.
|
|
1593
|
+
* Defaults to 20. An order at or above it is filled whatever fees it carries.
|
|
1594
|
+
*/
|
|
1595
|
+
getMinOrderSizeUsd() {
|
|
1596
|
+
return this.fillerConfig?.minOrderSizeUsd ?? DEFAULT_MIN_ORDER_SIZE_USD;
|
|
1597
|
+
}
|
|
1585
1598
|
/** Ceiling bps above user-requested output. Default 500 (5%). */
|
|
1586
1599
|
getMaxOverfillBps() {
|
|
1587
1600
|
return BigInt(this.fillerConfig?.overfillProtection?.maxOverfillBps ?? 500);
|
|
@@ -1685,6 +1698,12 @@ function validateConfig(config, cliWatchOnly = false) {
|
|
|
1685
1698
|
);
|
|
1686
1699
|
}
|
|
1687
1700
|
}
|
|
1701
|
+
const minOrderSize = config.simplex.minOrderSizeUsd;
|
|
1702
|
+
if (minOrderSize !== void 0) {
|
|
1703
|
+
if (!Number.isFinite(minOrderSize) || minOrderSize < 0) {
|
|
1704
|
+
throw new Error(`simplex.minOrderSizeUsd must be a number >= 0 (USD); got ${minOrderSize}`);
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1688
1707
|
if (config.allowlist) {
|
|
1689
1708
|
for (const user of config.allowlist.users ?? []) {
|
|
1690
1709
|
if (!(0, import_viem2.isAddress)(user)) {
|
|
@@ -2838,11 +2857,21 @@ var import_node_crypto = require("node:crypto");
|
|
|
2838
2857
|
var import_sdk4 = require("@hyperbridge/sdk");
|
|
2839
2858
|
|
|
2840
2859
|
// src/orderbook/amounts.ts
|
|
2860
|
+
var import_viem6 = require("viem");
|
|
2841
2861
|
var ORDERBOOK_SCALE = 10n ** 18n;
|
|
2842
2862
|
function toRaw(amount, decimals) {
|
|
2843
2863
|
const shift = 18 - decimals;
|
|
2844
2864
|
return shift >= 0 ? amount / 10n ** BigInt(shift) : amount * 10n ** BigInt(-shift);
|
|
2845
2865
|
}
|
|
2866
|
+
function budgetIdFor(limitOrderId) {
|
|
2867
|
+
return (0, import_viem6.keccak256)((0, import_viem6.stringToBytes)(limitOrderId));
|
|
2868
|
+
}
|
|
2869
|
+
function budgetFor(order, token, decimals) {
|
|
2870
|
+
return { budgetId: budgetIdFor(order.id), cap: toRaw(BigInt(order.size), decimals), token };
|
|
2871
|
+
}
|
|
2872
|
+
function budgetRoom(budget, spent, decimals) {
|
|
2873
|
+
return spent >= budget.cap ? 0n : toScaled(budget.cap - spent, decimals);
|
|
2874
|
+
}
|
|
2846
2875
|
function fromHuman(amount) {
|
|
2847
2876
|
const text = amount.trim();
|
|
2848
2877
|
if (!/^[0-9]+(\.[0-9]+)?$/.test(text)) {
|
|
@@ -2863,6 +2892,10 @@ function toScaled(amount, decimals) {
|
|
|
2863
2892
|
const shift = 18 - decimals;
|
|
2864
2893
|
return shift >= 0 ? amount * 10n ** BigInt(shift) : amount / 10n ** BigInt(-shift);
|
|
2865
2894
|
}
|
|
2895
|
+
function toScaledUp(amount, decimals) {
|
|
2896
|
+
const shift = 18 - decimals;
|
|
2897
|
+
return shift >= 0 ? amount * 10n ** BigInt(shift) : divCeil(amount, 10n ** BigInt(-shift));
|
|
2898
|
+
}
|
|
2866
2899
|
function divCeil(numerator, denominator) {
|
|
2867
2900
|
return (numerator + denominator - 1n) / denominator;
|
|
2868
2901
|
}
|
|
@@ -2904,6 +2937,87 @@ function inputFor(params) {
|
|
|
2904
2937
|
return divCeil(scaled, unit) * unit;
|
|
2905
2938
|
}
|
|
2906
2939
|
|
|
2940
|
+
// src/orderbook/matching.ts
|
|
2941
|
+
function limitOrderLegs(order) {
|
|
2942
|
+
return order.side === "BID" ? { input: order.base, output: order.quote } : { input: order.quote, output: order.base };
|
|
2943
|
+
}
|
|
2944
|
+
function matchLimitOrders(orders, incoming, resolve, now = /* @__PURE__ */ new Date()) {
|
|
2945
|
+
const qualifying = orders.filter((order) => serves(order, incoming, resolve, now)).map((order) => {
|
|
2946
|
+
const offer = offerFor({
|
|
2947
|
+
side: order.side,
|
|
2948
|
+
inputAmount: incoming.inputNet,
|
|
2949
|
+
price: BigInt(order.price),
|
|
2950
|
+
outputDecimals: incoming.outputDecimals
|
|
2951
|
+
});
|
|
2952
|
+
const available = BigInt(order.remaining);
|
|
2953
|
+
return { order, offer, available, payout: offer < available ? offer : available };
|
|
2954
|
+
}).filter((candidate) => candidate.offer >= incoming.requestedOutput && candidate.payout > 0n).sort(byBestOfferFirst);
|
|
2955
|
+
return qualifying;
|
|
2956
|
+
}
|
|
2957
|
+
function byBestOfferFirst(a, b) {
|
|
2958
|
+
if (a.offer !== b.offer) return a.offer > b.offer ? -1 : 1;
|
|
2959
|
+
if (a.available !== b.available) return a.available > b.available ? -1 : 1;
|
|
2960
|
+
return a.order.id < b.order.id ? -1 : 1;
|
|
2961
|
+
}
|
|
2962
|
+
function whyUnmatched(orders, incoming, resolve, now = /* @__PURE__ */ new Date()) {
|
|
2963
|
+
let furthest = -1;
|
|
2964
|
+
for (const order of orders) {
|
|
2965
|
+
const failed = firstFailed(order, incoming, resolve, now);
|
|
2966
|
+
furthest = Math.max(furthest, failed === null ? CHECKS.length : CHECKS.indexOf(failed));
|
|
2967
|
+
}
|
|
2968
|
+
const { source, destination, inputSymbol } = incoming;
|
|
2969
|
+
if (furthest < CHECKS.length) {
|
|
2970
|
+
switch (CHECKS[Math.max(furthest, 0)]) {
|
|
2971
|
+
case "open":
|
|
2972
|
+
return "no limit order is open";
|
|
2973
|
+
case "chain":
|
|
2974
|
+
return `no open limit order fills on ${destination}`;
|
|
2975
|
+
case "input":
|
|
2976
|
+
return `no limit order on ${destination} takes ${inputSymbol} in`;
|
|
2977
|
+
case "output":
|
|
2978
|
+
return `no limit order on ${destination} taking ${inputSymbol} in pays out the token this order asks for`;
|
|
2979
|
+
case "source":
|
|
2980
|
+
return `no limit order for this pair on ${destination} accepts swaps from ${source}`;
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
const priced = orders.filter((order) => firstFailed(order, incoming, resolve, now) === null).map((order) => ({
|
|
2984
|
+
order,
|
|
2985
|
+
offer: offerFor({
|
|
2986
|
+
side: order.side,
|
|
2987
|
+
inputAmount: incoming.inputNet,
|
|
2988
|
+
price: BigInt(order.price),
|
|
2989
|
+
outputDecimals: incoming.outputDecimals
|
|
2990
|
+
})
|
|
2991
|
+
}));
|
|
2992
|
+
if (priced.some(({ offer }) => offer >= incoming.requestedOutput)) {
|
|
2993
|
+
return "the limit orders that meet its rate have nothing left to pay out";
|
|
2994
|
+
}
|
|
2995
|
+
const best = priced.reduce((a, b) => b.offer > a.offer ? b : a);
|
|
2996
|
+
const symbol = limitOrderLegs(best.order).output;
|
|
2997
|
+
return `it asks for ${readable(incoming.requestedOutput)} ${symbol}; the best limit order offers ${readable(best.offer)} ${symbol}`;
|
|
2998
|
+
}
|
|
2999
|
+
var CHECKS = ["open", "chain", "input", "output", "source"];
|
|
3000
|
+
function serves(order, incoming, resolve, now) {
|
|
3001
|
+
return firstFailed(order, incoming, resolve, now) === null;
|
|
3002
|
+
}
|
|
3003
|
+
function firstFailed(order, incoming, resolve, now) {
|
|
3004
|
+
if (order.status !== "open") return "open";
|
|
3005
|
+
if (order.expiresAt !== null && new Date(order.expiresAt) <= now) return "open";
|
|
3006
|
+
if (order.fillChain !== incoming.destination) return "chain";
|
|
3007
|
+
const legs = limitOrderLegs(order);
|
|
3008
|
+
if (normalizeSymbol(legs.input) !== normalizeSymbol(incoming.inputSymbol)) return "input";
|
|
3009
|
+
const outputToken = resolve(legs.output, order.fillChain);
|
|
3010
|
+
if (!outputToken || outputToken.toLowerCase() !== incoming.outputToken.toLowerCase()) return "output";
|
|
3011
|
+
if (incoming.source === incoming.destination) return null;
|
|
3012
|
+
return order.acceptedSources.includes(incoming.source) ? null : "source";
|
|
3013
|
+
}
|
|
3014
|
+
function readable(amount) {
|
|
3015
|
+
const [whole, fraction] = toHuman(amount).split(".");
|
|
3016
|
+
const grouped = whole.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
3017
|
+
const cut = fraction?.slice(0, 6).replace(/0+$/, "");
|
|
3018
|
+
return cut ? `${grouped}.${cut}` : grouped;
|
|
3019
|
+
}
|
|
3020
|
+
|
|
2907
3021
|
// src/orderbook/limit-orders.ts
|
|
2908
3022
|
var LIMITS_TTL_MS = 5 * 60 * 1e3;
|
|
2909
3023
|
var POSTABLE = ["open", "resizing"];
|
|
@@ -3418,6 +3532,58 @@ var LimitOrderService = class {
|
|
|
3418
3532
|
}
|
|
3419
3533
|
return report;
|
|
3420
3534
|
}
|
|
3535
|
+
/**
|
|
3536
|
+
* Lowers every open order's `remaining` to what the solver's account will still
|
|
3537
|
+
* pay for it, and puts the order back on the book at that size.
|
|
3538
|
+
*
|
|
3539
|
+
* The account tallies each order's payouts on the fill chain and refuses a fill
|
|
3540
|
+
* that would take the tally past the order's size. A fill that never settled
|
|
3541
|
+
* here, or a store restored from an older copy, leaves `remaining` above that
|
|
3542
|
+
* room and the order posted at a size the account will refuse. A tally behind
|
|
3543
|
+
* the store changes nothing: `remaining` is never raised.
|
|
3544
|
+
*
|
|
3545
|
+
* Answers how many orders were lowered.
|
|
3546
|
+
*/
|
|
3547
|
+
async reconcileTallies() {
|
|
3548
|
+
let lowered = 0;
|
|
3549
|
+
for (const order of await this.store.list({ status: "open" })) {
|
|
3550
|
+
try {
|
|
3551
|
+
const clamped = await this.clampToTally(order);
|
|
3552
|
+
if (!clamped) continue;
|
|
3553
|
+
lowered++;
|
|
3554
|
+
await this.resize(clamped, BigInt(order.remaining) - BigInt(clamped.remaining));
|
|
3555
|
+
} catch (err) {
|
|
3556
|
+
this.logger.error({ id: order.id, err }, "Could not bring the limit order in line with its on-chain tally");
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
return lowered;
|
|
3560
|
+
}
|
|
3561
|
+
/** The order as the store now holds it when its tally lowered it, null when it was left alone. */
|
|
3562
|
+
async clampToTally(order) {
|
|
3563
|
+
const token = this.assetRegistry.getAddress(limitOrderLegs(order).output, order.fillChain);
|
|
3564
|
+
if (!token) return null;
|
|
3565
|
+
const decimals = await this.contractService.getTokenDecimals(token, order.fillChain);
|
|
3566
|
+
const budget = budgetFor(order, token, decimals);
|
|
3567
|
+
const spent = await this.contractService.limitOrderSpent(order.fillChain, budget.budgetId);
|
|
3568
|
+
if (spent === null) return null;
|
|
3569
|
+
const room = budgetRoom(budget, spent, decimals);
|
|
3570
|
+
const over = BigInt(order.remaining) - (room + BigInt(order.reserved));
|
|
3571
|
+
if (over < toScaled(1n, decimals)) return null;
|
|
3572
|
+
const clamped = await this.store.clampRemaining(order.id, room.toString());
|
|
3573
|
+
if (!clamped) return null;
|
|
3574
|
+
this.logger.warn(
|
|
3575
|
+
{
|
|
3576
|
+
id: order.id,
|
|
3577
|
+
before: order.remaining,
|
|
3578
|
+
after: clamped.remaining,
|
|
3579
|
+
spent: spent.toString(),
|
|
3580
|
+
cap: budget.cap.toString(),
|
|
3581
|
+
reserved: clamped.reserved
|
|
3582
|
+
},
|
|
3583
|
+
"Limit order had more left than its on-chain tally allows; lowering it"
|
|
3584
|
+
);
|
|
3585
|
+
return clamped;
|
|
3586
|
+
}
|
|
3421
3587
|
/**
|
|
3422
3588
|
* Withdraws every order that has outlived the operator's own `expiresAt`.
|
|
3423
3589
|
*
|
|
@@ -3679,7 +3845,7 @@ async function patchRuntimeState(store, patch) {
|
|
|
3679
3845
|
}
|
|
3680
3846
|
|
|
3681
3847
|
// src/services/wallet/mpcvault.ts
|
|
3682
|
-
var
|
|
3848
|
+
var import_viem7 = require("viem");
|
|
3683
3849
|
var grpc = __toESM(require("@grpc/grpc-js"), 1);
|
|
3684
3850
|
|
|
3685
3851
|
// src/proto/mpcvault/platform/v1/api.ts
|
|
@@ -12243,18 +12409,18 @@ var MpcVaultService = class {
|
|
|
12243
12409
|
// Signature assembly
|
|
12244
12410
|
// -----------------------------------------------------------------------
|
|
12245
12411
|
signatureFromParts(parts, normalizeV = false) {
|
|
12246
|
-
const r = (0,
|
|
12247
|
-
const s = (0,
|
|
12412
|
+
const r = (0, import_viem7.padHex)((0, import_viem7.toHex)(BigInt(parts.R || "0")), { size: 32 });
|
|
12413
|
+
const s = (0, import_viem7.padHex)((0, import_viem7.toHex)(BigInt(parts.S || "0")), { size: 32 });
|
|
12248
12414
|
const vInt = this.parseFlexibleBigInt(parts.V || "0");
|
|
12249
|
-
let v = (0,
|
|
12415
|
+
let v = (0, import_viem7.padHex)((0, import_viem7.toHex)(vInt), { size: 1 });
|
|
12250
12416
|
if (normalizeV) {
|
|
12251
|
-
v = (0,
|
|
12417
|
+
v = (0, import_viem7.padHex)((0, import_viem7.toHex)(vInt < 27n ? vInt + 27n : vInt), { size: 1 });
|
|
12252
12418
|
}
|
|
12253
|
-
return (0,
|
|
12419
|
+
return (0, import_viem7.concatHex)([r, s, v]);
|
|
12254
12420
|
}
|
|
12255
12421
|
ecdsaPartsToSignatureComponents(parts) {
|
|
12256
|
-
const r = (0,
|
|
12257
|
-
const s = (0,
|
|
12422
|
+
const r = (0, import_viem7.padHex)((0, import_viem7.toHex)(BigInt(parts.R || "0")), { size: 32 });
|
|
12423
|
+
const s = (0, import_viem7.padHex)((0, import_viem7.toHex)(BigInt(parts.S || "0")), { size: 32 });
|
|
12258
12424
|
const vInt = this.parseFlexibleBigInt(parts.V || "0");
|
|
12259
12425
|
const yParity = vInt >= 27n ? Number(vInt - 27n) : Number(vInt);
|
|
12260
12426
|
if (yParity !== 0 && yParity !== 1) {
|
|
@@ -12290,7 +12456,7 @@ var MpcVaultService = class {
|
|
|
12290
12456
|
const messageBytes = Buffer.from(messageHash.slice(2), "hex");
|
|
12291
12457
|
const prefix = Buffer.from(`Ethereum Signed Message:
|
|
12292
12458
|
${messageBytes.length}`);
|
|
12293
|
-
const ethSignedMessageHash = (0,
|
|
12459
|
+
const ethSignedMessageHash = (0, import_viem7.keccak256)((0, import_viem7.concatHex)([(0, import_viem7.toHex)(prefix), messageHash]));
|
|
12294
12460
|
const ethSignedBytes = Buffer.from(ethSignedMessageHash.slice(2), "hex");
|
|
12295
12461
|
const uuid = await this.createSigningRequest({
|
|
12296
12462
|
vaultUuid: this.vaultUuid,
|
|
@@ -12378,11 +12544,12 @@ var SignerType = /* @__PURE__ */ ((SignerType2) => {
|
|
|
12378
12544
|
SignerType2["PrivateKey"] = "privateKey";
|
|
12379
12545
|
SignerType2["MpcVault"] = "mpcVault";
|
|
12380
12546
|
SignerType2["Turnkey"] = "turnkey";
|
|
12547
|
+
SignerType2["SecretPhrase"] = "secretPhrase";
|
|
12381
12548
|
return SignerType2;
|
|
12382
12549
|
})(SignerType || {});
|
|
12383
12550
|
|
|
12384
12551
|
// src/services/wallet/accounts/mpc.ts
|
|
12385
|
-
var
|
|
12552
|
+
var import_viem8 = require("viem");
|
|
12386
12553
|
var import_utils = require("viem/utils");
|
|
12387
12554
|
function mpcVaultSigner(config) {
|
|
12388
12555
|
const service = new MpcVaultService({
|
|
@@ -12417,9 +12584,9 @@ function mpcVaultSigner(config) {
|
|
|
12417
12584
|
if (tx.authorizationList?.length) {
|
|
12418
12585
|
const serializable = { ...tx, type: "eip7702" };
|
|
12419
12586
|
const signature = await service.signRawHashComponents(
|
|
12420
|
-
(0,
|
|
12587
|
+
(0, import_viem8.keccak256)((0, import_viem8.serializeTransaction)(serializable))
|
|
12421
12588
|
);
|
|
12422
|
-
return (0,
|
|
12589
|
+
return (0, import_viem8.serializeTransaction)(serializable, signature);
|
|
12423
12590
|
}
|
|
12424
12591
|
return service.signTransaction({
|
|
12425
12592
|
chainId: tx.chainId,
|
|
@@ -12444,10 +12611,10 @@ function requireChainId(value) {
|
|
|
12444
12611
|
var import_accounts = require("viem/accounts");
|
|
12445
12612
|
|
|
12446
12613
|
// src/services/wallet/accounts/viem.ts
|
|
12447
|
-
var
|
|
12614
|
+
var import_viem9 = require("viem");
|
|
12448
12615
|
var import_utils2 = require("viem/utils");
|
|
12449
12616
|
function splitSignature(signature, source) {
|
|
12450
|
-
const sig = (0,
|
|
12617
|
+
const sig = (0, import_viem9.parseSignature)(signature);
|
|
12451
12618
|
const yParity = sig.yParity ?? (sig.v !== void 0 ? Number(sig.v >= 27n ? sig.v - 27n : sig.v) : void 0);
|
|
12452
12619
|
if (yParity !== 0 && yParity !== 1) {
|
|
12453
12620
|
throw new Error(`Failed to derive yParity from ${source} signature`);
|
|
@@ -12499,9 +12666,54 @@ function privateKeySigner(privateKey) {
|
|
|
12499
12666
|
return viemSigner((0, import_accounts.privateKeyToAccount)(privateKey));
|
|
12500
12667
|
}
|
|
12501
12668
|
|
|
12669
|
+
// src/services/wallet/accounts/secretphrase.ts
|
|
12670
|
+
var import_bip39 = require("@scure/bip39");
|
|
12671
|
+
var import_accounts2 = require("viem/accounts");
|
|
12672
|
+
var WORD_COUNTS = [12, 15, 18, 21, 24];
|
|
12673
|
+
var MAX_DERIVATION_INDEX = 2 ** 31 - 1;
|
|
12674
|
+
function normaliseSecretPhrase(phrase) {
|
|
12675
|
+
return phrase.trim().split(/\s+/).join(" ").toLowerCase();
|
|
12676
|
+
}
|
|
12677
|
+
function validateSecretPhrase(phrase) {
|
|
12678
|
+
if (typeof phrase !== "string") throw new Error("Secret phrase must be a string");
|
|
12679
|
+
const normalised = normaliseSecretPhrase(phrase);
|
|
12680
|
+
const words = normalised === "" ? [] : normalised.split(" ");
|
|
12681
|
+
if (!WORD_COUNTS.includes(words.length)) {
|
|
12682
|
+
throw new Error(`Secret phrase must have ${WORD_COUNTS.join(", ")} words; got ${words.length}`);
|
|
12683
|
+
}
|
|
12684
|
+
if (words.some((word) => !import_accounts2.english.includes(word))) {
|
|
12685
|
+
throw new Error("Secret phrase contains a word that is not in the BIP-39 English wordlist");
|
|
12686
|
+
}
|
|
12687
|
+
if (!(0, import_bip39.validateMnemonic)(normalised, import_accounts2.english)) {
|
|
12688
|
+
throw new Error("Secret phrase checksum does not match; check the words and their order");
|
|
12689
|
+
}
|
|
12690
|
+
}
|
|
12691
|
+
function assertDerivationIndex(index, name = "Derivation index") {
|
|
12692
|
+
const expected = `${name} must be an integer between 0 and ${MAX_DERIVATION_INDEX}`;
|
|
12693
|
+
if (typeof index !== "number") throw new Error(expected);
|
|
12694
|
+
if (!Number.isInteger(index) || index < 0 || index > MAX_DERIVATION_INDEX) {
|
|
12695
|
+
throw new Error(`${expected}; got ${index}`);
|
|
12696
|
+
}
|
|
12697
|
+
}
|
|
12698
|
+
function secretPhraseSigner(config) {
|
|
12699
|
+
validateSecretPhrase(config.phrase);
|
|
12700
|
+
const phrase = normaliseSecretPhrase(config.phrase);
|
|
12701
|
+
const accountIndex = config.accountIndex ?? 0;
|
|
12702
|
+
assertDerivationIndex(accountIndex, "accountIndex");
|
|
12703
|
+
const derive = (index) => {
|
|
12704
|
+
assertDerivationIndex(index);
|
|
12705
|
+
return { ...viemSigner((0, import_accounts2.mnemonicToAccount)(phrase, { addressIndex: index })), mode: "secretPhrase" };
|
|
12706
|
+
};
|
|
12707
|
+
return { ...derive(accountIndex), accountIndex, derive };
|
|
12708
|
+
}
|
|
12709
|
+
function canDerive(signer) {
|
|
12710
|
+
const candidate = signer;
|
|
12711
|
+
return typeof candidate.derive === "function" && typeof candidate.accountIndex === "number";
|
|
12712
|
+
}
|
|
12713
|
+
|
|
12502
12714
|
// src/services/wallet/accounts/turnkey.ts
|
|
12503
12715
|
var import_sdk_server = require("@turnkey/sdk-server");
|
|
12504
|
-
var
|
|
12716
|
+
var import_viem12 = require("@turnkey/viem");
|
|
12505
12717
|
async function turnkeySigner(config) {
|
|
12506
12718
|
const turnkey = new import_sdk_server.Turnkey({
|
|
12507
12719
|
defaultOrganizationId: config.organizationId,
|
|
@@ -12509,7 +12721,7 @@ async function turnkeySigner(config) {
|
|
|
12509
12721
|
apiPrivateKey: config.apiPrivateKey,
|
|
12510
12722
|
apiPublicKey: config.apiPublicKey
|
|
12511
12723
|
});
|
|
12512
|
-
const account = await (0,
|
|
12724
|
+
const account = await (0, import_viem12.createAccount)({
|
|
12513
12725
|
client: turnkey.apiClient(),
|
|
12514
12726
|
organizationId: config.organizationId,
|
|
12515
12727
|
signWith: config.signWith
|
|
@@ -12522,6 +12734,7 @@ async function createSigner(config) {
|
|
|
12522
12734
|
if (config.type === "privateKey" /* PrivateKey */) return privateKeySigner(config.key);
|
|
12523
12735
|
if (config.type === "mpcVault" /* MpcVault */) return mpcVaultSigner(config);
|
|
12524
12736
|
if (config.type === "turnkey" /* Turnkey */) return turnkeySigner(config);
|
|
12737
|
+
if (config.type === "secretPhrase" /* SecretPhrase */) return secretPhraseSigner(config);
|
|
12525
12738
|
throw new Error(`Unsupported signer mode: ${config.type ?? "unknown"}`);
|
|
12526
12739
|
}
|
|
12527
12740
|
function validateSignerConfig(config) {
|
|
@@ -12547,15 +12760,23 @@ function validateSignerConfig(config) {
|
|
|
12547
12760
|
if (!config.signWith) throw new Error("simplex.signer.signWith is required");
|
|
12548
12761
|
return;
|
|
12549
12762
|
}
|
|
12763
|
+
if (config.type === "secretPhrase" /* SecretPhrase */) {
|
|
12764
|
+
if (!config.phrase) {
|
|
12765
|
+
throw new Error("simplex.signer.phrase is required when simplex.signer.type=secretPhrase");
|
|
12766
|
+
}
|
|
12767
|
+
validateSecretPhrase(config.phrase);
|
|
12768
|
+
if (config.accountIndex !== void 0) assertDerivationIndex(config.accountIndex, "simplex.signer.accountIndex");
|
|
12769
|
+
return;
|
|
12770
|
+
}
|
|
12550
12771
|
throw new Error(`Unsupported signer mode: ${config.type ?? "unknown"}`);
|
|
12551
12772
|
}
|
|
12552
12773
|
|
|
12553
12774
|
// src/services/wallet/account.ts
|
|
12554
|
-
var
|
|
12775
|
+
var import_viem14 = require("viem");
|
|
12555
12776
|
var import_utils3 = require("viem/utils");
|
|
12556
|
-
var
|
|
12777
|
+
var import_accounts3 = require("viem/accounts");
|
|
12557
12778
|
function accountFor(signer) {
|
|
12558
|
-
return (0,
|
|
12779
|
+
return (0, import_accounts3.toAccount)({
|
|
12559
12780
|
address: signer.address,
|
|
12560
12781
|
signTypedData: (typedData) => signer.signTypedData(typedData),
|
|
12561
12782
|
signTransaction: async (transaction) => await signer.signTransaction(toSignerTransaction(transaction)),
|
|
@@ -12595,7 +12816,7 @@ function digestSigner(opts) {
|
|
|
12595
12816
|
return {
|
|
12596
12817
|
address: opts.address,
|
|
12597
12818
|
mode: opts.mode,
|
|
12598
|
-
signTypedData: async (typedData) => (0,
|
|
12819
|
+
signTypedData: async (typedData) => (0, import_viem14.serializeSignature)(await sign((0, import_viem14.hashTypedData)(typedData))),
|
|
12599
12820
|
signAuthorization: (auth) => sign(
|
|
12600
12821
|
(0, import_utils3.hashAuthorization)({
|
|
12601
12822
|
address: auth.contractAddress,
|
|
@@ -12605,8 +12826,8 @@ function digestSigner(opts) {
|
|
|
12605
12826
|
),
|
|
12606
12827
|
signTransaction: async (tx) => {
|
|
12607
12828
|
const serializable = tx;
|
|
12608
|
-
const signature = await sign((0,
|
|
12609
|
-
return (0,
|
|
12829
|
+
const signature = await sign((0, import_viem14.keccak256)((0, import_viem14.serializeTransaction)(serializable)));
|
|
12830
|
+
return (0, import_viem14.serializeTransaction)(serializable, signature);
|
|
12610
12831
|
}
|
|
12611
12832
|
};
|
|
12612
12833
|
}
|
|
@@ -12635,6 +12856,12 @@ async function deriveSubstrateKeyPair(key) {
|
|
|
12635
12856
|
}
|
|
12636
12857
|
|
|
12637
12858
|
// src/services/CacheService.ts
|
|
12859
|
+
function budgetToCache(budget) {
|
|
12860
|
+
return { budgetId: budget.budgetId, cap: budget.cap.toString(), token: budget.token };
|
|
12861
|
+
}
|
|
12862
|
+
function budgetFromCache(budget) {
|
|
12863
|
+
return { budgetId: budget.budgetId, cap: BigInt(budget.cap), token: budget.token };
|
|
12864
|
+
}
|
|
12638
12865
|
var CacheService = class {
|
|
12639
12866
|
constructor(loggers = defaultLoggerContext()) {
|
|
12640
12867
|
this.CACHE_EXPIRY_MS = 1 * 60 * 1e3;
|
|
@@ -12648,6 +12875,7 @@ var CacheService = class {
|
|
|
12648
12875
|
matchedLimitOrders: {},
|
|
12649
12876
|
bidPlans: {},
|
|
12650
12877
|
partialFills: {},
|
|
12878
|
+
feeChecksWaived: {},
|
|
12651
12879
|
feeTokens: {},
|
|
12652
12880
|
tokenDecimals: {},
|
|
12653
12881
|
solverSelection: {}
|
|
@@ -12687,6 +12915,10 @@ var CacheService = class {
|
|
|
12687
12915
|
stalePartialIds.forEach((orderId) => {
|
|
12688
12916
|
delete this.cacheData.partialFills[orderId];
|
|
12689
12917
|
});
|
|
12918
|
+
const staleWaivedIds = Object.entries(this.cacheData.feeChecksWaived).filter(([_, data]) => !this.isCacheValid(data.timestamp)).map(([orderId]) => orderId);
|
|
12919
|
+
staleWaivedIds.forEach((orderId) => {
|
|
12920
|
+
delete this.cacheData.feeChecksWaived[orderId];
|
|
12921
|
+
});
|
|
12690
12922
|
}
|
|
12691
12923
|
getGasEstimate(orderId) {
|
|
12692
12924
|
try {
|
|
@@ -12792,7 +13024,24 @@ var CacheService = class {
|
|
|
12792
13024
|
return null;
|
|
12793
13025
|
}
|
|
12794
13026
|
}
|
|
12795
|
-
|
|
13027
|
+
/**
|
|
13028
|
+
* The budget of the limit order that priced the outputs cached for this order, or
|
|
13029
|
+
* null when no limit order did.
|
|
13030
|
+
*
|
|
13031
|
+
* Kept on the same entry as the outputs, so the two are replaced and expire
|
|
13032
|
+
* together: a bid can never sign one limit order's payout beside another's budget.
|
|
13033
|
+
*/
|
|
13034
|
+
getBidBudget(orderId) {
|
|
13035
|
+
try {
|
|
13036
|
+
const cache = this.cacheData.fillerOutputs[orderId];
|
|
13037
|
+
if (cache?.budget && this.isCacheValid(cache.timestamp)) return budgetFromCache(cache.budget);
|
|
13038
|
+
return null;
|
|
13039
|
+
} catch (error) {
|
|
13040
|
+
this.logger.error({ err: error }, "Error getting bid budget");
|
|
13041
|
+
return null;
|
|
13042
|
+
}
|
|
13043
|
+
}
|
|
13044
|
+
setFillerOutputs(orderId, outputs, inputs = [], budget) {
|
|
12796
13045
|
try {
|
|
12797
13046
|
this.cleanupStaleData();
|
|
12798
13047
|
this.cacheData.fillerOutputs[orderId] = {
|
|
@@ -12804,6 +13053,7 @@ var CacheService = class {
|
|
|
12804
13053
|
token: o.token,
|
|
12805
13054
|
amount: o.amount.toString()
|
|
12806
13055
|
})),
|
|
13056
|
+
...budget ? { budget: budgetToCache(budget) } : {},
|
|
12807
13057
|
timestamp: Date.now()
|
|
12808
13058
|
};
|
|
12809
13059
|
} catch (error) {
|
|
@@ -12849,7 +13099,8 @@ var CacheService = class {
|
|
|
12849
13099
|
data: call.data
|
|
12850
13100
|
})),
|
|
12851
13101
|
partialFill: plan.partialFill,
|
|
12852
|
-
profit: plan.profit
|
|
13102
|
+
profit: plan.profit,
|
|
13103
|
+
...plan.budget ? { budget: budgetFromCache(plan.budget) } : {}
|
|
12853
13104
|
}));
|
|
12854
13105
|
} catch (error) {
|
|
12855
13106
|
this.logger.error({ err: error }, "Error getting bid plans");
|
|
@@ -12878,7 +13129,8 @@ var CacheService = class {
|
|
|
12878
13129
|
data: call.data
|
|
12879
13130
|
})),
|
|
12880
13131
|
partialFill: plan.partialFill,
|
|
12881
|
-
profit: plan.profit
|
|
13132
|
+
profit: plan.profit,
|
|
13133
|
+
...plan.budget ? { budget: budgetToCache(plan.budget) } : {}
|
|
12882
13134
|
})),
|
|
12883
13135
|
timestamp: Date.now()
|
|
12884
13136
|
};
|
|
@@ -12979,6 +13231,24 @@ var CacheService = class {
|
|
|
12979
13231
|
clearPartialFill(orderId) {
|
|
12980
13232
|
delete this.cacheData.partialFills[orderId];
|
|
12981
13233
|
}
|
|
13234
|
+
/**
|
|
13235
|
+
* Whether the strategy's completed evaluation let a bid through without holding
|
|
13236
|
+
* `order.fees` to the execution cost, because the order is at or above the
|
|
13237
|
+
* operator's minimum size.
|
|
13238
|
+
*
|
|
13239
|
+
* Written and cleared the way {@link isPartialFill} is, for the same reason: the
|
|
13240
|
+
* caller exempts such an order from its profit floor.
|
|
13241
|
+
*/
|
|
13242
|
+
isFeeCheckWaived(orderId) {
|
|
13243
|
+
const cache = this.cacheData.feeChecksWaived[orderId];
|
|
13244
|
+
return cache !== void 0 && this.isCacheValid(cache.timestamp) && cache.waived;
|
|
13245
|
+
}
|
|
13246
|
+
setFeeCheckWaived(orderId, waived) {
|
|
13247
|
+
this.cacheData.feeChecksWaived[orderId] = { waived, timestamp: Date.now() };
|
|
13248
|
+
}
|
|
13249
|
+
clearFeeCheckWaived(orderId) {
|
|
13250
|
+
delete this.cacheData.feeChecksWaived[orderId];
|
|
13251
|
+
}
|
|
12982
13252
|
getFeeTokenWithDecimals(chain) {
|
|
12983
13253
|
try {
|
|
12984
13254
|
const cache = this.cacheData.feeTokens[chain];
|
|
@@ -13036,12 +13306,12 @@ var CacheService = class {
|
|
|
13036
13306
|
};
|
|
13037
13307
|
|
|
13038
13308
|
// src/services/ChainClientManager.ts
|
|
13039
|
-
var
|
|
13040
|
-
var
|
|
13309
|
+
var import_viem17 = require("viem");
|
|
13310
|
+
var import_accounts4 = require("viem/accounts");
|
|
13041
13311
|
var import_sdk6 = require("@hyperbridge/sdk");
|
|
13042
13312
|
|
|
13043
13313
|
// src/services/QuorumPublicClient.ts
|
|
13044
|
-
var
|
|
13314
|
+
var import_viem16 = require("viem");
|
|
13045
13315
|
var import_sdk5 = require("@hyperbridge/sdk");
|
|
13046
13316
|
function quorumThreshold(numProviders) {
|
|
13047
13317
|
return Math.floor(2 * numProviders / 3) + 1;
|
|
@@ -13141,9 +13411,9 @@ var _QuorumPublicClient = class _QuorumPublicClient {
|
|
|
13141
13411
|
this.deadlineMs = budget.deadlineMs;
|
|
13142
13412
|
const chain = (0, import_sdk5.getViemChain)(chainId);
|
|
13143
13413
|
this.clients = this.rpcUrls.map(
|
|
13144
|
-
(url) => (0,
|
|
13414
|
+
(url) => (0, import_viem16.createPublicClient)({
|
|
13145
13415
|
chain,
|
|
13146
|
-
transport: (0,
|
|
13416
|
+
transport: (0, import_viem16.http)(url, {
|
|
13147
13417
|
timeout: budget.timeoutMs,
|
|
13148
13418
|
// No transport-level retry. viem's retry sleeps for the provider's
|
|
13149
13419
|
// `Retry-After` verbatim and uncapped, which reaches the socket
|
|
@@ -13559,9 +13829,9 @@ var HTTP_TRANSPORT_OPTS = {
|
|
|
13559
13829
|
};
|
|
13560
13830
|
function buildTransport(rpcUrls) {
|
|
13561
13831
|
if (rpcUrls.length === 1) {
|
|
13562
|
-
return (0,
|
|
13832
|
+
return (0, import_viem17.http)(rpcUrls[0], HTTP_TRANSPORT_OPTS);
|
|
13563
13833
|
}
|
|
13564
|
-
return (0,
|
|
13834
|
+
return (0, import_viem17.fallback)(rpcUrls.map((url) => (0, import_viem17.http)(url, HTTP_TRANSPORT_OPTS)));
|
|
13565
13835
|
}
|
|
13566
13836
|
function walletClientCacheKey(chainId, accountAddress) {
|
|
13567
13837
|
return `${chainId}:${accountAddress.toLowerCase()}`;
|
|
@@ -13587,7 +13857,7 @@ var ViemClientFactoryImpl = class {
|
|
|
13587
13857
|
if (!this.publicClients.has(chainConfig.chainId)) {
|
|
13588
13858
|
const chain = (0, import_sdk6.getViemChain)(chainConfig.chainId);
|
|
13589
13859
|
const urls = rpcUrls && rpcUrls.length > 0 ? rpcUrls : [chainConfig.rpcUrl];
|
|
13590
|
-
const publicClient = (0,
|
|
13860
|
+
const publicClient = (0, import_viem17.createPublicClient)({
|
|
13591
13861
|
chain,
|
|
13592
13862
|
transport: buildTransport(urls)
|
|
13593
13863
|
});
|
|
@@ -13600,7 +13870,7 @@ var ViemClientFactoryImpl = class {
|
|
|
13600
13870
|
if (!this.walletClients.has(key)) {
|
|
13601
13871
|
const chain = (0, import_sdk6.getViemChain)(chainConfig.chainId);
|
|
13602
13872
|
const urls = rpcUrls && rpcUrls.length > 0 ? rpcUrls : [chainConfig.rpcUrl];
|
|
13603
|
-
const walletClient = (0,
|
|
13873
|
+
const walletClient = (0, import_viem17.createWalletClient)({
|
|
13604
13874
|
chain,
|
|
13605
13875
|
account,
|
|
13606
13876
|
transport: buildTransport(urls)
|
|
@@ -13615,7 +13885,7 @@ var ChainClientManager = class {
|
|
|
13615
13885
|
this.clientFactory = new ViemClientFactoryImpl();
|
|
13616
13886
|
this.quorumClients = /* @__PURE__ */ new Map();
|
|
13617
13887
|
this.configService = configService;
|
|
13618
|
-
this.signer = signer ?? privateKeySigner((0,
|
|
13888
|
+
this.signer = signer ?? privateKeySigner((0, import_accounts4.generatePrivateKey)());
|
|
13619
13889
|
this.account = accountFor(this.signer);
|
|
13620
13890
|
}
|
|
13621
13891
|
/** This filler's logging destination, relayed from the config service. */
|
|
@@ -13684,9 +13954,47 @@ var ChainClientManager = class {
|
|
|
13684
13954
|
};
|
|
13685
13955
|
|
|
13686
13956
|
// src/services/ContractInteractionService.ts
|
|
13687
|
-
var
|
|
13957
|
+
var import_viem23 = require("viem");
|
|
13688
13958
|
var import_sdk7 = require("@hyperbridge/sdk");
|
|
13689
13959
|
|
|
13960
|
+
// src/config/abis/SolverAccount.ts
|
|
13961
|
+
var SOLVER_ACCOUNT_ABI = [
|
|
13962
|
+
{
|
|
13963
|
+
inputs: [
|
|
13964
|
+
{ name: "orderId", type: "bytes32" },
|
|
13965
|
+
{ name: "cap", type: "uint256" },
|
|
13966
|
+
{ name: "token", type: "address" },
|
|
13967
|
+
{ name: "approved", type: "uint256" },
|
|
13968
|
+
{ name: "fee", type: "uint256" }
|
|
13969
|
+
],
|
|
13970
|
+
name: "debitOrder",
|
|
13971
|
+
outputs: [],
|
|
13972
|
+
stateMutability: "nonpayable",
|
|
13973
|
+
type: "function"
|
|
13974
|
+
},
|
|
13975
|
+
{
|
|
13976
|
+
inputs: [{ name: "orderId", type: "bytes32" }],
|
|
13977
|
+
name: "spent",
|
|
13978
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
13979
|
+
stateMutability: "view",
|
|
13980
|
+
type: "function"
|
|
13981
|
+
},
|
|
13982
|
+
{
|
|
13983
|
+
inputs: [{ name: "sender", type: "address" }],
|
|
13984
|
+
name: "AccountUnauthorized",
|
|
13985
|
+
type: "error"
|
|
13986
|
+
},
|
|
13987
|
+
{
|
|
13988
|
+
inputs: [
|
|
13989
|
+
{ name: "orderId", type: "bytes32" },
|
|
13990
|
+
{ name: "total", type: "uint256" },
|
|
13991
|
+
{ name: "cap", type: "uint256" }
|
|
13992
|
+
],
|
|
13993
|
+
name: "LimitOrderExceeded",
|
|
13994
|
+
type: "error"
|
|
13995
|
+
}
|
|
13996
|
+
];
|
|
13997
|
+
|
|
13690
13998
|
// src/config/abis/EvmHost.ts
|
|
13691
13999
|
var EVM_HOST = [
|
|
13692
14000
|
{
|
|
@@ -17539,10 +17847,10 @@ var ENTRYPOINT_ABI = [
|
|
|
17539
17847
|
];
|
|
17540
17848
|
|
|
17541
17849
|
// src/services/paymaster/provider/simplex.ts
|
|
17542
|
-
var
|
|
17850
|
+
var import_viem22 = require("viem");
|
|
17543
17851
|
|
|
17544
17852
|
// src/services/paymaster/types.ts
|
|
17545
|
-
var
|
|
17853
|
+
var import_viem18 = require("viem");
|
|
17546
17854
|
var RECOMMENDED_AMOUNT_USD = 5n;
|
|
17547
17855
|
var VERIFICATION_GAS_LIMIT_PERMIT2 = 200000n;
|
|
17548
17856
|
var VERIFICATION_GAS_LIMIT_PERMIT = 250000n;
|
|
@@ -17550,19 +17858,19 @@ var PERMIT2_DEADLINE_SECONDS = 3600n;
|
|
|
17550
17858
|
var POST_OP_GAS_LIMIT_SIMPLEX = 40000n;
|
|
17551
17859
|
var DEPOSIT_HEADROOM_PERCENT = 150n;
|
|
17552
17860
|
function packPaymasterAndData(pm) {
|
|
17553
|
-
return (0,
|
|
17861
|
+
return (0, import_viem18.encodePacked)(
|
|
17554
17862
|
["address", "uint128", "uint128", "bytes"],
|
|
17555
17863
|
[pm.paymaster, pm.paymasterVerificationGasLimit, pm.paymasterPostOpGasLimit, pm.paymasterData]
|
|
17556
17864
|
);
|
|
17557
17865
|
}
|
|
17558
17866
|
|
|
17559
17867
|
// src/services/paymaster/permit.ts
|
|
17560
|
-
var
|
|
17868
|
+
var import_viem21 = require("viem");
|
|
17561
17869
|
|
|
17562
17870
|
// src/config/abis/EIP2612.ts
|
|
17563
|
-
var
|
|
17871
|
+
var import_viem19 = require("viem");
|
|
17564
17872
|
var EIP2612_ABI = [
|
|
17565
|
-
...
|
|
17873
|
+
...import_viem19.erc20Abi,
|
|
17566
17874
|
{
|
|
17567
17875
|
inputs: [{ internalType: "address", name: "owner", type: "address" }],
|
|
17568
17876
|
stateMutability: "view",
|
|
@@ -17580,7 +17888,7 @@ var EIP2612_ABI = [
|
|
|
17580
17888
|
];
|
|
17581
17889
|
|
|
17582
17890
|
// src/services/paymaster/permit2.ts
|
|
17583
|
-
var
|
|
17891
|
+
var import_viem20 = require("viem");
|
|
17584
17892
|
function permit2TransferTypedData(p) {
|
|
17585
17893
|
return {
|
|
17586
17894
|
types: {
|
|
@@ -17615,16 +17923,16 @@ function permit2TransferTypedData(p) {
|
|
|
17615
17923
|
};
|
|
17616
17924
|
}
|
|
17617
17925
|
function randomPermit2Nonce() {
|
|
17618
|
-
return (0,
|
|
17926
|
+
return (0, import_viem20.bytesToBigInt)(crypto.getRandomValues(new Uint8Array(32)));
|
|
17619
17927
|
}
|
|
17620
17928
|
async function signPermit2Transfer(signer, p) {
|
|
17621
17929
|
const signature = await signer.signTypedData(permit2TransferTypedData(p));
|
|
17622
17930
|
return normalizeSignature65(signature);
|
|
17623
17931
|
}
|
|
17624
17932
|
function normalizeSignature65(signature) {
|
|
17625
|
-
const length = (0,
|
|
17933
|
+
const length = (0, import_viem20.size)(signature);
|
|
17626
17934
|
if (length === 64) {
|
|
17627
|
-
return (0,
|
|
17935
|
+
return (0, import_viem20.serializeSignature)((0, import_viem20.compactSignatureToSignature)((0, import_viem20.parseCompactSignature)(signature)));
|
|
17628
17936
|
}
|
|
17629
17937
|
if (length !== 65) {
|
|
17630
17938
|
throw new Error(`Permit2 signature must be 64 or 65 bytes, got ${length}`);
|
|
@@ -17638,7 +17946,7 @@ function normalizeSignature65(signature) {
|
|
|
17638
17946
|
|
|
17639
17947
|
// src/services/paymaster/permit.ts
|
|
17640
17948
|
async function signEip2612Permit(client, signer, owner, spender, tokenAddress, value, chainId) {
|
|
17641
|
-
const token = (0,
|
|
17949
|
+
const token = (0, import_viem21.getContract)({
|
|
17642
17950
|
client,
|
|
17643
17951
|
address: tokenAddress,
|
|
17644
17952
|
abi: EIP2612_ABI
|
|
@@ -17676,7 +17984,7 @@ async function signEip2612Permit(client, signer, owner, spender, tokenAddress, v
|
|
|
17676
17984
|
spender,
|
|
17677
17985
|
value,
|
|
17678
17986
|
nonce,
|
|
17679
|
-
deadline:
|
|
17987
|
+
deadline: import_viem21.maxUint256
|
|
17680
17988
|
}
|
|
17681
17989
|
};
|
|
17682
17990
|
return normalizeSignature65(await signer.signTypedData(typedData));
|
|
@@ -17804,7 +18112,7 @@ async function selectToken(client, solverAccount, tokens, permit2) {
|
|
|
17804
18112
|
for (const token of tokens) {
|
|
17805
18113
|
const balance = await client.readContract({
|
|
17806
18114
|
address: token.address,
|
|
17807
|
-
abi:
|
|
18115
|
+
abi: import_viem22.erc20Abi,
|
|
17808
18116
|
functionName: "balanceOf",
|
|
17809
18117
|
args: [solverAccount]
|
|
17810
18118
|
});
|
|
@@ -17834,7 +18142,7 @@ async function paymasterSupportsPermit2(client, chainId, paymasterAddress) {
|
|
|
17834
18142
|
permit2Support.set(key, { supported: true, checkedAt: Date.now() });
|
|
17835
18143
|
return true;
|
|
17836
18144
|
} catch (error) {
|
|
17837
|
-
const isRevert = error instanceof
|
|
18145
|
+
const isRevert = error instanceof import_viem22.BaseError && error.walk((e) => e instanceof import_viem22.ContractFunctionRevertedError || e instanceof import_viem22.ContractFunctionZeroDataError);
|
|
17838
18146
|
if (isRevert) {
|
|
17839
18147
|
permit2Support.set(key, { supported: false, checkedAt: Date.now() });
|
|
17840
18148
|
return false;
|
|
@@ -17845,7 +18153,7 @@ async function paymasterSupportsPermit2(client, chainId, paymasterAddress) {
|
|
|
17845
18153
|
async function readAllowance(client, token, owner, spender) {
|
|
17846
18154
|
return await client.readContract({
|
|
17847
18155
|
address: token,
|
|
17848
|
-
abi:
|
|
18156
|
+
abi: import_viem22.erc20Abi,
|
|
17849
18157
|
functionName: "allowance",
|
|
17850
18158
|
args: [owner, spender]
|
|
17851
18159
|
});
|
|
@@ -17865,7 +18173,7 @@ async function buildPermit2Mode(signer, p) {
|
|
|
17865
18173
|
const r = `0x${signature.slice(2, 66)}`;
|
|
17866
18174
|
const s = `0x${signature.slice(66, 130)}`;
|
|
17867
18175
|
const v = Number.parseInt(signature.slice(130, 132), 16);
|
|
17868
|
-
const paymasterData = (0,
|
|
18176
|
+
const paymasterData = (0, import_viem22.encodePacked)(
|
|
17869
18177
|
["uint8", "address", "uint256", "uint256", "uint256", "uint8", "bytes32", "bytes32"],
|
|
17870
18178
|
[2, p.token, p.amount, nonce, deadline, v, r, s]
|
|
17871
18179
|
);
|
|
@@ -17889,9 +18197,9 @@ async function buildPermitMode(client, signer, solverAccount, paymasterAddress,
|
|
|
17889
18197
|
const r = `0x${permitSignature.slice(2, 66)}`;
|
|
17890
18198
|
const s = `0x${permitSignature.slice(66, 130)}`;
|
|
17891
18199
|
const v = Number.parseInt(permitSignature.slice(130, 132), 16);
|
|
17892
|
-
const paymasterData = (0,
|
|
18200
|
+
const paymasterData = (0, import_viem22.encodePacked)(
|
|
17893
18201
|
["uint8", "address", "uint256", "uint256", "uint8", "bytes32", "bytes32"],
|
|
17894
|
-
[0, tokenAddress, permitAmount,
|
|
18202
|
+
[0, tokenAddress, permitAmount, import_viem22.maxUint256, v, r, s]
|
|
17895
18203
|
);
|
|
17896
18204
|
return {
|
|
17897
18205
|
paymaster: paymasterAddress,
|
|
@@ -17905,7 +18213,7 @@ async function ensureFundedApprove(client, walletClient, chainId, solverAccount,
|
|
|
17905
18213
|
const key = `${chainId}:${tokenAddress.toLowerCase()}:${solverAccount.toLowerCase()}`;
|
|
17906
18214
|
const inFlight = approvalsInFlight.get(key);
|
|
17907
18215
|
if (inFlight) return inFlight;
|
|
17908
|
-
const pending = sendFundedApprove(client, walletClient, solverAccount, tokenAddress, spender,
|
|
18216
|
+
const pending = sendFundedApprove(client, walletClient, solverAccount, tokenAddress, spender, import_viem22.maxUint256).finally(
|
|
17909
18217
|
() => {
|
|
17910
18218
|
approvalsInFlight.delete(key);
|
|
17911
18219
|
}
|
|
@@ -17923,7 +18231,7 @@ async function sendFundedApprove(client, walletClient, solverAccount, tokenAddre
|
|
|
17923
18231
|
const requiredNative = APPROVE_TX_GAS * gasPrice * (needsReset ? 2n : 1n);
|
|
17924
18232
|
if (nativeBalance < requiredNative) {
|
|
17925
18233
|
throw new Error(
|
|
17926
|
-
`SimplexPaymaster needs a one-time Permit2 approval for the fee token on this chain; send native dust (>= ${(0,
|
|
18234
|
+
`SimplexPaymaster needs a one-time Permit2 approval for the fee token on this chain; send native dust (>= ${(0, import_viem22.formatEther)(requiredNative)}) to ${solverAccount}`
|
|
17927
18235
|
);
|
|
17928
18236
|
}
|
|
17929
18237
|
if (needsReset) {
|
|
@@ -17934,7 +18242,7 @@ async function sendFundedApprove(client, walletClient, solverAccount, tokenAddre
|
|
|
17934
18242
|
async function sendApproveTx(client, walletClient, tokenAddress, spender, amount) {
|
|
17935
18243
|
const hash = await walletClient.writeContract({
|
|
17936
18244
|
address: tokenAddress,
|
|
17937
|
-
abi:
|
|
18245
|
+
abi: import_viem22.erc20Abi,
|
|
17938
18246
|
functionName: "approve",
|
|
17939
18247
|
args: [spender, amount],
|
|
17940
18248
|
chain: walletClient.chain,
|
|
@@ -17959,7 +18267,7 @@ async function tokenSupportsPermit(client, tokenAddress) {
|
|
|
17959
18267
|
});
|
|
17960
18268
|
return true;
|
|
17961
18269
|
} catch (error) {
|
|
17962
|
-
const isRevert = error instanceof
|
|
18270
|
+
const isRevert = error instanceof import_viem22.BaseError && error.walk((e) => e instanceof import_viem22.ContractFunctionRevertedError || e instanceof import_viem22.ContractFunctionZeroDataError);
|
|
17963
18271
|
if (isRevert) return false;
|
|
17964
18272
|
throw error;
|
|
17965
18273
|
}
|
|
@@ -18066,6 +18374,10 @@ function describeShortfall(balances) {
|
|
|
18066
18374
|
// src/services/ContractInteractionService.ts
|
|
18067
18375
|
import_decimal2.Decimal.config({ precision: 28, rounding: 4 });
|
|
18068
18376
|
var FUNDING_GAS_PER_CALL = 400000n;
|
|
18377
|
+
var DEBIT_ORDER_GAS = 60000n;
|
|
18378
|
+
function settles(budget) {
|
|
18379
|
+
return !!budget && budget.token.toLowerCase() !== import_sdk7.ADDRESS_ZERO;
|
|
18380
|
+
}
|
|
18069
18381
|
var LIMIT_ORDER_CALL_GAS_LIMIT = 500000n;
|
|
18070
18382
|
var LIMIT_ORDER_VERIFICATION_GAS_LIMIT = 150000n;
|
|
18071
18383
|
var LIMIT_ORDER_PRE_VERIFICATION_GAS = 50000n;
|
|
@@ -18226,7 +18538,10 @@ var ContractInteractionService = class {
|
|
|
18226
18538
|
order,
|
|
18227
18539
|
prependCalls: void 0,
|
|
18228
18540
|
maxPriorityFeePerGasBumpPercent: gasFeeBumpConfig?.maxPriorityFeePerGasBumpPercent,
|
|
18229
|
-
maxFeePerGasBumpPercent: gasFeeBumpConfig?.maxFeePerGasBumpPercent
|
|
18541
|
+
maxFeePerGasBumpPercent: gasFeeBumpConfig?.maxFeePerGasBumpPercent,
|
|
18542
|
+
// No bid without the bundler's own figures: an op signed with the SDK's
|
|
18543
|
+
// fixed fallback limits does not execute, so a failed estimate skips the order.
|
|
18544
|
+
requireBundlerEstimate: true
|
|
18230
18545
|
});
|
|
18231
18546
|
this.logger.info({ orderId: order.id }, "Caching gas estimate");
|
|
18232
18547
|
this.logger.info({ estimate }, "Estimate");
|
|
@@ -18249,23 +18564,25 @@ var ContractInteractionService = class {
|
|
|
18249
18564
|
callGasLimit: estimate.callGasLimit
|
|
18250
18565
|
};
|
|
18251
18566
|
} catch (error) {
|
|
18252
|
-
this.logger.error({ err: error }, "Error estimating gas,
|
|
18567
|
+
this.logger.error({ err: error, orderId: order.id }, "Error estimating gas, not bidding on this order");
|
|
18253
18568
|
throw new Error(`Failed to estimate gas: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
18254
18569
|
}
|
|
18255
18570
|
}
|
|
18256
18571
|
/**
|
|
18257
18572
|
* The call gas a bid needs: the shared estimate plus room for the funding calls
|
|
18258
|
-
* this bid prepends.
|
|
18573
|
+
* this bid prepends and for settling its limit order's budget.
|
|
18259
18574
|
*
|
|
18260
18575
|
* Funding calls are not simulated (the V4 PositionManager's flash accounting does
|
|
18261
18576
|
* not resolve in the bundler's estimation context), so each one adds a fixed
|
|
18262
18577
|
* allowance for its modifyLiquidities + take. They differ between the bids on one
|
|
18263
18578
|
* order, since each draws on its own limit order and so on its own funding, which
|
|
18264
18579
|
* is why this is worked out per bid rather than baked into the cached estimate.
|
|
18580
|
+
* The estimate is of a lone fillOrder, so the budget call is not in it either.
|
|
18265
18581
|
*/
|
|
18266
|
-
callGasLimitFor(order, baseCallGasLimit) {
|
|
18582
|
+
callGasLimitFor(order, baseCallGasLimit, budget) {
|
|
18267
18583
|
const funding = order.id ? this.cacheService.getFundingPrepends(order.id) : null;
|
|
18268
|
-
|
|
18584
|
+
const fundingGas = FUNDING_GAS_PER_CALL * BigInt(funding?.calls?.length ?? 0);
|
|
18585
|
+
return baseCallGasLimit + fundingGas + (settles(budget) ? DEBIT_ORDER_GAS : 0n);
|
|
18269
18586
|
}
|
|
18270
18587
|
/**
|
|
18271
18588
|
* Gets the fee token address and decimals for a given chain.
|
|
@@ -18337,7 +18654,7 @@ var ContractInteractionService = class {
|
|
|
18337
18654
|
this.logger.warn(
|
|
18338
18655
|
{
|
|
18339
18656
|
chain,
|
|
18340
|
-
walletBalance: (0,
|
|
18657
|
+
walletBalance: (0, import_viem23.formatEther)(solverBalance),
|
|
18341
18658
|
walletGasUnits: walletGasUnits.toString(),
|
|
18342
18659
|
gasPrice: gasPrice.toString()
|
|
18343
18660
|
},
|
|
@@ -18352,10 +18669,10 @@ var ContractInteractionService = class {
|
|
|
18352
18669
|
this.logger.info(
|
|
18353
18670
|
{
|
|
18354
18671
|
chain,
|
|
18355
|
-
currentDeposit: (0,
|
|
18672
|
+
currentDeposit: (0, import_viem23.formatEther)(currentDeposit),
|
|
18356
18673
|
depositGasUnits: depositGasUnits.toString(),
|
|
18357
18674
|
targetGasUnits: targetGasUnits.toString(),
|
|
18358
|
-
walletBalance: (0,
|
|
18675
|
+
walletBalance: (0, import_viem23.formatEther)(solverBalance)
|
|
18359
18676
|
},
|
|
18360
18677
|
"EntryPoint deposit covers target gas units, no top-up needed"
|
|
18361
18678
|
);
|
|
@@ -18366,8 +18683,8 @@ var ContractInteractionService = class {
|
|
|
18366
18683
|
this.logger.warn(
|
|
18367
18684
|
{
|
|
18368
18685
|
chain,
|
|
18369
|
-
deficit: (0,
|
|
18370
|
-
solverBalance: (0,
|
|
18686
|
+
deficit: (0, import_viem23.formatEther)(deficit),
|
|
18687
|
+
solverBalance: (0, import_viem23.formatEther)(solverBalance),
|
|
18371
18688
|
depositGasUnits: depositGasUnits.toString(),
|
|
18372
18689
|
targetGasUnits: targetGasUnits.toString()
|
|
18373
18690
|
},
|
|
@@ -18379,10 +18696,10 @@ var ContractInteractionService = class {
|
|
|
18379
18696
|
this.logger.info(
|
|
18380
18697
|
{
|
|
18381
18698
|
chain,
|
|
18382
|
-
currentDeposit: (0,
|
|
18699
|
+
currentDeposit: (0, import_viem23.formatEther)(currentDeposit),
|
|
18383
18700
|
depositGasUnits: depositGasUnits.toString(),
|
|
18384
18701
|
targetGasUnits: targetGasUnits.toString(),
|
|
18385
|
-
topUpAmount: (0,
|
|
18702
|
+
topUpAmount: (0, import_viem23.formatEther)(deficit)
|
|
18386
18703
|
},
|
|
18387
18704
|
"Topping up EntryPoint deposit to cover target gas units"
|
|
18388
18705
|
);
|
|
@@ -18405,7 +18722,7 @@ var ContractInteractionService = class {
|
|
|
18405
18722
|
const addr = tokenAddress.toLowerCase();
|
|
18406
18723
|
if (addr !== sourceUsdc && addr !== sourceUsdt) continue;
|
|
18407
18724
|
const decimals = await this.getTokenDecimals(tokenAddress, order.source);
|
|
18408
|
-
const tokenAmount = new import_decimal2.Decimal((0,
|
|
18725
|
+
const tokenAmount = new import_decimal2.Decimal((0, import_viem23.formatUnits)(input.amount, decimals));
|
|
18409
18726
|
inputUsdValue = inputUsdValue.plus(tokenAmount);
|
|
18410
18727
|
}
|
|
18411
18728
|
return inputUsdValue;
|
|
@@ -18447,6 +18764,27 @@ var ContractInteractionService = class {
|
|
|
18447
18764
|
{ maxRetries: 3, backoffMs: 250, logMessage: "Failed to read token balance" }
|
|
18448
18765
|
);
|
|
18449
18766
|
}
|
|
18767
|
+
/**
|
|
18768
|
+
* What the solver's account has tallied against a limit order's budget on `chain`, in
|
|
18769
|
+
* the paid token's own units, or null when it cannot be read.
|
|
18770
|
+
*
|
|
18771
|
+
* Null rather than a throw: an account whose implementation keeps no tally answers
|
|
18772
|
+
* with a revert or with no data, and the caller carries on without the figure either
|
|
18773
|
+
* way. Not retried, since that answer does not change on a second ask.
|
|
18774
|
+
*/
|
|
18775
|
+
async limitOrderSpent(chain, budgetId) {
|
|
18776
|
+
try {
|
|
18777
|
+
return await this.clientManager.getPublicClient(chain).readContract({
|
|
18778
|
+
address: this.solverAccountAddress,
|
|
18779
|
+
abi: SOLVER_ACCOUNT_ABI,
|
|
18780
|
+
functionName: "spent",
|
|
18781
|
+
args: [budgetId]
|
|
18782
|
+
});
|
|
18783
|
+
} catch (err) {
|
|
18784
|
+
this.logger.warn({ err, chain, budgetId }, "Could not read the limit order's tally from the solver account");
|
|
18785
|
+
return null;
|
|
18786
|
+
}
|
|
18787
|
+
}
|
|
18450
18788
|
async getSolverEntryPointBalance(chain) {
|
|
18451
18789
|
const entryPointAddress = this.configService.getEntryPointAddress(chain);
|
|
18452
18790
|
if (!entryPointAddress) {
|
|
@@ -18475,7 +18813,7 @@ var ContractInteractionService = class {
|
|
|
18475
18813
|
const walletClient = this.clientManager.getWalletClient(chain);
|
|
18476
18814
|
const publicClient = this.clientManager.getPublicClient(chain);
|
|
18477
18815
|
this.logger.info(
|
|
18478
|
-
{ chain, solver: this.solverAccountAddress, amount: (0,
|
|
18816
|
+
{ chain, solver: this.solverAccountAddress, amount: (0, import_viem23.formatEther)(amount) },
|
|
18479
18817
|
"Depositing to EntryPoint"
|
|
18480
18818
|
);
|
|
18481
18819
|
const hash = await walletClient.writeContract({
|
|
@@ -18490,7 +18828,7 @@ var ContractInteractionService = class {
|
|
|
18490
18828
|
if (receipt.status !== "success") {
|
|
18491
18829
|
throw new Error(`EntryPoint deposit transaction reverted: ${hash}`);
|
|
18492
18830
|
}
|
|
18493
|
-
this.logger.info({ chain, txHash: hash, amount: (0,
|
|
18831
|
+
this.logger.info({ chain, txHash: hash, amount: (0, import_viem23.formatEther)(amount) }, "EntryPoint deposit confirmed");
|
|
18494
18832
|
return hash;
|
|
18495
18833
|
}
|
|
18496
18834
|
/**
|
|
@@ -18510,7 +18848,7 @@ var ContractInteractionService = class {
|
|
|
18510
18848
|
const walletClient = this.clientManager.getWalletClient(chain);
|
|
18511
18849
|
const publicClient = this.clientManager.getPublicClient(chain);
|
|
18512
18850
|
this.logger.info(
|
|
18513
|
-
{ chain, solver: this.solverAccountAddress, amount: (0,
|
|
18851
|
+
{ chain, solver: this.solverAccountAddress, amount: (0, import_viem23.formatEther)(balance) },
|
|
18514
18852
|
"Withdrawing from EntryPoint"
|
|
18515
18853
|
);
|
|
18516
18854
|
const hash = await walletClient.writeContract({
|
|
@@ -18524,7 +18862,7 @@ var ContractInteractionService = class {
|
|
|
18524
18862
|
if (receipt.status !== "success") {
|
|
18525
18863
|
throw new Error(`EntryPoint withdrawal transaction reverted: ${hash}`);
|
|
18526
18864
|
}
|
|
18527
|
-
this.logger.info({ chain, txHash: hash, amount: (0,
|
|
18865
|
+
this.logger.info({ chain, txHash: hash, amount: (0, import_viem23.formatEther)(balance) }, "EntryPoint withdrawal confirmed");
|
|
18528
18866
|
return hash;
|
|
18529
18867
|
}
|
|
18530
18868
|
/**
|
|
@@ -18587,7 +18925,8 @@ var ContractInteractionService = class {
|
|
|
18587
18925
|
if (!cachedEstimate) {
|
|
18588
18926
|
throw new Error(`No cached gas estimate found for order ${order.id}. Call estimateGasFillPost first.`);
|
|
18589
18927
|
}
|
|
18590
|
-
const
|
|
18928
|
+
const budget = this.cacheService.getBidBudget(order.id);
|
|
18929
|
+
const callGasLimit = this.callGasLimitFor(order, cachedEstimate.callGasLimit, budget);
|
|
18591
18930
|
const cachedFillerOutputs = this.cacheService.getFillerOutputs(order.id);
|
|
18592
18931
|
const cachedFillerInputs = this.cacheService.getFillerInputs(order.id);
|
|
18593
18932
|
if (!cachedFillerOutputs || !cachedFillerInputs) {
|
|
@@ -18614,7 +18953,8 @@ var ContractInteractionService = class {
|
|
|
18614
18953
|
order,
|
|
18615
18954
|
cachedFillerOutputs,
|
|
18616
18955
|
fillOptions,
|
|
18617
|
-
cachedEstimate.totalCostInSourceFeeToken + dispatchFeeTokenAmount
|
|
18956
|
+
cachedEstimate.totalCostInSourceFeeToken + dispatchFeeTokenAmount,
|
|
18957
|
+
budget ? { budget, solverAccount: solverAccountAddress } : void 0
|
|
18618
18958
|
);
|
|
18619
18959
|
const commitment = (0, import_sdk7.orderCommitment)(order);
|
|
18620
18960
|
const nonce = await this.clientManager.getPublicClient(order.destination).readContract({
|
|
@@ -18760,7 +19100,7 @@ var ContractInteractionService = class {
|
|
|
18760
19100
|
{
|
|
18761
19101
|
target: outputToken,
|
|
18762
19102
|
value: 0n,
|
|
18763
|
-
data: (0,
|
|
19103
|
+
data: (0, import_viem23.encodeFunctionData)({
|
|
18764
19104
|
abi: ERC20_ABI,
|
|
18765
19105
|
functionName: "approve",
|
|
18766
19106
|
args: [gateway, outputAmount]
|
|
@@ -18806,8 +19146,13 @@ var ContractInteractionService = class {
|
|
|
18806
19146
|
* Same-chain fills release escrow locally with no Hyperbridge dispatch, so the
|
|
18807
19147
|
* gateway never pulls the fee token — its approval is skipped. Only cross-chain
|
|
18808
19148
|
* fills, which dispatch a RedeemEscrow message paid in the fee token, need it.
|
|
19149
|
+
*
|
|
19150
|
+
* A bid priced by a limit order ends with `debitOrder`, which takes the budget's id
|
|
19151
|
+
* as its `orderId`, works out what the fill paid from what is left of the allowance
|
|
19152
|
+
* and reverts the whole batch if the order's payouts would pass its size. It has to
|
|
19153
|
+
* come last, after the gateway has drawn on the allowance.
|
|
18809
19154
|
*/
|
|
18810
|
-
async buildApprovalAndFillCalldata(order, fillerOutputs, fillOptions, requiredFeeTokenAmount) {
|
|
19155
|
+
async buildApprovalAndFillCalldata(order, fillerOutputs, fillOptions, requiredFeeTokenAmount, settlement) {
|
|
18811
19156
|
const chain = order.destination;
|
|
18812
19157
|
const intentGatewayV2Address = this.configService.getIntentGatewayAddress(chain);
|
|
18813
19158
|
const perTokenRequired = /* @__PURE__ */ new Map();
|
|
@@ -18817,9 +19162,10 @@ var ContractInteractionService = class {
|
|
|
18817
19162
|
const key = addr.toLowerCase();
|
|
18818
19163
|
perTokenRequired.set(key, (perTokenRequired.get(key) ?? 0n) + output.amount);
|
|
18819
19164
|
}
|
|
19165
|
+
let feeKey;
|
|
18820
19166
|
if (order.source !== order.destination) {
|
|
18821
19167
|
const feeToken = await this.getFeeTokenWithDecimals(chain);
|
|
18822
|
-
|
|
19168
|
+
feeKey = feeToken.address.toLowerCase();
|
|
18823
19169
|
perTokenRequired.set(feeKey, (perTokenRequired.get(feeKey) ?? 0n) + requiredFeeTokenAmount);
|
|
18824
19170
|
}
|
|
18825
19171
|
const fundingPrepends = order.id ? this.cacheService.getFundingPrepends(order.id) : null;
|
|
@@ -18831,7 +19177,7 @@ var ContractInteractionService = class {
|
|
|
18831
19177
|
calls.push({
|
|
18832
19178
|
target: tokenAddress,
|
|
18833
19179
|
value: 0n,
|
|
18834
|
-
data: (0,
|
|
19180
|
+
data: (0, import_viem23.encodeFunctionData)({
|
|
18835
19181
|
abi: ERC20_ABI,
|
|
18836
19182
|
functionName: "approve",
|
|
18837
19183
|
args: [intentGatewayV2Address, amount]
|
|
@@ -18845,17 +19191,37 @@ var ContractInteractionService = class {
|
|
|
18845
19191
|
value: nativeOutputValue,
|
|
18846
19192
|
data: (0, import_sdk7.encodeFillOrder)((0, import_sdk7.transformOrderForContract)(order), fillOptions)
|
|
18847
19193
|
});
|
|
19194
|
+
if (settlement && settles(settlement.budget)) {
|
|
19195
|
+
const { budget, solverAccount } = settlement;
|
|
19196
|
+
const budgetToken = budget.token.toLowerCase();
|
|
19197
|
+
const approved = perTokenRequired.get(budgetToken) ?? 0n;
|
|
19198
|
+
if (approved === 0n) {
|
|
19199
|
+
throw new Error(
|
|
19200
|
+
`Bid on order ${order.id} approves nothing in ${budget.token}, the token budget ${budget.budgetId} is measured in`
|
|
19201
|
+
);
|
|
19202
|
+
}
|
|
19203
|
+
const fee = feeKey === budgetToken ? fillOptions.relayerFee : 0n;
|
|
19204
|
+
calls.push({
|
|
19205
|
+
target: solverAccount,
|
|
19206
|
+
value: 0n,
|
|
19207
|
+
data: (0, import_viem23.encodeFunctionData)({
|
|
19208
|
+
abi: SOLVER_ACCOUNT_ABI,
|
|
19209
|
+
functionName: "debitOrder",
|
|
19210
|
+
args: [budget.budgetId, budget.cap, budgetToken, approved, fee]
|
|
19211
|
+
})
|
|
19212
|
+
});
|
|
19213
|
+
}
|
|
18848
19214
|
return (0, import_sdk7.encodeERC7821ExecuteBatch)(calls);
|
|
18849
19215
|
}
|
|
18850
19216
|
};
|
|
18851
19217
|
|
|
18852
19218
|
// src/services/DelegationService.ts
|
|
18853
|
-
var
|
|
19219
|
+
var import_viem25 = require("viem");
|
|
18854
19220
|
var import_sdk9 = require("@hyperbridge/sdk");
|
|
18855
19221
|
|
|
18856
19222
|
// src/services/UserOpSender.ts
|
|
18857
19223
|
var import_sdk8 = require("@hyperbridge/sdk");
|
|
18858
|
-
var
|
|
19224
|
+
var import_viem24 = require("viem");
|
|
18859
19225
|
var FALLBACK_VERIFICATION_GAS_LIMIT = 250000n;
|
|
18860
19226
|
var FALLBACK_CALL_GAS_LIMIT = 1500000n;
|
|
18861
19227
|
var FALLBACK_PRE_VERIFICATION_GAS = 150000n;
|
|
@@ -18928,11 +19294,11 @@ var UserOpSender = class {
|
|
|
18928
19294
|
});
|
|
18929
19295
|
const formattedAuth = auth ? {
|
|
18930
19296
|
address: auth.address,
|
|
18931
|
-
chainId: (0,
|
|
18932
|
-
nonce: (0,
|
|
19297
|
+
chainId: (0, import_viem24.toHex)(auth.chainId),
|
|
19298
|
+
nonce: (0, import_viem24.toHex)(auth.nonce),
|
|
18933
19299
|
r: auth.r,
|
|
18934
19300
|
s: auth.s,
|
|
18935
|
-
yParity: (0,
|
|
19301
|
+
yParity: (0, import_viem24.toHex)(auth.yParity)
|
|
18936
19302
|
} : void 0;
|
|
18937
19303
|
const { verificationGasLimit, callGasLimit, preVerificationGas } = gas ?? await this.estimateGas({
|
|
18938
19304
|
bundlerUrl,
|
|
@@ -19100,10 +19466,10 @@ function bootstrapCallData(approval) {
|
|
|
19100
19466
|
{
|
|
19101
19467
|
target: approval.token,
|
|
19102
19468
|
value: 0n,
|
|
19103
|
-
data: (0,
|
|
19104
|
-
abi:
|
|
19469
|
+
data: (0, import_viem25.encodeFunctionData)({
|
|
19470
|
+
abi: import_viem25.erc20Abi,
|
|
19105
19471
|
functionName: "approve",
|
|
19106
|
-
args: [approval.spender,
|
|
19472
|
+
args: [approval.spender, import_viem25.maxUint256]
|
|
19107
19473
|
})
|
|
19108
19474
|
}
|
|
19109
19475
|
]);
|
|
@@ -19154,10 +19520,10 @@ var DelegationService = class {
|
|
|
19154
19520
|
return await walletClient.sendTransaction({
|
|
19155
19521
|
to: approval.token,
|
|
19156
19522
|
value: 0n,
|
|
19157
|
-
data: (0,
|
|
19158
|
-
abi:
|
|
19523
|
+
data: (0, import_viem25.encodeFunctionData)({
|
|
19524
|
+
abi: import_viem25.erc20Abi,
|
|
19159
19525
|
functionName: "approve",
|
|
19160
|
-
args: [approval.spender,
|
|
19526
|
+
args: [approval.spender, import_viem25.maxUint256]
|
|
19161
19527
|
}),
|
|
19162
19528
|
authorizationList: [authorization],
|
|
19163
19529
|
chain: walletClient.chain,
|
|
@@ -19391,8 +19757,8 @@ var DelegationService = class {
|
|
|
19391
19757
|
{
|
|
19392
19758
|
chain,
|
|
19393
19759
|
authority,
|
|
19394
|
-
nativeBalance: (0,
|
|
19395
|
-
requiredNative: (0,
|
|
19760
|
+
nativeBalance: (0, import_viem25.formatEther)(native.nativeBalance),
|
|
19761
|
+
requiredNative: (0, import_viem25.formatEther)(native.requiredNative)
|
|
19396
19762
|
},
|
|
19397
19763
|
"Delegation failed: insufficient native balance for direct EIP-7702 tx and paymaster path unavailable"
|
|
19398
19764
|
);
|
|
@@ -19467,7 +19833,7 @@ var DelegationService = class {
|
|
|
19467
19833
|
{ chain, authority: this.signer.address, mode: this.signer.mode ?? "custom" },
|
|
19468
19834
|
"Revoking EIP-7702 delegation"
|
|
19469
19835
|
);
|
|
19470
|
-
const authorization = await this.buildAuthorization(chain,
|
|
19836
|
+
const authorization = await this.buildAuthorization(chain, import_viem25.zeroAddress);
|
|
19471
19837
|
const hash = await this.sendDelegationTransaction(chain, authorization);
|
|
19472
19838
|
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
19473
19839
|
if (receipt.status === "success") {
|
|
@@ -19483,13 +19849,13 @@ var DelegationService = class {
|
|
|
19483
19849
|
};
|
|
19484
19850
|
|
|
19485
19851
|
// src/services/RebalancingService.ts
|
|
19486
|
-
var
|
|
19852
|
+
var import_viem28 = require("viem");
|
|
19487
19853
|
var import_sdk13 = require("@hyperbridge/sdk");
|
|
19488
19854
|
var import_decimal3 = require("decimal.js");
|
|
19489
19855
|
|
|
19490
19856
|
// src/services/rebalancers/binance.ts
|
|
19491
19857
|
var import_wallet2 = require("@binance/wallet");
|
|
19492
|
-
var
|
|
19858
|
+
var import_viem26 = require("viem");
|
|
19493
19859
|
var import_sdk10 = require("@hyperbridge/sdk");
|
|
19494
19860
|
var DEPOSIT_STATUS_SUCCESS = 1;
|
|
19495
19861
|
var WITHDRAW_STATUS_COMPLETED = 6;
|
|
@@ -19801,7 +20167,7 @@ var BinanceRebalancer = class {
|
|
|
19801
20167
|
abi: ERC20_ABI,
|
|
19802
20168
|
functionName: "decimals"
|
|
19803
20169
|
});
|
|
19804
|
-
const amountWei = (0,
|
|
20170
|
+
const amountWei = (0, import_viem26.parseUnits)(amount, Number(tokenDecimals));
|
|
19805
20171
|
const balance = await publicClient.readContract({
|
|
19806
20172
|
address: tokenAddress,
|
|
19807
20173
|
abi: ERC20_ABI,
|
|
@@ -20025,7 +20391,7 @@ var CctpRebalancer = class {
|
|
|
20025
20391
|
};
|
|
20026
20392
|
|
|
20027
20393
|
// src/services/rebalancers/usdt0.ts
|
|
20028
|
-
var
|
|
20394
|
+
var import_viem27 = require("viem");
|
|
20029
20395
|
var import_sdk12 = require("@hyperbridge/sdk");
|
|
20030
20396
|
|
|
20031
20397
|
// src/config/abis/Oft.ts
|
|
@@ -20188,7 +20554,7 @@ var Usdt0Rebalancer = class {
|
|
|
20188
20554
|
const publicClient = this.chainClientManager.getPublicClient(source);
|
|
20189
20555
|
const walletClient = this.chainClientManager.getWalletClient(source);
|
|
20190
20556
|
const recipient = recipientAddress || this.account.address;
|
|
20191
|
-
const amountWei = (0,
|
|
20557
|
+
const amountWei = (0, import_viem27.parseUnits)(amount, 6);
|
|
20192
20558
|
if (sourceChainId === 1) {
|
|
20193
20559
|
const allowance = await publicClient.readContract({
|
|
20194
20560
|
address: tokenAddress,
|
|
@@ -20201,7 +20567,7 @@ var Usdt0Rebalancer = class {
|
|
|
20201
20567
|
address: tokenAddress,
|
|
20202
20568
|
abi: ERC20_ABI,
|
|
20203
20569
|
functionName: "approve",
|
|
20204
|
-
args: [oftAddress,
|
|
20570
|
+
args: [oftAddress, import_viem27.maxUint256],
|
|
20205
20571
|
chain: walletClient.chain
|
|
20206
20572
|
});
|
|
20207
20573
|
await publicClient.waitForTransactionReceipt({ hash: approveTx, confirmations: 1 });
|
|
@@ -20283,8 +20649,8 @@ var Usdt0Rebalancer = class {
|
|
|
20283
20649
|
if (!oftAddress) throw new Error(`Chain ${source} not supported by USDT0`);
|
|
20284
20650
|
const publicClient = this.chainClientManager.getPublicClient(source);
|
|
20285
20651
|
const recipient = recipientAddress || this.account.address;
|
|
20286
|
-
const amountWei = (0,
|
|
20287
|
-
const recipientBytes32 = (0,
|
|
20652
|
+
const amountWei = (0, import_viem27.parseUnits)(amount, 6);
|
|
20653
|
+
const recipientBytes32 = (0, import_viem27.padHex)(recipient, { size: 32 });
|
|
20288
20654
|
const sendParam = {
|
|
20289
20655
|
dstEid: destEid,
|
|
20290
20656
|
to: recipientBytes32,
|
|
@@ -20636,7 +21002,7 @@ var RebalancingService = class {
|
|
|
20636
21002
|
functionName: "balanceOf",
|
|
20637
21003
|
args: [this.walletAddress]
|
|
20638
21004
|
});
|
|
20639
|
-
const balanceDecimal = new import_decimal3.Decimal((0,
|
|
21005
|
+
const balanceDecimal = new import_decimal3.Decimal((0, import_viem28.formatUnits)(balanceWei, Number(decimals)));
|
|
20640
21006
|
return balanceDecimal.toNumber();
|
|
20641
21007
|
} catch (error) {
|
|
20642
21008
|
this.logger.error({ error, chain, asset }, "Failed to get current balance");
|
|
@@ -20684,11 +21050,11 @@ function byBestRate(a, b) {
|
|
|
20684
21050
|
return left === right ? 0 : left > right ? -1 : 1;
|
|
20685
21051
|
}
|
|
20686
21052
|
function chargedFor(hold, delivered, released) {
|
|
20687
|
-
|
|
20688
|
-
|
|
20689
|
-
|
|
20690
|
-
const atRate = (
|
|
20691
|
-
return atRate > delivered ? atRate : delivered;
|
|
21053
|
+
const take = hold.take === void 0 ? 0n : BigInt(hold.take);
|
|
21054
|
+
if (take === 0n || released === 0n) return toScaledUp(delivered.raw, delivered.decimals);
|
|
21055
|
+
const offered = toRaw(BigInt(hold.amount), delivered.decimals);
|
|
21056
|
+
const atRate = (offered * released + take - 1n) / take;
|
|
21057
|
+
return toScaledUp(atRate > delivered.raw ? atRate : delivered.raw, delivered.decimals);
|
|
20692
21058
|
}
|
|
20693
21059
|
var IntentFiller = class {
|
|
20694
21060
|
constructor(chainConfigs, strategies, config, configService, chainClientManager, contractService, signer, scanners, rebalancingService, bidStorage, limitOrders, assetRegistry) {
|
|
@@ -20765,6 +21131,17 @@ var IntentFiller = class {
|
|
|
20765
21131
|
setLimitOrderService(service) {
|
|
20766
21132
|
this.limitOrderService = service;
|
|
20767
21133
|
}
|
|
21134
|
+
/**
|
|
21135
|
+
* Brings the limit orders' `remaining` in line with their on-chain tallies.
|
|
21136
|
+
*
|
|
21137
|
+
* On the settlement queue, so the pass and a fill never resize the same order at
|
|
21138
|
+
* once. Resolves when the pass has run.
|
|
21139
|
+
*/
|
|
21140
|
+
async reconcileLimitOrderTallies() {
|
|
21141
|
+
await this.settlementQueue.add(async () => {
|
|
21142
|
+
await this.limitOrderService?.reconcileTallies();
|
|
21143
|
+
});
|
|
21144
|
+
}
|
|
20768
21145
|
/** Whether the given chain id is configured for watch-only (monitor, never fill). */
|
|
20769
21146
|
isChainWatchOnly(chainId) {
|
|
20770
21147
|
const watchOnly = this.config.watchOnly;
|
|
@@ -21342,6 +21719,23 @@ var IntentFiller = class {
|
|
|
21342
21719
|
this.monitor.emit("orderSkipped", { orderId: order.id, reason: "watch-only" });
|
|
21343
21720
|
return null;
|
|
21344
21721
|
}
|
|
21722
|
+
const destinationHead = await this.destinationHead(order);
|
|
21723
|
+
if (destinationHead !== null && order.deadline <= destinationHead) {
|
|
21724
|
+
this.logger.info(
|
|
21725
|
+
{
|
|
21726
|
+
orderId: order.id,
|
|
21727
|
+
destChain: order.destination,
|
|
21728
|
+
deadline: order.deadline.toString(),
|
|
21729
|
+
head: destinationHead.toString()
|
|
21730
|
+
},
|
|
21731
|
+
"Skipping order: its deadline has passed on the destination chain"
|
|
21732
|
+
);
|
|
21733
|
+
this.monitor.emit("orderSkipped", {
|
|
21734
|
+
orderId: order.id,
|
|
21735
|
+
reason: "Order deadline has passed on the destination chain"
|
|
21736
|
+
});
|
|
21737
|
+
return null;
|
|
21738
|
+
}
|
|
21345
21739
|
const evalStartMs = Date.now();
|
|
21346
21740
|
const eligibleStrategies = await Promise.all(
|
|
21347
21741
|
this.strategies.map(async (strategy) => {
|
|
@@ -21351,7 +21745,10 @@ var IntentFiller = class {
|
|
|
21351
21745
|
})
|
|
21352
21746
|
);
|
|
21353
21747
|
const fillsPartially = this.fillsPartially(order);
|
|
21354
|
-
const
|
|
21748
|
+
const feeCheckWaived = this.feeCheckWaived(order);
|
|
21749
|
+
const validStrategies = eligibleStrategies.filter(
|
|
21750
|
+
(s) => s !== null && (s.profitability > 0 || fillsPartially || feeCheckWaived)
|
|
21751
|
+
).sort((a, b) => b.profitability - a.profitability);
|
|
21355
21752
|
const evalDurationSec = (Date.now() - evalStartMs) / 1e3;
|
|
21356
21753
|
this.monitor.emit("orderTiming", { orderId: order.id, phase: "evaluation", durationSec: evalDurationSec });
|
|
21357
21754
|
if (validStrategies.length === 0) {
|
|
@@ -21376,6 +21773,23 @@ var IntentFiller = class {
|
|
|
21376
21773
|
);
|
|
21377
21774
|
return validStrategies[0];
|
|
21378
21775
|
}
|
|
21776
|
+
/**
|
|
21777
|
+
* The destination chain's latest block number, the clock `order.deadline` is read on.
|
|
21778
|
+
*
|
|
21779
|
+
* Null when the read fails. That is not an answer about the order, so evaluation goes
|
|
21780
|
+
* on: the gas estimate simulates the same deadline check and refuses an expired order.
|
|
21781
|
+
*/
|
|
21782
|
+
async destinationHead(order) {
|
|
21783
|
+
try {
|
|
21784
|
+
return await this.chainClientManager.getPublicClient(order.destination).getBlockNumber();
|
|
21785
|
+
} catch (err) {
|
|
21786
|
+
this.logger.warn(
|
|
21787
|
+
{ orderId: order.id, destChain: order.destination, err },
|
|
21788
|
+
"Could not read the destination head to check the order's deadline"
|
|
21789
|
+
);
|
|
21790
|
+
return null;
|
|
21791
|
+
}
|
|
21792
|
+
}
|
|
21379
21793
|
/**
|
|
21380
21794
|
* Whether the strategy's evaluation concluded in a deliberate partial fill.
|
|
21381
21795
|
*
|
|
@@ -21389,6 +21803,15 @@ var IntentFiller = class {
|
|
|
21389
21803
|
if (!order.id) return false;
|
|
21390
21804
|
return this.contractService.cacheService.isPartialFill(order.id);
|
|
21391
21805
|
}
|
|
21806
|
+
/**
|
|
21807
|
+
* Whether the strategy's evaluation let a bid through without the fee check, because
|
|
21808
|
+
* the order is at or above the operator's minimum size. Read from a flag the strategy
|
|
21809
|
+
* sets only once it has an answer, as {@link fillsPartially} is.
|
|
21810
|
+
*/
|
|
21811
|
+
feeCheckWaived(order) {
|
|
21812
|
+
if (!order.id) return false;
|
|
21813
|
+
return this.contractService.cacheService.isFeeCheckWaived(order.id);
|
|
21814
|
+
}
|
|
21392
21815
|
executeOrder(order, bestStrategy, solverSelectionActive, inputUsdValue, profitUsd) {
|
|
21393
21816
|
const chainQueue = this.chainQueues.get((0, import_sdk14.getChainId)(order.destination));
|
|
21394
21817
|
if (!chainQueue) {
|
|
@@ -21410,7 +21833,7 @@ var IntentFiller = class {
|
|
|
21410
21833
|
let reservation = [];
|
|
21411
21834
|
if (plan) {
|
|
21412
21835
|
if (order.id) {
|
|
21413
|
-
cache.setFillerOutputs(order.id, plan.fillerOutputs, plan.fillerInputs);
|
|
21836
|
+
cache.setFillerOutputs(order.id, plan.fillerOutputs, plan.fillerInputs, plan.budget);
|
|
21414
21837
|
cache.setPartialFill(order.id, plan.partialFill);
|
|
21415
21838
|
if (plan.fundingCalls.length > 0) cache.setFundingPrepends(order.id, plan.fundingCalls);
|
|
21416
21839
|
else cache.clearFundingPrepends(order.id);
|
|
@@ -21611,7 +22034,7 @@ var IntentFiller = class {
|
|
|
21611
22034
|
await this.releaseAll(claimed);
|
|
21612
22035
|
return;
|
|
21613
22036
|
}
|
|
21614
|
-
const settled = executedHold(claimed, delivered, released);
|
|
22037
|
+
const settled = executedHold(claimed, delivered.scaled, released);
|
|
21615
22038
|
const charged = chargedFor(settled, delivered, released);
|
|
21616
22039
|
const settledBid = rows.find(
|
|
21617
22040
|
(row) => row.reservations.some(
|
|
@@ -21664,15 +22087,15 @@ var IntentFiller = class {
|
|
|
21664
22087
|
if (delivered === null) return [];
|
|
21665
22088
|
const settled = executedHold(
|
|
21666
22089
|
holds.map((entry) => entry.hold),
|
|
21667
|
-
delivered,
|
|
22090
|
+
delivered.scaled,
|
|
21668
22091
|
released
|
|
21669
22092
|
);
|
|
21670
22093
|
const row = holds.find((entry) => entry.hold === settled).row;
|
|
21671
22094
|
return this.bidStorage.claimReservation(commitment, row.bid ?? void 0);
|
|
21672
22095
|
}
|
|
21673
22096
|
/**
|
|
21674
|
-
* What this fill delivered of the token the limit order pays,
|
|
21675
|
-
*
|
|
22097
|
+
* What this fill delivered of the token the limit order pays, or null when the
|
|
22098
|
+
* fill names none of it.
|
|
21676
22099
|
*/
|
|
21677
22100
|
async deliveredAgainst(limitOrderId, chainId, outputs) {
|
|
21678
22101
|
const order = await this.limitOrders?.get(limitOrderId);
|
|
@@ -21688,7 +22111,7 @@ var IntentFiller = class {
|
|
|
21688
22111
|
}
|
|
21689
22112
|
if (total === 0n) return null;
|
|
21690
22113
|
const decimals = await this.contractService.getTokenDecimals(address, chain);
|
|
21691
|
-
return toScaled(total, decimals);
|
|
22114
|
+
return { scaled: toScaled(total, decimals), raw: total, decimals };
|
|
21692
22115
|
}
|
|
21693
22116
|
/**
|
|
21694
22117
|
* Gives back whatever this bid still holds against its limit order.
|
|
@@ -21778,90 +22201,9 @@ var IntentFiller = class {
|
|
|
21778
22201
|
|
|
21779
22202
|
// src/strategies/fx.ts
|
|
21780
22203
|
var import_sdk15 = require("@hyperbridge/sdk");
|
|
21781
|
-
var
|
|
22204
|
+
var import_viem29 = require("viem");
|
|
21782
22205
|
var import_decimal6 = require("decimal.js");
|
|
21783
22206
|
|
|
21784
|
-
// src/orderbook/matching.ts
|
|
21785
|
-
function limitOrderLegs(order) {
|
|
21786
|
-
return order.side === "BID" ? { input: order.base, output: order.quote } : { input: order.quote, output: order.base };
|
|
21787
|
-
}
|
|
21788
|
-
function matchLimitOrders(orders, incoming, resolve, now = /* @__PURE__ */ new Date()) {
|
|
21789
|
-
const qualifying = orders.filter((order) => serves(order, incoming, resolve, now)).map((order) => {
|
|
21790
|
-
const offer = offerFor({
|
|
21791
|
-
side: order.side,
|
|
21792
|
-
inputAmount: incoming.inputNet,
|
|
21793
|
-
price: BigInt(order.price),
|
|
21794
|
-
outputDecimals: incoming.outputDecimals
|
|
21795
|
-
});
|
|
21796
|
-
const available = BigInt(order.remaining);
|
|
21797
|
-
return { order, offer, available, payout: offer < available ? offer : available };
|
|
21798
|
-
}).filter((candidate) => candidate.offer >= incoming.requestedOutput && candidate.payout > 0n).sort(byBestOfferFirst);
|
|
21799
|
-
return qualifying;
|
|
21800
|
-
}
|
|
21801
|
-
function byBestOfferFirst(a, b) {
|
|
21802
|
-
if (a.offer !== b.offer) return a.offer > b.offer ? -1 : 1;
|
|
21803
|
-
if (a.available !== b.available) return a.available > b.available ? -1 : 1;
|
|
21804
|
-
return a.order.id < b.order.id ? -1 : 1;
|
|
21805
|
-
}
|
|
21806
|
-
function whyUnmatched(orders, incoming, resolve, now = /* @__PURE__ */ new Date()) {
|
|
21807
|
-
let furthest = -1;
|
|
21808
|
-
for (const order of orders) {
|
|
21809
|
-
const failed = firstFailed(order, incoming, resolve, now);
|
|
21810
|
-
furthest = Math.max(furthest, failed === null ? CHECKS.length : CHECKS.indexOf(failed));
|
|
21811
|
-
}
|
|
21812
|
-
const { source, destination, inputSymbol } = incoming;
|
|
21813
|
-
if (furthest < CHECKS.length) {
|
|
21814
|
-
switch (CHECKS[Math.max(furthest, 0)]) {
|
|
21815
|
-
case "open":
|
|
21816
|
-
return "no limit order is open";
|
|
21817
|
-
case "chain":
|
|
21818
|
-
return `no open limit order fills on ${destination}`;
|
|
21819
|
-
case "input":
|
|
21820
|
-
return `no limit order on ${destination} takes ${inputSymbol} in`;
|
|
21821
|
-
case "output":
|
|
21822
|
-
return `no limit order on ${destination} taking ${inputSymbol} in pays out the token this order asks for`;
|
|
21823
|
-
case "source":
|
|
21824
|
-
return `no limit order for this pair on ${destination} accepts swaps from ${source}`;
|
|
21825
|
-
}
|
|
21826
|
-
}
|
|
21827
|
-
const priced = orders.filter((order) => firstFailed(order, incoming, resolve, now) === null).map((order) => ({
|
|
21828
|
-
order,
|
|
21829
|
-
offer: offerFor({
|
|
21830
|
-
side: order.side,
|
|
21831
|
-
inputAmount: incoming.inputNet,
|
|
21832
|
-
price: BigInt(order.price),
|
|
21833
|
-
outputDecimals: incoming.outputDecimals
|
|
21834
|
-
})
|
|
21835
|
-
}));
|
|
21836
|
-
if (priced.some(({ offer }) => offer >= incoming.requestedOutput)) {
|
|
21837
|
-
return "the limit orders that meet its rate have nothing left to pay out";
|
|
21838
|
-
}
|
|
21839
|
-
const best = priced.reduce((a, b) => b.offer > a.offer ? b : a);
|
|
21840
|
-
const symbol = limitOrderLegs(best.order).output;
|
|
21841
|
-
return `it asks for ${readable(incoming.requestedOutput)} ${symbol}; the best limit order offers ${readable(best.offer)} ${symbol}`;
|
|
21842
|
-
}
|
|
21843
|
-
var CHECKS = ["open", "chain", "input", "output", "source"];
|
|
21844
|
-
function serves(order, incoming, resolve, now) {
|
|
21845
|
-
return firstFailed(order, incoming, resolve, now) === null;
|
|
21846
|
-
}
|
|
21847
|
-
function firstFailed(order, incoming, resolve, now) {
|
|
21848
|
-
if (order.status !== "open") return "open";
|
|
21849
|
-
if (order.expiresAt !== null && new Date(order.expiresAt) <= now) return "open";
|
|
21850
|
-
if (order.fillChain !== incoming.destination) return "chain";
|
|
21851
|
-
const legs = limitOrderLegs(order);
|
|
21852
|
-
if (normalizeSymbol(legs.input) !== normalizeSymbol(incoming.inputSymbol)) return "input";
|
|
21853
|
-
const outputToken = resolve(legs.output, order.fillChain);
|
|
21854
|
-
if (!outputToken || outputToken.toLowerCase() !== incoming.outputToken.toLowerCase()) return "output";
|
|
21855
|
-
if (incoming.source === incoming.destination) return null;
|
|
21856
|
-
return order.acceptedSources.includes(incoming.source) ? null : "source";
|
|
21857
|
-
}
|
|
21858
|
-
function readable(amount) {
|
|
21859
|
-
const [whole, fraction] = toHuman(amount).split(".");
|
|
21860
|
-
const grouped = whole.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
21861
|
-
const cut = fraction?.slice(0, 6).replace(/0+$/, "");
|
|
21862
|
-
return cut ? `${grouped}.${cut}` : grouped;
|
|
21863
|
-
}
|
|
21864
|
-
|
|
21865
22207
|
// src/orderbook/usd.ts
|
|
21866
22208
|
var import_decimal5 = require("decimal.js");
|
|
21867
22209
|
function limitOrderUsdEdges(orders) {
|
|
@@ -21916,9 +22258,13 @@ var FXFiller = class _FXFiller {
|
|
|
21916
22258
|
*/
|
|
21917
22259
|
constructor(signer, configService, clientManager, contractService, pairs, registry, options) {
|
|
21918
22260
|
this.name = "FXFiller";
|
|
21919
|
-
/**
|
|
22261
|
+
/**
|
|
22262
|
+
* Consecutive orders where the overfill clamp activated. Dormant: the clamp
|
|
22263
|
+
* existed for venue-priced legs, which went with Uniswap V4 pricing, so every
|
|
22264
|
+
* outcome is now recorded unclamped and this never climbs. See `recordOrderOutcome`.
|
|
22265
|
+
*/
|
|
21920
22266
|
this.consecutiveClamps = 0;
|
|
21921
|
-
/** Once set, the filler refuses all orders until
|
|
22267
|
+
/** Once set, the filler refuses all orders until reset. Unreachable while the clamp is dormant. */
|
|
21922
22268
|
this.halted = false;
|
|
21923
22269
|
this.logger = moduleLogger(configService.loggers, "fx-simplex");
|
|
21924
22270
|
const { confirmationPolicy, fundingVenues = [], limitOrders } = options ?? {};
|
|
@@ -21967,10 +22313,9 @@ var FXFiller = class _FXFiller {
|
|
|
21967
22313
|
this.pairs.push(pair);
|
|
21968
22314
|
}
|
|
21969
22315
|
/**
|
|
21970
|
-
* Removes a market from the running engine.
|
|
21971
|
-
*
|
|
21972
|
-
*
|
|
21973
|
-
* rechecks of orders on the removed pair simply stop matching.
|
|
22316
|
+
* Removes a market from the running engine. At least one market must remain.
|
|
22317
|
+
* Matching reads the limit orders rather than the pairs, so removing a market
|
|
22318
|
+
* changes what the dashboard lists, not what the filler fills.
|
|
21974
22319
|
*/
|
|
21975
22320
|
removePair(pair) {
|
|
21976
22321
|
const index = this.pairs.indexOf(pair);
|
|
@@ -22026,25 +22371,25 @@ var FXFiller = class _FXFiller {
|
|
|
22026
22371
|
}
|
|
22027
22372
|
}
|
|
22028
22373
|
/**
|
|
22029
|
-
* Evaluates whether an order is profitable to fill
|
|
22030
|
-
*
|
|
22374
|
+
* Evaluates whether an order is profitable to fill against the operator's
|
|
22375
|
+
* limit orders and the filler's current token balances.
|
|
22031
22376
|
*
|
|
22032
22377
|
* High-level flow:
|
|
22033
|
-
* -
|
|
22034
|
-
* -
|
|
22035
|
-
*
|
|
22036
|
-
*
|
|
22037
|
-
*
|
|
22038
|
-
*
|
|
22039
|
-
*
|
|
22040
|
-
*
|
|
22041
|
-
*
|
|
22042
|
-
*
|
|
22043
|
-
*
|
|
22044
|
-
* outputs if the pair pricing makes that attractive. This is how we stay competitive.
|
|
22378
|
+
* - Match each (input, output) leg against the open limit orders.
|
|
22379
|
+
* - Build one bid per matching order, best offer first, each paying the
|
|
22380
|
+
* order's whole offer for the input at its own rate, capped by what the
|
|
22381
|
+
* order has left. The gateway credits the swapper the ask and splits
|
|
22382
|
+
* anything above it between the swapper and the protocol.
|
|
22383
|
+
* - Treat a payout below the ask as a partial fill, which only orders
|
|
22384
|
+
* without output calldata and without an earlier partial fill allow.
|
|
22385
|
+
* - Cap each payout by the filler's balance plus funding-venue withdrawals,
|
|
22386
|
+
* then gate it on fees (full fills of orders below `minOrderSizeUsd`) and the
|
|
22387
|
+
* same-asset spread.
|
|
22388
|
+
* - Cache the resulting bids for later use in `executeOrder`.
|
|
22045
22389
|
*/
|
|
22046
22390
|
async calculateProfitability(order) {
|
|
22047
22391
|
if (order.id) this.contractService.cacheService.clearPartialFill(order.id);
|
|
22392
|
+
if (order.id) this.contractService.cacheService.clearFeeCheckWaived(order.id);
|
|
22048
22393
|
if (this.halted) {
|
|
22049
22394
|
this.logger.warn({ orderId: order.id }, "FXFiller halted \u2014 rejecting order");
|
|
22050
22395
|
return 0;
|
|
@@ -22059,6 +22404,16 @@ var FXFiller = class _FXFiller {
|
|
|
22059
22404
|
const multiLeg = order.inputs.length > 1;
|
|
22060
22405
|
const plans = [];
|
|
22061
22406
|
const plannedOn = /* @__PURE__ */ new Map();
|
|
22407
|
+
const spentOn = /* @__PURE__ */ new Map();
|
|
22408
|
+
let atOrAboveMinSize;
|
|
22409
|
+
const feeCheckWaived = async () => {
|
|
22410
|
+
if (atOrAboveMinSize === void 0) {
|
|
22411
|
+
const usd = await this.getOrderUsdValue(order);
|
|
22412
|
+
atOrAboveMinSize = usd !== null && usd.inputUsd.gte(this.configService.getMinOrderSizeUsd());
|
|
22413
|
+
}
|
|
22414
|
+
return atOrAboveMinSize;
|
|
22415
|
+
};
|
|
22416
|
+
let feesWaived = false;
|
|
22062
22417
|
for (let leg = 0; leg < order.inputs.length; leg++) {
|
|
22063
22418
|
const input = order.inputs[leg];
|
|
22064
22419
|
const output = order.output.assets[leg];
|
|
@@ -22080,7 +22435,30 @@ var FXFiller = class _FXFiller {
|
|
|
22080
22435
|
for (const candidate of matches) {
|
|
22081
22436
|
let partialFill = false;
|
|
22082
22437
|
const fundingCalls = [];
|
|
22083
|
-
const
|
|
22438
|
+
const budget = budgetFor(candidate.order, outputToken, outputDecimals);
|
|
22439
|
+
if (!spentOn.has(candidate.order.id)) {
|
|
22440
|
+
spentOn.set(
|
|
22441
|
+
candidate.order.id,
|
|
22442
|
+
await this.contractService.limitOrderSpent(destChain, budget.budgetId)
|
|
22443
|
+
);
|
|
22444
|
+
}
|
|
22445
|
+
const spent = spentOn.get(candidate.order.id) ?? null;
|
|
22446
|
+
const room = spent === null ? candidate.available : budgetRoom(budget, spent, outputDecimals);
|
|
22447
|
+
if (room === 0n) {
|
|
22448
|
+
this.logger.info(
|
|
22449
|
+
{
|
|
22450
|
+
orderId: order.id,
|
|
22451
|
+
leg,
|
|
22452
|
+
limitOrder: candidate.order.id,
|
|
22453
|
+
spent: spent?.toString(),
|
|
22454
|
+
cap: budget.cap.toString()
|
|
22455
|
+
},
|
|
22456
|
+
"Skipping a bid: the limit order has nothing left on chain"
|
|
22457
|
+
);
|
|
22458
|
+
continue;
|
|
22459
|
+
}
|
|
22460
|
+
const available = room < candidate.available ? room : candidate.available;
|
|
22461
|
+
const left = available - (plannedOn.get(candidate.order.id) ?? 0n);
|
|
22084
22462
|
if (left <= 0n) {
|
|
22085
22463
|
this.logger.info(
|
|
22086
22464
|
{ orderId: order.id, leg, limitOrder: candidate.order.id },
|
|
@@ -22115,10 +22493,7 @@ var FXFiller = class _FXFiller {
|
|
|
22115
22493
|
{
|
|
22116
22494
|
orderId: order.id,
|
|
22117
22495
|
limitOrder: candidate.order.id,
|
|
22118
|
-
available: (0,
|
|
22119
|
-
candidate.available,
|
|
22120
|
-
18
|
|
22121
|
-
),
|
|
22496
|
+
available: (0, import_viem29.formatUnits)(available, 18),
|
|
22122
22497
|
userRequested: output.amount.toString(),
|
|
22123
22498
|
payout: targetOutput.toString(),
|
|
22124
22499
|
crossChain: sourceChain !== destChain
|
|
@@ -22163,7 +22538,10 @@ var FXFiller = class _FXFiller {
|
|
|
22163
22538
|
}
|
|
22164
22539
|
}
|
|
22165
22540
|
const effectiveBalance = walletContribution + credited;
|
|
22166
|
-
const finalOutputAmount =
|
|
22541
|
+
const finalOutputAmount = toRaw(
|
|
22542
|
+
toScaled(effectiveBalance > targetOutput ? targetOutput : effectiveBalance, outputDecimals),
|
|
22543
|
+
outputDecimals
|
|
22544
|
+
);
|
|
22167
22545
|
if (finalOutputAmount === 0n) {
|
|
22168
22546
|
this.logger.info(
|
|
22169
22547
|
{
|
|
@@ -22252,7 +22630,7 @@ var FXFiller = class _FXFiller {
|
|
|
22252
22630
|
const spread = convertedInput - finalOutputAmount;
|
|
22253
22631
|
if (spread <= 0n) sameAssetProfitable = false;
|
|
22254
22632
|
realizedSpreadProfit = adjustDecimalsFloor(spread, outputDecimals, feeTokenDecimals);
|
|
22255
|
-
const spreadUsd = outputSymbol && usdValueOf(usdFactors, outputSymbol, new import_decimal6.Decimal((0,
|
|
22633
|
+
const spreadUsd = outputSymbol && usdValueOf(usdFactors, outputSymbol, new import_decimal6.Decimal((0, import_viem29.formatUnits)(spread, outputDecimals)));
|
|
22256
22634
|
if (spreadUsd) sameAssetEdgeUsd = spreadUsd;
|
|
22257
22635
|
}
|
|
22258
22636
|
const payoutSurplusUsd = new import_decimal6.Decimal(0);
|
|
@@ -22267,9 +22645,9 @@ var FXFiller = class _FXFiller {
|
|
|
22267
22645
|
{
|
|
22268
22646
|
orderId: order.id,
|
|
22269
22647
|
feeToken: feeTokenLower,
|
|
22270
|
-
residual: (0,
|
|
22271
|
-
dispatchFee: (0,
|
|
22272
|
-
required: (0,
|
|
22648
|
+
residual: (0, import_viem29.formatUnits)(residual, feeToken.decimals),
|
|
22649
|
+
dispatchFee: (0, import_viem29.formatUnits)(dispatchFee, feeToken.decimals),
|
|
22650
|
+
required: (0, import_viem29.formatUnits)(required, feeToken.decimals)
|
|
22273
22651
|
},
|
|
22274
22652
|
"Skipping order: fill leaves too little of the fee token to dispatch the escrow release"
|
|
22275
22653
|
);
|
|
@@ -22279,27 +22657,31 @@ var FXFiller = class _FXFiller {
|
|
|
22279
22657
|
const executionCost = totalCostInSourceFeeToken + relayerFeeInSourceFeeToken;
|
|
22280
22658
|
const partialEdgeUsd = sameAssetEdgeUsd.plus(payoutSurplusUsd);
|
|
22281
22659
|
if (!partialFill && order.fees < executionCost) {
|
|
22282
|
-
|
|
22283
|
-
|
|
22284
|
-
|
|
22285
|
-
|
|
22286
|
-
|
|
22287
|
-
|
|
22288
|
-
|
|
22289
|
-
|
|
22290
|
-
|
|
22291
|
-
|
|
22292
|
-
|
|
22660
|
+
if (!await feeCheckWaived()) {
|
|
22661
|
+
this.logger.info(
|
|
22662
|
+
{
|
|
22663
|
+
orderId: order.id,
|
|
22664
|
+
orderFees: (0, import_viem29.formatUnits)(order.fees, feeTokenDecimals),
|
|
22665
|
+
fillGas: (0, import_viem29.formatUnits)(totalCostInSourceFeeToken, feeTokenDecimals),
|
|
22666
|
+
relayerFee: (0, import_viem29.formatUnits)(relayerFeeInSourceFeeToken, feeTokenDecimals),
|
|
22667
|
+
executionCost: (0, import_viem29.formatUnits)(executionCost, feeTokenDecimals),
|
|
22668
|
+
minOrderSizeUsd: this.configService.getMinOrderSizeUsd()
|
|
22669
|
+
},
|
|
22670
|
+
"Skipping order: attached fees do not cover execution cost (fill gas + relayer fee)"
|
|
22671
|
+
);
|
|
22672
|
+
continue;
|
|
22673
|
+
}
|
|
22674
|
+
feesWaived = true;
|
|
22293
22675
|
}
|
|
22294
22676
|
if (sameAsset && !sameAssetProfitable) {
|
|
22295
22677
|
this.logger.info(
|
|
22296
|
-
{ orderId: order.id, realizedSpreadProfit: (0,
|
|
22678
|
+
{ orderId: order.id, realizedSpreadProfit: (0, import_viem29.formatUnits)(realizedSpreadProfit, feeTokenDecimals) },
|
|
22297
22679
|
"Skipping order: a same-asset fill does not net a positive spread"
|
|
22298
22680
|
);
|
|
22299
22681
|
continue;
|
|
22300
22682
|
}
|
|
22301
22683
|
const feeProfit = order.fees - executionCost;
|
|
22302
|
-
const totalProfit = partialFill ? partialEdgeUsd.toNumber() : Number.parseFloat((0,
|
|
22684
|
+
const totalProfit = partialFill ? partialEdgeUsd.toNumber() : Number.parseFloat((0, import_viem29.formatUnits)(feeProfit + realizedSpreadProfit, feeTokenDecimals));
|
|
22303
22685
|
this.logger.info(
|
|
22304
22686
|
{
|
|
22305
22687
|
orderId: order.id,
|
|
@@ -22315,15 +22697,16 @@ var FXFiller = class _FXFiller {
|
|
|
22315
22697
|
limitOrders: matches.length,
|
|
22316
22698
|
available: candidate.available.toString(),
|
|
22317
22699
|
payout: targetOutput.toString(),
|
|
22318
|
-
orderFees: (0,
|
|
22319
|
-
fillGas: (0,
|
|
22320
|
-
relayerFee: (0,
|
|
22321
|
-
executionCost: (0,
|
|
22322
|
-
feeProfit: (0,
|
|
22323
|
-
realizedSpreadProfit: (0,
|
|
22700
|
+
orderFees: (0, import_viem29.formatUnits)(order.fees, feeTokenDecimals),
|
|
22701
|
+
fillGas: (0, import_viem29.formatUnits)(totalCostInSourceFeeToken, feeTokenDecimals),
|
|
22702
|
+
relayerFee: (0, import_viem29.formatUnits)(relayerFeeInSourceFeeToken, feeTokenDecimals),
|
|
22703
|
+
executionCost: (0, import_viem29.formatUnits)(executionCost, feeTokenDecimals),
|
|
22704
|
+
feeProfit: (0, import_viem29.formatUnits)(feeProfit, feeTokenDecimals),
|
|
22705
|
+
realizedSpreadProfit: (0, import_viem29.formatUnits)(realizedSpreadProfit, feeTokenDecimals),
|
|
22324
22706
|
payoutSurplusUsd: payoutSurplusUsd.toString(),
|
|
22325
22707
|
totalProfit,
|
|
22326
|
-
profitable: totalProfit > 0
|
|
22708
|
+
profitable: totalProfit > 0,
|
|
22709
|
+
feeCheckWaived: !partialFill && order.fees < executionCost
|
|
22327
22710
|
},
|
|
22328
22711
|
"FX swap profitability evaluation"
|
|
22329
22712
|
);
|
|
@@ -22339,7 +22722,8 @@ var FXFiller = class _FXFiller {
|
|
|
22339
22722
|
fillerInputs,
|
|
22340
22723
|
fundingCalls: [...fundingCalls],
|
|
22341
22724
|
partialFill,
|
|
22342
|
-
profit: totalProfit
|
|
22725
|
+
profit: totalProfit,
|
|
22726
|
+
budget
|
|
22343
22727
|
});
|
|
22344
22728
|
if (!partialEligibleCheap) break;
|
|
22345
22729
|
}
|
|
@@ -22347,8 +22731,14 @@ var FXFiller = class _FXFiller {
|
|
|
22347
22731
|
if (plans.length === 0) return 0;
|
|
22348
22732
|
if (order.id) {
|
|
22349
22733
|
this.contractService.cacheService.setBidPlans(order.id, plans);
|
|
22350
|
-
this.contractService.cacheService.setFillerOutputs(
|
|
22734
|
+
this.contractService.cacheService.setFillerOutputs(
|
|
22735
|
+
order.id,
|
|
22736
|
+
plans[0].fillerOutputs,
|
|
22737
|
+
plans[0].fillerInputs,
|
|
22738
|
+
plans[0].budget
|
|
22739
|
+
);
|
|
22351
22740
|
this.contractService.cacheService.setPartialFill(order.id, plans[0].partialFill);
|
|
22741
|
+
this.contractService.cacheService.setFeeCheckWaived(order.id, feesWaived);
|
|
22352
22742
|
this.contractService.cacheService.setMatchedLimitOrder(order.id, [
|
|
22353
22743
|
{ limitOrderId: plans[0].limitOrderId, payout: plans[0].payout }
|
|
22354
22744
|
]);
|
|
@@ -22442,10 +22832,14 @@ var FXFiller = class _FXFiller {
|
|
|
22442
22832
|
// =========================================================================
|
|
22443
22833
|
/**
|
|
22444
22834
|
* Update consecutive-clamp counter after a successful order evaluation.
|
|
22445
|
-
*
|
|
22446
|
-
*
|
|
22447
|
-
*
|
|
22448
|
-
*
|
|
22835
|
+
*
|
|
22836
|
+
* Dormant. Only venue-priced legs ever fed a clamped outcome here: a streak
|
|
22837
|
+
* of those meant a live market source had gone off (stale pool, manipulated
|
|
22838
|
+
* venue). Venue pricing went with Uniswap V4, and limit orders are prices the
|
|
22839
|
+
* operator set, so the only caller records `false` and the filler never
|
|
22840
|
+
* halts. `isHalted`, `resetHalt` and `[simplex.overfillProtection]
|
|
22841
|
+
* maxConsecutiveClamps` are kept so existing configs and the halt API still
|
|
22842
|
+
* load; they can go once nothing reads them.
|
|
22449
22843
|
*/
|
|
22450
22844
|
recordOrderOutcome(clamped, orderId) {
|
|
22451
22845
|
if (clamped) {
|
|
@@ -22471,18 +22865,6 @@ var FXFiller = class _FXFiller {
|
|
|
22471
22865
|
this.consecutiveClamps = 0;
|
|
22472
22866
|
this.logger.warn("FXFiller halt reset by operator \u2014 resuming order evaluation");
|
|
22473
22867
|
}
|
|
22474
|
-
/**
|
|
22475
|
-
* Given a single (input, output) leg and the remaining token0 budget of its
|
|
22476
|
-
* pair, computes how much token0 notional to allocate to this leg and the
|
|
22477
|
-
* corresponding maximum output amount at the pair's rate.
|
|
22478
|
-
*
|
|
22479
|
-
* `rate` is **token1 per 1 token0**:
|
|
22480
|
-
* - token0 input → token1 output: token0 × rate → token1 amount.
|
|
22481
|
-
* - token1 input → token0 output: token1 ÷ rate → token0 amount.
|
|
22482
|
-
*
|
|
22483
|
-
* Returns `null` when this leg cannot consume any of the pair's remaining
|
|
22484
|
-
* budget (e.g. the cap has already been exhausted).
|
|
22485
|
-
*/
|
|
22486
22868
|
/**
|
|
22487
22869
|
* Whether any solver has already delivered output against this order.
|
|
22488
22870
|
*
|
|
@@ -22622,7 +23004,7 @@ var FXFiller = class _FXFiller {
|
|
|
22622
23004
|
const symbol = this.registry.symbolFor(inputToken, order.source);
|
|
22623
23005
|
if (!symbol) return null;
|
|
22624
23006
|
const decimals = await this.contractService.getTokenDecimals(inputToken, order.source);
|
|
22625
|
-
const legUsd = usdValueOf(factors, symbol, new import_decimal6.Decimal((0,
|
|
23007
|
+
const legUsd = usdValueOf(factors, symbol, new import_decimal6.Decimal((0, import_viem29.formatUnits)(input.amount, decimals)));
|
|
22626
23008
|
if (!legUsd) return null;
|
|
22627
23009
|
inputUsd = inputUsd.plus(legUsd);
|
|
22628
23010
|
}
|
|
@@ -22635,7 +23017,7 @@ function minBigInt(first, ...rest) {
|
|
|
22635
23017
|
|
|
22636
23018
|
// src/services/TokenSender.ts
|
|
22637
23019
|
var import_sdk16 = require("@hyperbridge/sdk");
|
|
22638
|
-
var
|
|
23020
|
+
var import_viem30 = require("viem");
|
|
22639
23021
|
var TokenSender = class {
|
|
22640
23022
|
constructor(clientManager, solver, getVaults, userOpSender, configService) {
|
|
22641
23023
|
this.clientManager = clientManager;
|
|
@@ -22647,18 +23029,18 @@ var TokenSender = class {
|
|
|
22647
23029
|
}
|
|
22648
23030
|
async send(params) {
|
|
22649
23031
|
const { chain, token, amount, to } = params;
|
|
22650
|
-
if (!(0,
|
|
23032
|
+
if (!(0, import_viem30.isAddress)(to)) throw new Error(`Invalid recipient address: ${to}`);
|
|
22651
23033
|
const publicClient = this.clientManager.getPublicClient(chain);
|
|
22652
23034
|
const calls = [];
|
|
22653
23035
|
let redeemed = false;
|
|
22654
23036
|
if (token === "native") {
|
|
22655
|
-
const value = (0,
|
|
23037
|
+
const value = (0, import_viem30.parseUnits)(amount, 18);
|
|
22656
23038
|
if (value <= 0n) throw new Error("Amount must be greater than 0");
|
|
22657
23039
|
const balance = await publicClient.getBalance({ address: this.solver });
|
|
22658
23040
|
if (balance < value) throw new Error(`Insufficient native balance: have ${balance}, need ${value}`);
|
|
22659
23041
|
calls.push({ target: to, value, data: "0x" });
|
|
22660
23042
|
} else {
|
|
22661
|
-
if (!(0,
|
|
23043
|
+
if (!(0, import_viem30.isAddress)(token)) throw new Error(`Invalid token address: ${token}`);
|
|
22662
23044
|
const tokenLower = token.toLowerCase();
|
|
22663
23045
|
const [decimals, balance] = await Promise.all([
|
|
22664
23046
|
publicClient.readContract({ abi: ERC20_ABI, address: token, functionName: "decimals" }),
|
|
@@ -22669,7 +23051,7 @@ var TokenSender = class {
|
|
|
22669
23051
|
args: [this.solver]
|
|
22670
23052
|
})
|
|
22671
23053
|
]);
|
|
22672
|
-
const units = (0,
|
|
23054
|
+
const units = (0, import_viem30.parseUnits)(amount, decimals);
|
|
22673
23055
|
if (units <= 0n) throw new Error("Amount must be greater than 0");
|
|
22674
23056
|
const isVaultShare = this.getVaults().some(
|
|
22675
23057
|
(v) => v.chain === chain && v.vault.toLowerCase() === tokenLower
|
|
@@ -22680,7 +23062,7 @@ var TokenSender = class {
|
|
|
22680
23062
|
calls.push({
|
|
22681
23063
|
target: token,
|
|
22682
23064
|
value: 0n,
|
|
22683
|
-
data: (0,
|
|
23065
|
+
data: (0, import_viem30.encodeFunctionData)({ abi: ERC4626_ABI, functionName: "redeem", args: [units, to, this.solver] })
|
|
22684
23066
|
});
|
|
22685
23067
|
} else {
|
|
22686
23068
|
const reserve = this.userOpSender?.canSponsor(chain) && this.configService ? paymasterReserveForToken(chain, tokenLower, this.configService) : 0n;
|
|
@@ -22703,7 +23085,7 @@ var TokenSender = class {
|
|
|
22703
23085
|
calls.push({
|
|
22704
23086
|
target: token,
|
|
22705
23087
|
value: 0n,
|
|
22706
|
-
data: (0,
|
|
23088
|
+
data: (0, import_viem30.encodeFunctionData)({ abi: ERC20_ABI, functionName: "transfer", args: [to, units] })
|
|
22707
23089
|
});
|
|
22708
23090
|
}
|
|
22709
23091
|
}
|
|
@@ -22746,7 +23128,7 @@ var TokenSender = class {
|
|
|
22746
23128
|
});
|
|
22747
23129
|
const required = shortfall > 0n ? shortfall : 0n;
|
|
22748
23130
|
if (available < required) continue;
|
|
22749
|
-
const floor = vault.minBalance === void 0 ? 0n : (0,
|
|
23131
|
+
const floor = vault.minBalance === void 0 ? 0n : (0, import_viem30.parseUnits)(vault.minBalance, decimals);
|
|
22750
23132
|
const headroom = reserve > floor ? reserve : floor;
|
|
22751
23133
|
const wanted = shortfall + headroom;
|
|
22752
23134
|
const amount = available >= wanted ? wanted : required;
|
|
@@ -22754,7 +23136,7 @@ var TokenSender = class {
|
|
|
22754
23136
|
return {
|
|
22755
23137
|
target: vault.vault,
|
|
22756
23138
|
value: 0n,
|
|
22757
|
-
data: (0,
|
|
23139
|
+
data: (0, import_viem30.encodeFunctionData)({
|
|
22758
23140
|
abi: ERC4626_ABI,
|
|
22759
23141
|
functionName: "withdraw",
|
|
22760
23142
|
args: [amount, this.solver, this.solver]
|
|
@@ -22820,9 +23202,9 @@ var LimitOrderLifecycle = class {
|
|
|
22820
23202
|
const reconcileMs = this.options.reconcileIntervalSecs * 1e3;
|
|
22821
23203
|
this.every(heartbeatMs, "heartbeat", () => this.service.heartbeat());
|
|
22822
23204
|
this.every(EXPIRY_SWEEP_MS, "expiry", () => this.expire());
|
|
22823
|
-
this.every(reconcileMs, "reconciliation", () => this.
|
|
23205
|
+
this.every(reconcileMs, "reconciliation", () => this.upkeep());
|
|
22824
23206
|
this.logger.info({ heartbeatMs, expiryMs: EXPIRY_SWEEP_MS, reconcileMs }, "Orderbook lifecycle started");
|
|
22825
|
-
void this.
|
|
23207
|
+
void this.upkeep();
|
|
22826
23208
|
}
|
|
22827
23209
|
stop() {
|
|
22828
23210
|
this.timers.forEach((timer) => clearInterval(timer));
|
|
@@ -22833,6 +23215,14 @@ var LimitOrderLifecycle = class {
|
|
|
22833
23215
|
const expired = await this.service.expireStale();
|
|
22834
23216
|
if (expired > 0) this.logger.info({ expired }, "Withdrew limit orders that had outlived their expiry");
|
|
22835
23217
|
}
|
|
23218
|
+
/**
|
|
23219
|
+
* The tally pass first, so reconciliation against the orderbook reads the sizes
|
|
23220
|
+
* it left. Each fails on its own.
|
|
23221
|
+
*/
|
|
23222
|
+
async upkeep() {
|
|
23223
|
+
await this.run("tally reconciliation", async () => this.options.reconcileTallies());
|
|
23224
|
+
await this.run("reconciliation", () => this.reconcile());
|
|
23225
|
+
}
|
|
22836
23226
|
async reconcile() {
|
|
22837
23227
|
const report = await this.service.reconcile();
|
|
22838
23228
|
if (report.cancelled || report.reposted || report.underFunded) {
|
|
@@ -22868,7 +23258,7 @@ var LimitOrderLifecycle = class {
|
|
|
22868
23258
|
|
|
22869
23259
|
// src/services/BalanceProvider.ts
|
|
22870
23260
|
var import_node_events = require("node:events");
|
|
22871
|
-
var
|
|
23261
|
+
var import_viem31 = require("viem");
|
|
22872
23262
|
var BalanceProvider = class extends import_node_events.EventEmitter {
|
|
22873
23263
|
constructor(options) {
|
|
22874
23264
|
super();
|
|
@@ -22961,7 +23351,7 @@ var BalanceProvider = class extends import_node_events.EventEmitter {
|
|
|
22961
23351
|
try {
|
|
22962
23352
|
const native = await client.getBalance({ address: fillerAddr });
|
|
22963
23353
|
const symbol = nativeTokenSymbol(chainId);
|
|
22964
|
-
row.native = { symbol, amount: Number.parseFloat((0,
|
|
23354
|
+
row.native = { symbol, amount: Number.parseFloat((0, import_viem31.formatUnits)(native, 18)) };
|
|
22965
23355
|
} catch (err) {
|
|
22966
23356
|
issues.push({ chainId, source: "native", message: errorMessage(err) });
|
|
22967
23357
|
}
|
|
@@ -23032,7 +23422,7 @@ var BalanceProvider = class extends import_node_events.EventEmitter {
|
|
|
23032
23422
|
functionName: "balanceOf",
|
|
23033
23423
|
args: [fillerAddr]
|
|
23034
23424
|
});
|
|
23035
|
-
wallet = Number.parseFloat((0,
|
|
23425
|
+
wallet = Number.parseFloat((0, import_viem31.formatUnits)(balance, definition.decimals));
|
|
23036
23426
|
} catch (err) {
|
|
23037
23427
|
issues.push({
|
|
23038
23428
|
chainId,
|
|
@@ -23060,8 +23450,8 @@ var BalanceProvider = class extends import_node_events.EventEmitter {
|
|
|
23060
23450
|
available: wallet !== null && vaultDataAvailable ? Math.max(wallet - walletReserve, 0) + vaultAvailable : null,
|
|
23061
23451
|
vaults: positions.map((position) => ({
|
|
23062
23452
|
address: position.vault,
|
|
23063
|
-
position: Number.parseFloat((0,
|
|
23064
|
-
available: Number.parseFloat((0,
|
|
23453
|
+
position: Number.parseFloat((0, import_viem31.formatUnits)(position.positionAssets, position.decimals)),
|
|
23454
|
+
available: Number.parseFloat((0, import_viem31.formatUnits)(position.availableAssets, position.decimals)),
|
|
23065
23455
|
acceptsDeposits: position.acceptsDeposits
|
|
23066
23456
|
})),
|
|
23067
23457
|
status: complete ? "fresh" : wallet !== null || positions.length > 0 ? "partial" : "unavailable"
|
|
@@ -23104,8 +23494,8 @@ var BalanceProvider = class extends import_node_events.EventEmitter {
|
|
|
23104
23494
|
const api = await coprocessor.queryApi();
|
|
23105
23495
|
const account = await api.query.system.account(address);
|
|
23106
23496
|
const decimals = api.registry.chainDecimals[0] ?? 12;
|
|
23107
|
-
const free = Number.parseFloat((0,
|
|
23108
|
-
const reserved = Number.parseFloat((0,
|
|
23497
|
+
const free = Number.parseFloat((0, import_viem31.formatUnits)(BigInt(account.data.free.toString()), decimals));
|
|
23498
|
+
const reserved = Number.parseFloat((0, import_viem31.formatUnits)(BigInt(account.data.reserved.toString()), decimals));
|
|
23109
23499
|
this.snapshot = { ...this.snapshot, hyperbridge: { address, free, reserved } };
|
|
23110
23500
|
} catch (err) {
|
|
23111
23501
|
this.logger.warn({ err }, "Failed to fetch Hyperbridge balance");
|
|
@@ -23125,7 +23515,7 @@ function isConfiguredAddress(address) {
|
|
|
23125
23515
|
}
|
|
23126
23516
|
function sumFormatted(positions, field) {
|
|
23127
23517
|
return positions.reduce(
|
|
23128
|
-
(total, position) => total + Number.parseFloat((0,
|
|
23518
|
+
(total, position) => total + Number.parseFloat((0, import_viem31.formatUnits)(position[field], position.decimals)),
|
|
23129
23519
|
0
|
|
23130
23520
|
);
|
|
23131
23521
|
}
|
|
@@ -23590,6 +23980,7 @@ async function bootFiller(config, options) {
|
|
|
23590
23980
|
rebalancing: config.rebalancing,
|
|
23591
23981
|
targetGasUnits: config.simplex.targetGasUnits,
|
|
23592
23982
|
bidValiditySeconds: config.simplex.bidValiditySeconds,
|
|
23983
|
+
minOrderSizeUsd: config.simplex.minOrderSizeUsd,
|
|
23593
23984
|
blockScanIntervalSeconds: config.simplex.blockScanIntervalSeconds,
|
|
23594
23985
|
gasFeeBump: config.simplex.gasFeeBump,
|
|
23595
23986
|
overfillProtection: config.simplex.overfillProtection,
|
|
@@ -23633,7 +24024,7 @@ async function bootFiller(config, options) {
|
|
|
23633
24024
|
const sharedCacheService = new CacheService(options.loggers);
|
|
23634
24025
|
if (!options.signer && !globalWatchOnly && !allChainsWatchOnly(watchOnlyConfig, resolvedChains)) {
|
|
23635
24026
|
throw new Error(
|
|
23636
|
-
"A signer is required unless every chain is watch-only: pass `signer` to Simplex.start (privateKeySigner, turnkeySigner, mpcVaultSigner, viemSigner, or your own Signer implementation). Running the binary? Configure it under [simplex.signer]."
|
|
24027
|
+
"A signer is required unless every chain is watch-only: pass `signer` to Simplex.start (privateKeySigner, secretPhraseSigner, turnkeySigner, mpcVaultSigner, viemSigner, or your own Signer implementation). Running the binary? Configure it under [simplex.signer]."
|
|
23637
24028
|
);
|
|
23638
24029
|
}
|
|
23639
24030
|
const chainClientManager = new ChainClientManager(configService, options.signer);
|
|
@@ -23772,7 +24163,8 @@ async function bootFiller(config, options) {
|
|
|
23772
24163
|
const lifecycle = new LimitOrderLifecycle(
|
|
23773
24164
|
limitOrderService,
|
|
23774
24165
|
{
|
|
23775
|
-
reconcileIntervalSecs: config.orderbook?.reconcileIntervalSecs ?? DEFAULT_RECONCILE_INTERVAL_SECS
|
|
24166
|
+
reconcileIntervalSecs: config.orderbook?.reconcileIntervalSecs ?? DEFAULT_RECONCILE_INTERVAL_SECS,
|
|
24167
|
+
reconcileTallies: () => intentFiller.reconcileLimitOrderTallies()
|
|
23776
24168
|
},
|
|
23777
24169
|
options.loggers
|
|
23778
24170
|
);
|
|
@@ -23810,8 +24202,8 @@ async function bootFiller(config, options) {
|
|
|
23810
24202
|
if (vaultVenue) {
|
|
23811
24203
|
vaultVenue.onTx = ({ chain, kind, txHash, sponsored, movements }) => {
|
|
23812
24204
|
const rows = movements.length > 0 ? movements.map((move) => {
|
|
23813
|
-
const underlying = { token: move.symbol, amount: (0,
|
|
23814
|
-
const shares = move.shares === void 0 ? { token: null, amount: null } : { token: move.shareSymbol, amount: (0,
|
|
24205
|
+
const underlying = { token: move.symbol, amount: (0, import_viem32.formatUnits)(move.amount, move.decimals) };
|
|
24206
|
+
const shares = move.shares === void 0 ? { token: null, amount: null } : { token: move.shareSymbol, amount: (0, import_viem32.formatUnits)(move.shares, move.shareDecimals) };
|
|
23815
24207
|
const out = kind === "sweep" ? underlying : shares;
|
|
23816
24208
|
const back = kind === "sweep" ? shares : underlying;
|
|
23817
24209
|
return { token: out.token, amount: out.amount, to: move.vault, tokenIn: back.token, amountIn: back.amount };
|
|
@@ -24243,6 +24635,14 @@ var MemoryLimitOrderStore = class {
|
|
|
24243
24635
|
const remaining = BigInt(order.remaining) - BigInt(amount);
|
|
24244
24636
|
return this.patch(id, { remaining: (remaining > 0n ? remaining : 0n).toString() });
|
|
24245
24637
|
}
|
|
24638
|
+
async clampRemaining(id, room) {
|
|
24639
|
+
const order = this.orders.get(id);
|
|
24640
|
+
if (!order || order.status !== "open") return null;
|
|
24641
|
+
const sum = BigInt(room) + BigInt(order.reserved);
|
|
24642
|
+
const target = sum > 0n ? sum : 0n;
|
|
24643
|
+
if (target >= BigInt(order.remaining)) return null;
|
|
24644
|
+
return this.patch(id, { remaining: target.toString() });
|
|
24645
|
+
}
|
|
24246
24646
|
async release(id, amount) {
|
|
24247
24647
|
const order = this.orders.get(id);
|
|
24248
24648
|
if (!order) return;
|
|
@@ -24751,7 +25151,8 @@ var OrderScanner = class _OrderScanner {
|
|
|
24751
25151
|
* back from its own endpoints, which also proves they answer for one chain
|
|
24752
25152
|
* before a filler ever depends on them.
|
|
24753
25153
|
*
|
|
24754
|
-
* @throws if an endpoint set is invalid
|
|
25154
|
+
* @throws if an endpoint set is invalid, if none of a chain's endpoints answer,
|
|
25155
|
+
* if its endpoints disagree on the chain, if two entries resolve
|
|
24755
25156
|
* to the same chain, or if `scanIntervalSecs` is below the minimum.
|
|
24756
25157
|
*/
|
|
24757
25158
|
static async create(options) {
|
|
@@ -24811,7 +25212,10 @@ var OrderScanner = class _OrderScanner {
|
|
|
24811
25212
|
async addChainNow(chain) {
|
|
24812
25213
|
if (this.closed) throw new Error("This OrderScanner is closed");
|
|
24813
25214
|
const rpcUrls = validateRpcUrls(chain.rpcUrls);
|
|
24814
|
-
const chainId = chain.chainId ?? await
|
|
25215
|
+
const chainId = chain.chainId ?? (await resolveChainConfigs([{ rpcUrls, bundlerUrl: chain.bundlerUrl ?? "" }], {
|
|
25216
|
+
loggers: this.loggers,
|
|
25217
|
+
tolerateUnreachable: true
|
|
25218
|
+
}))[0].chainId;
|
|
24815
25219
|
if (this.closed) throw new Error("This OrderScanner is closed");
|
|
24816
25220
|
if (this.scanners.has(chainId)) {
|
|
24817
25221
|
throw new Error(`Chain ${chainId} is already in this scanner`);
|
|
@@ -24911,7 +25315,7 @@ var LimitOrderController = class {
|
|
|
24911
25315
|
get service() {
|
|
24912
25316
|
const service = this.runtime.limitOrders;
|
|
24913
25317
|
if (!service) {
|
|
24914
|
-
throw new Error("No orderbook is configured \u2014 set [orderbook]
|
|
25318
|
+
throw new Error("No orderbook is configured \u2014 set [orderbook] url to use limit orders");
|
|
24915
25319
|
}
|
|
24916
25320
|
return service;
|
|
24917
25321
|
}
|
|
@@ -25376,7 +25780,7 @@ var Simplex = class _Simplex extends import_node_events3.EventEmitter {
|
|
|
25376
25780
|
const tomlSigner = options.config.simplex?.signer;
|
|
25377
25781
|
if (tomlSigner && !options.signer) {
|
|
25378
25782
|
throw new Error(
|
|
25379
|
-
"config.simplex.signer describes a signer but none was passed. Simplex.start takes a Signer instance: `signer: await createSigner(config.simplex.signer)`, or build one directly with privateKeySigner/turnkeySigner/mpcVaultSigner/viemSigner."
|
|
25783
|
+
"config.simplex.signer describes a signer but none was passed. Simplex.start takes a Signer instance: `signer: await createSigner(config.simplex.signer)`, or build one directly with privateKeySigner/secretPhraseSigner/turnkeySigner/mpcVaultSigner/viemSigner."
|
|
25380
25784
|
);
|
|
25381
25785
|
}
|
|
25382
25786
|
const loggers = new LoggerContext({
|
|
@@ -25580,6 +25984,8 @@ var Simplex = class _Simplex extends import_node_events3.EventEmitter {
|
|
|
25580
25984
|
0 && (module.exports = {
|
|
25581
25985
|
AssetController,
|
|
25582
25986
|
ChainController,
|
|
25987
|
+
LimitOrderController,
|
|
25988
|
+
LimitOrderValidationError,
|
|
25583
25989
|
LoggerContext,
|
|
25584
25990
|
MemoryDataStore,
|
|
25585
25991
|
OrderScanner,
|
|
@@ -25592,6 +25998,7 @@ var Simplex = class _Simplex extends import_node_events3.EventEmitter {
|
|
|
25592
25998
|
WalletController,
|
|
25593
25999
|
addLogSink,
|
|
25594
26000
|
assertConfirmationCoverage,
|
|
26001
|
+
canDerive,
|
|
25595
26002
|
configureLogger,
|
|
25596
26003
|
createSigner,
|
|
25597
26004
|
digestSigner,
|
|
@@ -25604,10 +26011,12 @@ var Simplex = class _Simplex extends import_node_events3.EventEmitter {
|
|
|
25604
26011
|
pickAnchorStable,
|
|
25605
26012
|
privateKeySigner,
|
|
25606
26013
|
registrySymbols,
|
|
26014
|
+
secretPhraseSigner,
|
|
25607
26015
|
turnkeySigner,
|
|
25608
26016
|
validateAssetDefinitions,
|
|
25609
26017
|
validateConfig,
|
|
25610
26018
|
validatePairConfigs,
|
|
26019
|
+
validateSecretPhrase,
|
|
25611
26020
|
validateSignerConfig,
|
|
25612
26021
|
validateVaultToml,
|
|
25613
26022
|
viemSigner
|