@hyperbridge/simplex 0.16.7 → 0.16.11
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 +23907 -2147
- 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 +644 -340
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +205 -104
- package/dist/index.d.ts +205 -104
- package/dist/index.js +515 -216
- package/dist/index.js.map +1 -1
- package/dist/sqlite.cjs +13 -1
- 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 +13 -1
- package/dist/sqlite.js.map +1 -1
- package/dist/{types-DUDABLPI.d.cts → types-B5zkFhxR.d.cts} +17 -5
- package/dist/{types-DUDABLPI.d.ts → types-B5zkFhxR.d.ts} +17 -5
- package/dist/ui/assets/{index-C3T-gubp.css → index-B_p2oQkc.css} +1 -1
- package/dist/ui/assets/{index-Be446eYK.js → index-CD2NZXpG.js} +35 -35
- package/dist/ui/index.html +2 -2
- package/package.json +13 -2
package/dist/index.js
CHANGED
|
@@ -2781,11 +2781,21 @@ import { randomBytes, randomUUID } from "node:crypto";
|
|
|
2781
2781
|
import { getChainId, MAX_DECLARED_ENTRIES } from "@hyperbridge/sdk";
|
|
2782
2782
|
|
|
2783
2783
|
// src/orderbook/amounts.ts
|
|
2784
|
+
import { keccak256, stringToBytes } from "viem";
|
|
2784
2785
|
var ORDERBOOK_SCALE = 10n ** 18n;
|
|
2785
2786
|
function toRaw(amount, decimals) {
|
|
2786
2787
|
const shift = 18 - decimals;
|
|
2787
2788
|
return shift >= 0 ? amount / 10n ** BigInt(shift) : amount * 10n ** BigInt(-shift);
|
|
2788
2789
|
}
|
|
2790
|
+
function budgetIdFor(limitOrderId) {
|
|
2791
|
+
return keccak256(stringToBytes(limitOrderId));
|
|
2792
|
+
}
|
|
2793
|
+
function budgetFor(order, token, decimals) {
|
|
2794
|
+
return { budgetId: budgetIdFor(order.id), cap: toRaw(BigInt(order.size), decimals), token };
|
|
2795
|
+
}
|
|
2796
|
+
function budgetRoom(budget, spent, decimals) {
|
|
2797
|
+
return spent >= budget.cap ? 0n : toScaled(budget.cap - spent, decimals);
|
|
2798
|
+
}
|
|
2789
2799
|
function fromHuman(amount) {
|
|
2790
2800
|
const text = amount.trim();
|
|
2791
2801
|
if (!/^[0-9]+(\.[0-9]+)?$/.test(text)) {
|
|
@@ -2806,6 +2816,10 @@ function toScaled(amount, decimals) {
|
|
|
2806
2816
|
const shift = 18 - decimals;
|
|
2807
2817
|
return shift >= 0 ? amount * 10n ** BigInt(shift) : amount / 10n ** BigInt(-shift);
|
|
2808
2818
|
}
|
|
2819
|
+
function toScaledUp(amount, decimals) {
|
|
2820
|
+
const shift = 18 - decimals;
|
|
2821
|
+
return shift >= 0 ? amount * 10n ** BigInt(shift) : divCeil(amount, 10n ** BigInt(-shift));
|
|
2822
|
+
}
|
|
2809
2823
|
function divCeil(numerator, denominator) {
|
|
2810
2824
|
return (numerator + denominator - 1n) / denominator;
|
|
2811
2825
|
}
|
|
@@ -2847,6 +2861,87 @@ function inputFor(params) {
|
|
|
2847
2861
|
return divCeil(scaled, unit) * unit;
|
|
2848
2862
|
}
|
|
2849
2863
|
|
|
2864
|
+
// src/orderbook/matching.ts
|
|
2865
|
+
function limitOrderLegs(order) {
|
|
2866
|
+
return order.side === "BID" ? { input: order.base, output: order.quote } : { input: order.quote, output: order.base };
|
|
2867
|
+
}
|
|
2868
|
+
function matchLimitOrders(orders, incoming, resolve, now = /* @__PURE__ */ new Date()) {
|
|
2869
|
+
const qualifying = orders.filter((order) => serves(order, incoming, resolve, now)).map((order) => {
|
|
2870
|
+
const offer = offerFor({
|
|
2871
|
+
side: order.side,
|
|
2872
|
+
inputAmount: incoming.inputNet,
|
|
2873
|
+
price: BigInt(order.price),
|
|
2874
|
+
outputDecimals: incoming.outputDecimals
|
|
2875
|
+
});
|
|
2876
|
+
const available = BigInt(order.remaining);
|
|
2877
|
+
return { order, offer, available, payout: offer < available ? offer : available };
|
|
2878
|
+
}).filter((candidate) => candidate.offer >= incoming.requestedOutput && candidate.payout > 0n).sort(byBestOfferFirst);
|
|
2879
|
+
return qualifying;
|
|
2880
|
+
}
|
|
2881
|
+
function byBestOfferFirst(a, b) {
|
|
2882
|
+
if (a.offer !== b.offer) return a.offer > b.offer ? -1 : 1;
|
|
2883
|
+
if (a.available !== b.available) return a.available > b.available ? -1 : 1;
|
|
2884
|
+
return a.order.id < b.order.id ? -1 : 1;
|
|
2885
|
+
}
|
|
2886
|
+
function whyUnmatched(orders, incoming, resolve, now = /* @__PURE__ */ new Date()) {
|
|
2887
|
+
let furthest = -1;
|
|
2888
|
+
for (const order of orders) {
|
|
2889
|
+
const failed = firstFailed(order, incoming, resolve, now);
|
|
2890
|
+
furthest = Math.max(furthest, failed === null ? CHECKS.length : CHECKS.indexOf(failed));
|
|
2891
|
+
}
|
|
2892
|
+
const { source, destination, inputSymbol } = incoming;
|
|
2893
|
+
if (furthest < CHECKS.length) {
|
|
2894
|
+
switch (CHECKS[Math.max(furthest, 0)]) {
|
|
2895
|
+
case "open":
|
|
2896
|
+
return "no limit order is open";
|
|
2897
|
+
case "chain":
|
|
2898
|
+
return `no open limit order fills on ${destination}`;
|
|
2899
|
+
case "input":
|
|
2900
|
+
return `no limit order on ${destination} takes ${inputSymbol} in`;
|
|
2901
|
+
case "output":
|
|
2902
|
+
return `no limit order on ${destination} taking ${inputSymbol} in pays out the token this order asks for`;
|
|
2903
|
+
case "source":
|
|
2904
|
+
return `no limit order for this pair on ${destination} accepts swaps from ${source}`;
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
const priced = orders.filter((order) => firstFailed(order, incoming, resolve, now) === null).map((order) => ({
|
|
2908
|
+
order,
|
|
2909
|
+
offer: offerFor({
|
|
2910
|
+
side: order.side,
|
|
2911
|
+
inputAmount: incoming.inputNet,
|
|
2912
|
+
price: BigInt(order.price),
|
|
2913
|
+
outputDecimals: incoming.outputDecimals
|
|
2914
|
+
})
|
|
2915
|
+
}));
|
|
2916
|
+
if (priced.some(({ offer }) => offer >= incoming.requestedOutput)) {
|
|
2917
|
+
return "the limit orders that meet its rate have nothing left to pay out";
|
|
2918
|
+
}
|
|
2919
|
+
const best = priced.reduce((a, b) => b.offer > a.offer ? b : a);
|
|
2920
|
+
const symbol = limitOrderLegs(best.order).output;
|
|
2921
|
+
return `it asks for ${readable(incoming.requestedOutput)} ${symbol}; the best limit order offers ${readable(best.offer)} ${symbol}`;
|
|
2922
|
+
}
|
|
2923
|
+
var CHECKS = ["open", "chain", "input", "output", "source"];
|
|
2924
|
+
function serves(order, incoming, resolve, now) {
|
|
2925
|
+
return firstFailed(order, incoming, resolve, now) === null;
|
|
2926
|
+
}
|
|
2927
|
+
function firstFailed(order, incoming, resolve, now) {
|
|
2928
|
+
if (order.status !== "open") return "open";
|
|
2929
|
+
if (order.expiresAt !== null && new Date(order.expiresAt) <= now) return "open";
|
|
2930
|
+
if (order.fillChain !== incoming.destination) return "chain";
|
|
2931
|
+
const legs = limitOrderLegs(order);
|
|
2932
|
+
if (normalizeSymbol(legs.input) !== normalizeSymbol(incoming.inputSymbol)) return "input";
|
|
2933
|
+
const outputToken = resolve(legs.output, order.fillChain);
|
|
2934
|
+
if (!outputToken || outputToken.toLowerCase() !== incoming.outputToken.toLowerCase()) return "output";
|
|
2935
|
+
if (incoming.source === incoming.destination) return null;
|
|
2936
|
+
return order.acceptedSources.includes(incoming.source) ? null : "source";
|
|
2937
|
+
}
|
|
2938
|
+
function readable(amount) {
|
|
2939
|
+
const [whole, fraction] = toHuman(amount).split(".");
|
|
2940
|
+
const grouped = whole.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
2941
|
+
const cut = fraction?.slice(0, 6).replace(/0+$/, "");
|
|
2942
|
+
return cut ? `${grouped}.${cut}` : grouped;
|
|
2943
|
+
}
|
|
2944
|
+
|
|
2850
2945
|
// src/orderbook/limit-orders.ts
|
|
2851
2946
|
var LIMITS_TTL_MS = 5 * 60 * 1e3;
|
|
2852
2947
|
var POSTABLE = ["open", "resizing"];
|
|
@@ -3361,6 +3456,58 @@ var LimitOrderService = class {
|
|
|
3361
3456
|
}
|
|
3362
3457
|
return report;
|
|
3363
3458
|
}
|
|
3459
|
+
/**
|
|
3460
|
+
* Lowers every open order's `remaining` to what the solver's account will still
|
|
3461
|
+
* pay for it, and puts the order back on the book at that size.
|
|
3462
|
+
*
|
|
3463
|
+
* The account tallies each order's payouts on the fill chain and refuses a fill
|
|
3464
|
+
* that would take the tally past the order's size. A fill that never settled
|
|
3465
|
+
* here, or a store restored from an older copy, leaves `remaining` above that
|
|
3466
|
+
* room and the order posted at a size the account will refuse. A tally behind
|
|
3467
|
+
* the store changes nothing: `remaining` is never raised.
|
|
3468
|
+
*
|
|
3469
|
+
* Answers how many orders were lowered.
|
|
3470
|
+
*/
|
|
3471
|
+
async reconcileTallies() {
|
|
3472
|
+
let lowered = 0;
|
|
3473
|
+
for (const order of await this.store.list({ status: "open" })) {
|
|
3474
|
+
try {
|
|
3475
|
+
const clamped = await this.clampToTally(order);
|
|
3476
|
+
if (!clamped) continue;
|
|
3477
|
+
lowered++;
|
|
3478
|
+
await this.resize(clamped, BigInt(order.remaining) - BigInt(clamped.remaining));
|
|
3479
|
+
} catch (err) {
|
|
3480
|
+
this.logger.error({ id: order.id, err }, "Could not bring the limit order in line with its on-chain tally");
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
return lowered;
|
|
3484
|
+
}
|
|
3485
|
+
/** The order as the store now holds it when its tally lowered it, null when it was left alone. */
|
|
3486
|
+
async clampToTally(order) {
|
|
3487
|
+
const token = this.assetRegistry.getAddress(limitOrderLegs(order).output, order.fillChain);
|
|
3488
|
+
if (!token) return null;
|
|
3489
|
+
const decimals = await this.contractService.getTokenDecimals(token, order.fillChain);
|
|
3490
|
+
const budget = budgetFor(order, token, decimals);
|
|
3491
|
+
const spent = await this.contractService.limitOrderSpent(order.fillChain, budget.budgetId);
|
|
3492
|
+
if (spent === null) return null;
|
|
3493
|
+
const room = budgetRoom(budget, spent, decimals);
|
|
3494
|
+
const over = BigInt(order.remaining) - (room + BigInt(order.reserved));
|
|
3495
|
+
if (over < toScaled(1n, decimals)) return null;
|
|
3496
|
+
const clamped = await this.store.clampRemaining(order.id, room.toString());
|
|
3497
|
+
if (!clamped) return null;
|
|
3498
|
+
this.logger.warn(
|
|
3499
|
+
{
|
|
3500
|
+
id: order.id,
|
|
3501
|
+
before: order.remaining,
|
|
3502
|
+
after: clamped.remaining,
|
|
3503
|
+
spent: spent.toString(),
|
|
3504
|
+
cap: budget.cap.toString(),
|
|
3505
|
+
reserved: clamped.reserved
|
|
3506
|
+
},
|
|
3507
|
+
"Limit order had more left than its on-chain tally allows; lowering it"
|
|
3508
|
+
);
|
|
3509
|
+
return clamped;
|
|
3510
|
+
}
|
|
3364
3511
|
/**
|
|
3365
3512
|
* Withdraws every order that has outlived the operator's own `expiresAt`.
|
|
3366
3513
|
*
|
|
@@ -3622,7 +3769,7 @@ async function patchRuntimeState(store, patch) {
|
|
|
3622
3769
|
}
|
|
3623
3770
|
|
|
3624
3771
|
// src/services/wallet/mpcvault.ts
|
|
3625
|
-
import { concatHex, keccak256, padHex, toHex } from "viem";
|
|
3772
|
+
import { concatHex, keccak256 as keccak2562, padHex, toHex } from "viem";
|
|
3626
3773
|
import * as grpc from "@grpc/grpc-js";
|
|
3627
3774
|
|
|
3628
3775
|
// src/proto/mpcvault/platform/v1/api.ts
|
|
@@ -12235,7 +12382,7 @@ var MpcVaultService = class {
|
|
|
12235
12382
|
const messageBytes = Buffer.from(messageHash.slice(2), "hex");
|
|
12236
12383
|
const prefix = Buffer.from(`Ethereum Signed Message:
|
|
12237
12384
|
${messageBytes.length}`);
|
|
12238
|
-
const ethSignedMessageHash =
|
|
12385
|
+
const ethSignedMessageHash = keccak2562(concatHex([toHex(prefix), messageHash]));
|
|
12239
12386
|
const ethSignedBytes = Buffer.from(ethSignedMessageHash.slice(2), "hex");
|
|
12240
12387
|
const uuid = await this.createSigningRequest({
|
|
12241
12388
|
vaultUuid: this.vaultUuid,
|
|
@@ -12323,11 +12470,12 @@ var SignerType = /* @__PURE__ */ ((SignerType2) => {
|
|
|
12323
12470
|
SignerType2["PrivateKey"] = "privateKey";
|
|
12324
12471
|
SignerType2["MpcVault"] = "mpcVault";
|
|
12325
12472
|
SignerType2["Turnkey"] = "turnkey";
|
|
12473
|
+
SignerType2["SecretPhrase"] = "secretPhrase";
|
|
12326
12474
|
return SignerType2;
|
|
12327
12475
|
})(SignerType || {});
|
|
12328
12476
|
|
|
12329
12477
|
// src/services/wallet/accounts/mpc.ts
|
|
12330
|
-
import { keccak256 as
|
|
12478
|
+
import { keccak256 as keccak2563, serializeTransaction } from "viem";
|
|
12331
12479
|
import { hashAuthorization } from "viem/utils";
|
|
12332
12480
|
function mpcVaultSigner(config) {
|
|
12333
12481
|
const service = new MpcVaultService({
|
|
@@ -12362,7 +12510,7 @@ function mpcVaultSigner(config) {
|
|
|
12362
12510
|
if (tx.authorizationList?.length) {
|
|
12363
12511
|
const serializable = { ...tx, type: "eip7702" };
|
|
12364
12512
|
const signature = await service.signRawHashComponents(
|
|
12365
|
-
|
|
12513
|
+
keccak2563(serializeTransaction(serializable))
|
|
12366
12514
|
);
|
|
12367
12515
|
return serializeTransaction(serializable, signature);
|
|
12368
12516
|
}
|
|
@@ -12444,6 +12592,51 @@ function privateKeySigner(privateKey) {
|
|
|
12444
12592
|
return viemSigner(privateKeyToAccount(privateKey));
|
|
12445
12593
|
}
|
|
12446
12594
|
|
|
12595
|
+
// src/services/wallet/accounts/secretphrase.ts
|
|
12596
|
+
import { validateMnemonic } from "@scure/bip39";
|
|
12597
|
+
import { english, mnemonicToAccount } from "viem/accounts";
|
|
12598
|
+
var WORD_COUNTS = [12, 15, 18, 21, 24];
|
|
12599
|
+
var MAX_DERIVATION_INDEX = 2 ** 31 - 1;
|
|
12600
|
+
function normaliseSecretPhrase(phrase) {
|
|
12601
|
+
return phrase.trim().split(/\s+/).join(" ").toLowerCase();
|
|
12602
|
+
}
|
|
12603
|
+
function validateSecretPhrase(phrase) {
|
|
12604
|
+
if (typeof phrase !== "string") throw new Error("Secret phrase must be a string");
|
|
12605
|
+
const normalised = normaliseSecretPhrase(phrase);
|
|
12606
|
+
const words = normalised === "" ? [] : normalised.split(" ");
|
|
12607
|
+
if (!WORD_COUNTS.includes(words.length)) {
|
|
12608
|
+
throw new Error(`Secret phrase must have ${WORD_COUNTS.join(", ")} words; got ${words.length}`);
|
|
12609
|
+
}
|
|
12610
|
+
if (words.some((word) => !english.includes(word))) {
|
|
12611
|
+
throw new Error("Secret phrase contains a word that is not in the BIP-39 English wordlist");
|
|
12612
|
+
}
|
|
12613
|
+
if (!validateMnemonic(normalised, english)) {
|
|
12614
|
+
throw new Error("Secret phrase checksum does not match; check the words and their order");
|
|
12615
|
+
}
|
|
12616
|
+
}
|
|
12617
|
+
function assertDerivationIndex(index, name = "Derivation index") {
|
|
12618
|
+
const expected = `${name} must be an integer between 0 and ${MAX_DERIVATION_INDEX}`;
|
|
12619
|
+
if (typeof index !== "number") throw new Error(expected);
|
|
12620
|
+
if (!Number.isInteger(index) || index < 0 || index > MAX_DERIVATION_INDEX) {
|
|
12621
|
+
throw new Error(`${expected}; got ${index}`);
|
|
12622
|
+
}
|
|
12623
|
+
}
|
|
12624
|
+
function secretPhraseSigner(config) {
|
|
12625
|
+
validateSecretPhrase(config.phrase);
|
|
12626
|
+
const phrase = normaliseSecretPhrase(config.phrase);
|
|
12627
|
+
const accountIndex = config.accountIndex ?? 0;
|
|
12628
|
+
assertDerivationIndex(accountIndex, "accountIndex");
|
|
12629
|
+
const derive = (index) => {
|
|
12630
|
+
assertDerivationIndex(index);
|
|
12631
|
+
return { ...viemSigner(mnemonicToAccount(phrase, { addressIndex: index })), mode: "secretPhrase" };
|
|
12632
|
+
};
|
|
12633
|
+
return { ...derive(accountIndex), accountIndex, derive };
|
|
12634
|
+
}
|
|
12635
|
+
function canDerive(signer) {
|
|
12636
|
+
const candidate = signer;
|
|
12637
|
+
return typeof candidate.derive === "function" && typeof candidate.accountIndex === "number";
|
|
12638
|
+
}
|
|
12639
|
+
|
|
12447
12640
|
// src/services/wallet/accounts/turnkey.ts
|
|
12448
12641
|
import { Turnkey } from "@turnkey/sdk-server";
|
|
12449
12642
|
import { createAccount } from "@turnkey/viem";
|
|
@@ -12467,6 +12660,7 @@ async function createSigner(config) {
|
|
|
12467
12660
|
if (config.type === "privateKey" /* PrivateKey */) return privateKeySigner(config.key);
|
|
12468
12661
|
if (config.type === "mpcVault" /* MpcVault */) return mpcVaultSigner(config);
|
|
12469
12662
|
if (config.type === "turnkey" /* Turnkey */) return turnkeySigner(config);
|
|
12663
|
+
if (config.type === "secretPhrase" /* SecretPhrase */) return secretPhraseSigner(config);
|
|
12470
12664
|
throw new Error(`Unsupported signer mode: ${config.type ?? "unknown"}`);
|
|
12471
12665
|
}
|
|
12472
12666
|
function validateSignerConfig(config) {
|
|
@@ -12492,13 +12686,21 @@ function validateSignerConfig(config) {
|
|
|
12492
12686
|
if (!config.signWith) throw new Error("simplex.signer.signWith is required");
|
|
12493
12687
|
return;
|
|
12494
12688
|
}
|
|
12689
|
+
if (config.type === "secretPhrase" /* SecretPhrase */) {
|
|
12690
|
+
if (!config.phrase) {
|
|
12691
|
+
throw new Error("simplex.signer.phrase is required when simplex.signer.type=secretPhrase");
|
|
12692
|
+
}
|
|
12693
|
+
validateSecretPhrase(config.phrase);
|
|
12694
|
+
if (config.accountIndex !== void 0) assertDerivationIndex(config.accountIndex, "simplex.signer.accountIndex");
|
|
12695
|
+
return;
|
|
12696
|
+
}
|
|
12495
12697
|
throw new Error(`Unsupported signer mode: ${config.type ?? "unknown"}`);
|
|
12496
12698
|
}
|
|
12497
12699
|
|
|
12498
12700
|
// src/services/wallet/account.ts
|
|
12499
12701
|
import {
|
|
12500
12702
|
hashTypedData,
|
|
12501
|
-
keccak256 as
|
|
12703
|
+
keccak256 as keccak2564,
|
|
12502
12704
|
serializeSignature,
|
|
12503
12705
|
serializeTransaction as serializeTransaction2
|
|
12504
12706
|
} from "viem";
|
|
@@ -12555,7 +12757,7 @@ function digestSigner(opts) {
|
|
|
12555
12757
|
),
|
|
12556
12758
|
signTransaction: async (tx) => {
|
|
12557
12759
|
const serializable = tx;
|
|
12558
|
-
const signature = await sign(
|
|
12760
|
+
const signature = await sign(keccak2564(serializeTransaction2(serializable)));
|
|
12559
12761
|
return serializeTransaction2(serializable, signature);
|
|
12560
12762
|
}
|
|
12561
12763
|
};
|
|
@@ -12585,6 +12787,12 @@ async function deriveSubstrateKeyPair(key) {
|
|
|
12585
12787
|
}
|
|
12586
12788
|
|
|
12587
12789
|
// src/services/CacheService.ts
|
|
12790
|
+
function budgetToCache(budget) {
|
|
12791
|
+
return { budgetId: budget.budgetId, cap: budget.cap.toString(), token: budget.token };
|
|
12792
|
+
}
|
|
12793
|
+
function budgetFromCache(budget) {
|
|
12794
|
+
return { budgetId: budget.budgetId, cap: BigInt(budget.cap), token: budget.token };
|
|
12795
|
+
}
|
|
12588
12796
|
var CacheService = class {
|
|
12589
12797
|
constructor(loggers = defaultLoggerContext()) {
|
|
12590
12798
|
this.CACHE_EXPIRY_MS = 1 * 60 * 1e3;
|
|
@@ -12742,7 +12950,24 @@ var CacheService = class {
|
|
|
12742
12950
|
return null;
|
|
12743
12951
|
}
|
|
12744
12952
|
}
|
|
12745
|
-
|
|
12953
|
+
/**
|
|
12954
|
+
* The budget of the limit order that priced the outputs cached for this order, or
|
|
12955
|
+
* null when no limit order did.
|
|
12956
|
+
*
|
|
12957
|
+
* Kept on the same entry as the outputs, so the two are replaced and expire
|
|
12958
|
+
* together: a bid can never sign one limit order's payout beside another's budget.
|
|
12959
|
+
*/
|
|
12960
|
+
getBidBudget(orderId) {
|
|
12961
|
+
try {
|
|
12962
|
+
const cache = this.cacheData.fillerOutputs[orderId];
|
|
12963
|
+
if (cache?.budget && this.isCacheValid(cache.timestamp)) return budgetFromCache(cache.budget);
|
|
12964
|
+
return null;
|
|
12965
|
+
} catch (error) {
|
|
12966
|
+
this.logger.error({ err: error }, "Error getting bid budget");
|
|
12967
|
+
return null;
|
|
12968
|
+
}
|
|
12969
|
+
}
|
|
12970
|
+
setFillerOutputs(orderId, outputs, inputs = [], budget) {
|
|
12746
12971
|
try {
|
|
12747
12972
|
this.cleanupStaleData();
|
|
12748
12973
|
this.cacheData.fillerOutputs[orderId] = {
|
|
@@ -12754,6 +12979,7 @@ var CacheService = class {
|
|
|
12754
12979
|
token: o.token,
|
|
12755
12980
|
amount: o.amount.toString()
|
|
12756
12981
|
})),
|
|
12982
|
+
...budget ? { budget: budgetToCache(budget) } : {},
|
|
12757
12983
|
timestamp: Date.now()
|
|
12758
12984
|
};
|
|
12759
12985
|
} catch (error) {
|
|
@@ -12799,7 +13025,8 @@ var CacheService = class {
|
|
|
12799
13025
|
data: call.data
|
|
12800
13026
|
})),
|
|
12801
13027
|
partialFill: plan.partialFill,
|
|
12802
|
-
profit: plan.profit
|
|
13028
|
+
profit: plan.profit,
|
|
13029
|
+
...plan.budget ? { budget: budgetFromCache(plan.budget) } : {}
|
|
12803
13030
|
}));
|
|
12804
13031
|
} catch (error) {
|
|
12805
13032
|
this.logger.error({ err: error }, "Error getting bid plans");
|
|
@@ -12828,7 +13055,8 @@ var CacheService = class {
|
|
|
12828
13055
|
data: call.data
|
|
12829
13056
|
})),
|
|
12830
13057
|
partialFill: plan.partialFill,
|
|
12831
|
-
profit: plan.profit
|
|
13058
|
+
profit: plan.profit,
|
|
13059
|
+
...plan.budget ? { budget: budgetToCache(plan.budget) } : {}
|
|
12832
13060
|
})),
|
|
12833
13061
|
timestamp: Date.now()
|
|
12834
13062
|
};
|
|
@@ -13661,6 +13889,44 @@ import {
|
|
|
13661
13889
|
bytes20ToBytes32
|
|
13662
13890
|
} from "@hyperbridge/sdk";
|
|
13663
13891
|
|
|
13892
|
+
// src/config/abis/SolverAccount.ts
|
|
13893
|
+
var SOLVER_ACCOUNT_ABI = [
|
|
13894
|
+
{
|
|
13895
|
+
inputs: [
|
|
13896
|
+
{ name: "orderId", type: "bytes32" },
|
|
13897
|
+
{ name: "cap", type: "uint256" },
|
|
13898
|
+
{ name: "token", type: "address" },
|
|
13899
|
+
{ name: "approved", type: "uint256" },
|
|
13900
|
+
{ name: "fee", type: "uint256" }
|
|
13901
|
+
],
|
|
13902
|
+
name: "debitOrder",
|
|
13903
|
+
outputs: [],
|
|
13904
|
+
stateMutability: "nonpayable",
|
|
13905
|
+
type: "function"
|
|
13906
|
+
},
|
|
13907
|
+
{
|
|
13908
|
+
inputs: [{ name: "orderId", type: "bytes32" }],
|
|
13909
|
+
name: "spent",
|
|
13910
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
13911
|
+
stateMutability: "view",
|
|
13912
|
+
type: "function"
|
|
13913
|
+
},
|
|
13914
|
+
{
|
|
13915
|
+
inputs: [{ name: "sender", type: "address" }],
|
|
13916
|
+
name: "AccountUnauthorized",
|
|
13917
|
+
type: "error"
|
|
13918
|
+
},
|
|
13919
|
+
{
|
|
13920
|
+
inputs: [
|
|
13921
|
+
{ name: "orderId", type: "bytes32" },
|
|
13922
|
+
{ name: "total", type: "uint256" },
|
|
13923
|
+
{ name: "cap", type: "uint256" }
|
|
13924
|
+
],
|
|
13925
|
+
name: "LimitOrderExceeded",
|
|
13926
|
+
type: "error"
|
|
13927
|
+
}
|
|
13928
|
+
];
|
|
13929
|
+
|
|
13664
13930
|
// src/config/abis/EvmHost.ts
|
|
13665
13931
|
var EVM_HOST = [
|
|
13666
13932
|
{
|
|
@@ -18054,6 +18320,10 @@ function describeShortfall(balances) {
|
|
|
18054
18320
|
// src/services/ContractInteractionService.ts
|
|
18055
18321
|
Decimal2.config({ precision: 28, rounding: 4 });
|
|
18056
18322
|
var FUNDING_GAS_PER_CALL = 400000n;
|
|
18323
|
+
var DEBIT_ORDER_GAS = 60000n;
|
|
18324
|
+
function settles(budget) {
|
|
18325
|
+
return !!budget && budget.token.toLowerCase() !== ADDRESS_ZERO;
|
|
18326
|
+
}
|
|
18057
18327
|
var LIMIT_ORDER_CALL_GAS_LIMIT = 500000n;
|
|
18058
18328
|
var LIMIT_ORDER_VERIFICATION_GAS_LIMIT = 150000n;
|
|
18059
18329
|
var LIMIT_ORDER_PRE_VERIFICATION_GAS = 50000n;
|
|
@@ -18243,17 +18513,19 @@ var ContractInteractionService = class {
|
|
|
18243
18513
|
}
|
|
18244
18514
|
/**
|
|
18245
18515
|
* The call gas a bid needs: the shared estimate plus room for the funding calls
|
|
18246
|
-
* this bid prepends.
|
|
18516
|
+
* this bid prepends and for settling its limit order's budget.
|
|
18247
18517
|
*
|
|
18248
18518
|
* Funding calls are not simulated (the V4 PositionManager's flash accounting does
|
|
18249
18519
|
* not resolve in the bundler's estimation context), so each one adds a fixed
|
|
18250
18520
|
* allowance for its modifyLiquidities + take. They differ between the bids on one
|
|
18251
18521
|
* order, since each draws on its own limit order and so on its own funding, which
|
|
18252
18522
|
* is why this is worked out per bid rather than baked into the cached estimate.
|
|
18523
|
+
* The estimate is of a lone fillOrder, so the budget call is not in it either.
|
|
18253
18524
|
*/
|
|
18254
|
-
callGasLimitFor(order, baseCallGasLimit) {
|
|
18525
|
+
callGasLimitFor(order, baseCallGasLimit, budget) {
|
|
18255
18526
|
const funding = order.id ? this.cacheService.getFundingPrepends(order.id) : null;
|
|
18256
|
-
|
|
18527
|
+
const fundingGas = FUNDING_GAS_PER_CALL * BigInt(funding?.calls?.length ?? 0);
|
|
18528
|
+
return baseCallGasLimit + fundingGas + (settles(budget) ? DEBIT_ORDER_GAS : 0n);
|
|
18257
18529
|
}
|
|
18258
18530
|
/**
|
|
18259
18531
|
* Gets the fee token address and decimals for a given chain.
|
|
@@ -18435,6 +18707,27 @@ var ContractInteractionService = class {
|
|
|
18435
18707
|
{ maxRetries: 3, backoffMs: 250, logMessage: "Failed to read token balance" }
|
|
18436
18708
|
);
|
|
18437
18709
|
}
|
|
18710
|
+
/**
|
|
18711
|
+
* What the solver's account has tallied against a limit order's budget on `chain`, in
|
|
18712
|
+
* the paid token's own units, or null when it cannot be read.
|
|
18713
|
+
*
|
|
18714
|
+
* Null rather than a throw: an account whose implementation keeps no tally answers
|
|
18715
|
+
* with a revert or with no data, and the caller carries on without the figure either
|
|
18716
|
+
* way. Not retried, since that answer does not change on a second ask.
|
|
18717
|
+
*/
|
|
18718
|
+
async limitOrderSpent(chain, budgetId) {
|
|
18719
|
+
try {
|
|
18720
|
+
return await this.clientManager.getPublicClient(chain).readContract({
|
|
18721
|
+
address: this.solverAccountAddress,
|
|
18722
|
+
abi: SOLVER_ACCOUNT_ABI,
|
|
18723
|
+
functionName: "spent",
|
|
18724
|
+
args: [budgetId]
|
|
18725
|
+
});
|
|
18726
|
+
} catch (err) {
|
|
18727
|
+
this.logger.warn({ err, chain, budgetId }, "Could not read the limit order's tally from the solver account");
|
|
18728
|
+
return null;
|
|
18729
|
+
}
|
|
18730
|
+
}
|
|
18438
18731
|
async getSolverEntryPointBalance(chain) {
|
|
18439
18732
|
const entryPointAddress = this.configService.getEntryPointAddress(chain);
|
|
18440
18733
|
if (!entryPointAddress) {
|
|
@@ -18575,7 +18868,8 @@ var ContractInteractionService = class {
|
|
|
18575
18868
|
if (!cachedEstimate) {
|
|
18576
18869
|
throw new Error(`No cached gas estimate found for order ${order.id}. Call estimateGasFillPost first.`);
|
|
18577
18870
|
}
|
|
18578
|
-
const
|
|
18871
|
+
const budget = this.cacheService.getBidBudget(order.id);
|
|
18872
|
+
const callGasLimit = this.callGasLimitFor(order, cachedEstimate.callGasLimit, budget);
|
|
18579
18873
|
const cachedFillerOutputs = this.cacheService.getFillerOutputs(order.id);
|
|
18580
18874
|
const cachedFillerInputs = this.cacheService.getFillerInputs(order.id);
|
|
18581
18875
|
if (!cachedFillerOutputs || !cachedFillerInputs) {
|
|
@@ -18602,7 +18896,8 @@ var ContractInteractionService = class {
|
|
|
18602
18896
|
order,
|
|
18603
18897
|
cachedFillerOutputs,
|
|
18604
18898
|
fillOptions,
|
|
18605
|
-
cachedEstimate.totalCostInSourceFeeToken + dispatchFeeTokenAmount
|
|
18899
|
+
cachedEstimate.totalCostInSourceFeeToken + dispatchFeeTokenAmount,
|
|
18900
|
+
budget ? { budget, solverAccount: solverAccountAddress } : void 0
|
|
18606
18901
|
);
|
|
18607
18902
|
const commitment = orderCommitment2(order);
|
|
18608
18903
|
const nonce = await this.clientManager.getPublicClient(order.destination).readContract({
|
|
@@ -18630,20 +18925,32 @@ var ContractInteractionService = class {
|
|
|
18630
18925
|
} else {
|
|
18631
18926
|
this.logger.warn({ reason: pmResult.reason }, "No paymaster for bid UserOp; relying on EntryPoint deposit");
|
|
18632
18927
|
}
|
|
18633
|
-
const
|
|
18634
|
-
order,
|
|
18635
|
-
fillOptions,
|
|
18928
|
+
const bidGas = {
|
|
18636
18929
|
solverAccount: solverAccountAddress,
|
|
18637
|
-
solverSigner: sdkSigningAccount(this.signer),
|
|
18638
18930
|
nonce,
|
|
18639
18931
|
entryPointAddress,
|
|
18640
18932
|
callGasLimit,
|
|
18641
18933
|
verificationGasLimit: cachedEstimate.verificationGasLimit,
|
|
18642
|
-
preVerificationGas: cachedEstimate.preVerificationGas,
|
|
18643
18934
|
maxFeePerGas: cachedEstimate.maxFeePerGas,
|
|
18644
18935
|
maxPriorityFeePerGas: cachedEstimate.maxPriorityFeePerGas,
|
|
18645
18936
|
callData,
|
|
18646
18937
|
paymasterAndData
|
|
18938
|
+
};
|
|
18939
|
+
let preVerificationGas = cachedEstimate.preVerificationGas;
|
|
18940
|
+
try {
|
|
18941
|
+
preVerificationGas = await sdkHelper.estimateBidPreVerificationGas(bidGas);
|
|
18942
|
+
} catch (err) {
|
|
18943
|
+
this.logger.warn(
|
|
18944
|
+
{ orderId: order.id, err, preVerificationGas: preVerificationGas.toString() },
|
|
18945
|
+
"Could not estimate the bid's preVerificationGas; using the fill estimate's"
|
|
18946
|
+
);
|
|
18947
|
+
}
|
|
18948
|
+
const userOp = await sdkHelper.prepareSubmitBid({
|
|
18949
|
+
...bidGas,
|
|
18950
|
+
order,
|
|
18951
|
+
fillOptions,
|
|
18952
|
+
solverSigner: sdkSigningAccount(this.signer),
|
|
18953
|
+
preVerificationGas
|
|
18647
18954
|
});
|
|
18648
18955
|
const encodedUserOp = encodeUserOpScale(userOp);
|
|
18649
18956
|
this.logger.info(
|
|
@@ -18652,6 +18959,7 @@ var ContractInteractionService = class {
|
|
|
18652
18959
|
solverAccount: solverAccountAddress,
|
|
18653
18960
|
nonce: nonce.toString(),
|
|
18654
18961
|
callGasLimit: callGasLimit.toString(),
|
|
18962
|
+
preVerificationGas: preVerificationGas.toString(),
|
|
18655
18963
|
maxFeePerGas: cachedEstimate.maxFeePerGas.toString()
|
|
18656
18964
|
},
|
|
18657
18965
|
"Prepared bid UserOp"
|
|
@@ -18781,8 +19089,13 @@ var ContractInteractionService = class {
|
|
|
18781
19089
|
* Same-chain fills release escrow locally with no Hyperbridge dispatch, so the
|
|
18782
19090
|
* gateway never pulls the fee token — its approval is skipped. Only cross-chain
|
|
18783
19091
|
* fills, which dispatch a RedeemEscrow message paid in the fee token, need it.
|
|
19092
|
+
*
|
|
19093
|
+
* A bid priced by a limit order ends with `debitOrder`, which takes the budget's id
|
|
19094
|
+
* as its `orderId`, works out what the fill paid from what is left of the allowance
|
|
19095
|
+
* and reverts the whole batch if the order's payouts would pass its size. It has to
|
|
19096
|
+
* come last, after the gateway has drawn on the allowance.
|
|
18784
19097
|
*/
|
|
18785
|
-
async buildApprovalAndFillCalldata(order, fillerOutputs, fillOptions, requiredFeeTokenAmount) {
|
|
19098
|
+
async buildApprovalAndFillCalldata(order, fillerOutputs, fillOptions, requiredFeeTokenAmount, settlement) {
|
|
18786
19099
|
const chain = order.destination;
|
|
18787
19100
|
const intentGatewayV2Address = this.configService.getIntentGatewayAddress(chain);
|
|
18788
19101
|
const perTokenRequired = /* @__PURE__ */ new Map();
|
|
@@ -18792,9 +19105,10 @@ var ContractInteractionService = class {
|
|
|
18792
19105
|
const key = addr.toLowerCase();
|
|
18793
19106
|
perTokenRequired.set(key, (perTokenRequired.get(key) ?? 0n) + output.amount);
|
|
18794
19107
|
}
|
|
19108
|
+
let feeKey;
|
|
18795
19109
|
if (order.source !== order.destination) {
|
|
18796
19110
|
const feeToken = await this.getFeeTokenWithDecimals(chain);
|
|
18797
|
-
|
|
19111
|
+
feeKey = feeToken.address.toLowerCase();
|
|
18798
19112
|
perTokenRequired.set(feeKey, (perTokenRequired.get(feeKey) ?? 0n) + requiredFeeTokenAmount);
|
|
18799
19113
|
}
|
|
18800
19114
|
const fundingPrepends = order.id ? this.cacheService.getFundingPrepends(order.id) : null;
|
|
@@ -18820,6 +19134,26 @@ var ContractInteractionService = class {
|
|
|
18820
19134
|
value: nativeOutputValue,
|
|
18821
19135
|
data: encodeFillOrder(transformOrderForContract(order), fillOptions)
|
|
18822
19136
|
});
|
|
19137
|
+
if (settlement && settles(settlement.budget)) {
|
|
19138
|
+
const { budget, solverAccount } = settlement;
|
|
19139
|
+
const budgetToken = budget.token.toLowerCase();
|
|
19140
|
+
const approved = perTokenRequired.get(budgetToken) ?? 0n;
|
|
19141
|
+
if (approved === 0n) {
|
|
19142
|
+
throw new Error(
|
|
19143
|
+
`Bid on order ${order.id} approves nothing in ${budget.token}, the token budget ${budget.budgetId} is measured in`
|
|
19144
|
+
);
|
|
19145
|
+
}
|
|
19146
|
+
const fee = feeKey === budgetToken ? fillOptions.relayerFee : 0n;
|
|
19147
|
+
calls.push({
|
|
19148
|
+
target: solverAccount,
|
|
19149
|
+
value: 0n,
|
|
19150
|
+
data: encodeFunctionData2({
|
|
19151
|
+
abi: SOLVER_ACCOUNT_ABI,
|
|
19152
|
+
functionName: "debitOrder",
|
|
19153
|
+
args: [budget.budgetId, budget.cap, budgetToken, approved, fee]
|
|
19154
|
+
})
|
|
19155
|
+
});
|
|
19156
|
+
}
|
|
18823
19157
|
return encodeERC7821ExecuteBatch2(calls);
|
|
18824
19158
|
}
|
|
18825
19159
|
};
|
|
@@ -20672,11 +21006,11 @@ function byBestRate(a, b) {
|
|
|
20672
21006
|
return left === right ? 0 : left > right ? -1 : 1;
|
|
20673
21007
|
}
|
|
20674
21008
|
function chargedFor(hold, delivered, released) {
|
|
20675
|
-
|
|
20676
|
-
|
|
20677
|
-
|
|
20678
|
-
const atRate = (
|
|
20679
|
-
return atRate > delivered ? atRate : delivered;
|
|
21009
|
+
const take = hold.take === void 0 ? 0n : BigInt(hold.take);
|
|
21010
|
+
if (take === 0n || released === 0n) return toScaledUp(delivered.raw, delivered.decimals);
|
|
21011
|
+
const offered = toRaw(BigInt(hold.amount), delivered.decimals);
|
|
21012
|
+
const atRate = (offered * released + take - 1n) / take;
|
|
21013
|
+
return toScaledUp(atRate > delivered.raw ? atRate : delivered.raw, delivered.decimals);
|
|
20680
21014
|
}
|
|
20681
21015
|
var IntentFiller = class {
|
|
20682
21016
|
constructor(chainConfigs, strategies, config, configService, chainClientManager, contractService, signer, scanners, rebalancingService, bidStorage, limitOrders, assetRegistry) {
|
|
@@ -20753,6 +21087,17 @@ var IntentFiller = class {
|
|
|
20753
21087
|
setLimitOrderService(service) {
|
|
20754
21088
|
this.limitOrderService = service;
|
|
20755
21089
|
}
|
|
21090
|
+
/**
|
|
21091
|
+
* Brings the limit orders' `remaining` in line with their on-chain tallies.
|
|
21092
|
+
*
|
|
21093
|
+
* On the settlement queue, so the pass and a fill never resize the same order at
|
|
21094
|
+
* once. Resolves when the pass has run.
|
|
21095
|
+
*/
|
|
21096
|
+
async reconcileLimitOrderTallies() {
|
|
21097
|
+
await this.settlementQueue.add(async () => {
|
|
21098
|
+
await this.limitOrderService?.reconcileTallies();
|
|
21099
|
+
});
|
|
21100
|
+
}
|
|
20756
21101
|
/** Whether the given chain id is configured for watch-only (monitor, never fill). */
|
|
20757
21102
|
isChainWatchOnly(chainId) {
|
|
20758
21103
|
const watchOnly = this.config.watchOnly;
|
|
@@ -21398,7 +21743,7 @@ var IntentFiller = class {
|
|
|
21398
21743
|
let reservation = [];
|
|
21399
21744
|
if (plan) {
|
|
21400
21745
|
if (order.id) {
|
|
21401
|
-
cache.setFillerOutputs(order.id, plan.fillerOutputs, plan.fillerInputs);
|
|
21746
|
+
cache.setFillerOutputs(order.id, plan.fillerOutputs, plan.fillerInputs, plan.budget);
|
|
21402
21747
|
cache.setPartialFill(order.id, plan.partialFill);
|
|
21403
21748
|
if (plan.fundingCalls.length > 0) cache.setFundingPrepends(order.id, plan.fundingCalls);
|
|
21404
21749
|
else cache.clearFundingPrepends(order.id);
|
|
@@ -21599,7 +21944,7 @@ var IntentFiller = class {
|
|
|
21599
21944
|
await this.releaseAll(claimed);
|
|
21600
21945
|
return;
|
|
21601
21946
|
}
|
|
21602
|
-
const settled = executedHold(claimed, delivered, released);
|
|
21947
|
+
const settled = executedHold(claimed, delivered.scaled, released);
|
|
21603
21948
|
const charged = chargedFor(settled, delivered, released);
|
|
21604
21949
|
const settledBid = rows.find(
|
|
21605
21950
|
(row) => row.reservations.some(
|
|
@@ -21652,15 +21997,15 @@ var IntentFiller = class {
|
|
|
21652
21997
|
if (delivered === null) return [];
|
|
21653
21998
|
const settled = executedHold(
|
|
21654
21999
|
holds.map((entry) => entry.hold),
|
|
21655
|
-
delivered,
|
|
22000
|
+
delivered.scaled,
|
|
21656
22001
|
released
|
|
21657
22002
|
);
|
|
21658
22003
|
const row = holds.find((entry) => entry.hold === settled).row;
|
|
21659
22004
|
return this.bidStorage.claimReservation(commitment, row.bid ?? void 0);
|
|
21660
22005
|
}
|
|
21661
22006
|
/**
|
|
21662
|
-
* What this fill delivered of the token the limit order pays,
|
|
21663
|
-
*
|
|
22007
|
+
* What this fill delivered of the token the limit order pays, or null when the
|
|
22008
|
+
* fill names none of it.
|
|
21664
22009
|
*/
|
|
21665
22010
|
async deliveredAgainst(limitOrderId, chainId, outputs) {
|
|
21666
22011
|
const order = await this.limitOrders?.get(limitOrderId);
|
|
@@ -21676,7 +22021,7 @@ var IntentFiller = class {
|
|
|
21676
22021
|
}
|
|
21677
22022
|
if (total === 0n) return null;
|
|
21678
22023
|
const decimals = await this.contractService.getTokenDecimals(address, chain);
|
|
21679
|
-
return toScaled(total, decimals);
|
|
22024
|
+
return { scaled: toScaled(total, decimals), raw: total, decimals };
|
|
21680
22025
|
}
|
|
21681
22026
|
/**
|
|
21682
22027
|
* Gives back whatever this bid still holds against its limit order.
|
|
@@ -21772,91 +22117,6 @@ import {
|
|
|
21772
22117
|
import { formatUnits as formatUnits4 } from "viem";
|
|
21773
22118
|
import { Decimal as Decimal6 } from "decimal.js";
|
|
21774
22119
|
|
|
21775
|
-
// src/orderbook/matching.ts
|
|
21776
|
-
function limitOrderLegs(order) {
|
|
21777
|
-
return order.side === "BID" ? { input: order.base, output: order.quote } : { input: order.quote, output: order.base };
|
|
21778
|
-
}
|
|
21779
|
-
function availableOn(order) {
|
|
21780
|
-
const available = BigInt(order.remaining) - BigInt(order.reserved);
|
|
21781
|
-
return available > 0n ? available : 0n;
|
|
21782
|
-
}
|
|
21783
|
-
function matchLimitOrders(orders, incoming, resolve, now = /* @__PURE__ */ new Date()) {
|
|
21784
|
-
const qualifying = orders.filter((order) => serves(order, incoming, resolve, now)).map((order) => {
|
|
21785
|
-
const offer = offerFor({
|
|
21786
|
-
side: order.side,
|
|
21787
|
-
inputAmount: incoming.inputNet,
|
|
21788
|
-
price: BigInt(order.price),
|
|
21789
|
-
outputDecimals: incoming.outputDecimals
|
|
21790
|
-
});
|
|
21791
|
-
const available = availableOn(order);
|
|
21792
|
-
return { order, offer, available, payout: offer < available ? offer : available };
|
|
21793
|
-
}).filter((candidate) => candidate.offer >= incoming.requestedOutput && candidate.payout > 0n).sort(byBestOfferFirst);
|
|
21794
|
-
return qualifying;
|
|
21795
|
-
}
|
|
21796
|
-
function byBestOfferFirst(a, b) {
|
|
21797
|
-
if (a.offer !== b.offer) return a.offer > b.offer ? -1 : 1;
|
|
21798
|
-
if (a.available !== b.available) return a.available > b.available ? -1 : 1;
|
|
21799
|
-
return a.order.id < b.order.id ? -1 : 1;
|
|
21800
|
-
}
|
|
21801
|
-
function whyUnmatched(orders, incoming, resolve, now = /* @__PURE__ */ new Date()) {
|
|
21802
|
-
let furthest = -1;
|
|
21803
|
-
for (const order of orders) {
|
|
21804
|
-
const failed = firstFailed(order, incoming, resolve, now);
|
|
21805
|
-
furthest = Math.max(furthest, failed === null ? CHECKS.length : CHECKS.indexOf(failed));
|
|
21806
|
-
}
|
|
21807
|
-
const { source, destination, inputSymbol } = incoming;
|
|
21808
|
-
if (furthest < CHECKS.length) {
|
|
21809
|
-
switch (CHECKS[Math.max(furthest, 0)]) {
|
|
21810
|
-
case "open":
|
|
21811
|
-
return "no limit order is open";
|
|
21812
|
-
case "chain":
|
|
21813
|
-
return `no open limit order fills on ${destination}`;
|
|
21814
|
-
case "input":
|
|
21815
|
-
return `no limit order on ${destination} takes ${inputSymbol} in`;
|
|
21816
|
-
case "output":
|
|
21817
|
-
return `no limit order on ${destination} taking ${inputSymbol} in pays out the token this order asks for`;
|
|
21818
|
-
case "source":
|
|
21819
|
-
return `no limit order for this pair on ${destination} accepts swaps from ${source}`;
|
|
21820
|
-
}
|
|
21821
|
-
}
|
|
21822
|
-
const priced = orders.filter((order) => firstFailed(order, incoming, resolve, now) === null).map((order) => ({
|
|
21823
|
-
order,
|
|
21824
|
-
offer: offerFor({
|
|
21825
|
-
side: order.side,
|
|
21826
|
-
inputAmount: incoming.inputNet,
|
|
21827
|
-
price: BigInt(order.price),
|
|
21828
|
-
outputDecimals: incoming.outputDecimals
|
|
21829
|
-
})
|
|
21830
|
-
}));
|
|
21831
|
-
if (priced.some(({ offer }) => offer >= incoming.requestedOutput)) {
|
|
21832
|
-
return "the limit orders that meet its rate have nothing left to pay out";
|
|
21833
|
-
}
|
|
21834
|
-
const best = priced.reduce((a, b) => b.offer > a.offer ? b : a);
|
|
21835
|
-
const symbol = limitOrderLegs(best.order).output;
|
|
21836
|
-
return `it asks for ${readable(incoming.requestedOutput)} ${symbol}; the best limit order offers ${readable(best.offer)} ${symbol}`;
|
|
21837
|
-
}
|
|
21838
|
-
var CHECKS = ["open", "chain", "input", "output", "source"];
|
|
21839
|
-
function serves(order, incoming, resolve, now) {
|
|
21840
|
-
return firstFailed(order, incoming, resolve, now) === null;
|
|
21841
|
-
}
|
|
21842
|
-
function firstFailed(order, incoming, resolve, now) {
|
|
21843
|
-
if (order.status !== "open") return "open";
|
|
21844
|
-
if (order.expiresAt !== null && new Date(order.expiresAt) <= now) return "open";
|
|
21845
|
-
if (order.fillChain !== incoming.destination) return "chain";
|
|
21846
|
-
const legs = limitOrderLegs(order);
|
|
21847
|
-
if (normalizeSymbol(legs.input) !== normalizeSymbol(incoming.inputSymbol)) return "input";
|
|
21848
|
-
const outputToken = resolve(legs.output, order.fillChain);
|
|
21849
|
-
if (!outputToken || outputToken.toLowerCase() !== incoming.outputToken.toLowerCase()) return "output";
|
|
21850
|
-
if (incoming.source === incoming.destination) return null;
|
|
21851
|
-
return order.acceptedSources.includes(incoming.source) ? null : "source";
|
|
21852
|
-
}
|
|
21853
|
-
function readable(amount) {
|
|
21854
|
-
const [whole, fraction] = toHuman(amount).split(".");
|
|
21855
|
-
const grouped = whole.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
21856
|
-
const cut = fraction?.slice(0, 6).replace(/0+$/, "");
|
|
21857
|
-
return cut ? `${grouped}.${cut}` : grouped;
|
|
21858
|
-
}
|
|
21859
|
-
|
|
21860
22120
|
// src/orderbook/usd.ts
|
|
21861
22121
|
import { Decimal as Decimal5 } from "decimal.js";
|
|
21862
22122
|
function limitOrderUsdEdges(orders) {
|
|
@@ -21911,9 +22171,13 @@ var FXFiller = class _FXFiller {
|
|
|
21911
22171
|
*/
|
|
21912
22172
|
constructor(signer, configService, clientManager, contractService, pairs, registry, options) {
|
|
21913
22173
|
this.name = "FXFiller";
|
|
21914
|
-
/**
|
|
22174
|
+
/**
|
|
22175
|
+
* Consecutive orders where the overfill clamp activated. Dormant: the clamp
|
|
22176
|
+
* existed for venue-priced legs, which went with Uniswap V4 pricing, so every
|
|
22177
|
+
* outcome is now recorded unclamped and this never climbs. See `recordOrderOutcome`.
|
|
22178
|
+
*/
|
|
21915
22179
|
this.consecutiveClamps = 0;
|
|
21916
|
-
/** Once set, the filler refuses all orders until
|
|
22180
|
+
/** Once set, the filler refuses all orders until reset. Unreachable while the clamp is dormant. */
|
|
21917
22181
|
this.halted = false;
|
|
21918
22182
|
this.logger = moduleLogger(configService.loggers, "fx-simplex");
|
|
21919
22183
|
const { confirmationPolicy, fundingVenues = [], limitOrders } = options ?? {};
|
|
@@ -21962,10 +22226,9 @@ var FXFiller = class _FXFiller {
|
|
|
21962
22226
|
this.pairs.push(pair);
|
|
21963
22227
|
}
|
|
21964
22228
|
/**
|
|
21965
|
-
* Removes a market from the running engine.
|
|
21966
|
-
*
|
|
21967
|
-
*
|
|
21968
|
-
* rechecks of orders on the removed pair simply stop matching.
|
|
22229
|
+
* Removes a market from the running engine. At least one market must remain.
|
|
22230
|
+
* Matching reads the limit orders rather than the pairs, so removing a market
|
|
22231
|
+
* changes what the dashboard lists, not what the filler fills.
|
|
21969
22232
|
*/
|
|
21970
22233
|
removePair(pair) {
|
|
21971
22234
|
const index = this.pairs.indexOf(pair);
|
|
@@ -22021,22 +22284,20 @@ var FXFiller = class _FXFiller {
|
|
|
22021
22284
|
}
|
|
22022
22285
|
}
|
|
22023
22286
|
/**
|
|
22024
|
-
* Evaluates whether an order is profitable to fill
|
|
22025
|
-
*
|
|
22287
|
+
* Evaluates whether an order is profitable to fill against the operator's
|
|
22288
|
+
* limit orders and the filler's current token balances.
|
|
22026
22289
|
*
|
|
22027
22290
|
* High-level flow:
|
|
22028
|
-
* -
|
|
22029
|
-
* -
|
|
22030
|
-
*
|
|
22031
|
-
*
|
|
22032
|
-
*
|
|
22033
|
-
*
|
|
22034
|
-
*
|
|
22035
|
-
*
|
|
22036
|
-
*
|
|
22037
|
-
*
|
|
22038
|
-
* Note: we may intentionally overfill relative to the user's requested
|
|
22039
|
-
* outputs if the pair pricing makes that attractive. This is how we stay competitive.
|
|
22291
|
+
* - Match each (input, output) leg against the open limit orders.
|
|
22292
|
+
* - Build one bid per matching order, best offer first, each paying the
|
|
22293
|
+
* order's whole offer for the input at its own rate, capped by what the
|
|
22294
|
+
* order has left. The gateway credits the swapper the ask and splits
|
|
22295
|
+
* anything above it between the swapper and the protocol.
|
|
22296
|
+
* - Treat a payout below the ask as a partial fill, which only orders
|
|
22297
|
+
* without output calldata and without an earlier partial fill allow.
|
|
22298
|
+
* - Cap each payout by the filler's balance plus funding-venue withdrawals,
|
|
22299
|
+
* then gate it on fees (full fills) and the same-asset spread.
|
|
22300
|
+
* - Cache the resulting bids for later use in `executeOrder`.
|
|
22040
22301
|
*/
|
|
22041
22302
|
async calculateProfitability(order) {
|
|
22042
22303
|
if (order.id) this.contractService.cacheService.clearPartialFill(order.id);
|
|
@@ -22054,6 +22315,7 @@ var FXFiller = class _FXFiller {
|
|
|
22054
22315
|
const multiLeg = order.inputs.length > 1;
|
|
22055
22316
|
const plans = [];
|
|
22056
22317
|
const plannedOn = /* @__PURE__ */ new Map();
|
|
22318
|
+
const spentOn = /* @__PURE__ */ new Map();
|
|
22057
22319
|
for (let leg = 0; leg < order.inputs.length; leg++) {
|
|
22058
22320
|
const input = order.inputs[leg];
|
|
22059
22321
|
const output = order.output.assets[leg];
|
|
@@ -22075,7 +22337,30 @@ var FXFiller = class _FXFiller {
|
|
|
22075
22337
|
for (const candidate of matches) {
|
|
22076
22338
|
let partialFill = false;
|
|
22077
22339
|
const fundingCalls = [];
|
|
22078
|
-
const
|
|
22340
|
+
const budget = budgetFor(candidate.order, outputToken, outputDecimals);
|
|
22341
|
+
if (!spentOn.has(candidate.order.id)) {
|
|
22342
|
+
spentOn.set(
|
|
22343
|
+
candidate.order.id,
|
|
22344
|
+
await this.contractService.limitOrderSpent(destChain, budget.budgetId)
|
|
22345
|
+
);
|
|
22346
|
+
}
|
|
22347
|
+
const spent = spentOn.get(candidate.order.id) ?? null;
|
|
22348
|
+
const room = spent === null ? candidate.available : budgetRoom(budget, spent, outputDecimals);
|
|
22349
|
+
if (room === 0n) {
|
|
22350
|
+
this.logger.info(
|
|
22351
|
+
{
|
|
22352
|
+
orderId: order.id,
|
|
22353
|
+
leg,
|
|
22354
|
+
limitOrder: candidate.order.id,
|
|
22355
|
+
spent: spent?.toString(),
|
|
22356
|
+
cap: budget.cap.toString()
|
|
22357
|
+
},
|
|
22358
|
+
"Skipping a bid: the limit order has nothing left on chain"
|
|
22359
|
+
);
|
|
22360
|
+
continue;
|
|
22361
|
+
}
|
|
22362
|
+
const available = room < candidate.available ? room : candidate.available;
|
|
22363
|
+
const left = available - (plannedOn.get(candidate.order.id) ?? 0n);
|
|
22079
22364
|
if (left <= 0n) {
|
|
22080
22365
|
this.logger.info(
|
|
22081
22366
|
{ orderId: order.id, leg, limitOrder: candidate.order.id },
|
|
@@ -22110,10 +22395,7 @@ var FXFiller = class _FXFiller {
|
|
|
22110
22395
|
{
|
|
22111
22396
|
orderId: order.id,
|
|
22112
22397
|
limitOrder: candidate.order.id,
|
|
22113
|
-
available: formatUnits4(
|
|
22114
|
-
candidate.available,
|
|
22115
|
-
18
|
|
22116
|
-
),
|
|
22398
|
+
available: formatUnits4(available, 18),
|
|
22117
22399
|
userRequested: output.amount.toString(),
|
|
22118
22400
|
payout: targetOutput.toString(),
|
|
22119
22401
|
crossChain: sourceChain !== destChain
|
|
@@ -22158,7 +22440,10 @@ var FXFiller = class _FXFiller {
|
|
|
22158
22440
|
}
|
|
22159
22441
|
}
|
|
22160
22442
|
const effectiveBalance = walletContribution + credited;
|
|
22161
|
-
const finalOutputAmount =
|
|
22443
|
+
const finalOutputAmount = toRaw(
|
|
22444
|
+
toScaled(effectiveBalance > targetOutput ? targetOutput : effectiveBalance, outputDecimals),
|
|
22445
|
+
outputDecimals
|
|
22446
|
+
);
|
|
22162
22447
|
if (finalOutputAmount === 0n) {
|
|
22163
22448
|
this.logger.info(
|
|
22164
22449
|
{
|
|
@@ -22334,7 +22619,8 @@ var FXFiller = class _FXFiller {
|
|
|
22334
22619
|
fillerInputs,
|
|
22335
22620
|
fundingCalls: [...fundingCalls],
|
|
22336
22621
|
partialFill,
|
|
22337
|
-
profit: totalProfit
|
|
22622
|
+
profit: totalProfit,
|
|
22623
|
+
budget
|
|
22338
22624
|
});
|
|
22339
22625
|
if (!partialEligibleCheap) break;
|
|
22340
22626
|
}
|
|
@@ -22342,7 +22628,12 @@ var FXFiller = class _FXFiller {
|
|
|
22342
22628
|
if (plans.length === 0) return 0;
|
|
22343
22629
|
if (order.id) {
|
|
22344
22630
|
this.contractService.cacheService.setBidPlans(order.id, plans);
|
|
22345
|
-
this.contractService.cacheService.setFillerOutputs(
|
|
22631
|
+
this.contractService.cacheService.setFillerOutputs(
|
|
22632
|
+
order.id,
|
|
22633
|
+
plans[0].fillerOutputs,
|
|
22634
|
+
plans[0].fillerInputs,
|
|
22635
|
+
plans[0].budget
|
|
22636
|
+
);
|
|
22346
22637
|
this.contractService.cacheService.setPartialFill(order.id, plans[0].partialFill);
|
|
22347
22638
|
this.contractService.cacheService.setMatchedLimitOrder(order.id, [
|
|
22348
22639
|
{ limitOrderId: plans[0].limitOrderId, payout: plans[0].payout }
|
|
@@ -22437,10 +22728,14 @@ var FXFiller = class _FXFiller {
|
|
|
22437
22728
|
// =========================================================================
|
|
22438
22729
|
/**
|
|
22439
22730
|
* Update consecutive-clamp counter after a successful order evaluation.
|
|
22440
|
-
*
|
|
22441
|
-
*
|
|
22442
|
-
*
|
|
22443
|
-
*
|
|
22731
|
+
*
|
|
22732
|
+
* Dormant. Only venue-priced legs ever fed a clamped outcome here: a streak
|
|
22733
|
+
* of those meant a live market source had gone off (stale pool, manipulated
|
|
22734
|
+
* venue). Venue pricing went with Uniswap V4, and limit orders are prices the
|
|
22735
|
+
* operator set, so the only caller records `false` and the filler never
|
|
22736
|
+
* halts. `isHalted`, `resetHalt` and `[simplex.overfillProtection]
|
|
22737
|
+
* maxConsecutiveClamps` are kept so existing configs and the halt API still
|
|
22738
|
+
* load; they can go once nothing reads them.
|
|
22444
22739
|
*/
|
|
22445
22740
|
recordOrderOutcome(clamped, orderId) {
|
|
22446
22741
|
if (clamped) {
|
|
@@ -22466,18 +22761,6 @@ var FXFiller = class _FXFiller {
|
|
|
22466
22761
|
this.consecutiveClamps = 0;
|
|
22467
22762
|
this.logger.warn("FXFiller halt reset by operator \u2014 resuming order evaluation");
|
|
22468
22763
|
}
|
|
22469
|
-
/**
|
|
22470
|
-
* Given a single (input, output) leg and the remaining token0 budget of its
|
|
22471
|
-
* pair, computes how much token0 notional to allocate to this leg and the
|
|
22472
|
-
* corresponding maximum output amount at the pair's rate.
|
|
22473
|
-
*
|
|
22474
|
-
* `rate` is **token1 per 1 token0**:
|
|
22475
|
-
* - token0 input → token1 output: token0 × rate → token1 amount.
|
|
22476
|
-
* - token1 input → token0 output: token1 ÷ rate → token0 amount.
|
|
22477
|
-
*
|
|
22478
|
-
* Returns `null` when this leg cannot consume any of the pair's remaining
|
|
22479
|
-
* budget (e.g. the cap has already been exhausted).
|
|
22480
|
-
*/
|
|
22481
22764
|
/**
|
|
22482
22765
|
* Whether any solver has already delivered output against this order.
|
|
22483
22766
|
*
|
|
@@ -22815,9 +23098,9 @@ var LimitOrderLifecycle = class {
|
|
|
22815
23098
|
const reconcileMs = this.options.reconcileIntervalSecs * 1e3;
|
|
22816
23099
|
this.every(heartbeatMs, "heartbeat", () => this.service.heartbeat());
|
|
22817
23100
|
this.every(EXPIRY_SWEEP_MS, "expiry", () => this.expire());
|
|
22818
|
-
this.every(reconcileMs, "reconciliation", () => this.
|
|
23101
|
+
this.every(reconcileMs, "reconciliation", () => this.upkeep());
|
|
22819
23102
|
this.logger.info({ heartbeatMs, expiryMs: EXPIRY_SWEEP_MS, reconcileMs }, "Orderbook lifecycle started");
|
|
22820
|
-
void this.
|
|
23103
|
+
void this.upkeep();
|
|
22821
23104
|
}
|
|
22822
23105
|
stop() {
|
|
22823
23106
|
this.timers.forEach((timer) => clearInterval(timer));
|
|
@@ -22828,6 +23111,14 @@ var LimitOrderLifecycle = class {
|
|
|
22828
23111
|
const expired = await this.service.expireStale();
|
|
22829
23112
|
if (expired > 0) this.logger.info({ expired }, "Withdrew limit orders that had outlived their expiry");
|
|
22830
23113
|
}
|
|
23114
|
+
/**
|
|
23115
|
+
* The tally pass first, so reconciliation against the orderbook reads the sizes
|
|
23116
|
+
* it left. Each fails on its own.
|
|
23117
|
+
*/
|
|
23118
|
+
async upkeep() {
|
|
23119
|
+
await this.run("tally reconciliation", async () => this.options.reconcileTallies());
|
|
23120
|
+
await this.run("reconciliation", () => this.reconcile());
|
|
23121
|
+
}
|
|
22831
23122
|
async reconcile() {
|
|
22832
23123
|
const report = await this.service.reconcile();
|
|
22833
23124
|
if (report.cancelled || report.reposted || report.underFunded) {
|
|
@@ -23628,7 +23919,7 @@ async function bootFiller(config, options) {
|
|
|
23628
23919
|
const sharedCacheService = new CacheService(options.loggers);
|
|
23629
23920
|
if (!options.signer && !globalWatchOnly && !allChainsWatchOnly(watchOnlyConfig, resolvedChains)) {
|
|
23630
23921
|
throw new Error(
|
|
23631
|
-
"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]."
|
|
23922
|
+
"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]."
|
|
23632
23923
|
);
|
|
23633
23924
|
}
|
|
23634
23925
|
const chainClientManager = new ChainClientManager(configService, options.signer);
|
|
@@ -23675,48 +23966,42 @@ async function bootFiller(config, options) {
|
|
|
23675
23966
|
assertPairSymbolsResolve(config.pairs, assetRegistry, configuredChainNames);
|
|
23676
23967
|
}
|
|
23677
23968
|
const adminStrategies = [];
|
|
23678
|
-
const
|
|
23679
|
-
|
|
23680
|
-
|
|
23681
|
-
|
|
23682
|
-
|
|
23683
|
-
|
|
23684
|
-
|
|
23685
|
-
|
|
23686
|
-
|
|
23687
|
-
|
|
23688
|
-
|
|
23689
|
-
|
|
23690
|
-
token1: pair.token1,
|
|
23691
|
-
sameToken: normalizeSymbol(pair.token0) === normalizeSymbol(pair.token1)
|
|
23692
|
-
});
|
|
23969
|
+
const tradingPairs = (config.pairs ?? []).map((pair) => ({
|
|
23970
|
+
token0: pair.token0,
|
|
23971
|
+
token1: pair.token1
|
|
23972
|
+
}));
|
|
23973
|
+
tradingPairs.forEach((pair, pairIndex) => {
|
|
23974
|
+
adminStrategies.push({
|
|
23975
|
+
index: pairIndex,
|
|
23976
|
+
pairIndex,
|
|
23977
|
+
exotic: `${pair.token0}/${pair.token1}`,
|
|
23978
|
+
token0: pair.token0,
|
|
23979
|
+
token1: pair.token1,
|
|
23980
|
+
sameToken: normalizeSymbol(pair.token0) === normalizeSymbol(pair.token1)
|
|
23693
23981
|
});
|
|
23694
|
-
|
|
23695
|
-
|
|
23696
|
-
|
|
23697
|
-
)
|
|
23698
|
-
|
|
23699
|
-
|
|
23700
|
-
|
|
23701
|
-
|
|
23702
|
-
|
|
23703
|
-
|
|
23704
|
-
|
|
23705
|
-
|
|
23706
|
-
|
|
23707
|
-
|
|
23708
|
-
|
|
23709
|
-
|
|
23710
|
-
|
|
23711
|
-
|
|
23982
|
+
});
|
|
23983
|
+
const confirmationPolicy = assertConfirmationCoverage(
|
|
23984
|
+
config.confirmationPolicies,
|
|
23985
|
+
resolvedChains.map((c) => c.chainId)
|
|
23986
|
+
);
|
|
23987
|
+
const fundingVenues = vaultVenue ? [vaultVenue] : [];
|
|
23988
|
+
const engine = new FXFiller(
|
|
23989
|
+
runtimeSigner,
|
|
23990
|
+
configService,
|
|
23991
|
+
chainClientManager,
|
|
23992
|
+
contractService,
|
|
23993
|
+
tradingPairs,
|
|
23994
|
+
assetRegistry,
|
|
23995
|
+
{ confirmationPolicy, fundingVenues, limitOrders: options.data.limitOrders }
|
|
23996
|
+
);
|
|
23997
|
+
logger.info("Hydrating funding venue state...");
|
|
23998
|
+
await engine.initialise();
|
|
23999
|
+
const strategies = [engine];
|
|
23712
24000
|
const haltControls = strategies.map((engine2, index) => ({
|
|
23713
24001
|
index,
|
|
23714
24002
|
isHalted: () => engine2.isHalted(),
|
|
23715
24003
|
resetHalt: () => engine2.resetHalt()
|
|
23716
24004
|
}));
|
|
23717
|
-
if (vaultVenue) {
|
|
23718
|
-
await vaultVenue.initialise(runtimeSigner.address);
|
|
23719
|
-
}
|
|
23720
24005
|
let rebalancingService;
|
|
23721
24006
|
const rebalancingConfig = configService.getRebalancingConfig();
|
|
23722
24007
|
if (rebalancingConfig?.triggerPercentage !== void 0 && rebalancingConfig?.baseBalances) {
|
|
@@ -23773,7 +24058,8 @@ async function bootFiller(config, options) {
|
|
|
23773
24058
|
const lifecycle = new LimitOrderLifecycle(
|
|
23774
24059
|
limitOrderService,
|
|
23775
24060
|
{
|
|
23776
|
-
reconcileIntervalSecs: config.orderbook?.reconcileIntervalSecs ?? DEFAULT_RECONCILE_INTERVAL_SECS
|
|
24061
|
+
reconcileIntervalSecs: config.orderbook?.reconcileIntervalSecs ?? DEFAULT_RECONCILE_INTERVAL_SECS,
|
|
24062
|
+
reconcileTallies: () => intentFiller.reconcileLimitOrderTallies()
|
|
23777
24063
|
},
|
|
23778
24064
|
options.loggers
|
|
23779
24065
|
);
|
|
@@ -24233,8 +24519,8 @@ var MemoryLimitOrderStore = class {
|
|
|
24233
24519
|
async reserve(id, amount) {
|
|
24234
24520
|
const order = this.orders.get(id);
|
|
24235
24521
|
if (!order || order.status !== "open") return false;
|
|
24522
|
+
if (BigInt(amount) > BigInt(order.remaining)) return false;
|
|
24236
24523
|
const reserved = BigInt(order.reserved) + BigInt(amount);
|
|
24237
|
-
if (reserved > BigInt(order.remaining)) return false;
|
|
24238
24524
|
this.patch(id, { reserved: reserved.toString() });
|
|
24239
24525
|
return true;
|
|
24240
24526
|
}
|
|
@@ -24244,6 +24530,14 @@ var MemoryLimitOrderStore = class {
|
|
|
24244
24530
|
const remaining = BigInt(order.remaining) - BigInt(amount);
|
|
24245
24531
|
return this.patch(id, { remaining: (remaining > 0n ? remaining : 0n).toString() });
|
|
24246
24532
|
}
|
|
24533
|
+
async clampRemaining(id, room) {
|
|
24534
|
+
const order = this.orders.get(id);
|
|
24535
|
+
if (!order || order.status !== "open") return null;
|
|
24536
|
+
const sum = BigInt(room) + BigInt(order.reserved);
|
|
24537
|
+
const target = sum > 0n ? sum : 0n;
|
|
24538
|
+
if (target >= BigInt(order.remaining)) return null;
|
|
24539
|
+
return this.patch(id, { remaining: target.toString() });
|
|
24540
|
+
}
|
|
24247
24541
|
async release(id, amount) {
|
|
24248
24542
|
const order = this.orders.get(id);
|
|
24249
24543
|
if (!order) return;
|
|
@@ -24752,7 +25046,8 @@ var OrderScanner = class _OrderScanner {
|
|
|
24752
25046
|
* back from its own endpoints, which also proves they answer for one chain
|
|
24753
25047
|
* before a filler ever depends on them.
|
|
24754
25048
|
*
|
|
24755
|
-
* @throws if an endpoint set is invalid
|
|
25049
|
+
* @throws if an endpoint set is invalid, if none of a chain's endpoints answer,
|
|
25050
|
+
* if its endpoints disagree on the chain, if two entries resolve
|
|
24756
25051
|
* to the same chain, or if `scanIntervalSecs` is below the minimum.
|
|
24757
25052
|
*/
|
|
24758
25053
|
static async create(options) {
|
|
@@ -24812,7 +25107,10 @@ var OrderScanner = class _OrderScanner {
|
|
|
24812
25107
|
async addChainNow(chain) {
|
|
24813
25108
|
if (this.closed) throw new Error("This OrderScanner is closed");
|
|
24814
25109
|
const rpcUrls = validateRpcUrls(chain.rpcUrls);
|
|
24815
|
-
const chainId = chain.chainId ?? await
|
|
25110
|
+
const chainId = chain.chainId ?? (await resolveChainConfigs([{ rpcUrls, bundlerUrl: chain.bundlerUrl ?? "" }], {
|
|
25111
|
+
loggers: this.loggers,
|
|
25112
|
+
tolerateUnreachable: true
|
|
25113
|
+
}))[0].chainId;
|
|
24816
25114
|
if (this.closed) throw new Error("This OrderScanner is closed");
|
|
24817
25115
|
if (this.scanners.has(chainId)) {
|
|
24818
25116
|
throw new Error(`Chain ${chainId} is already in this scanner`);
|
|
@@ -24912,7 +25210,7 @@ var LimitOrderController = class {
|
|
|
24912
25210
|
get service() {
|
|
24913
25211
|
const service = this.runtime.limitOrders;
|
|
24914
25212
|
if (!service) {
|
|
24915
|
-
throw new Error("No orderbook is configured \u2014 set [orderbook]
|
|
25213
|
+
throw new Error("No orderbook is configured \u2014 set [orderbook] url to use limit orders");
|
|
24916
25214
|
}
|
|
24917
25215
|
return service;
|
|
24918
25216
|
}
|
|
@@ -24986,10 +25284,7 @@ var PairController = class {
|
|
|
24986
25284
|
* the pair's symbols have to resolve before the market can be hydrated.
|
|
24987
25285
|
*/
|
|
24988
25286
|
async add(pair, options) {
|
|
24989
|
-
const { engine,
|
|
24990
|
-
if (!engine || !tradingPairs) {
|
|
24991
|
-
throw new Error("No trading engine is running \u2014 this filler was started without pairs");
|
|
24992
|
-
}
|
|
25287
|
+
const { engine, assetRegistry, adminStrategies, balanceTokens, config } = this.runtime;
|
|
24993
25288
|
const assets = options?.assets;
|
|
24994
25289
|
const nextAssets = { ...config.assets ?? {}, ...assets ?? {} };
|
|
24995
25290
|
const nextPairs = [...this.pairs, pair];
|
|
@@ -25028,7 +25323,6 @@ var PairController = class {
|
|
|
25028
25323
|
/** Closes a market. Orders already in flight against it still complete. */
|
|
25029
25324
|
async remove(index) {
|
|
25030
25325
|
const { engine, tradingPairs, adminStrategies, config } = this.runtime;
|
|
25031
|
-
if (!engine || !tradingPairs) throw new Error("No trading engine is running");
|
|
25032
25326
|
if (index < 0 || index >= this.pairs.length) throw new Error(`Unknown pair ${index}`);
|
|
25033
25327
|
engine.removePair(tradingPairs[index]);
|
|
25034
25328
|
config.pairs = this.pairs.filter((_, i) => i !== index);
|
|
@@ -25040,7 +25334,7 @@ var PairController = class {
|
|
|
25040
25334
|
await this.persist();
|
|
25041
25335
|
}
|
|
25042
25336
|
livePair(index) {
|
|
25043
|
-
const pair = this.runtime.tradingPairs
|
|
25337
|
+
const pair = this.runtime.tradingPairs[index];
|
|
25044
25338
|
if (!pair) throw new Error(`Unknown pair ${index}`);
|
|
25045
25339
|
return pair;
|
|
25046
25340
|
}
|
|
@@ -25132,9 +25426,9 @@ var ChainController = class {
|
|
|
25132
25426
|
throw error;
|
|
25133
25427
|
}
|
|
25134
25428
|
const curve = confirmationPolicies[String(chainId)];
|
|
25135
|
-
if (curve) this.runtime.confirmationPolicy
|
|
25136
|
-
else if (!this.runtime.confirmationPolicy
|
|
25137
|
-
this.runtime.confirmationPolicy
|
|
25429
|
+
if (curve) this.runtime.confirmationPolicy.add(chainId, curve);
|
|
25430
|
+
else if (!this.runtime.confirmationPolicy.has(chainId)) {
|
|
25431
|
+
this.runtime.confirmationPolicy.adopt(chainId, resolvedPolicy);
|
|
25138
25432
|
}
|
|
25139
25433
|
this.runtime.resolvedChains.push(resolved);
|
|
25140
25434
|
config.chains = [...config.chains, { rpcUrls: chain.rpcUrls, bundlerUrl: chain.bundlerUrl }];
|
|
@@ -25162,7 +25456,7 @@ var ChainController = class {
|
|
|
25162
25456
|
await intentFiller.removeChain(chainId);
|
|
25163
25457
|
if (this.ownsScanner) await this.scanner.removeChain(chainId);
|
|
25164
25458
|
configService.removeChain(chainId);
|
|
25165
|
-
this.runtime.confirmationPolicy
|
|
25459
|
+
this.runtime.confirmationPolicy.remove(chainId);
|
|
25166
25460
|
this.runtime.chainClientManager.invalidate(chainKey);
|
|
25167
25461
|
this.runtime.resolvedChains.splice(index, 1);
|
|
25168
25462
|
config.chains = config.chains.filter((_, i) => i !== index);
|
|
@@ -25381,7 +25675,7 @@ var Simplex = class _Simplex extends EventEmitter4 {
|
|
|
25381
25675
|
const tomlSigner = options.config.simplex?.signer;
|
|
25382
25676
|
if (tomlSigner && !options.signer) {
|
|
25383
25677
|
throw new Error(
|
|
25384
|
-
"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."
|
|
25678
|
+
"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."
|
|
25385
25679
|
);
|
|
25386
25680
|
}
|
|
25387
25681
|
const loggers = new LoggerContext({
|
|
@@ -25584,6 +25878,8 @@ var Simplex = class _Simplex extends EventEmitter4 {
|
|
|
25584
25878
|
export {
|
|
25585
25879
|
AssetController,
|
|
25586
25880
|
ChainController,
|
|
25881
|
+
LimitOrderController,
|
|
25882
|
+
LimitOrderValidationError,
|
|
25587
25883
|
LoggerContext,
|
|
25588
25884
|
MemoryDataStore,
|
|
25589
25885
|
OrderScanner,
|
|
@@ -25596,6 +25892,7 @@ export {
|
|
|
25596
25892
|
WalletController,
|
|
25597
25893
|
addLogSink,
|
|
25598
25894
|
assertConfirmationCoverage,
|
|
25895
|
+
canDerive,
|
|
25599
25896
|
configureLogger,
|
|
25600
25897
|
createSigner,
|
|
25601
25898
|
digestSigner,
|
|
@@ -25608,10 +25905,12 @@ export {
|
|
|
25608
25905
|
pickAnchorStable,
|
|
25609
25906
|
privateKeySigner,
|
|
25610
25907
|
registrySymbols,
|
|
25908
|
+
secretPhraseSigner,
|
|
25611
25909
|
turnkeySigner,
|
|
25612
25910
|
validateAssetDefinitions,
|
|
25613
25911
|
validateConfig,
|
|
25614
25912
|
validatePairConfigs,
|
|
25913
|
+
validateSecretPhrase,
|
|
25615
25914
|
validateSignerConfig,
|
|
25616
25915
|
validateVaultToml,
|
|
25617
25916
|
viemSigner
|