@across-protocol/sdk 3.2.13 → 3.2.14

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.
Files changed (93) hide show
  1. package/dist/cjs/clients/BundleDataClient/utils/SuperstructUtils.d.ts +30 -30
  2. package/dist/cjs/coingecko/Coingecko.d.ts +9 -1
  3. package/dist/cjs/coingecko/Coingecko.js +94 -46
  4. package/dist/cjs/coingecko/Coingecko.js.map +1 -1
  5. package/dist/cjs/gasPriceOracle/adapters/arbitrum-viem.d.ts +3 -0
  6. package/dist/cjs/gasPriceOracle/adapters/arbitrum-viem.js +21 -0
  7. package/dist/cjs/gasPriceOracle/adapters/arbitrum-viem.js.map +1 -0
  8. package/dist/cjs/gasPriceOracle/adapters/ethereum-viem.d.ts +4 -0
  9. package/dist/cjs/gasPriceOracle/adapters/ethereum-viem.js +26 -0
  10. package/dist/cjs/gasPriceOracle/adapters/ethereum-viem.js.map +1 -0
  11. package/dist/cjs/gasPriceOracle/adapters/polygon-viem.d.ts +3 -0
  12. package/dist/cjs/gasPriceOracle/adapters/polygon-viem.js +84 -0
  13. package/dist/cjs/gasPriceOracle/adapters/polygon-viem.js.map +1 -0
  14. package/dist/cjs/gasPriceOracle/oracle.d.ts +3 -1
  15. package/dist/cjs/gasPriceOracle/oracle.js +71 -4
  16. package/dist/cjs/gasPriceOracle/oracle.js.map +1 -1
  17. package/dist/cjs/gasPriceOracle/types.d.ts +3 -2
  18. package/dist/cjs/gasPriceOracle/util.d.ts +3 -3
  19. package/dist/cjs/gasPriceOracle/util.js +12 -1
  20. package/dist/cjs/gasPriceOracle/util.js.map +1 -1
  21. package/dist/cjs/providers/types.d.ts +3 -3
  22. package/dist/cjs/relayFeeCalculator/chain-queries/baseQuery.d.ts +7 -1
  23. package/dist/cjs/relayFeeCalculator/chain-queries/baseQuery.js +13 -8
  24. package/dist/cjs/relayFeeCalculator/chain-queries/baseQuery.js.map +1 -1
  25. package/dist/cjs/relayFeeCalculator/relayFeeCalculator.d.ts +8 -2
  26. package/dist/cjs/relayFeeCalculator/relayFeeCalculator.js +4 -2
  27. package/dist/cjs/relayFeeCalculator/relayFeeCalculator.js.map +1 -1
  28. package/dist/cjs/utils/common.d.ts +6 -1
  29. package/dist/cjs/utils/common.js +21 -17
  30. package/dist/cjs/utils/common.js.map +1 -1
  31. package/dist/esm/clients/BundleDataClient/utils/SuperstructUtils.d.ts +30 -30
  32. package/dist/esm/coingecko/Coingecko.d.ts +9 -1
  33. package/dist/esm/coingecko/Coingecko.js +96 -46
  34. package/dist/esm/coingecko/Coingecko.js.map +1 -1
  35. package/dist/esm/gasPriceOracle/adapters/arbitrum-viem.d.ts +3 -0
  36. package/dist/esm/gasPriceOracle/adapters/arbitrum-viem.js +20 -0
  37. package/dist/esm/gasPriceOracle/adapters/arbitrum-viem.js.map +1 -0
  38. package/dist/esm/gasPriceOracle/adapters/ethereum-viem.d.ts +4 -0
  39. package/dist/esm/gasPriceOracle/adapters/ethereum-viem.js +21 -0
  40. package/dist/esm/gasPriceOracle/adapters/ethereum-viem.js.map +1 -0
  41. package/dist/esm/gasPriceOracle/adapters/polygon-viem.d.ts +3 -0
  42. package/dist/esm/gasPriceOracle/adapters/polygon-viem.js +82 -0
  43. package/dist/esm/gasPriceOracle/adapters/polygon-viem.js.map +1 -0
  44. package/dist/esm/gasPriceOracle/oracle.d.ts +9 -1
  45. package/dist/esm/gasPriceOracle/oracle.js +82 -3
  46. package/dist/esm/gasPriceOracle/oracle.js.map +1 -1
  47. package/dist/esm/gasPriceOracle/types.d.ts +3 -2
  48. package/dist/esm/gasPriceOracle/util.d.ts +3 -3
  49. package/dist/esm/gasPriceOracle/util.js +9 -0
  50. package/dist/esm/gasPriceOracle/util.js.map +1 -1
  51. package/dist/esm/providers/types.d.ts +3 -3
  52. package/dist/esm/relayFeeCalculator/chain-queries/baseQuery.d.ts +12 -3
  53. package/dist/esm/relayFeeCalculator/chain-queries/baseQuery.js +18 -10
  54. package/dist/esm/relayFeeCalculator/chain-queries/baseQuery.js.map +1 -1
  55. package/dist/esm/relayFeeCalculator/relayFeeCalculator.d.ts +8 -2
  56. package/dist/esm/relayFeeCalculator/relayFeeCalculator.js +4 -2
  57. package/dist/esm/relayFeeCalculator/relayFeeCalculator.js.map +1 -1
  58. package/dist/esm/utils/common.d.ts +10 -3
  59. package/dist/esm/utils/common.js +25 -19
  60. package/dist/esm/utils/common.js.map +1 -1
  61. package/dist/types/clients/BundleDataClient/utils/SuperstructUtils.d.ts +30 -30
  62. package/dist/types/coingecko/Coingecko.d.ts +9 -1
  63. package/dist/types/coingecko/Coingecko.d.ts.map +1 -1
  64. package/dist/types/gasPriceOracle/adapters/arbitrum-viem.d.ts +4 -0
  65. package/dist/types/gasPriceOracle/adapters/arbitrum-viem.d.ts.map +1 -0
  66. package/dist/types/gasPriceOracle/adapters/ethereum-viem.d.ts +5 -0
  67. package/dist/types/gasPriceOracle/adapters/ethereum-viem.d.ts.map +1 -0
  68. package/dist/types/gasPriceOracle/adapters/polygon-viem.d.ts +4 -0
  69. package/dist/types/gasPriceOracle/adapters/polygon-viem.d.ts.map +1 -0
  70. package/dist/types/gasPriceOracle/oracle.d.ts +9 -1
  71. package/dist/types/gasPriceOracle/oracle.d.ts.map +1 -1
  72. package/dist/types/gasPriceOracle/types.d.ts +3 -2
  73. package/dist/types/gasPriceOracle/types.d.ts.map +1 -1
  74. package/dist/types/gasPriceOracle/util.d.ts +3 -3
  75. package/dist/types/gasPriceOracle/util.d.ts.map +1 -1
  76. package/dist/types/providers/types.d.ts +3 -3
  77. package/dist/types/relayFeeCalculator/chain-queries/baseQuery.d.ts +12 -3
  78. package/dist/types/relayFeeCalculator/chain-queries/baseQuery.d.ts.map +1 -1
  79. package/dist/types/relayFeeCalculator/relayFeeCalculator.d.ts +8 -2
  80. package/dist/types/relayFeeCalculator/relayFeeCalculator.d.ts.map +1 -1
  81. package/dist/types/utils/common.d.ts +10 -3
  82. package/dist/types/utils/common.d.ts.map +1 -1
  83. package/package.json +3 -2
  84. package/src/coingecko/Coingecko.ts +94 -47
  85. package/src/gasPriceOracle/adapters/arbitrum-viem.ts +13 -0
  86. package/src/gasPriceOracle/adapters/ethereum-viem.ts +19 -0
  87. package/src/gasPriceOracle/adapters/polygon-viem.ts +86 -0
  88. package/src/gasPriceOracle/oracle.ts +69 -5
  89. package/src/gasPriceOracle/types.ts +4 -2
  90. package/src/gasPriceOracle/util.ts +12 -3
  91. package/src/relayFeeCalculator/chain-queries/baseQuery.ts +19 -7
  92. package/src/relayFeeCalculator/relayFeeCalculator.ts +15 -12
  93. package/src/utils/common.ts +15 -6
@@ -29,6 +29,11 @@ type PriceCache = {
29
29
  };
30
30
  };
31
31
 
32
+ type CGTokenPrice = {
33
+ [currency: string]: number;
34
+ last_updated_at: number;
35
+ };
36
+
32
37
  // Singleton Coingecko class.
33
38
  export class Coingecko {
34
39
  private static instance: Coingecko | undefined;
@@ -118,38 +123,53 @@ export class Coingecko {
118
123
  getContractDetails(contract_address: string, platform_id = "ethereum") {
119
124
  return this.call(`coins/${platform_id}/contract/${contract_address.toLowerCase()}`);
120
125
  }
126
+
121
127
  async getCurrentPriceByContract(
122
- contract_address: string,
128
+ contractAddress: string,
123
129
  currency = "usd",
124
130
  platform_id = "ethereum"
125
131
  ): Promise<[string, number]> {
126
132
  const priceCache: { [addr: string]: CoinGeckoPrice } = this.getPriceCache(currency, platform_id);
127
- const now: number = msToS(Date.now());
128
- let tokenPrice: CoinGeckoPrice | undefined = priceCache[contract_address];
133
+ let tokenPrice = this.getCachedAddressPrice(contractAddress, currency, platform_id);
134
+ if (tokenPrice === undefined) {
135
+ await this.getContractPrices([contractAddress], currency, platform_id);
136
+ tokenPrice = priceCache[contractAddress];
137
+ }
129
138
 
130
- if (tokenPrice === undefined || tokenPrice.timestamp + this.maxPriceAge <= now) {
131
- if (this.maxPriceAge > 0) {
139
+ assert(tokenPrice !== undefined);
140
+ return [tokenPrice.timestamp.toString(), tokenPrice.price];
141
+ }
142
+
143
+ async getCurrentPriceById(
144
+ contractAddress: string,
145
+ currency = "usd",
146
+ platform_id = "ethereum"
147
+ ): Promise<[string, number]> {
148
+ const priceCache: { [addr: string]: CoinGeckoPrice } = this.getPriceCache(currency, platform_id);
149
+ let tokenPrice = this.getCachedAddressPrice(contractAddress, currency, platform_id);
150
+ if (tokenPrice === undefined) {
151
+ const coingeckoId = getCoingeckoTokenIdByAddress(contractAddress);
152
+ // Build the path for the Coingecko API request
153
+ const result = await this.call(
154
+ `simple/price?ids=${coingeckoId}&vs_currencies=${currency}&include_last_updated_at=true`
155
+ );
156
+ const cgPrice = result?.[coingeckoId];
157
+ if (cgPrice === undefined || !cgPrice?.[currency]) {
158
+ const errMsg = `No price found for ${coingeckoId}`;
132
159
  this.logger.debug({
133
- at: "Coingecko#getCurrentPriceByContract",
134
- message: `Cache miss on ${platform_id}/${currency} for ${contract_address}`,
135
- maxPriceAge: this.maxPriceAge,
136
- tokenPrice: tokenPrice,
160
+ at: "Coingecko#getCurrentPriceById",
161
+ message: errMsg,
137
162
  });
163
+ throw new Error(errMsg);
164
+ } else {
165
+ this.updatePriceCache(cgPrice, contractAddress, currency, platform_id);
138
166
  }
139
-
140
- await this.getContractPrices([contract_address], currency, platform_id);
141
- tokenPrice = priceCache[contract_address];
142
- } else {
143
- this.logger.debug({
144
- at: "Coingecko#getCurrentPriceByContract",
145
- message: `Cache hit on token ${contract_address} (age ${now - tokenPrice.timestamp} S).`,
146
- price: tokenPrice,
147
- });
148
167
  }
149
-
168
+ tokenPrice = priceCache[contractAddress];
150
169
  assert(tokenPrice !== undefined);
151
170
  return [tokenPrice.timestamp.toString(), tokenPrice.price];
152
171
  }
172
+
153
173
  // Return an array of spot prices for an array of collateral addresses in one async call. Note we might in future
154
174
  // This was adapted from packages/merkle-distributor/kpi-options-helpers/calculate-uma-tvl.ts
155
175
  async getContractPrices(
@@ -176,10 +196,6 @@ export class Coingecko {
176
196
  });
177
197
 
178
198
  // annoying, but have to type this to iterate over entries
179
- type CGTokenPrice = {
180
- [currency: string]: number;
181
- last_updated_at: number;
182
- };
183
199
  type Result = {
184
200
  [address: string]: CGTokenPrice;
185
201
  };
@@ -195,7 +211,7 @@ export class Coingecko {
195
211
  } catch (err) {
196
212
  const errMsg = `Failed to retrieve ${platform_id}/${currency} prices (${err})`;
197
213
  this.logger.debug({
198
- at: "Coingecko#getCurrentPriceByContract",
214
+ at: "Coingecko#getContractPrices",
199
215
  message: errMsg,
200
216
  tokens: contract_addresses,
201
217
  });
@@ -204,38 +220,17 @@ export class Coingecko {
204
220
 
205
221
  // Note: contract_addresses is a reliable reference for the price lookup.
206
222
  // priceCache might have been updated subsequently by concurrent price requests.
207
- const updated: string[] = [];
208
223
  contract_addresses.forEach((addr) => {
209
224
  const cgPrice: CGTokenPrice | undefined = result[addr.toLowerCase()];
210
-
211
225
  if (cgPrice === undefined) {
212
226
  this.logger.debug({
213
227
  at: "Coingecko#getContractPrices",
214
228
  message: `Token ${addr} not included in CoinGecko response.`,
215
229
  });
216
- } else if (cgPrice.last_updated_at > priceCache[addr].timestamp) {
217
- priceCache[addr] = {
218
- address: addr,
219
- price: cgPrice[currency],
220
- timestamp: cgPrice.last_updated_at,
221
- };
222
- updated.push(addr);
223
- } else if (cgPrice.last_updated_at === priceCache[addr].timestamp) {
224
- this.logger.debug({
225
- at: "Coingecko#getContractPrices",
226
- message: `No new price available for token ${addr}.`,
227
- token: cgPrice,
228
- });
230
+ } else {
231
+ this.updatePriceCache(cgPrice, addr, currency, platform_id);
229
232
  }
230
233
  });
231
-
232
- if (updated.length > 0) {
233
- this.logger.debug({
234
- at: "Coingecko#updatePriceCache",
235
- message: `Updated ${platform_id}/${currency} token price cache.`,
236
- tokens: updated,
237
- });
238
- }
239
234
  return addresses.map((addr: string) => priceCache[addr]);
240
235
  }
241
236
 
@@ -275,6 +270,58 @@ export class Coingecko {
275
270
  return this.prices[platform_id][currency];
276
271
  }
277
272
 
273
+ protected getCachedAddressPrice(
274
+ contractAddress: string,
275
+ currency: string,
276
+ platform_id: string
277
+ ): CoinGeckoPrice | undefined {
278
+ const priceCache = this.getPriceCache(currency, platform_id);
279
+ const now: number = msToS(Date.now());
280
+ const tokenPrice: CoinGeckoPrice | undefined = priceCache[contractAddress];
281
+ if (tokenPrice === undefined || tokenPrice.timestamp + this.maxPriceAge <= now) {
282
+ if (this.maxPriceAge > 0) {
283
+ this.logger.debug({
284
+ at: "Coingecko#getCachedAddressPrice",
285
+ message: `Cache miss on ${platform_id}/${currency} for ${contractAddress}`,
286
+ maxPriceAge: this.maxPriceAge,
287
+ tokenPrice: tokenPrice,
288
+ });
289
+ }
290
+ return undefined;
291
+ } else {
292
+ this.logger.debug({
293
+ at: "Coingecko#getCachedAddressPrice",
294
+ message: `Cache hit on token ${contractAddress} (age ${now - tokenPrice.timestamp} S).`,
295
+ price: tokenPrice,
296
+ });
297
+ return tokenPrice;
298
+ }
299
+ }
300
+
301
+ protected updatePriceCache(cgPrice: CGTokenPrice, contractAddress: string, currency: string, platform_id: string) {
302
+ const priceCache = this.getPriceCache(currency, platform_id);
303
+ if (priceCache[contractAddress] === undefined) {
304
+ priceCache[contractAddress] = { address: contractAddress, price: 0, timestamp: 0 };
305
+ }
306
+ if (cgPrice.last_updated_at > priceCache[contractAddress].timestamp) {
307
+ priceCache[contractAddress] = {
308
+ address: contractAddress,
309
+ price: cgPrice[currency],
310
+ timestamp: cgPrice.last_updated_at,
311
+ };
312
+ this.logger.debug({
313
+ at: "Coingecko#updatePriceCache",
314
+ message: `Updated ${platform_id}/${currency}/${contractAddress} token price cache.`,
315
+ });
316
+ } else {
317
+ this.logger.debug({
318
+ at: "Coingecko#updatePriceCache",
319
+ message: `No new price available for token ${contractAddress}.`,
320
+ token: cgPrice,
321
+ });
322
+ }
323
+ }
324
+
278
325
  private async _callBasic(path: string, timeout?: number) {
279
326
  const url = `${this.host}/${path}`;
280
327
 
@@ -0,0 +1,13 @@
1
+ import { PublicClient } from "viem";
2
+ import { InternalGasPriceEstimate } from "../types";
3
+
4
+ const MAX_PRIORITY_FEE_PER_GAS = BigInt(1);
5
+
6
+ // Arbitrum Nitro implements EIP-1559 pricing, but the priority fee is always refunded to the caller.
7
+ // Swap it for 1 Wei to avoid inaccurate transaction cost estimates.
8
+ // Reference: https://developer.arbitrum.io/faqs/gas-faqs#q-priority
9
+ export async function eip1559(provider: PublicClient, _chainId: number): Promise<InternalGasPriceEstimate> {
10
+ const { maxFeePerGas: _maxFeePerGas, maxPriorityFeePerGas } = await provider.estimateFeesPerGas();
11
+ const maxFeePerGas = BigInt(_maxFeePerGas) - maxPriorityFeePerGas + MAX_PRIORITY_FEE_PER_GAS;
12
+ return { maxFeePerGas, maxPriorityFeePerGas: MAX_PRIORITY_FEE_PER_GAS };
13
+ }
@@ -0,0 +1,19 @@
1
+ import { PublicClient } from "viem";
2
+ import { InternalGasPriceEstimate } from "../types";
3
+
4
+ export function eip1559(provider: PublicClient, _chainId: number): Promise<InternalGasPriceEstimate> {
5
+ return provider.estimateFeesPerGas();
6
+ }
7
+
8
+ export async function legacy(
9
+ provider: PublicClient,
10
+ _chainId: number,
11
+ _test?: number
12
+ ): Promise<InternalGasPriceEstimate> {
13
+ const gasPrice = await provider.getGasPrice();
14
+
15
+ return {
16
+ maxFeePerGas: gasPrice,
17
+ maxPriorityFeePerGas: BigInt(0),
18
+ };
19
+ }
@@ -0,0 +1,86 @@
1
+ import { PublicClient } from "viem";
2
+ import { BaseHTTPAdapter, BaseHTTPAdapterArgs } from "../../priceClient/adapters/baseAdapter";
3
+ import { isDefined } from "../../utils";
4
+ import { CHAIN_IDs } from "../../constants";
5
+ import { InternalGasPriceEstimate } from "../types";
6
+ import { gasPriceError } from "../util";
7
+ import { eip1559 } from "./ethereum-viem";
8
+
9
+ type Polygon1559GasPrice = {
10
+ maxPriorityFee: number | string;
11
+ maxFee: number | string;
12
+ };
13
+
14
+ type GasStationV2Response = {
15
+ safeLow: Polygon1559GasPrice;
16
+ standard: Polygon1559GasPrice;
17
+ fast: Polygon1559GasPrice;
18
+ estimatedBaseFee: number | string;
19
+ blockTime: number | string;
20
+ blockNumber: number | string;
21
+ };
22
+
23
+ type GasStationArgs = BaseHTTPAdapterArgs & {
24
+ chainId?: number;
25
+ host?: string;
26
+ };
27
+
28
+ const { POLYGON } = CHAIN_IDs;
29
+
30
+ const GWEI = BigInt(1_000_000_000);
31
+ class PolygonGasStation extends BaseHTTPAdapter {
32
+ readonly chainId: number;
33
+
34
+ constructor({ chainId = POLYGON, host, timeout = 1500, retries = 1 }: GasStationArgs = {}) {
35
+ host = host ?? chainId === POLYGON ? "gasstation.polygon.technology" : "gasstation-testnet.polygon.technology";
36
+
37
+ super("Polygon Gas Station", host, { timeout, retries });
38
+ this.chainId = chainId;
39
+ }
40
+
41
+ async getFeeData(strategy: "safeLow" | "standard" | "fast" = "fast"): Promise<InternalGasPriceEstimate> {
42
+ const gas = await this.query("v2", {});
43
+
44
+ const gasPrice = (gas as GasStationV2Response)?.[strategy];
45
+ if (!this.isPolygon1559GasPrice(gasPrice)) {
46
+ // @todo: generalise gasPriceError() to accept a reason/cause?
47
+ gasPriceError("getFeeData()", this.chainId, gasPrice);
48
+ }
49
+
50
+ const maxPriorityFeePerGas = BigInt(gasPrice.maxPriorityFee) * GWEI;
51
+ const maxFeePerGas = BigInt(gasPrice.maxFee) * GWEI;
52
+
53
+ return { maxPriorityFeePerGas, maxFeePerGas };
54
+ }
55
+
56
+ protected isPolygon1559GasPrice(gasPrice: unknown): gasPrice is Polygon1559GasPrice {
57
+ if (!isDefined(gasPrice)) {
58
+ return false;
59
+ }
60
+ const _gasPrice = gasPrice as Polygon1559GasPrice;
61
+ return [_gasPrice.maxPriorityFee, _gasPrice.maxFee].every((field) => ["number", "string"].includes(typeof field));
62
+ }
63
+ }
64
+
65
+ export async function gasStation(provider: PublicClient, chainId: number): Promise<InternalGasPriceEstimate> {
66
+ const gasStation = new PolygonGasStation({ chainId, timeout: 2000, retries: 0 });
67
+ let maxPriorityFeePerGas: bigint;
68
+ let maxFeePerGas: bigint;
69
+ try {
70
+ ({ maxPriorityFeePerGas, maxFeePerGas } = await gasStation.getFeeData());
71
+ } catch (err) {
72
+ // Fall back to the RPC provider. May be less accurate.
73
+ ({ maxPriorityFeePerGas, maxFeePerGas } = await eip1559(provider, chainId));
74
+
75
+ // Per the GasStation docs, the minimum priority fee on Polygon is 30 Gwei.
76
+ // https://docs.polygon.technology/tools/gas/polygon-gas-station/#interpretation
77
+ const minPriorityFee = BigInt(30) * GWEI;
78
+ if (minPriorityFee > maxPriorityFeePerGas) {
79
+ const priorityDelta = minPriorityFee - maxPriorityFeePerGas;
80
+ maxPriorityFeePerGas = minPriorityFee;
81
+ maxFeePerGas = maxFeePerGas + priorityDelta;
82
+ }
83
+ }
84
+
85
+ return { maxPriorityFeePerGas, maxFeePerGas };
86
+ }
@@ -1,11 +1,15 @@
1
+ import { Transport } from "viem";
1
2
  import { providers } from "ethers";
2
3
  import { CHAIN_IDs } from "../constants";
3
- import { chainIsOPStack } from "../utils";
4
- import { GasPriceEstimate, GasPriceFeed } from "./types";
4
+ import { BigNumber, chainIsOPStack } from "../utils";
5
+ import { GasPriceEstimate } from "./types";
6
+ import { getPublicClient } from "./util";
5
7
  import * as arbitrum from "./adapters/arbitrum";
6
8
  import * as ethereum from "./adapters/ethereum";
7
9
  import * as linea from "./adapters/linea";
8
10
  import * as polygon from "./adapters/polygon";
11
+ import * as arbitrumViem from "./adapters/arbitrum-viem";
12
+ import * as polygonViem from "./adapters/polygon-viem";
9
13
 
10
14
  /**
11
15
  * Provide an estimate for the current gas price for a particular chain.
@@ -17,17 +21,39 @@ import * as polygon from "./adapters/polygon";
17
21
  export async function getGasPriceEstimate(
18
22
  provider: providers.Provider,
19
23
  chainId?: number,
24
+ transport?: Transport,
20
25
  legacyFallback = true
21
26
  ): Promise<GasPriceEstimate> {
22
27
  if (chainId === undefined) {
23
28
  ({ chainId } = await provider.getNetwork());
24
29
  }
25
30
 
26
- const gasPriceFeeds: { [chainId: number]: GasPriceFeed } = {
31
+ const useViem = process.env[`NEW_GAS_PRICE_ORACLE_${chainId}`] === "true";
32
+ return useViem
33
+ ? getViemGasPriceEstimate(chainId, transport)
34
+ : getEthersGasPriceEstimate(provider, chainId, legacyFallback);
35
+ }
36
+
37
+ /**
38
+ * Provide an estimate for the current gas price for a particular chain.
39
+ * @param chainId The chain ID to query for gas prices.
40
+ * @param provider A valid ethers provider.
41
+ * @param legacyFallback In the case of an unrecognised chain, fall back to type 0 gas estimation.
42
+ * @returns Am object of type GasPriceEstimate.
43
+ */
44
+ async function getEthersGasPriceEstimate(
45
+ provider: providers.Provider,
46
+ chainId?: number,
47
+ legacyFallback = true
48
+ ): Promise<GasPriceEstimate> {
49
+ if (chainId === undefined) {
50
+ ({ chainId } = await provider.getNetwork());
51
+ }
52
+
53
+ const gasPriceFeeds = {
27
54
  [CHAIN_IDs.ALEPH_ZERO]: arbitrum.eip1559,
28
55
  [CHAIN_IDs.ARBITRUM]: arbitrum.eip1559,
29
56
  [CHAIN_IDs.BASE]: ethereum.eip1559,
30
- [CHAIN_IDs.BOBA]: ethereum.legacy,
31
57
  [CHAIN_IDs.LINEA]: linea.eip1559, // @todo: Support linea_estimateGas in adapter.
32
58
  [CHAIN_IDs.MAINNET]: ethereum.eip1559,
33
59
  [CHAIN_IDs.MODE]: ethereum.eip1559,
@@ -35,7 +61,7 @@ export async function getGasPriceEstimate(
35
61
  [CHAIN_IDs.POLYGON]: polygon.gasStation,
36
62
  [CHAIN_IDs.ZK_SYNC]: ethereum.legacy,
37
63
  [CHAIN_IDs.SCROLL]: ethereum.legacy,
38
- };
64
+ } as const;
39
65
 
40
66
  let gasPriceFeed = gasPriceFeeds[chainId];
41
67
  if (gasPriceFeed === undefined) {
@@ -47,3 +73,41 @@ export async function getGasPriceEstimate(
47
73
 
48
74
  return gasPriceFeed(provider, chainId);
49
75
  }
76
+
77
+ /**
78
+ * Provide an estimate for the current gas price for a particular chain.
79
+ * @param providerOrChainId A valid ethers provider or a chain ID.
80
+ * @param transport An optional transport object for custom gas price retrieval.
81
+ * @returns Am object of type GasPriceEstimate.
82
+ */
83
+ export async function getViemGasPriceEstimate(
84
+ providerOrChainId: providers.Provider | number,
85
+ transport?: Transport
86
+ ): Promise<GasPriceEstimate> {
87
+ const chainId =
88
+ typeof providerOrChainId === "number" ? providerOrChainId : (await providerOrChainId.getNetwork()).chainId;
89
+ const viemProvider = getPublicClient(chainId, transport);
90
+
91
+ const gasPriceFeeds = {
92
+ [CHAIN_IDs.ALEPH_ZERO]: arbitrumViem.eip1559,
93
+ [CHAIN_IDs.ARBITRUM]: arbitrumViem.eip1559,
94
+ [CHAIN_IDs.POLYGON]: polygonViem.gasStation,
95
+ } as const;
96
+
97
+ let maxFeePerGas: bigint;
98
+ let maxPriorityFeePerGas: bigint;
99
+ if (gasPriceFeeds[chainId]) {
100
+ ({ maxFeePerGas, maxPriorityFeePerGas } = await gasPriceFeeds[chainId](viemProvider, chainId));
101
+ } else {
102
+ let gasPrice: bigint | undefined;
103
+ ({ maxFeePerGas, maxPriorityFeePerGas, gasPrice } = await viemProvider.estimateFeesPerGas());
104
+
105
+ maxFeePerGas ??= gasPrice!;
106
+ maxPriorityFeePerGas ??= BigInt(0);
107
+ }
108
+
109
+ return {
110
+ maxFeePerGas: BigNumber.from(maxFeePerGas.toString()),
111
+ maxPriorityFeePerGas: BigNumber.from(maxPriorityFeePerGas.toString()),
112
+ };
113
+ }
@@ -1,11 +1,13 @@
1
- import { providers } from "ethers";
1
+ import { type Chain, type Transport, PublicClient, FeeValuesEIP1559 } from "viem";
2
2
  import { BigNumber } from "../utils";
3
3
 
4
+ export type InternalGasPriceEstimate = FeeValuesEIP1559;
5
+
4
6
  export type GasPriceEstimate = {
5
7
  maxFeePerGas: BigNumber;
6
8
  maxPriorityFeePerGas: BigNumber;
7
9
  };
8
10
 
9
11
  export interface GasPriceFeed {
10
- (provider: providers.Provider, chainId: number): Promise<GasPriceEstimate>;
12
+ (provider: PublicClient<Transport, Chain>, chainId: number): Promise<InternalGasPriceEstimate>;
11
13
  }
@@ -1,6 +1,15 @@
1
- import { providers } from "ethers";
2
- import { BigNumber } from "../utils";
1
+ import assert from "assert";
2
+ import { type Chain, type PublicClient, createPublicClient, http, Transport } from "viem";
3
+ import * as chains from "viem/chains";
3
4
 
4
- export function gasPriceError(method: string, chainId: number, data: providers.FeeData | BigNumber): void {
5
+ export function gasPriceError(method: string, chainId: number, data: unknown): void {
5
6
  throw new Error(`Malformed ${method} response on chain ID ${chainId} (${JSON.stringify(data)})`);
6
7
  }
8
+
9
+ export function getPublicClient(chainId: number, transport?: Transport): PublicClient<Transport, Chain> {
10
+ transport ??= http(); // @todo: Inherit URL from provider.
11
+ const chain: Chain | undefined = Object.values(chains).find((chain) => chain.id === chainId);
12
+ assert(chain);
13
+
14
+ return createPublicClient({ chain, transport });
15
+ }
@@ -14,6 +14,7 @@ import {
14
14
  toBNWei,
15
15
  } from "../../utils";
16
16
  import { Logger, QueryInterface } from "../relayFeeCalculator";
17
+ import { Transport } from "viem";
17
18
 
18
19
  type Provider = providers.Provider;
19
20
  type OptimismProvider = L2Provider<Provider>;
@@ -61,17 +62,25 @@ export class QueryBase implements QueryInterface {
61
62
  * Retrieves the current gas costs of performing a fillRelay contract at the referenced SpokePool.
62
63
  * @param deposit V3 deposit instance.
63
64
  * @param relayerAddress Relayer address to simulate with.
64
- * @param gasPrice Optional gas price to use for the simulation.
65
- * @param gasUnits Optional gas units to use for the simulation.
65
+ * @param options
66
+ * @param options.gasPrice Optional gas price to use for the simulation.
67
+ * @param options.gasUnits Optional gas units to use for the simulation.
68
+ * @param options.omitMarkup Optional flag to omit the gas markup.
69
+ * @param options.transport Optional transport object for custom gas price retrieval.
66
70
  * @returns The gas estimate for this function call (multiplied with the optional buffer).
67
71
  */
68
72
  async getGasCosts(
69
73
  deposit: Deposit,
70
74
  relayer = DEFAULT_SIMULATED_RELAYER_ADDRESS,
71
- gasPrice = this.fixedGasPrice,
72
- gasUnits?: BigNumberish,
73
- omitMarkup?: boolean
75
+ options: Partial<{
76
+ gasPrice: BigNumberish;
77
+ gasUnits: BigNumberish;
78
+ omitMarkup: boolean;
79
+ transport: Transport;
80
+ }> = {}
74
81
  ): Promise<TransactionCostEstimate> {
82
+ const { gasPrice = this.fixedGasPrice, gasUnits, omitMarkup, transport } = options;
83
+
75
84
  const gasMarkup = omitMarkup ? 0 : this.gasMarkup;
76
85
  assert(
77
86
  gasMarkup > -1 && gasMarkup <= 4,
@@ -84,8 +93,11 @@ export class QueryBase implements QueryInterface {
84
93
  tx,
85
94
  relayer,
86
95
  this.provider,
87
- gasPrice,
88
- gasUnits
96
+ {
97
+ gasPrice,
98
+ gasUnits,
99
+ transport,
100
+ }
89
101
  );
90
102
 
91
103
  return {
@@ -17,14 +17,14 @@ import {
17
17
  toBN,
18
18
  toBNWei,
19
19
  } from "../utils";
20
+ import { Transport } from "viem";
20
21
 
21
22
  // This needs to be implemented for every chain and passed into RelayFeeCalculator
22
23
  export interface QueryInterface {
23
24
  getGasCosts: (
24
25
  deposit: Deposit,
25
26
  relayer: string,
26
- gasPrice?: BigNumberish,
27
- gasLimit?: BigNumberish
27
+ options?: Partial<{ gasPrice: BigNumberish; gasUnits: BigNumberish; omitMarkup: boolean; transport: Transport }>
28
28
  ) => Promise<TransactionCostEstimate>;
29
29
  getTokenPrice: (tokenSymbol: string) => Promise<number>;
30
30
  getTokenDecimals: (tokenSymbol: string) => number;
@@ -230,7 +230,8 @@ export class RelayFeeCalculator {
230
230
  _tokenPrice?: number,
231
231
  tokenMapping = TOKEN_SYMBOLS_MAP,
232
232
  gasPrice?: BigNumberish,
233
- gasLimit?: BigNumberish
233
+ gasLimit?: BigNumberish,
234
+ transport?: Transport
234
235
  ): Promise<BigNumber> {
235
236
  if (toBN(amountToRelay).eq(bnZero)) return MAX_BIG_INT;
236
237
 
@@ -245,16 +246,18 @@ export class RelayFeeCalculator {
245
246
  const simulatedAmount = simulateZeroFill ? safeOutputAmount : toBN(amountToRelay);
246
247
  deposit = { ...deposit, outputAmount: simulatedAmount };
247
248
 
248
- const getGasCosts = this.queries.getGasCosts(deposit, relayerAddress, gasPrice, gasLimit).catch((error) => {
249
- this.logger.error({
250
- at: "sdk/gasFeePercent",
251
- message: "Error while fetching gas costs",
252
- error,
253
- simulateZeroFill,
254
- deposit,
249
+ const getGasCosts = this.queries
250
+ .getGasCosts(deposit, relayerAddress, { gasPrice, gasUnits: gasLimit, transport })
251
+ .catch((error) => {
252
+ this.logger.error({
253
+ at: "sdk/gasFeePercent",
254
+ message: "Error while fetching gas costs",
255
+ error,
256
+ simulateZeroFill,
257
+ deposit,
258
+ });
259
+ throw error;
255
260
  });
256
- throw error;
257
- });
258
261
  const [{ tokenGasCost }, tokenPrice] = await Promise.all([
259
262
  getGasCosts,
260
263
  _tokenPrice ??
@@ -8,6 +8,7 @@ import { TypedMessage } from "../interfaces/TypedData";
8
8
  import { BigNumber, BigNumberish, BN, formatUnits, parseUnits, toBN } from "./BigNumberUtils";
9
9
  import { ConvertDecimals } from "./FormattingUtils";
10
10
  import { chainIsOPStack } from "./NetworkUtils";
11
+ import { Transport } from "viem";
11
12
 
12
13
  export type Decimalish = string | number | Decimal;
13
14
  export const AddressZero = ethers.constants.AddressZero;
@@ -239,17 +240,24 @@ export type TransactionCostEstimate = {
239
240
  * @param unsignedTx The unsigned transaction that this function will estimate.
240
241
  * @param senderAddress The address that the transaction will be submitted from.
241
242
  * @param provider A valid ethers provider - will be used to reason the gas price.
242
- * @param gasPrice A manually provided gas price - if set, this function will not resolve the current gas price.
243
- * @param gasUnits A manually provided gas units - if set, this function will not estimate the gas units.
243
+ * @param options
244
+ * @param options.gasPrice A manually provided gas price - if set, this function will not resolve the current gas price.
245
+ * @param options.gasUnits A manually provided gas units - if set, this function will not estimate the gas units.
246
+ * @param options.transport A custom transport object for custom gas price retrieval.
244
247
  * @returns Estimated cost in units of gas and the underlying gas token (gasPrice * estimatedGasUnits).
245
248
  */
246
249
  export async function estimateTotalGasRequiredByUnsignedTransaction(
247
250
  unsignedTx: PopulatedTransaction,
248
251
  senderAddress: string,
249
252
  provider: providers.Provider | L2Provider<providers.Provider>,
250
- gasPrice?: BigNumberish,
251
- gasUnits?: BigNumberish
253
+ options: Partial<{
254
+ gasPrice: BigNumberish;
255
+ gasUnits: BigNumberish;
256
+ transport: Transport;
257
+ }> = {}
252
258
  ): Promise<TransactionCostEstimate> {
259
+ const { gasPrice: _gasPrice, gasUnits, transport } = options || {};
260
+
253
261
  const { chainId } = await provider.getNetwork();
254
262
  const voidSigner = new VoidSigner(senderAddress, provider);
255
263
 
@@ -268,13 +276,14 @@ export async function estimateTotalGasRequiredByUnsignedTransaction(
268
276
  // `provider.estimateTotalGasCost` to improve performance.
269
277
  const [l1GasCost, l2GasPrice] = await Promise.all([
270
278
  provider.estimateL1GasCost(populatedTransaction),
271
- gasPrice || provider.getGasPrice(),
279
+ _gasPrice || provider.getGasPrice(),
272
280
  ]);
273
281
  const l2GasCost = nativeGasCost.mul(l2GasPrice);
274
282
  tokenGasCost = l1GasCost.add(l2GasCost);
275
283
  } else {
284
+ let gasPrice = _gasPrice;
276
285
  if (!gasPrice) {
277
- const gasPriceEstimate = await getGasPriceEstimate(provider, chainId);
286
+ const gasPriceEstimate = await getGasPriceEstimate(provider, chainId, transport);
278
287
  gasPrice = gasPriceEstimate.maxFeePerGas;
279
288
  }
280
289
  tokenGasCost = nativeGasCost.mul(gasPrice);