@across-protocol/sdk 4.3.152 → 4.3.154
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/src/clients/SpokePoolClient/SpokePoolClient.js +1 -4
- package/dist/cjs/src/clients/SpokePoolClient/SpokePoolClient.js.map +1 -1
- package/dist/cjs/src/coingecko/Coingecko.js +22 -15
- package/dist/cjs/src/coingecko/Coingecko.js.map +1 -1
- package/dist/cjs/src/coingecko/CoingeckoErrors.d.ts +12 -0
- package/dist/cjs/src/coingecko/CoingeckoErrors.js +17 -0
- package/dist/cjs/src/coingecko/CoingeckoErrors.js.map +1 -0
- package/dist/cjs/src/coingecko/index.d.ts +1 -0
- package/dist/cjs/src/coingecko/index.js +1 -0
- package/dist/cjs/src/coingecko/index.js.map +1 -1
- package/dist/cjs/src/gasPriceOracle/adapters/solana.js +17 -7
- package/dist/cjs/src/gasPriceOracle/adapters/solana.js.map +1 -1
- package/dist/cjs/src/gasPriceOracle/errors.d.ts +5 -0
- package/dist/cjs/src/gasPriceOracle/errors.js +11 -0
- package/dist/cjs/src/gasPriceOracle/errors.js.map +1 -0
- package/dist/cjs/src/gasPriceOracle/index.d.ts +1 -0
- package/dist/cjs/src/gasPriceOracle/index.js +3 -1
- package/dist/cjs/src/gasPriceOracle/index.js.map +1 -1
- package/dist/cjs/src/utils/DepositUtils.d.ts +4 -2
- package/dist/cjs/src/utils/DepositUtils.js +4 -1
- package/dist/cjs/src/utils/DepositUtils.js.map +1 -1
- package/dist/esm/src/clients/SpokePoolClient/SpokePoolClient.js +1 -4
- package/dist/esm/src/clients/SpokePoolClient/SpokePoolClient.js.map +1 -1
- package/dist/esm/src/coingecko/Coingecko.js +22 -15
- package/dist/esm/src/coingecko/Coingecko.js.map +1 -1
- package/dist/esm/src/coingecko/CoingeckoErrors.d.ts +18 -0
- package/dist/esm/src/coingecko/CoingeckoErrors.js +13 -0
- package/dist/esm/src/coingecko/CoingeckoErrors.js.map +1 -0
- package/dist/esm/src/coingecko/index.d.ts +1 -0
- package/dist/esm/src/coingecko/index.js +1 -0
- package/dist/esm/src/coingecko/index.js.map +1 -1
- package/dist/esm/src/gasPriceOracle/adapters/solana.js +25 -9
- package/dist/esm/src/gasPriceOracle/adapters/solana.js.map +1 -1
- package/dist/esm/src/gasPriceOracle/errors.d.ts +13 -0
- package/dist/esm/src/gasPriceOracle/errors.js +15 -0
- package/dist/esm/src/gasPriceOracle/errors.js.map +1 -0
- package/dist/esm/src/gasPriceOracle/index.d.ts +1 -0
- package/dist/esm/src/gasPriceOracle/index.js +1 -0
- package/dist/esm/src/gasPriceOracle/index.js.map +1 -1
- package/dist/esm/src/utils/DepositUtils.d.ts +6 -3
- package/dist/esm/src/utils/DepositUtils.js +6 -2
- package/dist/esm/src/utils/DepositUtils.js.map +1 -1
- package/dist/types/src/clients/SpokePoolClient/SpokePoolClient.d.ts.map +1 -1
- package/dist/types/src/coingecko/Coingecko.d.ts.map +1 -1
- package/dist/types/src/coingecko/CoingeckoErrors.d.ts +19 -0
- package/dist/types/src/coingecko/CoingeckoErrors.d.ts.map +1 -0
- package/dist/types/src/coingecko/index.d.ts +1 -0
- package/dist/types/src/coingecko/index.d.ts.map +1 -1
- package/dist/types/src/gasPriceOracle/adapters/solana.d.ts.map +1 -1
- package/dist/types/src/gasPriceOracle/errors.d.ts +14 -0
- package/dist/types/src/gasPriceOracle/errors.d.ts.map +1 -0
- package/dist/types/src/gasPriceOracle/index.d.ts +1 -0
- package/dist/types/src/gasPriceOracle/index.d.ts.map +1 -1
- package/dist/types/src/utils/DepositUtils.d.ts +6 -3
- package/dist/types/src/utils/DepositUtils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/clients/SpokePoolClient/SpokePoolClient.ts +1 -4
- package/src/coingecko/Coingecko.ts +30 -17
- package/src/coingecko/CoingeckoErrors.ts +24 -0
- package/src/coingecko/index.ts +1 -0
- package/src/gasPriceOracle/adapters/solana.ts +31 -11
- package/src/gasPriceOracle/errors.ts +14 -0
- package/src/gasPriceOracle/index.ts +1 -0
- package/src/utils/DepositUtils.ts +10 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import assert from "assert";
|
|
2
2
|
import { fetchWithTimeout, getCoingeckoTokenIdByAddress, retry } from "../utils";
|
|
3
3
|
import { Logger } from "../relayFeeCalculator";
|
|
4
|
+
import { CoingeckoPriceNotFoundError } from "./CoingeckoErrors";
|
|
4
5
|
|
|
5
6
|
export function msToS(ms: number) {
|
|
6
7
|
return Math.floor(ms / 1000);
|
|
@@ -206,7 +207,9 @@ export class Coingecko {
|
|
|
206
207
|
const _from = msToS(from);
|
|
207
208
|
const _to = msToS(to);
|
|
208
209
|
const result = await this.call<HistoricPriceChartData>(
|
|
209
|
-
`coins/ethereum/contract/${
|
|
210
|
+
`coins/ethereum/contract/${encodeURIComponent(
|
|
211
|
+
contract.toLowerCase()
|
|
212
|
+
)}/market_chart/range/?vs_currency=${encodeURIComponent(currency)}&from=${_from}&to=${_to}`
|
|
210
213
|
);
|
|
211
214
|
// fyi timestamps are returned in ms in contrast to the current price endpoint
|
|
212
215
|
if (result.prices) return result.prices;
|
|
@@ -230,7 +233,7 @@ export class Coingecko {
|
|
|
230
233
|
const coingeckoTokenIdentifier = await this.getCoingeckoTokenId(contractAddress, chainId);
|
|
231
234
|
assert(date, "Requires date string");
|
|
232
235
|
// Build the path for the Coingecko API request
|
|
233
|
-
const url = `coins/${coingeckoTokenIdentifier}/history`;
|
|
236
|
+
const url = `coins/${encodeURIComponent(coingeckoTokenIdentifier)}/history`;
|
|
234
237
|
// Build the query parameters for the Coingecko API request
|
|
235
238
|
const queryParams = {
|
|
236
239
|
date,
|
|
@@ -244,7 +247,9 @@ export class Coingecko {
|
|
|
244
247
|
}
|
|
245
248
|
|
|
246
249
|
getContractDetails(contract_address: string, platform_id = "ethereum") {
|
|
247
|
-
return this.call(
|
|
250
|
+
return this.call(
|
|
251
|
+
`coins/${encodeURIComponent(platform_id)}/contract/${encodeURIComponent(contract_address.toLowerCase())}`
|
|
252
|
+
);
|
|
248
253
|
}
|
|
249
254
|
|
|
250
255
|
async getCurrentPriceByContract(contractAddress: string, currency = "usd", chainId = 1): Promise<[string, number]> {
|
|
@@ -256,7 +261,9 @@ export class Coingecko {
|
|
|
256
261
|
tokenPrice = priceCache[contractAddress];
|
|
257
262
|
}
|
|
258
263
|
|
|
259
|
-
|
|
264
|
+
if (tokenPrice === undefined) {
|
|
265
|
+
throw new CoingeckoPriceNotFoundError({ identifier: contractAddress, currency, lookupType: "address" });
|
|
266
|
+
}
|
|
260
267
|
return [tokenPrice.timestamp.toString(), tokenPrice.price];
|
|
261
268
|
}
|
|
262
269
|
|
|
@@ -268,22 +275,25 @@ export class Coingecko {
|
|
|
268
275
|
const coingeckoId = await this.getCoingeckoTokenId(contractAddress, chainId);
|
|
269
276
|
// Build the path for the Coingecko API request
|
|
270
277
|
const result = await this.call<Record<string, CGTokenPrice>>(
|
|
271
|
-
`simple/price?ids=${coingeckoId}&vs_currencies=${
|
|
278
|
+
`simple/price?ids=${encodeURIComponent(coingeckoId)}&vs_currencies=${encodeURIComponent(
|
|
279
|
+
currency
|
|
280
|
+
)}&include_last_updated_at=true`
|
|
272
281
|
);
|
|
273
282
|
const cgPrice = result?.[coingeckoId];
|
|
274
283
|
if (cgPrice === undefined || !cgPrice?.[currency]) {
|
|
275
|
-
const errMsg = `No price found for ${coingeckoId}`;
|
|
276
284
|
this.logger.debug({
|
|
277
285
|
at: "Coingecko#getCurrentPriceById",
|
|
278
|
-
message:
|
|
286
|
+
message: `No Coingecko price found for id '${coingeckoId}' in ${currency}`,
|
|
279
287
|
});
|
|
280
|
-
throw new
|
|
288
|
+
throw new CoingeckoPriceNotFoundError({ identifier: coingeckoId, currency, lookupType: "id" });
|
|
281
289
|
} else {
|
|
282
290
|
this.updatePriceCache(cgPrice, contractAddress, currency, platform_id);
|
|
283
291
|
}
|
|
284
292
|
}
|
|
285
293
|
tokenPrice = priceCache[contractAddress];
|
|
286
|
-
|
|
294
|
+
if (tokenPrice === undefined) {
|
|
295
|
+
throw new CoingeckoPriceNotFoundError({ identifier: contractAddress, currency, lookupType: "address" });
|
|
296
|
+
}
|
|
287
297
|
return [tokenPrice.timestamp.toString(), tokenPrice.price];
|
|
288
298
|
}
|
|
289
299
|
|
|
@@ -291,22 +301,25 @@ export class Coingecko {
|
|
|
291
301
|
let tokenPrice = this.getCachedSymbolPrice(symbol, currency);
|
|
292
302
|
if (tokenPrice === undefined) {
|
|
293
303
|
const result = await this.call<Record<string, CGTokenPrice>>(
|
|
294
|
-
`simple/price?symbols=${symbol}&vs_currencies=${
|
|
304
|
+
`simple/price?symbols=${encodeURIComponent(symbol)}&vs_currencies=${encodeURIComponent(
|
|
305
|
+
currency
|
|
306
|
+
)}&include_last_updated_at=true`
|
|
295
307
|
);
|
|
296
308
|
const cgPrice = result?.[symbol.toLowerCase()] || result?.[symbol.toUpperCase()];
|
|
297
309
|
if (cgPrice === undefined || !cgPrice?.[currency]) {
|
|
298
|
-
const errMsg = `Failed to retrieve ${symbol}/${currency} price via Coingecko API`;
|
|
299
310
|
this.logger.debug({
|
|
300
311
|
at: "Coingecko#getCurrentPriceBySymbol",
|
|
301
|
-
message:
|
|
312
|
+
message: `No Coingecko price found for symbol '${symbol}' in ${currency}`,
|
|
302
313
|
});
|
|
303
|
-
throw new
|
|
314
|
+
throw new CoingeckoPriceNotFoundError({ identifier: symbol, currency, lookupType: "symbol" });
|
|
304
315
|
} else {
|
|
305
316
|
this.updatePriceCacheBySymbol(cgPrice, symbol, currency);
|
|
306
317
|
}
|
|
307
318
|
}
|
|
308
319
|
tokenPrice = this.getCachedSymbolPrice(symbol, currency);
|
|
309
|
-
|
|
320
|
+
if (tokenPrice === undefined) {
|
|
321
|
+
throw new CoingeckoPriceNotFoundError({ identifier: symbol, currency, lookupType: "symbol" });
|
|
322
|
+
}
|
|
310
323
|
return [tokenPrice.timestamp.toString(), tokenPrice.price];
|
|
311
324
|
}
|
|
312
325
|
|
|
@@ -344,9 +357,9 @@ export class Coingecko {
|
|
|
344
357
|
try {
|
|
345
358
|
// Coingecko expects a comma-delimited (%2c) list.
|
|
346
359
|
result = await this.call(
|
|
347
|
-
`simple/token_price/${platform_id}?contract_addresses=${contract_addresses
|
|
348
|
-
|
|
349
|
-
|
|
360
|
+
`simple/token_price/${encodeURIComponent(platform_id)}?contract_addresses=${contract_addresses
|
|
361
|
+
.map((addr) => encodeURIComponent(addr))
|
|
362
|
+
.join("%2C")}&vs_currencies=${encodeURIComponent(currency)}&include_last_updated_at=true`
|
|
350
363
|
);
|
|
351
364
|
} catch (err) {
|
|
352
365
|
const errMsg = `Failed to retrieve ${platform_id}/${currency} prices (${err})`;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown when a price lookup against the Coingecko API does not return a
|
|
3
|
+
* usable price for the requested identifier. Callers can use `instanceof` to
|
|
4
|
+
* map this to a 404 / not-found response without needing to string-match
|
|
5
|
+
* the underlying message.
|
|
6
|
+
*/
|
|
7
|
+
export type CoingeckoLookupType = "symbol" | "address" | "id";
|
|
8
|
+
|
|
9
|
+
export class CoingeckoPriceNotFoundError extends Error {
|
|
10
|
+
readonly identifier: string;
|
|
11
|
+
readonly currency: string;
|
|
12
|
+
readonly lookupType: CoingeckoLookupType;
|
|
13
|
+
|
|
14
|
+
constructor(args: { identifier: string; currency: string; lookupType: CoingeckoLookupType; cause?: unknown }) {
|
|
15
|
+
super(
|
|
16
|
+
`No Coingecko price found for ${args.lookupType} '${args.identifier}' in ${args.currency}`,
|
|
17
|
+
args.cause !== undefined ? { cause: args.cause } : undefined
|
|
18
|
+
);
|
|
19
|
+
this.name = "CoingeckoPriceNotFoundError";
|
|
20
|
+
this.identifier = args.identifier;
|
|
21
|
+
this.currency = args.currency;
|
|
22
|
+
this.lookupType = args.lookupType;
|
|
23
|
+
}
|
|
24
|
+
}
|
package/src/coingecko/index.ts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { SVMProvider } from "../../arch/svm";
|
|
2
|
-
import { toBN, dedupArray } from "../../utils";
|
|
2
|
+
import { BN, toBN, dedupArray } from "../../utils";
|
|
3
3
|
import { SvmGasPriceEstimate } from "../types";
|
|
4
4
|
import { GasPriceEstimateOptions } from "../oracle";
|
|
5
|
+
import { SvmGasPriceUnavailableError } from "../errors";
|
|
5
6
|
import {
|
|
6
7
|
TransactionMessage,
|
|
7
8
|
TransactionMessageBytesBase64,
|
|
8
9
|
TransactionMessageWithFeePayer,
|
|
9
10
|
compileTransaction,
|
|
11
|
+
setTransactionMessageLifetimeUsingBlockhash,
|
|
10
12
|
} from "@solana/kit";
|
|
11
13
|
|
|
14
|
+
const MAX_BASE_FEE_ATTEMPTS = 2;
|
|
15
|
+
|
|
12
16
|
/**
|
|
13
17
|
* @notice Returns result of getFeeForMessage and getRecentPrioritizationFees RPC calls.
|
|
14
18
|
* @returns GasPriceEstimate
|
|
@@ -18,13 +22,8 @@ export async function messageFee(provider: SVMProvider, opts: GasPriceEstimateOp
|
|
|
18
22
|
|
|
19
23
|
// Cast the opaque unsignedTx type to a solana-kit TransactionMessage with fee payer.
|
|
20
24
|
const unsignedTx = _unsignedTx as TransactionMessage & TransactionMessageWithFeePayer;
|
|
21
|
-
const compiledTransaction = compileTransaction(unsignedTx);
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
const encodedTransactionMessage = Buffer.from(compiledTransaction.messageBytes).toString(
|
|
25
|
-
"base64"
|
|
26
|
-
) as TransactionMessageBytesBase64;
|
|
27
|
-
const baseFeeResponse = await provider.getFeeForMessage(encodedTransactionMessage).send();
|
|
26
|
+
const baseFee = await getBaseFee(provider, unsignedTx);
|
|
28
27
|
|
|
29
28
|
// Get the priority fee by calling `getRecentPrioritzationFees` on all the addresses in the transaction's instruction array.
|
|
30
29
|
const instructionAddresses = dedupArray(unsignedTx.instructions.map((instruction) => instruction.programAddress));
|
|
@@ -40,8 +39,29 @@ export async function messageFee(provider: SVMProvider, opts: GasPriceEstimateOp
|
|
|
40
39
|
const microLamportsPerComputeUnit = toBN(
|
|
41
40
|
totalPrioritizationFees / BigInt(Math.max(nonzeroPrioritizationFees.length, 1))
|
|
42
41
|
);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
|
|
43
|
+
return { baseFee, microLamportsPerComputeUnit };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// `getFeeForMessage` returns `{ value: null }` when the cluster has not yet recognised
|
|
47
|
+
// the blockhash referenced by the message. With a load-balanced RPC pool, this happens
|
|
48
|
+
// when the request lands on a node that hasn't seen the blockhash from an earlier
|
|
49
|
+
// `getLatestBlockhash` call. We side-step the race by always refreshing to a `confirmed`
|
|
50
|
+
// blockhash before each attempt — by definition propagated to all healthy nodes — and
|
|
51
|
+
// retrying once if a fee call still comes back null. Fee estimation is never sent
|
|
52
|
+
// on-chain, so blockhash freshness doesn't matter.
|
|
53
|
+
async function getBaseFee(provider: SVMProvider, tx: TransactionMessage & TransactionMessageWithFeePayer): Promise<BN> {
|
|
54
|
+
for (let attempt = 0; attempt < MAX_BASE_FEE_ATTEMPTS; attempt++) {
|
|
55
|
+
const { value: confirmedBlockhash } = await provider.getLatestBlockhash({ commitment: "confirmed" }).send();
|
|
56
|
+
const refreshedTx = setTransactionMessageLifetimeUsingBlockhash(confirmedBlockhash, tx);
|
|
57
|
+
const compiled = compileTransaction(refreshedTx);
|
|
58
|
+
const encoded = Buffer.from(compiled.messageBytes).toString("base64") as TransactionMessageBytesBase64;
|
|
59
|
+
const { value } = await provider.getFeeForMessage(encoded).send();
|
|
60
|
+
if (value !== null && value !== undefined) {
|
|
61
|
+
return toBN(value);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
throw new SvmGasPriceUnavailableError(
|
|
65
|
+
"Solana getFeeForMessage returned null even after retrying with a confirmed blockhash"
|
|
66
|
+
);
|
|
47
67
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown when the SVM gas-price oracle cannot determine a base fee for the
|
|
3
|
+
* supplied message — most commonly because Solana's `getFeeForMessage` RPC
|
|
4
|
+
* returned `{ value: null }` even after retrying with a confirmed blockhash.
|
|
5
|
+
*
|
|
6
|
+
* Callers can use `instanceof` to map this to a transient upstream-RPC error
|
|
7
|
+
* (e.g. HTTP 502 / 503) rather than treating it as an unhandled exception.
|
|
8
|
+
*/
|
|
9
|
+
export class SvmGasPriceUnavailableError extends Error {
|
|
10
|
+
constructor(message: string, opts?: { cause?: unknown }) {
|
|
11
|
+
super(message, opts?.cause !== undefined ? { cause: opts.cause } : undefined);
|
|
12
|
+
this.name = "SvmGasPriceUnavailableError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
Fill,
|
|
9
9
|
RelayData,
|
|
10
10
|
SlowFillRequest,
|
|
11
|
+
SpeedUpCommon,
|
|
11
12
|
ConvertedRelayData,
|
|
12
13
|
ConvertedFill,
|
|
13
14
|
} from "../interfaces";
|
|
@@ -244,10 +245,16 @@ export function isFillOrSlowFillRequestMessageEmpty(message: string): boolean {
|
|
|
244
245
|
/**
|
|
245
246
|
* Determines if a deposit was updated via a speed-up transaction.
|
|
246
247
|
* @param deposit Deposit to evaluate.
|
|
247
|
-
* @returns True if the deposit was updated, otherwise false.
|
|
248
|
+
* @returns True if the deposit was updated, otherwise false. Narrows the deposit type so callers
|
|
249
|
+
* can safely access updatedRecipient/updatedOutputAmount/updatedMessage/speedUpSignature.
|
|
248
250
|
*/
|
|
249
|
-
export function isDepositSpedUp(deposit: Deposit):
|
|
250
|
-
return
|
|
251
|
+
export function isDepositSpedUp(deposit: Deposit): deposit is Deposit & SpeedUpCommon & { speedUpSignature: string } {
|
|
252
|
+
return (
|
|
253
|
+
isDefined(deposit.speedUpSignature) &&
|
|
254
|
+
isDefined(deposit.updatedOutputAmount) &&
|
|
255
|
+
isDefined(deposit.updatedRecipient) &&
|
|
256
|
+
isDefined(deposit.updatedMessage)
|
|
257
|
+
);
|
|
251
258
|
}
|
|
252
259
|
|
|
253
260
|
/**
|