hermes-swap 0.6.4 → 0.6.6

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.
@@ -43,6 +43,7 @@ var _Aggregator = class {
43
43
  constructor(config, providerClient, quoterClient) {
44
44
  this.builderConfigMap = /* @__PURE__ */ new Map();
45
45
  this.broadcastRpcsMap = /* @__PURE__ */ new Map();
46
+ this.sequencerRpcsMap = /* @__PURE__ */ new Map();
46
47
  this.aggregatorAddressMap = /* @__PURE__ */ new Map();
47
48
  this.aggregatorMap = /* @__PURE__ */ new Map();
48
49
  this.config = config;
@@ -52,16 +53,17 @@ var _Aggregator = class {
52
53
  if (config.flashbotSigner) {
53
54
  this.flashbotSigner = new import_ethers.ethers.Wallet(config.flashbotSigner);
54
55
  }
55
- if (config.builderConfig) {
56
- for (const [chainName, builderCfg] of Object.entries(config.builderConfig)) {
57
- this.builderConfigMap.set(chainName, builderCfg);
56
+ for (const [chainName, builderCfg] of Object.entries(config.builderConfig ?? {})) {
57
+ this.builderConfigMap.set(chainName, builderCfg);
58
+ }
59
+ for (const [chainName, urls] of Object.entries(config.broadcastRpcs ?? {})) {
60
+ if (urls.length > 0) {
61
+ this.broadcastRpcsMap.set(chainName, urls);
58
62
  }
59
63
  }
60
- if (config.broadcastRpcs) {
61
- for (const [chainName, urls] of Object.entries(config.broadcastRpcs)) {
62
- if (urls.length > 0) {
63
- this.broadcastRpcsMap.set(chainName, urls);
64
- }
64
+ for (const [chainName, url] of Object.entries(this.getSequencerRpcs(config))) {
65
+ if (url) {
66
+ this.sequencerRpcsMap.set(chainName, url);
65
67
  }
66
68
  }
67
69
  for (const [chainName, aggregatorAddress] of Object.entries(config.aggregatorAddress)) {
@@ -116,6 +118,15 @@ var _Aggregator = class {
116
118
  }
117
119
  throw new Error(`No builder config for chain: ${chain}`);
118
120
  }
121
+ getSequencerRpcs(config) {
122
+ const merged = {
123
+ ..._Aggregator.DEFAULT_SEQUENCER_RPC_BY_CHAIN
124
+ };
125
+ for (const [chainName, url] of Object.entries(config.sequencerRpcs ?? {})) {
126
+ merged[chainName] = url;
127
+ }
128
+ return merged;
129
+ }
119
130
  buildBundleRequestBody(builderCfg, rawTxs, targetBlock) {
120
131
  const method = builderCfg.method ?? "eth_sendBundle";
121
132
  if (builderCfg.blockParamStyle === "decimal") {
@@ -863,6 +874,9 @@ var _Aggregator = class {
863
874
  })
864
875
  );
865
876
  }
877
+ static isBenignBroadcastError(message) {
878
+ return message.includes("already known") || message.includes("nonce too low") || message.includes("nonce has already been used") || message.includes("ALREADY_EXISTS") || message.includes("NONCE_EXPIRED");
879
+ }
866
880
  async waitForBundleInclusion(provider, txHash, maxTargetBlock, signal) {
867
881
  const deadline = Date.now() + _Aggregator.BUNDLE_INCLUSION_TIMEOUT_MS;
868
882
  while (Date.now() < deadline) {
@@ -881,21 +895,31 @@ var _Aggregator = class {
881
895
  }
882
896
  return null;
883
897
  }
884
- broadcastToAll(chain, signedTx, txHash, providers) {
885
- for (const { provider, label } of providers) {
886
- const sendTs = Date.now();
887
- provider.broadcastTransaction(signedTx).then(
888
- () => _Aggregator.traceLog(chain, "log", `广播${label} OK, txHash=${txHash}, 耗时=${Date.now() - sendTs}ms`),
889
- (err) => {
890
- const msg = (err == null ? void 0 : err.message) ?? String(err);
891
- if (msg.includes("already known") || msg.includes("nonce too low") || msg.includes("nonce has already been used") || msg.includes("ALREADY_EXISTS") || msg.includes("NONCE_EXPIRED")) {
898
+ async sendSignedTxToChannels(chain, signedTx, txHash, channels) {
899
+ const results = await Promise.all(
900
+ channels.map(async ({ label, send }) => {
901
+ var _a, _b, _c, _d, _e;
902
+ const sendTs = Date.now();
903
+ try {
904
+ await send();
905
+ _Aggregator.traceLog(chain, "log", `广播${label} OK, txHash=${txHash}, 耗时=${Date.now() - sendTs}ms`);
906
+ return { label, ok: true };
907
+ } catch (err) {
908
+ const msg = ((_c = (_b = (_a = err == null ? void 0 : err.response) == null ? void 0 : _a.data) == null ? void 0 : _b.error) == null ? void 0 : _c.message) ?? ((_e = (_d = err == null ? void 0 : err.response) == null ? void 0 : _d.data) == null ? void 0 : _e.message) ?? (err == null ? void 0 : err.message) ?? String(err);
909
+ if (_Aggregator.isBenignBroadcastError(msg)) {
892
910
  _Aggregator.traceLog(chain, "log", `广播${label} already-known, txHash=${txHash}, 耗时=${Date.now() - sendTs}ms`);
893
- } else {
894
- _Aggregator.traceLog(chain, "warn", `广播${label} 失败: ${msg}, txHash=${txHash}, 耗时=${Date.now() - sendTs}ms`);
911
+ return { label, ok: true };
895
912
  }
913
+ _Aggregator.traceLog(chain, "warn", `广播${label} 失败: ${msg}, txHash=${txHash}, 耗时=${Date.now() - sendTs}ms`);
914
+ return { label, ok: false, msg };
896
915
  }
897
- );
916
+ })
917
+ );
918
+ if (results.some((result) => result.ok)) {
919
+ return;
898
920
  }
921
+ const detail = results.map((result) => `${result.label}: ${result.msg ?? "unknown error"}`).join("; ");
922
+ throw new Error(`All broadcast channels failed for ${txHash}: ${detail}`);
899
923
  }
900
924
  raceForReceipt(chain, txHash, providers, ownedProviders = [], timeoutMs = _Aggregator.TX_CONFIRM_TIMEOUT_MS) {
901
925
  return new Promise((resolve, reject) => {
@@ -948,17 +972,66 @@ var _Aggregator = class {
948
972
  }, timeoutMs);
949
973
  });
950
974
  }
975
+ buildSequencerChannel(chain, signedTx) {
976
+ const sequencerUrl = _Aggregator.SEQUENCER_CHAINS.has(chain) ? this.sequencerRpcsMap.get(chain) : void 0;
977
+ if (!sequencerUrl)
978
+ return null;
979
+ return {
980
+ label: `Sequencer[${new URL(sequencerUrl).hostname}]`,
981
+ send: async () => {
982
+ var _a;
983
+ const resp = await import_axios.default.post(
984
+ sequencerUrl,
985
+ { jsonrpc: "2.0", id: 1, method: "eth_sendRawTransaction", params: [signedTx] },
986
+ { timeout: 5e3 }
987
+ );
988
+ if ((_a = resp.data) == null ? void 0 : _a.error) {
989
+ throw new Error(resp.data.error.message ?? JSON.stringify(resp.data.error));
990
+ }
991
+ return resp;
992
+ }
993
+ };
994
+ }
951
995
  async sendContractTx(chain, contract, method, args, txReq) {
952
996
  const extraUrls = this.broadcastRpcsMap.get(chain);
997
+ const hasSequencer = _Aggregator.SEQUENCER_CHAINS.has(chain) && this.sequencerRpcsMap.has(chain);
953
998
  if (!extraUrls || extraUrls.length === 0) {
954
- _Aggregator.traceLog(chain, "log", `渠道=单RPC直发, method=${method}, nonce=${txReq.nonce}, gasLimit=${txReq.gasLimit}`);
955
- const sendTs = Date.now();
956
- const txResponse = await contract[method](...args, txReq);
957
- _Aggregator.traceLog(chain, "log", `单RPC已发送, txHash=${txResponse.hash}, 耗时=${Date.now() - sendTs}ms`);
958
- const receipt2 = await txResponse.wait();
959
- const singleProvider = this.providerClient.getProvider(chain);
960
- const blockTs2 = await this.getBlockTimestampStr(singleProvider, receipt2.blockNumber);
961
- _Aggregator.traceLog(chain, "log", `单RPC上链确认, txHash=${receipt2.hash}, blockNumber=${receipt2.blockNumber}, 出块时间=${blockTs2}, gasUsed=${receipt2.gasUsed}, status=${receipt2.status}`);
999
+ if (!hasSequencer) {
1000
+ _Aggregator.traceLog(chain, "log", `渠道=单RPC直发, method=${method}, nonce=${txReq.nonce}, gasLimit=${txReq.gasLimit}`);
1001
+ const sendTs = Date.now();
1002
+ const txResponse = await contract[method](...args, txReq);
1003
+ _Aggregator.traceLog(chain, "log", `单RPC已发送, txHash=${txResponse.hash}, 耗时=${Date.now() - sendTs}ms`);
1004
+ const receipt3 = await txResponse.wait();
1005
+ const singleProvider = this.providerClient.getProvider(chain);
1006
+ const blockTs3 = await this.getBlockTimestampStr(singleProvider, receipt3.blockNumber);
1007
+ _Aggregator.traceLog(chain, "log", `单RPC上链确认, txHash=${receipt3.hash}, blockNumber=${receipt3.blockNumber}, 出块时间=${blockTs3}, gasUsed=${receipt3.gasUsed}, status=${receipt3.status}`);
1008
+ if (receipt3.status === 0) {
1009
+ throw new import_types.TransactionRevertedError(receipt3.hash, receipt3.gasUsed);
1010
+ }
1011
+ return receipt3;
1012
+ }
1013
+ _Aggregator.traceLog(chain, "log", `渠道=单RPC+排序器直发, method=${method}, nonce=${txReq.nonce}, gasLimit=${txReq.gasLimit}`);
1014
+ const wallet2 = this.providerClient.getWallet(chain);
1015
+ const populated2 = await contract[method].populateTransaction(...args, txReq);
1016
+ delete populated2.from;
1017
+ const network2 = await wallet2.provider.getNetwork();
1018
+ populated2.chainId = network2.chainId;
1019
+ const signedTx2 = await wallet2.signTransaction(populated2);
1020
+ const txHash2 = import_ethers.ethers.keccak256(signedTx2);
1021
+ const mainProvider2 = wallet2.provider;
1022
+ _Aggregator.traceLog(chain, "log", `单RPC+排序器并行广播, txHash=${txHash2}`);
1023
+ const broadcastTs2 = Date.now();
1024
+ const providers = [{ provider: mainProvider2, label: "RPC[0/主]" }];
1025
+ const sequencerChannel2 = this.buildSequencerChannel(chain, signedTx2);
1026
+ const channels2 = [
1027
+ ...providers.map(({ provider, label }) => ({ label, send: () => provider.broadcastTransaction(signedTx2) })),
1028
+ ...sequencerChannel2 ? [sequencerChannel2] : []
1029
+ ];
1030
+ const receiptPromise2 = this.raceForReceipt(chain, txHash2, providers);
1031
+ await this.sendSignedTxToChannels(chain, signedTx2, txHash2, channels2);
1032
+ const receipt2 = await receiptPromise2;
1033
+ const blockTs2 = await this.getBlockTimestampStr(mainProvider2, receipt2.blockNumber);
1034
+ _Aggregator.traceLog(chain, "log", `单RPC+排序器上链确认, txHash=${receipt2.hash}, blockNumber=${receipt2.blockNumber}, 出块时间=${blockTs2}, gasUsed=${receipt2.gasUsed}, status=${receipt2.status}, 总耗时=${Date.now() - broadcastTs2}ms`);
962
1035
  if (receipt2.status === 0) {
963
1036
  throw new import_types.TransactionRevertedError(receipt2.hash, receipt2.gasUsed);
964
1037
  }
@@ -982,11 +1055,17 @@ var _Aggregator = class {
982
1055
  _Aggregator.traceLog(
983
1056
  chain,
984
1057
  "log",
985
- `渠道=多RPC广播, method=${method}, nonce=${txReq.nonce}, gasLimit=${txReq.gasLimit}, 当前区块=${currentBlock}, 期望上链区块=${currentBlock + 1}, txHash=${txHash}, RPC数量=${allProviders.length}(主1+副${extraUrls.length})`
1058
+ `渠道=多RPC广播, method=${method}, nonce=${txReq.nonce}, gasLimit=${txReq.gasLimit}, 当前区块=${currentBlock}, 期望上链区块=${currentBlock + 1}, txHash=${txHash}, RPC数量=${allProviders.length}(主1+副${extraUrls.length})${hasSequencer ? ", +排序器直发" : ""}`
986
1059
  );
987
1060
  const broadcastTs = Date.now();
988
- this.broadcastToAll(chain, signedTx, txHash, allProviders);
989
- const receipt = await this.raceForReceipt(chain, txHash, allProviders, ownedProviders);
1061
+ const sequencerChannel = this.buildSequencerChannel(chain, signedTx);
1062
+ const channels = [
1063
+ ...allProviders.map(({ provider, label }) => ({ label, send: () => provider.broadcastTransaction(signedTx) })),
1064
+ ...sequencerChannel ? [sequencerChannel] : []
1065
+ ];
1066
+ const receiptPromise = this.raceForReceipt(chain, txHash, allProviders, ownedProviders);
1067
+ await this.sendSignedTxToChannels(chain, signedTx, txHash, channels);
1068
+ const receipt = await receiptPromise;
990
1069
  const blockTs = await this.getBlockTimestampStr(mainProvider, receipt.blockNumber);
991
1070
  _Aggregator.traceLog(
992
1071
  chain,
@@ -1225,6 +1304,14 @@ Aggregator.DEFAULT_ETH_BUILDERS = {
1225
1304
  nfactorial: "https://rpc.nfactorial.xyz"
1226
1305
  // 新增,支持 bundle rebate
1227
1306
  };
1307
+ Aggregator.DEFAULT_SEQUENCER_RPC_BY_CHAIN = {
1308
+ [import_types.ChainNameEnum.ARB]: "https://arb1-sequencer.arbitrum.io/rpc",
1309
+ [import_types.ChainNameEnum.BASE]: "https://mainnet-sequencer.base.org",
1310
+ [import_types.ChainNameEnum.ETHERLINK]: "https://relay.mainnet.etherlink.com"
1311
+ };
1312
+ Aggregator.SEQUENCER_CHAINS = new Set(
1313
+ Object.keys(_Aggregator.DEFAULT_SEQUENCER_RPC_BY_CHAIN)
1314
+ );
1228
1315
  Aggregator.TX_CONFIRM_TIMEOUT_MS = 12e4;
1229
1316
  Aggregator.BUNDLE_POLL_INTERVAL_MS = 500;
1230
1317
  Aggregator.BUNDLE_INCLUSION_TIMEOUT_MS = 3e4;
@@ -10,17 +10,21 @@ declare class Aggregator {
10
10
  private readonly flashbotSigner?;
11
11
  private readonly builderConfigMap;
12
12
  private readonly broadcastRpcsMap;
13
+ private readonly sequencerRpcsMap;
13
14
  private aggregatorAddressMap;
14
15
  private aggregatorMap;
15
16
  private static traceLog;
16
17
  private static formatBlockTimestamp;
17
18
  private getBlockTimestampStr;
18
19
  private static readonly DEFAULT_ETH_BUILDERS;
20
+ private static readonly DEFAULT_SEQUENCER_RPC_BY_CHAIN;
21
+ private static readonly SEQUENCER_CHAINS;
19
22
  constructor(config: IConfig, providerClient?: Provider, quoterClient?: Quoter);
20
23
  getAggregator(chain: ChainNameEnum): Contract;
21
24
  getAggregatorAddress(chain: ChainNameEnum): string;
22
25
  signRequestBody(body: unknown, signer: ethers.Wallet): Promise<string>;
23
26
  private getBuilderConfig;
27
+ private getSequencerRpcs;
24
28
  private buildBundleRequestBody;
25
29
  sendBundle(chain: ChainNameEnum, rawTxs: string[], targetBlock: number, signer?: ethers.Wallet): Promise<{
26
30
  sent: number;
@@ -53,9 +57,11 @@ declare class Aggregator {
53
57
  private static readonly TX_CONFIRM_TIMEOUT_MS;
54
58
  private static readonly BUNDLE_POLL_INTERVAL_MS;
55
59
  private static readonly BUNDLE_INCLUSION_TIMEOUT_MS;
60
+ private static isBenignBroadcastError;
56
61
  private waitForBundleInclusion;
57
- private broadcastToAll;
62
+ private sendSignedTxToChannels;
58
63
  private raceForReceipt;
64
+ private buildSequencerChannel;
59
65
  private sendContractTx;
60
66
  private submitBundleTx;
61
67
  private swapByFlashbots;
@@ -76,7 +76,13 @@ var Quoter = class {
76
76
  var _a, _b, _c, _d;
77
77
  try {
78
78
  const res = await import_axios.default.get(`${this.hermesSignalDomain}/api/v1/quote-best`, {
79
- params: { chain: params.chain, fromToken: params.fromTokenAddress, toToken: params.toTokenAddress, amountInWei: params.amountInWei.toString() }
79
+ params: {
80
+ chain: params.chain,
81
+ fromToken: params.fromTokenAddress,
82
+ toToken: params.toTokenAddress,
83
+ amountInWei: params.amountInWei.toString(),
84
+ ...params.blockNumber ? { blockNumber: params.blockNumber } : {}
85
+ }
80
86
  });
81
87
  const raw = res.data;
82
88
  if (raw.code !== 200) {
@@ -14,6 +14,7 @@ export interface IConfig {
14
14
  flashbotSigner?: string;
15
15
  builderConfig?: Record<string, IBuilderConfig>;
16
16
  broadcastRpcs?: Record<string, string[]>;
17
+ sequencerRpcs?: Record<string, string>;
17
18
  }
18
19
  export interface IExpectByPathParams {
19
20
  chain: ChainNameEnum;
@@ -25,6 +26,7 @@ export interface IExpectSplitOrderParams {
25
26
  amountInWei: bigint;
26
27
  fromTokenAddress: string;
27
28
  toTokenAddress: string;
29
+ blockNumber?: number;
28
30
  }
29
31
  export interface IExpectSplitOrderResp {
30
32
  routePath: IRouterPath[];
@@ -10,17 +10,21 @@ declare class Aggregator {
10
10
  private readonly flashbotSigner?;
11
11
  private readonly builderConfigMap;
12
12
  private readonly broadcastRpcsMap;
13
+ private readonly sequencerRpcsMap;
13
14
  private aggregatorAddressMap;
14
15
  private aggregatorMap;
15
16
  private static traceLog;
16
17
  private static formatBlockTimestamp;
17
18
  private getBlockTimestampStr;
18
19
  private static readonly DEFAULT_ETH_BUILDERS;
20
+ private static readonly DEFAULT_SEQUENCER_RPC_BY_CHAIN;
21
+ private static readonly SEQUENCER_CHAINS;
19
22
  constructor(config: IConfig, providerClient?: Provider, quoterClient?: Quoter);
20
23
  getAggregator(chain: ChainNameEnum): Contract;
21
24
  getAggregatorAddress(chain: ChainNameEnum): string;
22
25
  signRequestBody(body: unknown, signer: ethers.Wallet): Promise<string>;
23
26
  private getBuilderConfig;
27
+ private getSequencerRpcs;
24
28
  private buildBundleRequestBody;
25
29
  sendBundle(chain: ChainNameEnum, rawTxs: string[], targetBlock: number, signer?: ethers.Wallet): Promise<{
26
30
  sent: number;
@@ -53,9 +57,11 @@ declare class Aggregator {
53
57
  private static readonly TX_CONFIRM_TIMEOUT_MS;
54
58
  private static readonly BUNDLE_POLL_INTERVAL_MS;
55
59
  private static readonly BUNDLE_INCLUSION_TIMEOUT_MS;
60
+ private static isBenignBroadcastError;
56
61
  private waitForBundleInclusion;
57
- private broadcastToAll;
62
+ private sendSignedTxToChannels;
58
63
  private raceForReceipt;
64
+ private buildSequencerChannel;
59
65
  private sendContractTx;
60
66
  private submitBundleTx;
61
67
  private swapByFlashbots;