@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.
- package/dist/cjs/clients/BundleDataClient/utils/SuperstructUtils.d.ts +30 -30
- package/dist/cjs/coingecko/Coingecko.d.ts +9 -1
- package/dist/cjs/coingecko/Coingecko.js +94 -46
- package/dist/cjs/coingecko/Coingecko.js.map +1 -1
- package/dist/cjs/gasPriceOracle/adapters/arbitrum-viem.d.ts +3 -0
- package/dist/cjs/gasPriceOracle/adapters/arbitrum-viem.js +21 -0
- package/dist/cjs/gasPriceOracle/adapters/arbitrum-viem.js.map +1 -0
- package/dist/cjs/gasPriceOracle/adapters/ethereum-viem.d.ts +4 -0
- package/dist/cjs/gasPriceOracle/adapters/ethereum-viem.js +26 -0
- package/dist/cjs/gasPriceOracle/adapters/ethereum-viem.js.map +1 -0
- package/dist/cjs/gasPriceOracle/adapters/polygon-viem.d.ts +3 -0
- package/dist/cjs/gasPriceOracle/adapters/polygon-viem.js +84 -0
- package/dist/cjs/gasPriceOracle/adapters/polygon-viem.js.map +1 -0
- package/dist/cjs/gasPriceOracle/oracle.d.ts +3 -1
- package/dist/cjs/gasPriceOracle/oracle.js +71 -4
- package/dist/cjs/gasPriceOracle/oracle.js.map +1 -1
- package/dist/cjs/gasPriceOracle/types.d.ts +3 -2
- package/dist/cjs/gasPriceOracle/util.d.ts +3 -3
- package/dist/cjs/gasPriceOracle/util.js +12 -1
- package/dist/cjs/gasPriceOracle/util.js.map +1 -1
- package/dist/cjs/providers/types.d.ts +3 -3
- package/dist/cjs/relayFeeCalculator/chain-queries/baseQuery.d.ts +7 -1
- package/dist/cjs/relayFeeCalculator/chain-queries/baseQuery.js +13 -8
- package/dist/cjs/relayFeeCalculator/chain-queries/baseQuery.js.map +1 -1
- package/dist/cjs/relayFeeCalculator/relayFeeCalculator.d.ts +8 -2
- package/dist/cjs/relayFeeCalculator/relayFeeCalculator.js +4 -2
- package/dist/cjs/relayFeeCalculator/relayFeeCalculator.js.map +1 -1
- package/dist/cjs/utils/common.d.ts +6 -1
- package/dist/cjs/utils/common.js +21 -17
- package/dist/cjs/utils/common.js.map +1 -1
- package/dist/esm/clients/BundleDataClient/utils/SuperstructUtils.d.ts +30 -30
- package/dist/esm/coingecko/Coingecko.d.ts +9 -1
- package/dist/esm/coingecko/Coingecko.js +96 -46
- package/dist/esm/coingecko/Coingecko.js.map +1 -1
- package/dist/esm/gasPriceOracle/adapters/arbitrum-viem.d.ts +3 -0
- package/dist/esm/gasPriceOracle/adapters/arbitrum-viem.js +20 -0
- package/dist/esm/gasPriceOracle/adapters/arbitrum-viem.js.map +1 -0
- package/dist/esm/gasPriceOracle/adapters/ethereum-viem.d.ts +4 -0
- package/dist/esm/gasPriceOracle/adapters/ethereum-viem.js +21 -0
- package/dist/esm/gasPriceOracle/adapters/ethereum-viem.js.map +1 -0
- package/dist/esm/gasPriceOracle/adapters/polygon-viem.d.ts +3 -0
- package/dist/esm/gasPriceOracle/adapters/polygon-viem.js +82 -0
- package/dist/esm/gasPriceOracle/adapters/polygon-viem.js.map +1 -0
- package/dist/esm/gasPriceOracle/oracle.d.ts +9 -1
- package/dist/esm/gasPriceOracle/oracle.js +82 -3
- package/dist/esm/gasPriceOracle/oracle.js.map +1 -1
- package/dist/esm/gasPriceOracle/types.d.ts +3 -2
- package/dist/esm/gasPriceOracle/util.d.ts +3 -3
- package/dist/esm/gasPriceOracle/util.js +9 -0
- package/dist/esm/gasPriceOracle/util.js.map +1 -1
- package/dist/esm/providers/types.d.ts +3 -3
- package/dist/esm/relayFeeCalculator/chain-queries/baseQuery.d.ts +12 -3
- package/dist/esm/relayFeeCalculator/chain-queries/baseQuery.js +18 -10
- package/dist/esm/relayFeeCalculator/chain-queries/baseQuery.js.map +1 -1
- package/dist/esm/relayFeeCalculator/relayFeeCalculator.d.ts +8 -2
- package/dist/esm/relayFeeCalculator/relayFeeCalculator.js +4 -2
- package/dist/esm/relayFeeCalculator/relayFeeCalculator.js.map +1 -1
- package/dist/esm/utils/common.d.ts +10 -3
- package/dist/esm/utils/common.js +25 -19
- package/dist/esm/utils/common.js.map +1 -1
- package/dist/types/clients/BundleDataClient/utils/SuperstructUtils.d.ts +30 -30
- package/dist/types/coingecko/Coingecko.d.ts +9 -1
- package/dist/types/coingecko/Coingecko.d.ts.map +1 -1
- package/dist/types/gasPriceOracle/adapters/arbitrum-viem.d.ts +4 -0
- package/dist/types/gasPriceOracle/adapters/arbitrum-viem.d.ts.map +1 -0
- package/dist/types/gasPriceOracle/adapters/ethereum-viem.d.ts +5 -0
- package/dist/types/gasPriceOracle/adapters/ethereum-viem.d.ts.map +1 -0
- package/dist/types/gasPriceOracle/adapters/polygon-viem.d.ts +4 -0
- package/dist/types/gasPriceOracle/adapters/polygon-viem.d.ts.map +1 -0
- package/dist/types/gasPriceOracle/oracle.d.ts +9 -1
- package/dist/types/gasPriceOracle/oracle.d.ts.map +1 -1
- package/dist/types/gasPriceOracle/types.d.ts +3 -2
- package/dist/types/gasPriceOracle/types.d.ts.map +1 -1
- package/dist/types/gasPriceOracle/util.d.ts +3 -3
- package/dist/types/gasPriceOracle/util.d.ts.map +1 -1
- package/dist/types/providers/types.d.ts +3 -3
- package/dist/types/relayFeeCalculator/chain-queries/baseQuery.d.ts +12 -3
- package/dist/types/relayFeeCalculator/chain-queries/baseQuery.d.ts.map +1 -1
- package/dist/types/relayFeeCalculator/relayFeeCalculator.d.ts +8 -2
- package/dist/types/relayFeeCalculator/relayFeeCalculator.d.ts.map +1 -1
- package/dist/types/utils/common.d.ts +10 -3
- package/dist/types/utils/common.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/coingecko/Coingecko.ts +94 -47
- package/src/gasPriceOracle/adapters/arbitrum-viem.ts +13 -0
- package/src/gasPriceOracle/adapters/ethereum-viem.ts +19 -0
- package/src/gasPriceOracle/adapters/polygon-viem.ts +86 -0
- package/src/gasPriceOracle/oracle.ts +69 -5
- package/src/gasPriceOracle/types.ts +4 -2
- package/src/gasPriceOracle/util.ts +12 -3
- package/src/relayFeeCalculator/chain-queries/baseQuery.ts +19 -7
- package/src/relayFeeCalculator/relayFeeCalculator.ts +15 -12
- 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
|
-
|
|
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
|
-
|
|
128
|
-
|
|
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
|
-
|
|
131
|
-
|
|
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#
|
|
134
|
-
message:
|
|
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#
|
|
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
|
|
217
|
-
|
|
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
|
|
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
|
|
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 {
|
|
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:
|
|
12
|
+
(provider: PublicClient<Transport, Chain>, chainId: number): Promise<InternalGasPriceEstimate>;
|
|
11
13
|
}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
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:
|
|
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
|
|
65
|
-
* @param
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
88
|
-
|
|
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
|
-
|
|
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
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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 ??
|
package/src/utils/common.ts
CHANGED
|
@@ -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
|
|
243
|
-
* @param
|
|
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
|
-
|
|
251
|
-
|
|
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
|
-
|
|
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);
|