@hyperbridge/sdk 2.8.7 → 2.8.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/index.d.ts +469 -36
- package/dist/browser/index.js +921 -128
- package/dist/browser/index.js.map +1 -1
- package/dist/node/index.cjs +930 -124
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.d.cts +61 -25
- package/dist/node/index.d.ts +61 -25
- package/dist/node/index.js +921 -128
- package/dist/node/index.js.map +1 -1
- package/dist/node/{intents-helpers-BFc6YnD3.d.cts → intents-helpers-BKj42imQ.d.cts} +732 -224
- package/dist/node/{intents-helpers-BFc6YnD3.d.ts → intents-helpers-BKj42imQ.d.ts} +732 -224
- package/dist/node/intents-helpers.cjs +109 -23
- package/dist/node/intents-helpers.cjs.map +1 -1
- package/dist/node/intents-helpers.d.cts +2 -1
- package/dist/node/intents-helpers.d.ts +2 -1
- package/dist/node/intents-helpers.js +103 -25
- package/dist/node/intents-helpers.js.map +1 -1
- package/package.json +1 -1
package/dist/node/index.js
CHANGED
|
@@ -7,10 +7,10 @@ import { baseSepolia, optimismSepolia, arbitrumSepolia, soneium, gnosis, optimis
|
|
|
7
7
|
import { TronWeb } from 'tronweb';
|
|
8
8
|
import { flatten, zip, capitalize, maxBy, isNil } from 'lodash-es';
|
|
9
9
|
import { match } from 'ts-pattern';
|
|
10
|
-
import { WsProvider, ApiPromise,
|
|
10
|
+
import { WsProvider, ApiPromise, Keyring, HttpProvider } from '@polkadot/api';
|
|
11
11
|
import { Struct, Vector, u8, Bytes, Enum, Tuple, _void, u64, u32, Option, bool, u128 } from 'scale-ts';
|
|
12
|
-
import { keccakAsU8a, decodeAddress, keccakAsHex,
|
|
13
|
-
import {
|
|
12
|
+
import { xxhashAsU8a, keccakAsU8a, decodeAddress, keccakAsHex, blake2AsU8a } from '@polkadot/util-crypto';
|
|
13
|
+
import { u8aToHex, u8aConcat, hexToU8a, stringToU8a, isHex as isHex$1, u8aToString } from '@polkadot/util';
|
|
14
14
|
import PQueue from 'p-queue';
|
|
15
15
|
import { hasWindow, isNode, env } from 'std-env';
|
|
16
16
|
import mergeRace from '@async-generator/merge-race';
|
|
@@ -2797,7 +2797,7 @@ var chainConfigs = {
|
|
|
2797
2797
|
// "Usdt0Oft": Not available on BSC
|
|
2798
2798
|
},
|
|
2799
2799
|
rpcEnvKey: "BSC_MAINNET",
|
|
2800
|
-
defaultRpcUrl: "https://
|
|
2800
|
+
defaultRpcUrl: "https://bsc-rpc.publicnode.com",
|
|
2801
2801
|
consensusStateId: "BSC0",
|
|
2802
2802
|
coingeckoId: "binance-smart-chain",
|
|
2803
2803
|
erc4626Vaults: [
|
|
@@ -3847,6 +3847,11 @@ var ABI3 = [
|
|
|
3847
3847
|
type: "uint256",
|
|
3848
3848
|
internalType: "uint256"
|
|
3849
3849
|
},
|
|
3850
|
+
{
|
|
3851
|
+
name: "validUntil",
|
|
3852
|
+
type: "uint256",
|
|
3853
|
+
internalType: "uint256"
|
|
3854
|
+
},
|
|
3850
3855
|
{
|
|
3851
3856
|
name: "outputs",
|
|
3852
3857
|
type: "tuple[]",
|
|
@@ -7814,6 +7819,255 @@ function encodeISMPMessage(message) {
|
|
|
7814
7819
|
throw new Error("Failed to encode ISMP message", { cause: error });
|
|
7815
7820
|
}
|
|
7816
7821
|
}
|
|
7822
|
+
|
|
7823
|
+
// src/utils/rateLimiter.ts
|
|
7824
|
+
var TokenBucket = class {
|
|
7825
|
+
/**
|
|
7826
|
+
* @param ratePerSecond - sustained requests per second.
|
|
7827
|
+
* @param burst - how many requests may go out back-to-back before pacing starts. Defaults to
|
|
7828
|
+
* one second's worth, which is the shape most limiters police.
|
|
7829
|
+
*/
|
|
7830
|
+
constructor(ratePerSecond, burst = ratePerSecond) {
|
|
7831
|
+
this.ratePerSecond = ratePerSecond;
|
|
7832
|
+
this.burst = burst;
|
|
7833
|
+
if (ratePerSecond <= 0) throw new Error(`TokenBucket rate must be positive, got ${ratePerSecond}`);
|
|
7834
|
+
this.tokens = burst;
|
|
7835
|
+
this.lastRefill = Date.now();
|
|
7836
|
+
}
|
|
7837
|
+
ratePerSecond;
|
|
7838
|
+
burst;
|
|
7839
|
+
/** Fractional on purpose: a partial token is real capacity, just not yet a whole request. */
|
|
7840
|
+
tokens;
|
|
7841
|
+
lastRefill;
|
|
7842
|
+
waiting = [];
|
|
7843
|
+
drainTimer = null;
|
|
7844
|
+
/** Resolves once this caller may send. */
|
|
7845
|
+
async acquire() {
|
|
7846
|
+
if (this.waiting.length === 0 && this.take()) return;
|
|
7847
|
+
return new Promise((resolve) => {
|
|
7848
|
+
this.waiting.push(resolve);
|
|
7849
|
+
this.scheduleDrain();
|
|
7850
|
+
});
|
|
7851
|
+
}
|
|
7852
|
+
/** Requests currently waiting on a token. Exposed for tests and diagnostics. */
|
|
7853
|
+
get queued() {
|
|
7854
|
+
return this.waiting.length;
|
|
7855
|
+
}
|
|
7856
|
+
refill() {
|
|
7857
|
+
const now = Date.now();
|
|
7858
|
+
const elapsed = now - this.lastRefill;
|
|
7859
|
+
if (elapsed <= 0) {
|
|
7860
|
+
if (elapsed < 0) this.lastRefill = now;
|
|
7861
|
+
return;
|
|
7862
|
+
}
|
|
7863
|
+
this.tokens = Math.min(this.burst, this.tokens + elapsed * this.ratePerSecond / 1e3);
|
|
7864
|
+
this.lastRefill = now;
|
|
7865
|
+
}
|
|
7866
|
+
take() {
|
|
7867
|
+
this.refill();
|
|
7868
|
+
if (this.tokens < 1) return false;
|
|
7869
|
+
this.tokens -= 1;
|
|
7870
|
+
return true;
|
|
7871
|
+
}
|
|
7872
|
+
scheduleDrain() {
|
|
7873
|
+
if (this.drainTimer) return;
|
|
7874
|
+
this.refill();
|
|
7875
|
+
const deficit = 1 - this.tokens;
|
|
7876
|
+
const waitMs = deficit <= 0 ? 0 : Math.ceil(deficit * 1e3 / this.ratePerSecond);
|
|
7877
|
+
const timer = setTimeout(() => {
|
|
7878
|
+
this.drainTimer = null;
|
|
7879
|
+
this.drain();
|
|
7880
|
+
}, waitMs);
|
|
7881
|
+
timer.unref?.();
|
|
7882
|
+
this.drainTimer = timer;
|
|
7883
|
+
}
|
|
7884
|
+
drain() {
|
|
7885
|
+
while (this.waiting.length > 0 && this.take()) {
|
|
7886
|
+
this.waiting.shift()?.();
|
|
7887
|
+
}
|
|
7888
|
+
if (this.waiting.length > 0) this.scheduleDrain();
|
|
7889
|
+
}
|
|
7890
|
+
};
|
|
7891
|
+
var BATCHES_NOT_SUPPORTED_CODE = -32005;
|
|
7892
|
+
var TOO_BIG_BATCH_REQUEST_CODE = -32010;
|
|
7893
|
+
var DEFAULT_MAX_BATCH_SIZE = 32;
|
|
7894
|
+
function rpcError({ code, message, data }) {
|
|
7895
|
+
const suffix = data === void 0 ? "" : `: ${typeof data === "string" ? data : JSON.stringify(data)}`;
|
|
7896
|
+
const error = new Error(`${code}: ${message}${suffix}`);
|
|
7897
|
+
error.code = code;
|
|
7898
|
+
error.data = data;
|
|
7899
|
+
return error;
|
|
7900
|
+
}
|
|
7901
|
+
var BatchingHttpProvider = class _BatchingHttpProvider extends HttpProvider {
|
|
7902
|
+
#endpoint;
|
|
7903
|
+
#headers;
|
|
7904
|
+
#limiter;
|
|
7905
|
+
#maxBatchSize;
|
|
7906
|
+
#batchingSupported = true;
|
|
7907
|
+
#pending = [];
|
|
7908
|
+
#flushTimer = null;
|
|
7909
|
+
#flushing = false;
|
|
7910
|
+
#nextId = 1;
|
|
7911
|
+
constructor(endpoint, headers, limiter, maxBatchSize = DEFAULT_MAX_BATCH_SIZE) {
|
|
7912
|
+
super(endpoint, headers, 0);
|
|
7913
|
+
this.#endpoint = endpoint;
|
|
7914
|
+
this.#headers = headers;
|
|
7915
|
+
this.#limiter = limiter;
|
|
7916
|
+
this.#maxBatchSize = maxBatchSize;
|
|
7917
|
+
}
|
|
7918
|
+
/**
|
|
7919
|
+
* `isCacheable` is accepted for interface compatibility and ignored: this provider has no
|
|
7920
|
+
* response cache, which is deliberate — see the note in `IntentsCoprocessor.http`.
|
|
7921
|
+
*/
|
|
7922
|
+
async send(method, params, _isCacheable) {
|
|
7923
|
+
return new Promise((resolve, reject) => {
|
|
7924
|
+
this.#pending.push({
|
|
7925
|
+
id: this.#nextId++,
|
|
7926
|
+
method,
|
|
7927
|
+
params,
|
|
7928
|
+
resolve,
|
|
7929
|
+
reject
|
|
7930
|
+
});
|
|
7931
|
+
if (this.#pending.length >= this.#maxBatchSize) this.#flushNow();
|
|
7932
|
+
else this.#scheduleFlush();
|
|
7933
|
+
});
|
|
7934
|
+
}
|
|
7935
|
+
clone() {
|
|
7936
|
+
return new _BatchingHttpProvider(this.#endpoint, this.#headers, this.#limiter, this.#maxBatchSize);
|
|
7937
|
+
}
|
|
7938
|
+
/** Calls waiting for a flush. Exposed for tests and diagnostics. */
|
|
7939
|
+
get queued() {
|
|
7940
|
+
return this.#pending.length;
|
|
7941
|
+
}
|
|
7942
|
+
/**
|
|
7943
|
+
* Collect for the rest of this macrotask, then send.
|
|
7944
|
+
*
|
|
7945
|
+
* A macrotask rather than a microtask because the bursts worth catching are not synchronous:
|
|
7946
|
+
* `Promise.all` over a set of reads starts them in one synchronous run, but each then advances
|
|
7947
|
+
* through several microtask turns of its own before reaching the provider. A microtask flush
|
|
7948
|
+
* would fire between those turns and split one burst across several requests.
|
|
7949
|
+
*/
|
|
7950
|
+
#scheduleFlush() {
|
|
7951
|
+
if (this.#flushTimer || this.#flushing) return;
|
|
7952
|
+
const timer = setTimeout(() => {
|
|
7953
|
+
this.#flushTimer = null;
|
|
7954
|
+
void this.#flush();
|
|
7955
|
+
}, 0);
|
|
7956
|
+
timer.unref?.();
|
|
7957
|
+
this.#flushTimer = timer;
|
|
7958
|
+
}
|
|
7959
|
+
#flushNow() {
|
|
7960
|
+
if (this.#flushing) return;
|
|
7961
|
+
if (this.#flushTimer) {
|
|
7962
|
+
clearTimeout(this.#flushTimer);
|
|
7963
|
+
this.#flushTimer = null;
|
|
7964
|
+
}
|
|
7965
|
+
void this.#flush();
|
|
7966
|
+
}
|
|
7967
|
+
async #flush() {
|
|
7968
|
+
if (this.#flushing) return;
|
|
7969
|
+
const calls = this.#pending.splice(0, this.#maxBatchSize);
|
|
7970
|
+
if (calls.length === 0) return;
|
|
7971
|
+
this.#flushing = true;
|
|
7972
|
+
try {
|
|
7973
|
+
await this.#limiter.acquire();
|
|
7974
|
+
await this.#post(calls);
|
|
7975
|
+
} finally {
|
|
7976
|
+
this.#flushing = false;
|
|
7977
|
+
if (this.#pending.length > 0) this.#scheduleFlush();
|
|
7978
|
+
}
|
|
7979
|
+
}
|
|
7980
|
+
async #post(calls) {
|
|
7981
|
+
const single = calls.length === 1;
|
|
7982
|
+
const payload = calls.map(({ id, method, params }) => ({ id, jsonrpc: "2.0", method, params }));
|
|
7983
|
+
const body = JSON.stringify(single ? payload[0] : payload);
|
|
7984
|
+
let parsed;
|
|
7985
|
+
try {
|
|
7986
|
+
const response = await fetch(this.#endpoint, {
|
|
7987
|
+
body,
|
|
7988
|
+
headers: {
|
|
7989
|
+
Accept: "application/json",
|
|
7990
|
+
"Content-Type": "application/json",
|
|
7991
|
+
...this.#headers
|
|
7992
|
+
},
|
|
7993
|
+
method: "POST"
|
|
7994
|
+
});
|
|
7995
|
+
if (!response.ok) throw new Error(`[${response.status}]: ${response.statusText}`);
|
|
7996
|
+
parsed = JSON.parse(await response.text());
|
|
7997
|
+
} catch (err) {
|
|
7998
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
7999
|
+
error.message = `${error.message}
|
|
8000
|
+
Failed HTTP Request: ${JSON.stringify(
|
|
8001
|
+
calls.map(({ method, params }) => ({ method, params }))
|
|
8002
|
+
)}`;
|
|
8003
|
+
for (const call of calls) call.reject(error);
|
|
8004
|
+
return;
|
|
8005
|
+
}
|
|
8006
|
+
if (Array.isArray(parsed)) {
|
|
8007
|
+
this.#settleBatch(calls, parsed);
|
|
8008
|
+
return;
|
|
8009
|
+
}
|
|
8010
|
+
if (!single) {
|
|
8011
|
+
this.#handleBatchRefusal(calls, parsed);
|
|
8012
|
+
return;
|
|
8013
|
+
}
|
|
8014
|
+
this.#settle(calls[0], parsed);
|
|
8015
|
+
}
|
|
8016
|
+
#settleBatch(calls, responses) {
|
|
8017
|
+
const byId = /* @__PURE__ */ new Map();
|
|
8018
|
+
for (const response of responses) {
|
|
8019
|
+
if (typeof response?.id === "number") byId.set(response.id, response);
|
|
8020
|
+
}
|
|
8021
|
+
for (const call of calls) {
|
|
8022
|
+
const response = byId.get(call.id);
|
|
8023
|
+
if (response) this.#settle(call, response);
|
|
8024
|
+
else call.reject(new Error(`No response for ${call.method} in batch reply`));
|
|
8025
|
+
}
|
|
8026
|
+
}
|
|
8027
|
+
#settle(call, response) {
|
|
8028
|
+
if (response?.error) {
|
|
8029
|
+
call.reject(rpcError(response.error));
|
|
8030
|
+
return;
|
|
8031
|
+
}
|
|
8032
|
+
if (!response || response.result === void 0) {
|
|
8033
|
+
call.reject(new Error("No result found in jsonrpc response"));
|
|
8034
|
+
return;
|
|
8035
|
+
}
|
|
8036
|
+
call.resolve(response.result);
|
|
8037
|
+
}
|
|
8038
|
+
/**
|
|
8039
|
+
* The server rejected the batch itself rather than any call in it. Both forms are recoverable
|
|
8040
|
+
* without losing a call, and neither should ever surface to a caller as a failure.
|
|
8041
|
+
*/
|
|
8042
|
+
#handleBatchRefusal(calls, response) {
|
|
8043
|
+
const code = response?.error?.code;
|
|
8044
|
+
if (code === BATCHES_NOT_SUPPORTED_CODE) {
|
|
8045
|
+
this.#batchingSupported = false;
|
|
8046
|
+
this.#maxBatchSize = 1;
|
|
8047
|
+
this.#requeue(calls);
|
|
8048
|
+
return;
|
|
8049
|
+
}
|
|
8050
|
+
if (code === TOO_BIG_BATCH_REQUEST_CODE) {
|
|
8051
|
+
this.#maxBatchSize = Math.max(1, Math.floor(this.#maxBatchSize / 2));
|
|
8052
|
+
this.#requeue(calls);
|
|
8053
|
+
return;
|
|
8054
|
+
}
|
|
8055
|
+
const error = response?.error ? rpcError(response.error) : new Error("Malformed batch reply: neither an array nor an error");
|
|
8056
|
+
for (const call of calls) call.reject(error);
|
|
8057
|
+
}
|
|
8058
|
+
/** Puts calls back at the head of the queue, so a refused batch keeps its place in line. */
|
|
8059
|
+
#requeue(calls) {
|
|
8060
|
+
this.#pending.unshift(...calls);
|
|
8061
|
+
this.#scheduleFlush();
|
|
8062
|
+
}
|
|
8063
|
+
/** Whether batches are still being attempted. Exposed for tests and diagnostics. */
|
|
8064
|
+
get batchingSupported() {
|
|
8065
|
+
return this.#batchingSupported;
|
|
8066
|
+
}
|
|
8067
|
+
};
|
|
8068
|
+
|
|
8069
|
+
// src/chains/intentsCoprocessor.ts
|
|
8070
|
+
var SYSTEM_EVENTS_KEY = u8aToHex(u8aConcat(xxhashAsU8a("System", 128), xxhashAsU8a("Events", 128)));
|
|
7817
8071
|
var OFFCHAIN_BID_PREFIX = new TextEncoder().encode("intents::bid::");
|
|
7818
8072
|
var OFFCHAIN_PHANTOM_PREFIX = new TextEncoder().encode("intents::phantom::order::");
|
|
7819
8073
|
var HYPERBRIDGE_TYPES_BUNDLE = {
|
|
@@ -7827,6 +8081,63 @@ var HTTP_CONNECT_TIMEOUT_MS = 2e4;
|
|
|
7827
8081
|
var INCLUSION_TIMEOUT_MS = 2e4;
|
|
7828
8082
|
var PHANTOM_POLL_INTERVAL_MS = 15e3;
|
|
7829
8083
|
var GARGANTUA_PHANTOM_POLL_INTERVAL_MS = 6e3;
|
|
8084
|
+
var DEFAULT_RPC_MAX_RPS = 8;
|
|
8085
|
+
var DEFAULT_MAX_BLOCKS_PER_POLL = 10;
|
|
8086
|
+
var MAX_RATE_LIMIT_BACKOFF_TICKS = 8;
|
|
8087
|
+
var rpcLimiters = /* @__PURE__ */ new Map();
|
|
8088
|
+
function limiterFor(httpUrl) {
|
|
8089
|
+
const key = new URL(httpUrl).origin;
|
|
8090
|
+
let limiter = rpcLimiters.get(key);
|
|
8091
|
+
if (!limiter) {
|
|
8092
|
+
limiter = new TokenBucket(configuredRpcMaxRps());
|
|
8093
|
+
rpcLimiters.set(key, limiter);
|
|
8094
|
+
}
|
|
8095
|
+
return limiter;
|
|
8096
|
+
}
|
|
8097
|
+
function configuredRpcMaxRps() {
|
|
8098
|
+
const raw = typeof process !== "undefined" ? process.env?.HYPERBRIDGE_RPC_MAX_RPS : void 0;
|
|
8099
|
+
const parsed = raw === void 0 ? Number.NaN : Number(raw);
|
|
8100
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_RPC_MAX_RPS;
|
|
8101
|
+
}
|
|
8102
|
+
function isRateLimited(err) {
|
|
8103
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
8104
|
+
return message.includes("[429]") || /too many requests/i.test(message);
|
|
8105
|
+
}
|
|
8106
|
+
function isMethodUnavailable(err) {
|
|
8107
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
8108
|
+
const code = err?.code;
|
|
8109
|
+
return code === -32601 || /method not found|unsafe to be called externally/i.test(message);
|
|
8110
|
+
}
|
|
8111
|
+
var EventDecodeError = class extends Error {
|
|
8112
|
+
};
|
|
8113
|
+
function phantomOrdersFrom(records) {
|
|
8114
|
+
if (records == null || typeof records[Symbol.iterator] !== "function") {
|
|
8115
|
+
throw new EventDecodeError(`Expected a decoded event vector, got ${typeof records}`);
|
|
8116
|
+
}
|
|
8117
|
+
const orders = [];
|
|
8118
|
+
for (const record of records) {
|
|
8119
|
+
if (typeof record !== "object" || record === null || !("event" in record)) {
|
|
8120
|
+
throw new EventDecodeError(
|
|
8121
|
+
"system.events did not decode to event records \u2014 the storage entry's metadata was probably not carried through, leaving the value as raw bytes"
|
|
8122
|
+
);
|
|
8123
|
+
}
|
|
8124
|
+
const { event } = record;
|
|
8125
|
+
if (event.section !== "intentsCoprocessor" || event.method !== "PhantomOrderRegistered") continue;
|
|
8126
|
+
const [commitment, chain, createdAt, legs] = event.data;
|
|
8127
|
+
orders.push({
|
|
8128
|
+
commitment: commitment.toHex(),
|
|
8129
|
+
chain: new TextDecoder().decode(hexToU8a(chain.toHex())),
|
|
8130
|
+
createdAt: createdAt.toNumber(),
|
|
8131
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8132
|
+
legs: legs.map((leg) => ({
|
|
8133
|
+
tokenA: leg.tokenA.toHex(),
|
|
8134
|
+
tokenB: leg.tokenB.toHex(),
|
|
8135
|
+
standardAmount: BigInt(leg.standardAmount.toString())
|
|
8136
|
+
}))
|
|
8137
|
+
});
|
|
8138
|
+
}
|
|
8139
|
+
return orders;
|
|
8140
|
+
}
|
|
7830
8141
|
function rejectAfter(ms, message) {
|
|
7831
8142
|
return new Promise((_resolve, reject) => {
|
|
7832
8143
|
const timer = setTimeout(() => reject(new Error(message)), ms);
|
|
@@ -7898,8 +8209,14 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
7898
8209
|
ownsConnection;
|
|
7899
8210
|
/** Cached result of whether the node exposes intents_* RPC methods */
|
|
7900
8211
|
hasIntentsRpc = null;
|
|
8212
|
+
/** The pallet's phantom timings, read once. Cleared on failure so the read retries. */
|
|
8213
|
+
phantomTimingsRead = null;
|
|
7901
8214
|
/** The HTTP-backed api, connected on first use. Cleared after a failed attempt so it retries. */
|
|
7902
8215
|
httpApi = null;
|
|
8216
|
+
/** Last runtime version read from the node, for {@link confirmedRuntimeVersion} to compare against. */
|
|
8217
|
+
lastRuntimeVersion;
|
|
8218
|
+
/** Set once the node refuses `state_queryStorage`, so the poll stops asking for it. */
|
|
8219
|
+
rangeQueryUnavailable = false;
|
|
7903
8220
|
// Serialises every extrinsic submission on this instance's substrate account. All submit/retract
|
|
7904
8221
|
// methods funnel through signAndSendExtrinsic, each using the API's auto-nonce; fired in parallel
|
|
7905
8222
|
// (bids for orders on different chains, or several phantom orders in one interval) they would grab
|
|
@@ -7999,7 +8316,11 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
7999
8316
|
// replayed from memory on every tick, faster than the TTL could lapse, and the node never
|
|
8000
8317
|
// saw a second request. The cache bought nothing here anyway: the poll reads each block
|
|
8001
8318
|
// once, and `api.at(hash)` reuses registries at the api layer regardless.
|
|
8002
|
-
|
|
8319
|
+
// Concurrent calls are coalesced into one JSON-RPC batch request, and every request
|
|
8320
|
+
// to this endpoint is paced by a bucket shared with any other coprocessor in this
|
|
8321
|
+
// process pointed at the same host — the limit is the server's, and it counts
|
|
8322
|
+
// requests per address rather than per connection.
|
|
8323
|
+
provider: new BatchingHttpProvider(httpUrl, {}, limiterFor(httpUrl)),
|
|
8003
8324
|
typesBundle: HYPERBRIDGE_TYPES_BUNDLE,
|
|
8004
8325
|
// A second connection to the node the ws api already reported on; its init warnings
|
|
8005
8326
|
// would just be duplicates.
|
|
@@ -8567,29 +8888,77 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
8567
8888
|
}
|
|
8568
8889
|
/**
|
|
8569
8890
|
* Reads the PhantomOrderRegistered events emitted in a single block.
|
|
8891
|
+
*
|
|
8892
|
+
* Costs two RPCs per block when `knownVersion` is supplied and four without it, which is why the
|
|
8893
|
+
* poll goes to the trouble of establishing one. `api.at(hash)` has to work out which metadata to
|
|
8894
|
+
* decode the block against, and with nothing to go on it fetches the header and then the runtime
|
|
8895
|
+
* version at its parent — every block, forever. Its cheaper paths are a registry already pinned
|
|
8896
|
+
* to this exact hash (only ever the previous block's) or one matching a version the caller
|
|
8897
|
+
* names, so naming the version is the only way out. See `getBlockRegistry` in
|
|
8898
|
+
* `@polkadot/api/base/Init`; the `getUpgradeVersion` shortcut that would otherwise skip the
|
|
8899
|
+
* lookup only covers chains hardcoded in `@polkadot/types-known`, which Hyperbridge is not.
|
|
8900
|
+
*
|
|
8901
|
+
* @param knownVersion - the runtime version this block is known to run, if the caller has
|
|
8902
|
+
* established one. Passing a version the block does not actually run decodes it against the
|
|
8903
|
+
* wrong metadata, so this is for callers that have checked, not a place to pass a guess.
|
|
8570
8904
|
*/
|
|
8571
|
-
async getPhantomOrdersInBlock(blockNumber) {
|
|
8905
|
+
async getPhantomOrdersInBlock(blockNumber, knownVersion) {
|
|
8572
8906
|
const api = await this.http();
|
|
8573
8907
|
const blockHash = await api.rpc.chain.getBlockHash(blockNumber);
|
|
8574
|
-
|
|
8575
|
-
|
|
8576
|
-
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8908
|
+
return await this.getPhantomOrdersAtHash(blockHash.toHex(), knownVersion);
|
|
8909
|
+
}
|
|
8910
|
+
/**
|
|
8911
|
+
* The same read, for a caller that already holds the block's hash.
|
|
8912
|
+
*
|
|
8913
|
+
* Split out so the poll can fetch a whole range's hashes in one concurrent wave — which the
|
|
8914
|
+
* provider coalesces into a single batched request — and then read each block's events knowing
|
|
8915
|
+
* its hash. `chain_getBlockHash` is the half of the pair that parallelises safely: it takes no
|
|
8916
|
+
* historic block hash, so it never triggers polkadot-js's per-hash registry resolution, and
|
|
8917
|
+
* concurrent calls cannot race each other's registry state.
|
|
8918
|
+
*/
|
|
8919
|
+
async getPhantomOrdersAtHash(blockHash, knownVersion) {
|
|
8920
|
+
const api = await this.http();
|
|
8921
|
+
const apiAt = await api.at(blockHash, knownVersion);
|
|
8922
|
+
return phantomOrdersFrom(await apiAt.query.system.events());
|
|
8923
|
+
}
|
|
8924
|
+
/**
|
|
8925
|
+
* Every block's phantom orders across a whole range, in one `state_queryStorage` call.
|
|
8926
|
+
*
|
|
8927
|
+
* This is the cheap path: the request cost of a scan stops depending on how many blocks it
|
|
8928
|
+
* covers. The events key is the only key queried, and both bounds are block hashes the caller
|
|
8929
|
+
* already holds.
|
|
8930
|
+
*
|
|
8931
|
+
* Two properties of the RPC shape the result.
|
|
8932
|
+
*
|
|
8933
|
+
* It returns *diffs*: `query_storage_unfiltered` in `sc-rpc` pushes a change set for a block only
|
|
8934
|
+
* when the value differs from the previous block in the range (`has_changed`, and the set is
|
|
8935
|
+
* dropped when empty), so a block whose events encode byte-for-byte identically to its
|
|
8936
|
+
* predecessor's is simply absent. That happens on a quiet chain, where consecutive blocks carry
|
|
8937
|
+
* nothing but the timestamp inherent's `ExtrinsicSuccess`. It is safe here because an absent
|
|
8938
|
+
* block provably carries no phantom orders: a `PhantomOrderRegistered` commitment is derived from
|
|
8939
|
+
* the block number (`phantom_order_commitment`), so a block that registered orders can never
|
|
8940
|
+
* encode identically to any other block. Absent therefore means "same as the previous block",
|
|
8941
|
+
* and the previous block having orders would contradict that.
|
|
8942
|
+
*
|
|
8943
|
+
* And it is gated by `--rpc-methods` (`check_if_safe` in `sc-rpc`), which answers a denied call
|
|
8944
|
+
* with `Method not found`. The node this reads from must already run unsafe RPC to serve
|
|
8945
|
+
* `offchain_localStorageGet` for the orders themselves, so this is normally available; the poll
|
|
8946
|
+
* falls back to reading block by block when it is not.
|
|
8947
|
+
*
|
|
8948
|
+
* @returns one entry per block the node reported a change for, in ascending block order.
|
|
8949
|
+
*/
|
|
8950
|
+
async getPhantomOrdersInRange(fromBlockHash, toBlockHash) {
|
|
8951
|
+
const api = await this.http();
|
|
8952
|
+
const changeSets = await api.rpc.state.queryStorage.raw(
|
|
8953
|
+
[SYSTEM_EVENTS_KEY],
|
|
8954
|
+
fromBlockHash,
|
|
8955
|
+
toBlockHash
|
|
8956
|
+
);
|
|
8957
|
+
return changeSets.map((changeSet) => {
|
|
8958
|
+
const value = changeSet?.changes?.find(([key]) => key === SYSTEM_EVENTS_KEY)?.[1];
|
|
8959
|
+
if (!value) return [];
|
|
8960
|
+
return phantomOrdersFrom(api.registry.createType("Vec<EventRecord>", value));
|
|
8961
|
+
});
|
|
8593
8962
|
}
|
|
8594
8963
|
/**
|
|
8595
8964
|
* Polls for newly registered phantom orders, invoking the callback once per block that carries
|
|
@@ -8617,30 +8986,85 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
8617
8986
|
* socket that looks alive while delivering nothing. It also means a websocket outage does not
|
|
8618
8987
|
* pause phantom bidding at all — the two transports fail independently.
|
|
8619
8988
|
*
|
|
8989
|
+
* What the cadence does *not* describe is the request rate, which is what rate limiters police.
|
|
8990
|
+
* A tick costs four requests whatever the range covers — the head, the runtime version, the two
|
|
8991
|
+
* bounding block hashes as one batched request, and one `state_queryStorage` for every block's
|
|
8992
|
+
* events — and they go out back-to-back, so an interval well under any per-second limit could
|
|
8993
|
+
* still arrive as a burst over it. Three things keep that in bounds: the provider coalesces
|
|
8994
|
+
* concurrent calls into one request and paces requests through the endpoint's token bucket (see
|
|
8995
|
+
* `http`), and `maxBlocksPerPoll` bounds the range. A 429 that gets through anyway backs the
|
|
8996
|
+
* poll off for a doubling number of ticks, so a limiter that is already shedding load is not
|
|
8997
|
+
* handed the next window's budget in rejections too.
|
|
8998
|
+
*
|
|
8999
|
+
* Where the node will not serve `state_queryStorage` the poll reads block by block instead, at
|
|
9000
|
+
* three requests plus one per block; see {@link scanRangeAtOnce}.
|
|
9001
|
+
*
|
|
8620
9002
|
* Returns a function that stops polling.
|
|
8621
9003
|
*/
|
|
8622
9004
|
pollPhantomOrders(callback, options = {}) {
|
|
8623
|
-
const {
|
|
9005
|
+
const {
|
|
9006
|
+
intervalMs,
|
|
9007
|
+
maxBlocksPerPoll = DEFAULT_MAX_BLOCKS_PER_POLL,
|
|
9008
|
+
onError,
|
|
9009
|
+
onSkip
|
|
9010
|
+
} = options;
|
|
8624
9011
|
let cursor = null;
|
|
8625
9012
|
let inFlight = false;
|
|
8626
9013
|
let stopped = false;
|
|
9014
|
+
let backoffTicks = 0;
|
|
9015
|
+
let backoffLength = 0;
|
|
8627
9016
|
const tick = async () => {
|
|
8628
9017
|
if (inFlight || stopped) return;
|
|
9018
|
+
if (backoffTicks > 0) {
|
|
9019
|
+
backoffTicks -= 1;
|
|
9020
|
+
return;
|
|
9021
|
+
}
|
|
8629
9022
|
inFlight = true;
|
|
8630
9023
|
try {
|
|
8631
|
-
const
|
|
9024
|
+
const api = await this.http();
|
|
9025
|
+
const head = (await api.rpc.chain.getHeader()).number.toNumber();
|
|
8632
9026
|
if (cursor === null) {
|
|
8633
|
-
cursor = Math.max(head - 1
|
|
9027
|
+
cursor = Math.max(head - 1, -1);
|
|
9028
|
+
} else {
|
|
9029
|
+
const { bidWindowBlocks, intervalBlocks } = await this.phantomTimings();
|
|
9030
|
+
if (head - cursor > bidWindowBlocks + Math.max(intervalBlocks, bidWindowBlocks)) {
|
|
9031
|
+
const from = cursor + 1;
|
|
9032
|
+
cursor = Math.max(head - 1 - bidWindowBlocks, -1);
|
|
9033
|
+
onSkip?.({ from, to: cursor, head });
|
|
9034
|
+
}
|
|
8634
9035
|
}
|
|
8635
9036
|
if (head <= cursor) return;
|
|
9037
|
+
const knownVersion = await this.confirmedRuntimeVersion();
|
|
8636
9038
|
const to = Math.min(head, cursor + maxBlocksPerPoll);
|
|
8637
|
-
|
|
9039
|
+
const ranged = await this.scanRangeAtOnce(api, cursor + 1, to, knownVersion, onError);
|
|
9040
|
+
if (ranged) {
|
|
9041
|
+
for (const orders of ranged) {
|
|
9042
|
+
if (stopped) return;
|
|
9043
|
+
if (orders.length > 0) callback(orders);
|
|
9044
|
+
}
|
|
9045
|
+
cursor = to;
|
|
9046
|
+
backoffLength = 0;
|
|
9047
|
+
return;
|
|
9048
|
+
}
|
|
9049
|
+
const numbers = [];
|
|
9050
|
+
for (let blockNumber = cursor + 1; blockNumber <= to; blockNumber++) numbers.push(blockNumber);
|
|
9051
|
+
const hashes = await Promise.allSettled(
|
|
9052
|
+
numbers.map((blockNumber) => api.rpc.chain.getBlockHash(blockNumber))
|
|
9053
|
+
);
|
|
9054
|
+
for (let index = 0; index < numbers.length; index++) {
|
|
8638
9055
|
if (stopped) return;
|
|
8639
|
-
const
|
|
9056
|
+
const hash = hashes[index];
|
|
9057
|
+
if (hash.status === "rejected") throw hash.reason;
|
|
9058
|
+
const orders = await this.getPhantomOrdersAtHash(hash.value.toHex(), knownVersion);
|
|
8640
9059
|
if (orders.length > 0) callback(orders);
|
|
8641
|
-
cursor =
|
|
9060
|
+
cursor = numbers[index];
|
|
8642
9061
|
}
|
|
9062
|
+
backoffLength = 0;
|
|
8643
9063
|
} catch (err) {
|
|
9064
|
+
if (isRateLimited(err)) {
|
|
9065
|
+
backoffLength = Math.min(backoffLength === 0 ? 1 : backoffLength * 2, MAX_RATE_LIMIT_BACKOFF_TICKS);
|
|
9066
|
+
backoffTicks = backoffLength;
|
|
9067
|
+
}
|
|
8644
9068
|
onError?.(err);
|
|
8645
9069
|
} finally {
|
|
8646
9070
|
inFlight = false;
|
|
@@ -8659,6 +9083,125 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
|
|
|
8659
9083
|
if (timer) clearInterval(timer);
|
|
8660
9084
|
};
|
|
8661
9085
|
}
|
|
9086
|
+
/**
|
|
9087
|
+
* The Hyperbridge head, over HTTP like every other read here.
|
|
9088
|
+
*
|
|
9089
|
+
* Exposed for callers that have to know how old something is: a phantom order carries the block
|
|
9090
|
+
* it was registered at, and only against the head does that become "still biddable" or "long
|
|
9091
|
+
* expired".
|
|
9092
|
+
*/
|
|
9093
|
+
async latestBlockNumber() {
|
|
9094
|
+
const api = await this.http();
|
|
9095
|
+
return (await api.rpc.chain.getHeader()).number.toNumber();
|
|
9096
|
+
}
|
|
9097
|
+
/**
|
|
9098
|
+
* The pallet's phantom timings, read from chain state.
|
|
9099
|
+
*
|
|
9100
|
+
* Both are governance-settable and neither is derivable: on Nexus today the window is 15 while
|
|
9101
|
+
* the runtime constant behind it is 25, so anything hard-coded is wrong in one direction or the
|
|
9102
|
+
* other — too tight and live orders are dropped, too loose and bids are sent into a closed
|
|
9103
|
+
* window for the pallet to reject.
|
|
9104
|
+
*
|
|
9105
|
+
* Read once per instance and cached, because a governance change to either is rare and a read
|
|
9106
|
+
* per poll tick would be a request per tick forever. The cost is that a change is picked up on
|
|
9107
|
+
* the next restart rather than immediately. A failed read is not cached, so it retries.
|
|
9108
|
+
*/
|
|
9109
|
+
async phantomTimings() {
|
|
9110
|
+
if (!this.phantomTimingsRead) {
|
|
9111
|
+
this.phantomTimingsRead = this.readPhantomTimings().catch((err) => {
|
|
9112
|
+
this.phantomTimingsRead = null;
|
|
9113
|
+
throw err;
|
|
9114
|
+
});
|
|
9115
|
+
}
|
|
9116
|
+
return this.phantomTimingsRead;
|
|
9117
|
+
}
|
|
9118
|
+
async readPhantomTimings() {
|
|
9119
|
+
const api = await this.http();
|
|
9120
|
+
const [window, interval] = await Promise.all([
|
|
9121
|
+
api.query.intentsCoprocessor.phantomBidWindow(),
|
|
9122
|
+
api.query.intentsCoprocessor.phantomOrderInterval()
|
|
9123
|
+
]);
|
|
9124
|
+
const stored = Number(window.toString());
|
|
9125
|
+
return {
|
|
9126
|
+
bidWindowBlocks: stored === 0 ? Number(api.consts.intentsCoprocessor.phantomOrderBidWindowBlocks.toString()) : stored,
|
|
9127
|
+
intervalBlocks: Number(interval.toString())
|
|
9128
|
+
};
|
|
9129
|
+
}
|
|
9130
|
+
/**
|
|
9131
|
+
* A whole range of blocks in one `state_queryStorage` call, or `null` when that is not available
|
|
9132
|
+
* and the caller should read block by block.
|
|
9133
|
+
*
|
|
9134
|
+
* Two conditions have to hold, and both are about decoding rather than the range itself.
|
|
9135
|
+
*
|
|
9136
|
+
* The version must be confirmed for this tick — an upgrade inside the range means blocks decode
|
|
9137
|
+
* against different metadata, and one call cannot do that.
|
|
9138
|
+
*
|
|
9139
|
+
* And that confirmed version must still be the one the api's own registry was built for.
|
|
9140
|
+
* `state_queryStorage` declares no historic block hash, so rpc-core skips its registry swap and
|
|
9141
|
+
* decodes the reply against the default registry — fixed at connect, with no
|
|
9142
|
+
* `subscribeRuntimeVersion` on an HTTP api to refresh it. After an upgrade the two diverge, and
|
|
9143
|
+
* the per-block path takes over for good: `api.at(hash, version)` resolves, and builds, the right
|
|
9144
|
+
* registry. That costs a restart to get the cheap path back, which is the correct direction to
|
|
9145
|
+
* fail in.
|
|
9146
|
+
*/
|
|
9147
|
+
async scanRangeAtOnce(api, from, to, knownVersion, onError) {
|
|
9148
|
+
if (this.rangeQueryUnavailable || !knownVersion) return null;
|
|
9149
|
+
const registryVersion = api.runtimeVersion?.specVersion;
|
|
9150
|
+
if (!registryVersion || !knownVersion.specVersion.eq(registryVersion)) return null;
|
|
9151
|
+
const [fromHash, toHash] = from === to ? await Promise.all([api.rpc.chain.getBlockHash(from)]).then(([only]) => [only, only]) : await Promise.all([api.rpc.chain.getBlockHash(from), api.rpc.chain.getBlockHash(to)]);
|
|
9152
|
+
try {
|
|
9153
|
+
return await this.getPhantomOrdersInRange(fromHash.toHex(), toHash.toHex());
|
|
9154
|
+
} catch (err) {
|
|
9155
|
+
if (isMethodUnavailable(err)) {
|
|
9156
|
+
this.rangeQueryUnavailable = true;
|
|
9157
|
+
return null;
|
|
9158
|
+
}
|
|
9159
|
+
if (err instanceof EventDecodeError) {
|
|
9160
|
+
this.rangeQueryUnavailable = true;
|
|
9161
|
+
onError?.(err);
|
|
9162
|
+
return null;
|
|
9163
|
+
}
|
|
9164
|
+
throw err;
|
|
9165
|
+
}
|
|
9166
|
+
}
|
|
9167
|
+
/**
|
|
9168
|
+
* The runtime version this tick's blocks may be decoded against, or `undefined` when that cannot
|
|
9169
|
+
* be established and each block must resolve its own.
|
|
9170
|
+
*
|
|
9171
|
+
* Naming a version to `api.at` is what removes two of the four RPCs a block scan costs, and it
|
|
9172
|
+
* is only sound while the version is actually the block's. Getting that wrong is not a loud
|
|
9173
|
+
* failure: events decoded against the wrong metadata come back as a shape the scan does not
|
|
9174
|
+
* recognise, so the block reads as carrying no phantom orders and the cursor advances past it —
|
|
9175
|
+
* exactly the silent miss the block cursor exists to rule out.
|
|
9176
|
+
*
|
|
9177
|
+
* So the version is read fresh each tick and only used when it matches the previous reading.
|
|
9178
|
+
* `specVersion` only ever increases, and this read happens *after* the head read, so two equal
|
|
9179
|
+
* readings mean no upgrade landed anywhere in between — and therefore none in the range about to
|
|
9180
|
+
* be scanned. A reading that differs means an upgrade landed inside the range: that tick falls
|
|
9181
|
+
* back to per-block resolution, which is exact, and the version is used from the next tick on
|
|
9182
|
+
* once it has been seen twice.
|
|
9183
|
+
*
|
|
9184
|
+
* The gap this leaves is a backlog reaching back past an upgrade, whose oldest blocks predate
|
|
9185
|
+
* even the previous reading. Recovering from an outage that long means those bid windows closed
|
|
9186
|
+
* many upgrades ago, so nothing is lost that was still winnable.
|
|
9187
|
+
*
|
|
9188
|
+
* A version that cannot be read at all yields `undefined` rather than an error: the scan is
|
|
9189
|
+
* about to make the same request against the same endpoint and is the better place to report it.
|
|
9190
|
+
*/
|
|
9191
|
+
async confirmedRuntimeVersion() {
|
|
9192
|
+
let api;
|
|
9193
|
+
let current;
|
|
9194
|
+
try {
|
|
9195
|
+
api = await this.http();
|
|
9196
|
+
current = await api.rpc.state.getRuntimeVersion();
|
|
9197
|
+
} catch {
|
|
9198
|
+
return void 0;
|
|
9199
|
+
}
|
|
9200
|
+
const previous = this.lastRuntimeVersion ?? api.runtimeVersion;
|
|
9201
|
+
this.lastRuntimeVersion = current;
|
|
9202
|
+
if (!previous?.specVersion || !previous?.specName) return void 0;
|
|
9203
|
+
return current.specVersion.eq(previous.specVersion) && current.specName.eq(previous.specName) ? current : void 0;
|
|
9204
|
+
}
|
|
8662
9205
|
/**
|
|
8663
9206
|
* The poll cadence for the runtime this instance is connected to: Gargantua polls every block,
|
|
8664
9207
|
* everything else every 15s. Falls back to the slower cadence if the runtime cannot be read,
|
|
@@ -12090,40 +12633,17 @@ query AvailableLiquidity(
|
|
|
12090
12633
|
}
|
|
12091
12634
|
}`;
|
|
12092
12635
|
var BUY_AND_SELL_RATES = `
|
|
12093
|
-
query
|
|
12094
|
-
|
|
12095
|
-
$directChain: String!
|
|
12096
|
-
$directDirection: String!
|
|
12097
|
-
$reverseChain: String!
|
|
12098
|
-
$reverseDirection: String!
|
|
12099
|
-
) {
|
|
12100
|
-
direct: poolChainLiquidities(
|
|
12101
|
-
filter: {
|
|
12102
|
-
and: [
|
|
12103
|
-
{ poolId: { equalToInsensitive: $poolId } }
|
|
12104
|
-
{ chain: { equalTo: $directChain } }
|
|
12105
|
-
{ direction: { equalTo: $directDirection } }
|
|
12106
|
-
]
|
|
12107
|
-
}
|
|
12636
|
+
query GetLiquidityPoolRate($poolId: String!) {
|
|
12637
|
+
liquidityPools(
|
|
12108
12638
|
first: 1
|
|
12639
|
+
filter: { id: { equalToInsensitive: $poolId } }
|
|
12109
12640
|
) {
|
|
12110
12641
|
nodes {
|
|
12111
|
-
|
|
12112
|
-
|
|
12113
|
-
|
|
12114
|
-
|
|
12115
|
-
|
|
12116
|
-
filter: {
|
|
12117
|
-
and: [
|
|
12118
|
-
{ poolId: { equalToInsensitive: $poolId } }
|
|
12119
|
-
{ chain: { equalTo: $reverseChain } }
|
|
12120
|
-
{ direction: { equalTo: $reverseDirection } }
|
|
12121
|
-
]
|
|
12122
|
-
}
|
|
12123
|
-
first: 1
|
|
12124
|
-
) {
|
|
12125
|
-
nodes {
|
|
12126
|
-
rate
|
|
12642
|
+
id
|
|
12643
|
+
token0Symbol
|
|
12644
|
+
token1Symbol
|
|
12645
|
+
sellRate
|
|
12646
|
+
buyRate
|
|
12127
12647
|
lastUpdatedAt
|
|
12128
12648
|
}
|
|
12129
12649
|
}
|
|
@@ -17059,6 +17579,115 @@ var OrderCanceller = class _OrderCanceller {
|
|
|
17059
17579
|
return feeInDestFeeToken * 1005n / 1000n;
|
|
17060
17580
|
}
|
|
17061
17581
|
};
|
|
17582
|
+
var FILL_ORDER_V1_ABI = [
|
|
17583
|
+
{
|
|
17584
|
+
type: "function",
|
|
17585
|
+
name: "fillOrder",
|
|
17586
|
+
stateMutability: "payable",
|
|
17587
|
+
outputs: [],
|
|
17588
|
+
inputs: [
|
|
17589
|
+
ABI3.find((e) => e.type === "function" && e.name === "fillOrder").inputs[0],
|
|
17590
|
+
{
|
|
17591
|
+
name: "options",
|
|
17592
|
+
type: "tuple",
|
|
17593
|
+
internalType: "struct FillOptions",
|
|
17594
|
+
components: [
|
|
17595
|
+
{ name: "relayerFee", type: "uint256", internalType: "uint256" },
|
|
17596
|
+
{ name: "nativeDispatchFee", type: "uint256", internalType: "uint256" },
|
|
17597
|
+
{
|
|
17598
|
+
name: "outputs",
|
|
17599
|
+
type: "tuple[]",
|
|
17600
|
+
internalType: "struct TokenInfo[]",
|
|
17601
|
+
components: [
|
|
17602
|
+
{ name: "token", type: "bytes32", internalType: "bytes32" },
|
|
17603
|
+
{ name: "amount", type: "uint256", internalType: "uint256" }
|
|
17604
|
+
]
|
|
17605
|
+
}
|
|
17606
|
+
]
|
|
17607
|
+
}
|
|
17608
|
+
]
|
|
17609
|
+
}
|
|
17610
|
+
];
|
|
17611
|
+
var ERC1967_IMPLEMENTATION_SLOT = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
|
|
17612
|
+
var LEGACY_FILL_OPTIONS_IMPLEMENTATIONS = /* @__PURE__ */ new Set([
|
|
17613
|
+
// The pre-validUntil IntentGatewayV2 implementation. One entry covers every chain: the
|
|
17614
|
+
// protocol contracts are CREATE2-deployed, so this is the implementation address on all
|
|
17615
|
+
// of them (confirmed with the maintainers).
|
|
17616
|
+
"0x976b268b06f545c4a2bf44866aa2465bd8b3c67d"
|
|
17617
|
+
]);
|
|
17618
|
+
var CHAINS_WITHOUT_VALID_UNTIL = /* @__PURE__ */ new Set([
|
|
17619
|
+
97,
|
|
17620
|
+
// BNB testnet
|
|
17621
|
+
10200,
|
|
17622
|
+
// Gnosis Chiado
|
|
17623
|
+
80002,
|
|
17624
|
+
// Polygon Amoy
|
|
17625
|
+
84532,
|
|
17626
|
+
// Base Sepolia
|
|
17627
|
+
421614,
|
|
17628
|
+
// Arbitrum Sepolia
|
|
17629
|
+
688689,
|
|
17630
|
+
// Pharos testnet
|
|
17631
|
+
11155111,
|
|
17632
|
+
// Sepolia
|
|
17633
|
+
11155420,
|
|
17634
|
+
// Optimism Sepolia
|
|
17635
|
+
420420417
|
|
17636
|
+
// Polkadot Hub Paseo
|
|
17637
|
+
]);
|
|
17638
|
+
var knownV2Gateways = /* @__PURE__ */ new Set();
|
|
17639
|
+
function resetFillOptionsVersionCache() {
|
|
17640
|
+
knownV2Gateways.clear();
|
|
17641
|
+
}
|
|
17642
|
+
async function resolveImplementation(client, gateway) {
|
|
17643
|
+
const slot = await client.getStorageAt({ address: gateway, slot: ERC1967_IMPLEMENTATION_SLOT });
|
|
17644
|
+
if (!slot || slot.length < 66) return gateway;
|
|
17645
|
+
const addr = `0x${slot.slice(-40)}`;
|
|
17646
|
+
return /^0x0{40}$/.test(addr) ? gateway : addr;
|
|
17647
|
+
}
|
|
17648
|
+
async function getFillOptionsVersion(client, gateway) {
|
|
17649
|
+
const chainId = client.chain?.id;
|
|
17650
|
+
if (chainId !== void 0 && CHAINS_WITHOUT_VALID_UNTIL.has(chainId)) return 1;
|
|
17651
|
+
const key = gateway.toLowerCase();
|
|
17652
|
+
if (knownV2Gateways.has(key)) return 2;
|
|
17653
|
+
const implementation = await resolveImplementation(client, gateway);
|
|
17654
|
+
if (LEGACY_FILL_OPTIONS_IMPLEMENTATIONS.has(implementation.toLowerCase())) return 1;
|
|
17655
|
+
knownV2Gateways.add(key);
|
|
17656
|
+
return 2;
|
|
17657
|
+
}
|
|
17658
|
+
function encodeFillOrder(order, options, version) {
|
|
17659
|
+
if (version === 2) {
|
|
17660
|
+
return encodeFunctionData({
|
|
17661
|
+
abi: ABI3,
|
|
17662
|
+
functionName: "fillOrder",
|
|
17663
|
+
args: [order, options]
|
|
17664
|
+
});
|
|
17665
|
+
}
|
|
17666
|
+
const { relayerFee, nativeDispatchFee, outputs } = options;
|
|
17667
|
+
return encodeFunctionData({
|
|
17668
|
+
abi: FILL_ORDER_V1_ABI,
|
|
17669
|
+
functionName: "fillOrder",
|
|
17670
|
+
args: [order, { relayerFee, nativeDispatchFee, outputs }]
|
|
17671
|
+
});
|
|
17672
|
+
}
|
|
17673
|
+
function decodeFillOrder(data) {
|
|
17674
|
+
try {
|
|
17675
|
+
const decoded = decodeFunctionData({ abi: ABI3, data });
|
|
17676
|
+
if (decoded.functionName === "fillOrder" && decoded.args && decoded.args.length >= 2) {
|
|
17677
|
+
return { order: decoded.args[0], options: decoded.args[1] };
|
|
17678
|
+
}
|
|
17679
|
+
} catch {
|
|
17680
|
+
}
|
|
17681
|
+
try {
|
|
17682
|
+
const decoded = decodeFunctionData({ abi: FILL_ORDER_V1_ABI, data });
|
|
17683
|
+
if (decoded.functionName === "fillOrder" && decoded.args && decoded.args.length >= 2) {
|
|
17684
|
+
const legacy = decoded.args[1];
|
|
17685
|
+
return { order: decoded.args[0], options: { ...legacy, validUntil: 0n } };
|
|
17686
|
+
}
|
|
17687
|
+
} catch {
|
|
17688
|
+
}
|
|
17689
|
+
return null;
|
|
17690
|
+
}
|
|
17062
17691
|
var BidImpl = class {
|
|
17063
17692
|
solverAddress;
|
|
17064
17693
|
outputs;
|
|
@@ -17489,19 +18118,9 @@ var BidManager = class {
|
|
|
17489
18118
|
const innerCalls = this.crypto.decodeERC7821Execute(bid.userOp.callData);
|
|
17490
18119
|
if (!innerCalls || innerCalls.length === 0) return null;
|
|
17491
18120
|
for (const call of innerCalls) {
|
|
17492
|
-
|
|
17493
|
-
|
|
17494
|
-
|
|
17495
|
-
data: call.data
|
|
17496
|
-
});
|
|
17497
|
-
if (decoded?.functionName === "fillOrder" && decoded.args && decoded.args.length >= 2) {
|
|
17498
|
-
const fillOptions = decoded.args[1];
|
|
17499
|
-
if (fillOptions?.outputs?.length > 0) {
|
|
17500
|
-
return fillOptions;
|
|
17501
|
-
}
|
|
17502
|
-
}
|
|
17503
|
-
} catch {
|
|
17504
|
-
continue;
|
|
18121
|
+
const decoded = decodeFillOrder(call.data);
|
|
18122
|
+
if (decoded && decoded.options?.outputs?.length > 0) {
|
|
18123
|
+
return decoded.options;
|
|
17505
18124
|
}
|
|
17506
18125
|
}
|
|
17507
18126
|
} catch {
|
|
@@ -17879,6 +18498,10 @@ var GasEstimator = class {
|
|
|
17879
18498
|
relayerFee: crossChainFees.postRequestFee,
|
|
17880
18499
|
// Always dispatch with the fee token (see the method docs).
|
|
17881
18500
|
nativeDispatchFee: 0n,
|
|
18501
|
+
// Unbounded for estimation: this call is simulated, never submitted, and a real
|
|
18502
|
+
// bound here would only risk the estimate reverting on a slow bundler round trip.
|
|
18503
|
+
// The caller sets the real one on the options it actually signs.
|
|
18504
|
+
validUntil: 0n,
|
|
17882
18505
|
outputs: order.output.assets.map((asset) => ({
|
|
17883
18506
|
...asset,
|
|
17884
18507
|
token: normalizeAddressForEvmBytes32(asset.token)
|
|
@@ -17891,11 +18514,12 @@ var GasEstimator = class {
|
|
|
17891
18514
|
let maxFeePerGas = gasPrice + gasPrice * BigInt(maxFeeBumpPercent) / 100n;
|
|
17892
18515
|
const orderForEstimation = { ...order, session: solverAccountAddress };
|
|
17893
18516
|
const commitment = orderCommitment(orderForEstimation);
|
|
17894
|
-
const
|
|
17895
|
-
|
|
17896
|
-
|
|
17897
|
-
|
|
17898
|
-
|
|
18517
|
+
const fillOptionsVersion = await getFillOptionsVersion(this.ctx.dest.client, intentGatewayV2Address);
|
|
18518
|
+
const fillOrderCalldata = encodeFillOrder(
|
|
18519
|
+
transformOrderForContract(orderForEstimation),
|
|
18520
|
+
fillOptions,
|
|
18521
|
+
fillOptionsVersion
|
|
18522
|
+
);
|
|
17899
18523
|
let callGasLimit = 500000n;
|
|
17900
18524
|
let verificationGasLimit = 100000n;
|
|
17901
18525
|
let preVerificationGas = 100000n;
|
|
@@ -18335,29 +18959,29 @@ var LiquidityEngine = class {
|
|
|
18335
18959
|
};
|
|
18336
18960
|
}
|
|
18337
18961
|
/**
|
|
18338
|
-
* Returns
|
|
18339
|
-
* per one base token.
|
|
18962
|
+
* Returns the indexed pool's aggregate buy and sell rates in less-valued
|
|
18963
|
+
* quote-token units per one base token.
|
|
18340
18964
|
*
|
|
18341
|
-
* The
|
|
18342
|
-
*
|
|
18343
|
-
*
|
|
18965
|
+
* The indexer depth-weights fresh per-chain samples into the pool rates. The
|
|
18966
|
+
* source and destination chains remain part of the result because they define
|
|
18967
|
+
* the cross-chain route whose configured token symbols were resolved.
|
|
18344
18968
|
*/
|
|
18345
18969
|
async getBuyAndSellRates(params) {
|
|
18346
18970
|
const pool = resolveLiquidityPool(params.tokenInSymbol, params.tokenOutSymbol);
|
|
18347
|
-
const directDirection = params.tokenInSymbol.toLowerCase() === pool.token0Symbol.toLowerCase() ? SELL : BUY;
|
|
18348
|
-
const reverseDirection = directDirection === SELL ? BUY : SELL;
|
|
18349
18971
|
const response = await this.queryClient.request(BUY_AND_SELL_RATES, {
|
|
18350
|
-
poolId: pool.poolId
|
|
18351
|
-
directChain: params.destinationChain,
|
|
18352
|
-
directDirection,
|
|
18353
|
-
reverseChain: params.sourceChain,
|
|
18354
|
-
reverseDirection
|
|
18972
|
+
poolId: pool.poolId
|
|
18355
18973
|
});
|
|
18356
|
-
if (!response?.
|
|
18357
|
-
throw new InvalidLiquidityIndexerResponseError("
|
|
18358
|
-
}
|
|
18359
|
-
const
|
|
18360
|
-
|
|
18974
|
+
if (!response?.liquidityPools?.nodes) {
|
|
18975
|
+
throw new InvalidLiquidityIndexerResponseError("liquidity pool connection is missing");
|
|
18976
|
+
}
|
|
18977
|
+
const indexedPool = response.liquidityPools.nodes[0];
|
|
18978
|
+
if (!indexedPool) return void 0;
|
|
18979
|
+
validateIndexedPool(indexedPool, pool);
|
|
18980
|
+
const sell = readIndexedRate(indexedPool.sellRate, indexedPool.lastUpdatedAt, "pool sell rate");
|
|
18981
|
+
const buy = readIndexedRate(indexedPool.buyRate, indexedPool.lastUpdatedAt, "pool buy rate");
|
|
18982
|
+
const inputIsToken0 = params.tokenInSymbol.toLowerCase() === pool.token0Symbol.toLowerCase();
|
|
18983
|
+
const direct = inputIsToken0 ? sell : buy;
|
|
18984
|
+
const reverse = inputIsToken0 ? buy : sell;
|
|
18361
18985
|
if (!direct && !reverse) return void 0;
|
|
18362
18986
|
const quoteTokenSymbol = resolveQuoteTokenSymbol(
|
|
18363
18987
|
params.tokenInSymbol,
|
|
@@ -18366,17 +18990,17 @@ var LiquidityEngine = class {
|
|
|
18366
18990
|
reverse?.scaledRate
|
|
18367
18991
|
);
|
|
18368
18992
|
const quoteIsTokenOut = quoteTokenSymbol === params.tokenOutSymbol;
|
|
18369
|
-
const
|
|
18370
|
-
const
|
|
18993
|
+
const orientedBuy = quoteIsTokenOut ? direct : reverse;
|
|
18994
|
+
const orientedSell = quoteIsTokenOut ? reverse : direct;
|
|
18371
18995
|
return {
|
|
18372
18996
|
baseTokenSymbol: quoteIsTokenOut ? params.tokenInSymbol : params.tokenOutSymbol,
|
|
18373
18997
|
quoteTokenSymbol,
|
|
18374
18998
|
sourceChain: params.sourceChain,
|
|
18375
18999
|
destinationChain: params.destinationChain,
|
|
18376
|
-
buyRate:
|
|
18377
|
-
sellRate:
|
|
18378
|
-
buyRateUpdatedAt:
|
|
18379
|
-
sellRateUpdatedAt:
|
|
19000
|
+
buyRate: orientedBuy ? formatUnits(orientedBuy.scaledRate, INDEXER_FIXED_POINT_DECIMALS) : null,
|
|
19001
|
+
sellRate: orientedSell ? formatUnits(reciprocalRate(orientedSell.scaledRate, "sell rate"), INDEXER_FIXED_POINT_DECIMALS) : null,
|
|
19002
|
+
buyRateUpdatedAt: orientedBuy?.updatedAt ?? null,
|
|
19003
|
+
sellRateUpdatedAt: orientedSell?.updatedAt ?? null
|
|
18380
19004
|
};
|
|
18381
19005
|
}
|
|
18382
19006
|
};
|
|
@@ -18418,20 +19042,25 @@ function readIndexerDate(value, label) {
|
|
|
18418
19042
|
if (Number.isNaN(date.getTime())) throw new InvalidLiquidityIndexerResponseError(`${label} is invalid`);
|
|
18419
19043
|
return date;
|
|
18420
19044
|
}
|
|
18421
|
-
function readIndexedRate(
|
|
18422
|
-
if (
|
|
19045
|
+
function readIndexedRate(value, lastUpdatedAt, label) {
|
|
19046
|
+
if (value === null) return void 0;
|
|
18423
19047
|
try {
|
|
18424
|
-
const scaledRate = BigInt(
|
|
19048
|
+
const scaledRate = BigInt(value);
|
|
18425
19049
|
if (scaledRate <= 0n) throw new Error();
|
|
18426
19050
|
return {
|
|
18427
19051
|
scaledRate,
|
|
18428
|
-
updatedAt: readIndexerDate(
|
|
19052
|
+
updatedAt: readIndexerDate(lastUpdatedAt, `${label} lastUpdatedAt`)
|
|
18429
19053
|
};
|
|
18430
19054
|
} catch (error) {
|
|
18431
19055
|
if (error instanceof InvalidLiquidityIndexerResponseError) throw error;
|
|
18432
19056
|
throw new InvalidLiquidityIndexerResponseError(`${label} is not a positive integer`);
|
|
18433
19057
|
}
|
|
18434
19058
|
}
|
|
19059
|
+
function validateIndexedPool(indexedPool, expected) {
|
|
19060
|
+
if (indexedPool.id.toLowerCase() !== expected.poolId.toLowerCase() || indexedPool.token0Symbol.toLowerCase() !== expected.token0Symbol.toLowerCase() || indexedPool.token1Symbol.toLowerCase() !== expected.token1Symbol.toLowerCase()) {
|
|
19061
|
+
throw new InvalidLiquidityIndexerResponseError(`pool identity does not match ${expected.poolId}`);
|
|
19062
|
+
}
|
|
19063
|
+
}
|
|
18435
19064
|
function resolveQuoteTokenSymbol(tokenInSymbol, tokenOutSymbol, directRate, reverseRate) {
|
|
18436
19065
|
const inputIsUsdStable = USD_STABLE_SYMBOLS.has(tokenInSymbol);
|
|
18437
19066
|
const outputIsUsdStable = USD_STABLE_SYMBOLS.has(tokenOutSymbol);
|
|
@@ -18441,7 +19070,8 @@ function resolveQuoteTokenSymbol(tokenInSymbol, tokenOutSymbol, directRate, reve
|
|
|
18441
19070
|
throw new InvalidLiquidityIndexerResponseError("cannot orient an empty rate pair");
|
|
18442
19071
|
}
|
|
18443
19072
|
function reciprocalRate(rate, label) {
|
|
18444
|
-
const
|
|
19073
|
+
const numerator = POOL_RATE_SCALE * POOL_RATE_SCALE;
|
|
19074
|
+
const reciprocal = (numerator + rate - 1n) / rate;
|
|
18445
19075
|
if (reciprocal <= 0n) throw new InvalidLiquidityIndexerResponseError(`${label} reciprocal underflowed`);
|
|
18446
19076
|
return reciprocal;
|
|
18447
19077
|
}
|
|
@@ -18473,6 +19103,20 @@ var InvalidPhantomSnapshotError = class extends Error {
|
|
|
18473
19103
|
this.name = "InvalidPhantomSnapshotError";
|
|
18474
19104
|
}
|
|
18475
19105
|
};
|
|
19106
|
+
var IndexedRateUnavailableError = class extends Error {
|
|
19107
|
+
constructor(params) {
|
|
19108
|
+
const route = params.source && params.destination && params.tokenIn && params.tokenOut ? ` for ${params.tokenIn} -> ${params.tokenOut} on ${params.source} -> ${params.destination}` : "";
|
|
19109
|
+
const side = params.side ? ` ${params.side}` : "";
|
|
19110
|
+
super(`No indexed${side} rate available${route}`);
|
|
19111
|
+
this.name = "IndexedRateUnavailableError";
|
|
19112
|
+
}
|
|
19113
|
+
};
|
|
19114
|
+
var InvalidIndexedRateError = class extends Error {
|
|
19115
|
+
constructor(reason) {
|
|
19116
|
+
super(`Invalid indexed intent rate: ${reason}`);
|
|
19117
|
+
this.name = "InvalidIndexedRateError";
|
|
19118
|
+
}
|
|
19119
|
+
};
|
|
18476
19120
|
var BPS_DENOMINATOR = 10000n;
|
|
18477
19121
|
function validateQuoteParams(params) {
|
|
18478
19122
|
const hasAmountIn = params.amountIn !== void 0;
|
|
@@ -18839,9 +19483,142 @@ function isSupportedSnapshotPair(tokenA, tokenB) {
|
|
|
18839
19483
|
function isConfiguredAddress(address) {
|
|
18840
19484
|
return Boolean(address && address !== "0x" && !/^0x0{40}$/i.test(address));
|
|
18841
19485
|
}
|
|
19486
|
+
var INDEXED_RATE_DECIMALS = 18;
|
|
19487
|
+
var INDEXED_RATE_SCALE = 10n ** BigInt(INDEXED_RATE_DECIMALS);
|
|
19488
|
+
var IndexedRateIntentQuoteStrategy = class {
|
|
19489
|
+
constructor(chainConfigService, getQueryClient) {
|
|
19490
|
+
this.chainConfigService = chainConfigService;
|
|
19491
|
+
this.getQueryClient = getQueryClient;
|
|
19492
|
+
}
|
|
19493
|
+
chainConfigService;
|
|
19494
|
+
getQueryClient;
|
|
19495
|
+
async quote(params, source, destination) {
|
|
19496
|
+
validateQuoteParams(params);
|
|
19497
|
+
const sourceConfig = getConfigByStateMachineId(source.stateMachineId);
|
|
19498
|
+
const destinationConfig = getConfigByStateMachineId(destination.stateMachineId);
|
|
19499
|
+
if (!sourceConfig) throw new UnsupportedLiquidityChainError(source.stateMachineId);
|
|
19500
|
+
if (!destinationConfig) throw new UnsupportedLiquidityChainError(destination.stateMachineId);
|
|
19501
|
+
const tokenIn = this.resolveAsset(sourceConfig.stateMachineId, params.tokenIn);
|
|
19502
|
+
const tokenOut = this.resolveAsset(destinationConfig.stateMachineId, params.tokenOut);
|
|
19503
|
+
const [protocolFeeBps, rates] = await Promise.all([
|
|
19504
|
+
readProtocolFeeBps(this.chainConfigService, source),
|
|
19505
|
+
new LiquidityEngine(this.getQueryClient()).getBuyAndSellRates({
|
|
19506
|
+
sourceChain: sourceConfig.stateMachineId,
|
|
19507
|
+
destinationChain: destinationConfig.stateMachineId,
|
|
19508
|
+
tokenInSymbol: tokenIn.symbol,
|
|
19509
|
+
tokenOutSymbol: tokenOut.symbol
|
|
19510
|
+
})
|
|
19511
|
+
]);
|
|
19512
|
+
if (!rates) {
|
|
19513
|
+
throw new IndexedRateUnavailableError({
|
|
19514
|
+
source: sourceConfig.stateMachineId,
|
|
19515
|
+
destination: destinationConfig.stateMachineId,
|
|
19516
|
+
tokenIn: tokenIn.symbol,
|
|
19517
|
+
tokenOut: tokenOut.symbol
|
|
19518
|
+
});
|
|
19519
|
+
}
|
|
19520
|
+
const selectedRate = selectIndexedRate(rates, tokenIn.symbol, tokenOut.symbol);
|
|
19521
|
+
return quoteWithIndexedRate(params, tokenIn, tokenOut, selectedRate, rates, protocolFeeBps);
|
|
19522
|
+
}
|
|
19523
|
+
resolveAsset(chain, address) {
|
|
19524
|
+
const asset = this.chainConfigService.getAssetMetadataByAddress(chain, address);
|
|
19525
|
+
if (!asset) throw new UnsupportedLiquidityAssetError(chain, address);
|
|
19526
|
+
const { decimals } = asset;
|
|
19527
|
+
if (decimals === void 0 || !Number.isSafeInteger(decimals) || decimals < 0) {
|
|
19528
|
+
throw new InvalidIndexedRateError(`decimals are not configured for ${asset.symbol} on ${chain}`);
|
|
19529
|
+
}
|
|
19530
|
+
return { ...asset, decimals };
|
|
19531
|
+
}
|
|
19532
|
+
};
|
|
19533
|
+
function selectIndexedRate(rates, tokenInSymbol, tokenOutSymbol) {
|
|
19534
|
+
if (tokenInSymbol === rates.baseTokenSymbol && tokenOutSymbol === rates.quoteTokenSymbol) {
|
|
19535
|
+
return readIndexedRate2(
|
|
19536
|
+
"buy",
|
|
19537
|
+
rates.buyRate,
|
|
19538
|
+
rates.buyRateUpdatedAt,
|
|
19539
|
+
rates,
|
|
19540
|
+
tokenInSymbol,
|
|
19541
|
+
tokenOutSymbol
|
|
19542
|
+
);
|
|
19543
|
+
}
|
|
19544
|
+
if (tokenInSymbol === rates.quoteTokenSymbol && tokenOutSymbol === rates.baseTokenSymbol) {
|
|
19545
|
+
return readIndexedRate2(
|
|
19546
|
+
"sell",
|
|
19547
|
+
rates.sellRate,
|
|
19548
|
+
rates.sellRateUpdatedAt,
|
|
19549
|
+
rates,
|
|
19550
|
+
tokenInSymbol,
|
|
19551
|
+
tokenOutSymbol
|
|
19552
|
+
);
|
|
19553
|
+
}
|
|
19554
|
+
throw new InvalidIndexedRateError(
|
|
19555
|
+
`indexed pair ${rates.baseTokenSymbol}/${rates.quoteTokenSymbol} does not match ${tokenInSymbol}/${tokenOutSymbol}`
|
|
19556
|
+
);
|
|
19557
|
+
}
|
|
19558
|
+
function readIndexedRate2(side, rate, updatedAt, rates, tokenInSymbol, tokenOutSymbol) {
|
|
19559
|
+
if (!rate || !updatedAt) {
|
|
19560
|
+
throw new IndexedRateUnavailableError({
|
|
19561
|
+
source: rates.sourceChain,
|
|
19562
|
+
destination: rates.destinationChain,
|
|
19563
|
+
tokenIn: tokenInSymbol,
|
|
19564
|
+
tokenOut: tokenOutSymbol,
|
|
19565
|
+
side
|
|
19566
|
+
});
|
|
19567
|
+
}
|
|
19568
|
+
try {
|
|
19569
|
+
const scaledRate = parseUnits(rate, INDEXED_RATE_DECIMALS);
|
|
19570
|
+
if (scaledRate <= 0n || Number.isNaN(updatedAt.getTime())) throw new Error();
|
|
19571
|
+
return { side, rate, scaledRate, updatedAt };
|
|
19572
|
+
} catch {
|
|
19573
|
+
throw new InvalidIndexedRateError(`${side} rate or timestamp is invalid`);
|
|
19574
|
+
}
|
|
19575
|
+
}
|
|
19576
|
+
function quoteWithIndexedRate(params, tokenIn, tokenOut, selectedRate, rates, protocolFeeBps) {
|
|
19577
|
+
const inputUnit = 10n ** BigInt(tokenIn.decimals);
|
|
19578
|
+
const outputUnit = 10n ** BigInt(tokenOut.decimals);
|
|
19579
|
+
if (params.amountIn !== void 0) {
|
|
19580
|
+
const netAmountIn2 = deductProtocolFee(params.amountIn, protocolFeeBps);
|
|
19581
|
+
const amountOut = selectedRate.side === "buy" ? netAmountIn2 * selectedRate.scaledRate * outputUnit / (inputUnit * INDEXED_RATE_SCALE) : netAmountIn2 * outputUnit * INDEXED_RATE_SCALE / (inputUnit * selectedRate.scaledRate);
|
|
19582
|
+
if (amountOut <= 0n) throw new InvalidIndexedRateError("quote rounds down to zero output");
|
|
19583
|
+
return buildResult("EXACT_INPUT", params.amountIn, amountOut, selectedRate, rates, protocolFeeBps);
|
|
19584
|
+
}
|
|
19585
|
+
if (params.amountOut === void 0) throw new Error("Quote amount is missing after validation");
|
|
19586
|
+
const netAmountIn = selectedRate.side === "buy" ? divCeil(params.amountOut * inputUnit * INDEXED_RATE_SCALE, selectedRate.scaledRate * outputUnit) : divCeil(params.amountOut * inputUnit * selectedRate.scaledRate, outputUnit * INDEXED_RATE_SCALE);
|
|
19587
|
+
const amountIn = grossUpForProtocolFee(netAmountIn, protocolFeeBps);
|
|
19588
|
+
return buildResult("EXACT_OUTPUT", amountIn, params.amountOut, selectedRate, rates, protocolFeeBps);
|
|
19589
|
+
}
|
|
19590
|
+
function buildResult(tradeType, amountIn, amountOut, selectedRate, rates, protocolFeeBps) {
|
|
19591
|
+
return {
|
|
19592
|
+
strategy: "indexed_rates",
|
|
19593
|
+
tradeType,
|
|
19594
|
+
amountIn,
|
|
19595
|
+
amountOut,
|
|
19596
|
+
quoteMetadata: {
|
|
19597
|
+
sourceChain: rates.sourceChain,
|
|
19598
|
+
destinationChain: rates.destinationChain,
|
|
19599
|
+
baseTokenSymbol: rates.baseTokenSymbol,
|
|
19600
|
+
quoteTokenSymbol: rates.quoteTokenSymbol,
|
|
19601
|
+
rateSide: selectedRate.side,
|
|
19602
|
+
rate: selectedRate.rate,
|
|
19603
|
+
rateUpdatedAt: selectedRate.updatedAt,
|
|
19604
|
+
protocolFeeBps
|
|
19605
|
+
}
|
|
19606
|
+
};
|
|
19607
|
+
}
|
|
18842
19608
|
|
|
18843
19609
|
// src/protocols/intents/IntentGateway.ts
|
|
18844
|
-
var
|
|
19610
|
+
var ORDER_FEE_GAS_PRICE_BUMP_POLICY = {
|
|
19611
|
+
defaultPercent: 10n,
|
|
19612
|
+
bySourceStateMachineId: {
|
|
19613
|
+
["EVM-1" /* MAINNET */]: 50n
|
|
19614
|
+
}
|
|
19615
|
+
};
|
|
19616
|
+
function resolveOrderFeeGasPriceBump(sourceStateMachineId, isSameChain) {
|
|
19617
|
+
if (isSameChain) {
|
|
19618
|
+
return 0n;
|
|
19619
|
+
}
|
|
19620
|
+
return ORDER_FEE_GAS_PRICE_BUMP_POLICY.bySourceStateMachineId[sourceStateMachineId] ?? ORDER_FEE_GAS_PRICE_BUMP_POLICY.defaultPercent;
|
|
19621
|
+
}
|
|
18845
19622
|
var IntentGateway = class _IntentGateway {
|
|
18846
19623
|
/** EVM chain on which orders are placed and escrowed. */
|
|
18847
19624
|
source;
|
|
@@ -18913,6 +19690,10 @@ var IntentGateway = class _IntentGateway {
|
|
|
18913
19690
|
this.gasEstimator = gasEstimator;
|
|
18914
19691
|
this._crypto = crypto;
|
|
18915
19692
|
this.quoteStrategies = {
|
|
19693
|
+
indexed_rates: new IndexedRateIntentQuoteStrategy(
|
|
19694
|
+
dest.configService,
|
|
19695
|
+
() => this.requireIndexer().queryClient
|
|
19696
|
+
),
|
|
18916
19697
|
phantom_snapshot: new PhantomSnapshotIntentQuoteStrategy(
|
|
18917
19698
|
dest.configService,
|
|
18918
19699
|
() => this.requireIndexer().queryClient
|
|
@@ -18966,26 +19747,26 @@ var IntentGateway = class _IntentGateway {
|
|
|
18966
19747
|
/**
|
|
18967
19748
|
* Quotes an intent between this gateway's source and destination chains.
|
|
18968
19749
|
*
|
|
18969
|
-
* Uses the latest directional
|
|
18970
|
-
*
|
|
18971
|
-
* requesting a
|
|
19750
|
+
* Uses the indexer's latest aggregate directional pool rate by default. Pass
|
|
19751
|
+
* `strategy: "phantom_snapshot"` or `strategy: "uniswap_v4"` only when
|
|
19752
|
+
* explicitly requesting a legacy quote source. Provide exactly one of
|
|
19753
|
+
* `amountIn` or `amountOut`.
|
|
18972
19754
|
*
|
|
18973
|
-
*
|
|
18974
|
-
*
|
|
19755
|
+
* The gateway's source and destination chains resolve the configured order
|
|
19756
|
+
* tokens; the indexer supplies the depth-weighted pool rate. Returned
|
|
18975
19757
|
* `amountIn`/`amountOut` already account for the gateway's protocol fee
|
|
18976
|
-
* (`quoteMetadata.protocolFeeBps`), which the gateway deducts from order
|
|
18977
|
-
* inputs; use the returned amounts directly when placing the order.
|
|
19758
|
+
* (`quoteMetadata.protocolFeeBps`), which the gateway deducts from order inputs.
|
|
18978
19759
|
*
|
|
18979
19760
|
* @param params - Token pair, amount, and optional strategy/pool overrides.
|
|
18980
19761
|
* @returns The quoted amounts plus strategy-specific metadata.
|
|
18981
19762
|
* @throws {UnsupportedIntentQuoteStrategyError} For unknown strategies.
|
|
18982
19763
|
* @throws {UnsupportedIntentQuotePairError} When the selected strategy does not support the pair.
|
|
18983
|
-
* @throws {
|
|
19764
|
+
* @throws {IndexedRateUnavailableError} When the requested direction has no indexed rate.
|
|
18984
19765
|
*/
|
|
18985
19766
|
async quoteIntent(params) {
|
|
18986
19767
|
const source = { stateMachineId: this.source.config.stateMachineId, client: this.source.client };
|
|
18987
19768
|
const destination = { stateMachineId: this.dest.config.stateMachineId, client: this.dest.client };
|
|
18988
|
-
const strategy = params.strategy ?? "
|
|
19769
|
+
const strategy = params.strategy ?? "indexed_rates";
|
|
18989
19770
|
const handler = this.quoteStrategies[strategy];
|
|
18990
19771
|
if (!handler) throw new UnsupportedIntentQuoteStrategyError(strategy);
|
|
18991
19772
|
return handler.quote({ ...params, strategy }, source, destination);
|
|
@@ -19025,9 +19806,9 @@ var IntentGateway = class _IntentGateway {
|
|
|
19025
19806
|
});
|
|
19026
19807
|
}
|
|
19027
19808
|
/**
|
|
19028
|
-
* Returns
|
|
19029
|
-
* without requiring token addresses. Symbols are matched
|
|
19030
|
-
* chain IDs
|
|
19809
|
+
* Returns aggregate indexed pool buy and sell rates in less-valued quote-token
|
|
19810
|
+
* units without requiring token addresses. Symbols are matched
|
|
19811
|
+
* case-insensitively; chain IDs resolve configured token deployments.
|
|
19031
19812
|
*/
|
|
19032
19813
|
async queryBuyAndSellRates(params) {
|
|
19033
19814
|
const { queryClient } = this.requireIndexer();
|
|
@@ -19056,8 +19837,9 @@ var IntentGateway = class _IntentGateway {
|
|
|
19056
19837
|
* **Yield/receive protocol:**
|
|
19057
19838
|
* 1. If `order.fees` is unset or zero, prices the fee on an internal copy
|
|
19058
19839
|
* via {@link quoteOrderFees}: same-chain fees are twice the fill-gas
|
|
19059
|
-
* estimate without a gas-price bump; cross-chain
|
|
19060
|
-
*
|
|
19840
|
+
* estimate without a gas-price bump; cross-chain order fees originating on
|
|
19841
|
+
* Ethereum price gas 50% above the live price, while other source chains use
|
|
19842
|
+
* 10%, before attaching (fill gas + the settlement relayer fee)
|
|
19061
19843
|
* with a further 5% buffer over the whole sum — strictly above the solver's
|
|
19062
19844
|
* unpadded requirement. Direct solver estimates remain unbumped. The wei
|
|
19063
19845
|
* cost used for the `value` field receives a 2% buffer.
|
|
@@ -19427,7 +20209,8 @@ var IntentGateway = class _IntentGateway {
|
|
|
19427
20209
|
* transaction (check the native balance).
|
|
19428
20210
|
*
|
|
19429
20211
|
* @param order - The order to quote. `order.fees` is ignored and not mutated.
|
|
19430
|
-
* Gas prices used to derive cross-chain `fees` receive
|
|
20212
|
+
* Gas prices used to derive cross-chain `fees` receive 50% SDK-only headroom
|
|
20213
|
+
* when the source chain is Ethereum mainnet and 10% for other source chains.
|
|
19431
20214
|
* Same-chain quotes and direct calls to {@link estimateFillOrder}, including
|
|
19432
20215
|
* Simplex solver estimates, remain unbumped.
|
|
19433
20216
|
*
|
|
@@ -19438,15 +20221,14 @@ var IntentGateway = class _IntentGateway {
|
|
|
19438
20221
|
*/
|
|
19439
20222
|
async quoteOrderFees(order, options) {
|
|
19440
20223
|
const isSameChain = this.source.config.stateMachineId === this.dest.config.stateMachineId;
|
|
20224
|
+
const orderFeeGasPriceBumpPercent = resolveOrderFeeGasPriceBump(this.source.config.stateMachineId, isSameChain);
|
|
19441
20225
|
const estimate = await this.gasEstimator.estimateFillOrder(
|
|
19442
20226
|
{
|
|
19443
20227
|
order,
|
|
19444
20228
|
maxPriorityFeePerGasBumpPercent: options?.maxPriorityFeePerGasBumpPercent,
|
|
19445
20229
|
maxFeePerGasBumpPercent: options?.maxFeePerGasBumpPercent
|
|
19446
20230
|
},
|
|
19447
|
-
{
|
|
19448
|
-
orderFeeGasPriceBumpPercent: isSameChain ? 0n : CROSS_CHAIN_ORDER_FEE_GAS_PRICE_BUMP_PERCENT
|
|
19449
|
-
}
|
|
20231
|
+
{ orderFeeGasPriceBumpPercent }
|
|
19450
20232
|
);
|
|
19451
20233
|
if (estimate.totalGasCostWei === 0n || estimate.totalGasInFeeToken === 0n) {
|
|
19452
20234
|
throw new Error("Gas estimation failed");
|
|
@@ -19675,6 +20457,17 @@ function encodeAcceptedSourceChains(chains2) {
|
|
|
19675
20457
|
function decodeAcceptedSourceChains(paymasterAndData) {
|
|
19676
20458
|
return decodePhantomBidDeclaration(paymasterAndData).acceptedSources;
|
|
19677
20459
|
}
|
|
20460
|
+
var UNISWAP_QUOTE_HAIRCUT_BPS = 10n;
|
|
20461
|
+
var PHANTOM_QUOTE_HAIRCUT_BPS = 5n;
|
|
20462
|
+
function haircut(amount, bps) {
|
|
20463
|
+
return amount * (10000n - bps) / 10000n;
|
|
20464
|
+
}
|
|
20465
|
+
function applyUniswapQuoteHaircut(amount) {
|
|
20466
|
+
return haircut(amount, UNISWAP_QUOTE_HAIRCUT_BPS);
|
|
20467
|
+
}
|
|
20468
|
+
function applyPhantomQuoteHaircut(amount) {
|
|
20469
|
+
return haircut(amount, PHANTOM_QUOTE_HAIRCUT_BPS);
|
|
20470
|
+
}
|
|
19678
20471
|
FILL_ORDER_ABI.find(
|
|
19679
20472
|
(item) => item?.type === "function" && item?.name === "fillOrder"
|
|
19680
20473
|
)?.inputs?.[0];
|
|
@@ -24148,6 +24941,6 @@ async function teleportDot(param_) {
|
|
|
24148
24941
|
return stream;
|
|
24149
24942
|
}
|
|
24150
24943
|
|
|
24151
|
-
export { ADDRESS_ZERO2 as ADDRESS_ZERO, BundlerMethod, ChainConfigService, Chains, CryptoUtils, DEFAULT_ADDRESS, DEFAULT_GRAFFITI, DOMAIN_TYPEHASH, DUMMY_PRIVATE_KEY, ERC20Method, ERC7821_BATCH_MODE, EvmChain, ABI as EvmHostABI, EvmLanguage, HyperClientStatus, HyperFungibleToken, HyperFungibleTokenABI, INCLUSION_TIMEOUT_MS, IntentGateway, ABI3 as IntentGatewayABI, IntentOrderStatus, IntentsCoprocessor, InvalidLiquidityIndexerResponseError, InvalidPhantomSnapshotError, IsmpClient, MOCK_ADDRESS, ORDER_V2_PARAM_TYPE, OrderStatus, OrderStatusChecker, PACKED_USEROP_TYPEHASH, PLACE_ORDER_SELECTOR, PhantomSnapshotUnavailableError, PharosChain, PolkadotHubChain, REQUEST_COMMITMENTS_SLOT, REQUEST_RECEIPTS_SLOT, RESPONSE_COMMITMENTS_SLOT, RESPONSE_RECEIPTS_SLOT, RequestKind, RequestStatus, SELECT_SOLVER_TYPEHASH, STATE_COMMITMENTS_SLOT, SubstrateChain, Swap, TESTNET_CHAINS, TeleportStatus, TimeoutStatus, TokenGateway, TronChain, USE_ETHERSCAN_CHAINS, UnsupportedIntentQuotePairError, UnsupportedIntentQuoteStrategyError, UnsupportedLiquidityAssetError, UnsupportedLiquidityChainError, WrappedHyperFungibleTokenABI, __test, adjustDecimals, bytes20ToBytes32, bytes32ToBytes20, calculateAllowanceMappingLocation, calculateBalanceMappingLocation, chainConfigs, constructRedeemEscrowRequestBody, constructRefundEscrowRequestBody, convertCodecToIGetRequest, convertCodecToIProof, convertIGetRequestToCodec, convertIProofToCodec, convertStateIdToStateMachineId, convertStateMachineEnumToString, convertStateMachineIdToEnum, createEvmChain, createQueryClient, decodeAcceptedSourceChains, decodeERC7821ExecuteBatch, decodePhantomBidDeclaration, decodeUserOpScale, deriveHttpUrl, encodeAcceptedSourceChains, encodeERC7821ExecuteBatch, encodeISMPMessage, encodePhantomBidDeclaration, encodeStateMachineId, encodeUserOpScale, encodeWithdrawalRequest, estimateGasForPost, fetchPrice, fetchSourceProof, generateRootWithProof, getChainId, getConfigByStateMachineId, getContractCallInput, getContractCallInputs, getGasPriceFromEtherscan, getOrFetchStorageSlot, getOrderPlacedFromTx, getPostRequestEventFromTx, getPostResponseEventFromTx, getRequestCommitment, getStateCommitmentFieldSlot, getStateCommitmentSlot, getStorageSlot, getViemChain, hexToString, hyperbridgeAddress, maxBigInt, normalizeAddressForEvmBytes32, normalizeAddressForStateMachine, normalizeEvmAddress, normalizeEvmChainId, normalizeStateMachineId, orderCommitment, parseStateMachineId, pharosAtlantic, pharosMainnet, polkadotAssetHubPaseo, polkadotHubMainnet, poolSlug, postRequestCommitment, queryAssetTeleported, queryGetRequest, queryPostRequest, quoteUniswap, requestCommitmentKey, responseCommitmentKey, retryPromise, sortPoolSymbols, teleport, teleportDot, transformOrderForContract, tronChainIds, tronNile };
|
|
24944
|
+
export { ADDRESS_ZERO2 as ADDRESS_ZERO, BundlerMethod, CHAINS_WITHOUT_VALID_UNTIL, ChainConfigService, Chains, CryptoUtils, DEFAULT_ADDRESS, DEFAULT_GRAFFITI, DOMAIN_TYPEHASH, DUMMY_PRIVATE_KEY, ERC20Method, ERC7821_BATCH_MODE, EvmChain, ABI as EvmHostABI, EvmLanguage, FILL_ORDER_V1_ABI, HyperClientStatus, HyperFungibleToken, HyperFungibleTokenABI, INCLUSION_TIMEOUT_MS, IndexedRateUnavailableError, IntentGateway, ABI3 as IntentGatewayABI, IntentOrderStatus, IntentsCoprocessor, InvalidIndexedRateError, InvalidLiquidityIndexerResponseError, InvalidPhantomSnapshotError, IsmpClient, LEGACY_FILL_OPTIONS_IMPLEMENTATIONS, MOCK_ADDRESS, ORDER_V2_PARAM_TYPE, OrderStatus, OrderStatusChecker, PACKED_USEROP_TYPEHASH, PHANTOM_QUOTE_HAIRCUT_BPS, PLACE_ORDER_SELECTOR, PhantomSnapshotUnavailableError, PharosChain, PolkadotHubChain, REQUEST_COMMITMENTS_SLOT, REQUEST_RECEIPTS_SLOT, RESPONSE_COMMITMENTS_SLOT, RESPONSE_RECEIPTS_SLOT, RequestKind, RequestStatus, SELECT_SOLVER_TYPEHASH, STATE_COMMITMENTS_SLOT, SubstrateChain, Swap, TESTNET_CHAINS, TeleportStatus, TimeoutStatus, TokenGateway, TronChain, UNISWAP_QUOTE_HAIRCUT_BPS, USE_ETHERSCAN_CHAINS, UnsupportedIntentQuotePairError, UnsupportedIntentQuoteStrategyError, UnsupportedLiquidityAssetError, UnsupportedLiquidityChainError, WrappedHyperFungibleTokenABI, __test, adjustDecimals, applyPhantomQuoteHaircut, applyUniswapQuoteHaircut, bytes20ToBytes32, bytes32ToBytes20, calculateAllowanceMappingLocation, calculateBalanceMappingLocation, chainConfigs, constructRedeemEscrowRequestBody, constructRefundEscrowRequestBody, convertCodecToIGetRequest, convertCodecToIProof, convertIGetRequestToCodec, convertIProofToCodec, convertStateIdToStateMachineId, convertStateMachineEnumToString, convertStateMachineIdToEnum, createEvmChain, createQueryClient, decodeAcceptedSourceChains, decodeERC7821ExecuteBatch, decodeFillOrder, decodePhantomBidDeclaration, decodeUserOpScale, deriveHttpUrl, encodeAcceptedSourceChains, encodeERC7821ExecuteBatch, encodeFillOrder, encodeISMPMessage, encodePhantomBidDeclaration, encodeStateMachineId, encodeUserOpScale, encodeWithdrawalRequest, estimateGasForPost, fetchPrice, fetchSourceProof, generateRootWithProof, getChainId, getConfigByStateMachineId, getContractCallInput, getContractCallInputs, getFillOptionsVersion, getGasPriceFromEtherscan, getOrFetchStorageSlot, getOrderPlacedFromTx, getPostRequestEventFromTx, getPostResponseEventFromTx, getRequestCommitment, getStateCommitmentFieldSlot, getStateCommitmentSlot, getStorageSlot, getViemChain, hexToString, hyperbridgeAddress, maxBigInt, normalizeAddressForEvmBytes32, normalizeAddressForStateMachine, normalizeEvmAddress, normalizeEvmChainId, normalizeStateMachineId, orderCommitment, parseStateMachineId, pharosAtlantic, pharosMainnet, polkadotAssetHubPaseo, polkadotHubMainnet, poolSlug, postRequestCommitment, queryAssetTeleported, queryGetRequest, queryPostRequest, quoteUniswap, requestCommitmentKey, resetFillOptionsVersionCache, responseCommitmentKey, retryPromise, sortPoolSymbols, teleport, teleportDot, transformOrderForContract, tronChainIds, tronNile };
|
|
24152
24945
|
//# sourceMappingURL=index.js.map
|
|
24153
24946
|
//# sourceMappingURL=index.js.map
|