@across-protocol/sdk 4.3.143 → 4.3.145-alpha.0
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/caching/Arweave/ArweaveClient.d.ts +11 -6
- package/dist/cjs/src/caching/Arweave/ArweaveClient.js +98 -74
- package/dist/cjs/src/caching/Arweave/ArweaveClient.js.map +1 -1
- package/dist/cjs/src/coingecko/Coingecko.js +4 -1
- package/dist/cjs/src/coingecko/Coingecko.js.map +1 -1
- package/dist/cjs/src/utils/common.d.ts +6 -1
- package/dist/cjs/src/utils/common.js +20 -7
- package/dist/cjs/src/utils/common.js.map +1 -1
- package/dist/esm/src/caching/Arweave/ArweaveClient.d.ts +21 -8
- package/dist/esm/src/caching/Arweave/ArweaveClient.js +113 -80
- package/dist/esm/src/caching/Arweave/ArweaveClient.js.map +1 -1
- package/dist/esm/src/coingecko/Coingecko.js +4 -1
- package/dist/esm/src/coingecko/Coingecko.js.map +1 -1
- package/dist/esm/src/utils/common.d.ts +17 -5
- package/dist/esm/src/utils/common.js +23 -11
- package/dist/esm/src/utils/common.js.map +1 -1
- package/dist/types/src/caching/Arweave/ArweaveClient.d.ts +21 -8
- package/dist/types/src/caching/Arweave/ArweaveClient.d.ts.map +1 -1
- package/dist/types/src/coingecko/Coingecko.d.ts.map +1 -1
- package/dist/types/src/utils/common.d.ts +17 -5
- package/dist/types/src/utils/common.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/caching/Arweave/ArweaveClient.ts +126 -76
- package/src/coingecko/Coingecko.ts +4 -1
- package/src/utils/common.ts +38 -11
|
@@ -120,13 +120,25 @@ export declare const getSamplesBetween: (min: number, max: number, size: number)
|
|
|
120
120
|
*/
|
|
121
121
|
export declare function delay(seconds: number): Promise<unknown>;
|
|
122
122
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
|
|
123
|
+
* Configures {@link retry}. Retries always use exponential backoff
|
|
124
|
+
* (`delaySeconds * 2 ** attempt + random()` seconds) to play nicely with upstream
|
|
125
|
+
* rate-limits; callers that want tighter spacing should lower {@link delaySeconds}.
|
|
126
|
+
*/
|
|
127
|
+
export type RetryOptions = {
|
|
128
|
+
/** Maximum number of retry attempts after the initial call (total attempts = retries + 1). Defaults to 2 (3 total tries). */
|
|
129
|
+
retries?: number;
|
|
130
|
+
/** Base delay in seconds for the exponential backoff. Defaults to 1. */
|
|
131
|
+
delaySeconds?: number;
|
|
132
|
+
/** Predicate evaluated against the thrown error to decide whether to retry. Defaults to retrying every error. */
|
|
133
|
+
isRetryable?: (err: unknown) => boolean;
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* Attempt to retry a function call with exponential backoff and a retryability predicate.
|
|
137
|
+
* @param call The function to call.
|
|
138
|
+
* @param options Retry configuration — see {@link RetryOptions}. All fields are optional; omitted fields inherit the SDK defaults.
|
|
127
139
|
* @returns The result of the function call.
|
|
128
140
|
*/
|
|
129
|
-
export declare function retry<T>(call: () => Promise<T>,
|
|
141
|
+
export declare function retry<T>(call: () => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
130
142
|
export type TransactionCostEstimate = {
|
|
131
143
|
nativeGasCost: BigNumber;
|
|
132
144
|
tokenGasCost: BigNumber;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../../../src/utils/common.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,YAAY,CAAC;AACjC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,EAAE,EAAiC,MAAM,kBAAkB,CAAC;AAG9F,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AACnD,eAAO,MAAM,WAAW,+CAA+B,CAAC;AACxD,eAAO,MAAM,WAAW,kBAAqD,CAAC;AAE9E,eAAO,MAAQ,SAAS,+BAAiB,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,CAAC;AAEhB;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,GAAI,KAAK,YAAY,EAAE,WAAW,MAAM,KAAG,EAU9D,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,GAAI,KAAK,YAAY,EAAE,WAAW,MAAM,KAAG,MAA+C,CAAC;AAE/G;;;;;;GAMG;AACH,wBAAgB,GAAG,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,GAAG,EAAE,CAIxD;AACD;;;;;;GAMG;AACH,wBAAgB,GAAG,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,GAAG,EAAE,CAIxD;AAED,eAAO,MAAM,oBAAoB,kBAAe,CAAC;AAEjD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE,YAAY,EACxB,KAAK,GAAE,MAAM,GAAG,MAAU,EAC1B,UAAU,SAAK,EACf,cAAc,SAAK,GAClB,MAAM,CAIR;AAED;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,GAAI,KAAK,YAAY,EAAE,UAAU,YAAY,KAAG,SAEnE,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,YAAY,EACtB,KAAK,GAAE,MAAM,GAAG,MAAU,EAC1B,QAAQ,SAAK,GACZ,MAAM,CAGR;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,GAAG,EAAE,CAE9E;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,8BAA8B,GACzC,aAAa,UAAU,EACvB,WAAW,UAAU,EACrB,gBAAgB,UAAU,EAC1B,gBAAgB,UAAU,KACzB,MAGF,CAAC;AACF;;;;;;;;GAQG;AACH,eAAO,MAAM,4BAA4B,GACvC,aAAa,UAAU,EACvB,WAAW,UAAU,EACrB,gBAAgB,UAAU,EAC1B,gBAAgB,UAAU,KACzB,MAaF,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,OAAO,GAClB,aAAa,UAAU,EACvB,WAAW,UAAU,EACrB,gBAAgB,UAAU,EAC1B,gBAAgB,UAAU,KACzB,MAEF,CAAC;AACF;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAAI,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,MAAM,MAAM,eAYvE,CAAC;AAEF;;;GAGG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,oBAEpC;AAED
|
|
1
|
+
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../../../src/utils/common.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -6,38 +6,105 @@ import winston from "winston";
|
|
|
6
6
|
import { ARWEAVE_TAG_APP_NAME, ARWEAVE_TAG_APP_VERSION, DEFAULT_ARWEAVE_STORAGE_ADDRESS } from "../../constants";
|
|
7
7
|
import { BigNumber, delay, fetchWithTimeout, isDefined, jsonReplacerWithBigNumbers, toBN } from "../../utils";
|
|
8
8
|
|
|
9
|
+
export interface ArweaveGatewayConfig {
|
|
10
|
+
host: string;
|
|
11
|
+
protocol?: string;
|
|
12
|
+
port?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const DEFAULT_ARWEAVE_GATEWAYS: ArweaveGatewayConfig[] = [{ host: "arweave.net" }, { host: "ar-io.net" }];
|
|
16
|
+
|
|
17
|
+
interface Gateway {
|
|
18
|
+
client: Arweave;
|
|
19
|
+
url: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
9
22
|
export class ArweaveClient {
|
|
10
|
-
private
|
|
11
|
-
private gatewayUrl: string;
|
|
23
|
+
private gateways: Gateway[];
|
|
12
24
|
|
|
13
25
|
public constructor(
|
|
14
26
|
private arweaveJWT: JWKInterface,
|
|
15
27
|
private logger: winston.Logger,
|
|
16
|
-
|
|
17
|
-
public protocol = "https",
|
|
18
|
-
port = 443,
|
|
28
|
+
gateways: ArweaveGatewayConfig[] = DEFAULT_ARWEAVE_GATEWAYS,
|
|
19
29
|
private readonly retries = 2,
|
|
20
30
|
private readonly retryDelaySeconds = 1
|
|
21
31
|
) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
port,
|
|
26
|
-
protocol,
|
|
27
|
-
timeout: 20000,
|
|
28
|
-
logging: false,
|
|
29
|
-
});
|
|
30
|
-
this.logger.debug({
|
|
31
|
-
at: "ArweaveClient:constructor",
|
|
32
|
-
message: "Arweave client initialized",
|
|
33
|
-
gateway: this.gatewayUrl,
|
|
34
|
-
});
|
|
32
|
+
if (gateways.length === 0) {
|
|
33
|
+
throw new Error("At least one gateway must be provided");
|
|
34
|
+
}
|
|
35
35
|
if (this.retries < 0) {
|
|
36
36
|
throw new Error(`retries cannot be < 0 and must be an integer. Currently set to ${this.retries}`);
|
|
37
37
|
}
|
|
38
38
|
if (this.retryDelaySeconds < 0) {
|
|
39
39
|
throw new Error(`delay cannot be < 0. Currently set to ${this.retryDelaySeconds}`);
|
|
40
40
|
}
|
|
41
|
+
this.gateways = gateways.map(({ host, protocol = "https", port = 443 }) => ({
|
|
42
|
+
client: new Arweave({ host, port, protocol, timeout: 20000, logging: false }),
|
|
43
|
+
url: `${protocol}://${host}:${port}`,
|
|
44
|
+
}));
|
|
45
|
+
this.logger.debug({
|
|
46
|
+
at: "ArweaveClient:constructor",
|
|
47
|
+
message: "Arweave client initialized",
|
|
48
|
+
gateways: this.gateways.map((g) => g.url),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Races a request across all gateways, returning the first successful response.
|
|
54
|
+
* If all gateways fail, throws an error with details from each gateway.
|
|
55
|
+
*/
|
|
56
|
+
private async _raceGateways<T>(label: string, fn: (gw: Gateway) => Promise<T>): Promise<T> {
|
|
57
|
+
try {
|
|
58
|
+
return await Promise.any(this.gateways.map((gw) => this._retryRequest(() => fn(gw), 0)));
|
|
59
|
+
} catch (e) {
|
|
60
|
+
if (e instanceof AggregateError) {
|
|
61
|
+
const details = this.gateways.map((gw, i) => `${gw.url}: ${e.errors[i]}`).join("; ");
|
|
62
|
+
throw new Error(`All Arweave gateways failed for ${label}: ${details}`);
|
|
63
|
+
}
|
|
64
|
+
throw e;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Tries gateways sequentially, returning the first successful response.
|
|
70
|
+
* Used for write operations where we want exactly one successful submission.
|
|
71
|
+
*/
|
|
72
|
+
private async _failoverGateways<T>(label: string, fn: (gw: Gateway) => Promise<T>): Promise<T> {
|
|
73
|
+
const errors: Error[] = [];
|
|
74
|
+
for (const gw of this.gateways) {
|
|
75
|
+
try {
|
|
76
|
+
return await this._retryRequest(() => fn(gw), 0);
|
|
77
|
+
} catch (e) {
|
|
78
|
+
errors.push(e as Error);
|
|
79
|
+
this.logger.debug({
|
|
80
|
+
at: "ArweaveClient:failoverGateways",
|
|
81
|
+
message: `Gateway ${gw.url} failed for ${label}, trying next: ${e}`,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const details = this.gateways.map((gw, i) => `${gw.url}: ${errors[i]}`).join("; ");
|
|
86
|
+
throw new Error(`All Arweave gateways failed for ${label}: ${details}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private async _retryRequest<T>(request: () => Promise<T>, retryCount: number): Promise<T> {
|
|
90
|
+
try {
|
|
91
|
+
return await request();
|
|
92
|
+
} catch (e) {
|
|
93
|
+
if (retryCount < this.retries) {
|
|
94
|
+
// Implement a slightly aggressive exponential backoff to account for fierce parallelism.
|
|
95
|
+
const baseDelay = this.retryDelaySeconds * Math.pow(2, retryCount);
|
|
96
|
+
const delayS = baseDelay + baseDelay * Math.random();
|
|
97
|
+
this.logger.debug({
|
|
98
|
+
at: "ArweaveClient:retryRequest",
|
|
99
|
+
message: `Arweave request failed, retrying after waiting ${delayS} seconds: ${e}`,
|
|
100
|
+
retryCount,
|
|
101
|
+
});
|
|
102
|
+
await delay(delayS);
|
|
103
|
+
return this._retryRequest(request, retryCount + 1);
|
|
104
|
+
} else {
|
|
105
|
+
throw e;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
41
108
|
}
|
|
42
109
|
|
|
43
110
|
/**
|
|
@@ -47,10 +114,12 @@ export class ArweaveClient {
|
|
|
47
114
|
* @param value The value to store
|
|
48
115
|
* @param topicTag An optional topic tag to add to the transaction
|
|
49
116
|
* @returns The transaction ID of the stored value
|
|
50
|
-
* @
|
|
51
117
|
*/
|
|
52
118
|
async set(value: Record<string, unknown>, topicTag?: string | undefined): Promise<string | undefined> {
|
|
53
|
-
|
|
119
|
+
// Get a template client to use for creating a transaction. Since clients are equal up to the gateway URL,
|
|
120
|
+
// it does not matter which gateway is used, so we just choose the first one.
|
|
121
|
+
const templateClient = this.gateways[0].client;
|
|
122
|
+
const transaction = await templateClient.createTransaction(
|
|
54
123
|
{ data: JSON.stringify(value, jsonReplacerWithBigNumbers) },
|
|
55
124
|
this.arweaveJWT
|
|
56
125
|
);
|
|
@@ -64,29 +133,32 @@ export class ArweaveClient {
|
|
|
64
133
|
}
|
|
65
134
|
|
|
66
135
|
// Sign the transaction
|
|
67
|
-
await
|
|
136
|
+
await templateClient.transactions.sign(transaction, this.arweaveJWT);
|
|
68
137
|
// Send the transaction
|
|
69
|
-
const result = await this.client.transactions.post(transaction);
|
|
70
138
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
message
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
139
|
+
return await this._failoverGateways("set", async ({ client }) => {
|
|
140
|
+
const result = await client.transactions.post(transaction);
|
|
141
|
+
|
|
142
|
+
// Ensure that the result is successful
|
|
143
|
+
if (result.status !== 200) {
|
|
144
|
+
const message = result?.data?.error?.msg ?? "Unknown error";
|
|
145
|
+
this.logger.error({
|
|
146
|
+
at: "ArweaveClient:set",
|
|
147
|
+
message,
|
|
148
|
+
result,
|
|
149
|
+
txn: transaction.id,
|
|
150
|
+
address: await this.getAddress(),
|
|
151
|
+
balance: (await this.getBalance()).toString(),
|
|
152
|
+
});
|
|
153
|
+
throw new Error(message);
|
|
154
|
+
}
|
|
155
|
+
|
|
84
156
|
this.logger.debug({
|
|
85
157
|
at: "ArweaveClient:set",
|
|
86
158
|
message: `Arweave transaction posted with ${transaction.id}`,
|
|
87
159
|
});
|
|
88
|
-
|
|
89
|
-
|
|
160
|
+
return transaction.id;
|
|
161
|
+
});
|
|
90
162
|
}
|
|
91
163
|
|
|
92
164
|
/**
|
|
@@ -97,15 +169,12 @@ export class ArweaveClient {
|
|
|
97
169
|
* @returns The record if it exists, otherwise null
|
|
98
170
|
*/
|
|
99
171
|
async get<T>(transactionID: string, validator: Struct<T>): Promise<T | null> {
|
|
100
|
-
//
|
|
101
|
-
const transactionUrl = `${this.gatewayUrl}/${transactionID}`;
|
|
102
|
-
// We should query in via Axios directly to the gateway URL. The reasoning behind this is
|
|
172
|
+
// We query via fetchWithTimeout directly to the gateway URL. The reasoning behind this is
|
|
103
173
|
// that the Arweave SDK's `getData` method is too slow and does not provide a way to set a timeout.
|
|
104
174
|
// Therefore, something that could take milliseconds to complete could take tens of minutes.
|
|
105
|
-
const
|
|
106
|
-
return await fetchWithTimeout(
|
|
107
|
-
};
|
|
108
|
-
const data = await this._retryRequest(request, 0);
|
|
175
|
+
const data = await this._raceGateways("get", async ({ url }) => {
|
|
176
|
+
return await fetchWithTimeout(`${url}/${transactionID}`, {}, {}, 20_000);
|
|
177
|
+
});
|
|
109
178
|
try {
|
|
110
179
|
// We should validate the data and perform any logical coercion here.
|
|
111
180
|
return create(data, validator);
|
|
@@ -149,15 +218,15 @@ export class ArweaveClient {
|
|
|
149
218
|
) { edges { node { id } } }
|
|
150
219
|
}`;
|
|
151
220
|
|
|
152
|
-
const response = await this.
|
|
153
|
-
const response = await
|
|
221
|
+
const response = await this._raceGateways("getByTopic", async ({ client }) => {
|
|
222
|
+
const response = await client.api.post<{
|
|
154
223
|
data: { transactions: { edges: { node: { id: string } }[] } };
|
|
155
224
|
}>("/graphql", { query });
|
|
156
225
|
if (!response.ok) {
|
|
157
226
|
throw new Error(`Arweave GraphQL request failed with status ${response.status}`);
|
|
158
227
|
}
|
|
159
228
|
return response;
|
|
160
|
-
}
|
|
229
|
+
});
|
|
161
230
|
|
|
162
231
|
const entries = response?.data?.data?.transactions?.edges ?? [];
|
|
163
232
|
this.logger.debug({
|
|
@@ -198,7 +267,9 @@ export class ArweaveClient {
|
|
|
198
267
|
* @returns The metadata of the transaction if it exists, otherwise null
|
|
199
268
|
*/
|
|
200
269
|
async getMetadata(transactionID: string): Promise<Record<string, string> | null> {
|
|
201
|
-
const transaction = await this.client
|
|
270
|
+
const transaction = await this._raceGateways("getMetadata", async ({ client }) => {
|
|
271
|
+
return await client.transactions.get(transactionID);
|
|
272
|
+
});
|
|
202
273
|
if (!isDefined(transaction)) {
|
|
203
274
|
return null;
|
|
204
275
|
}
|
|
@@ -216,32 +287,12 @@ export class ArweaveClient {
|
|
|
216
287
|
}
|
|
217
288
|
|
|
218
289
|
/**
|
|
219
|
-
* Returns the address of the signer of the JWT
|
|
290
|
+
* Returns the address of the signer of the JWT. This is a local crypto
|
|
291
|
+
* operation and does not require a network call.
|
|
220
292
|
* @returns The address of the signer in this client
|
|
221
293
|
*/
|
|
222
294
|
getAddress(): Promise<string> {
|
|
223
|
-
return this.client.wallets.jwkToAddress(this.arweaveJWT);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
private async _retryRequest<T>(request: () => Promise<T>, retryCount: number): Promise<T> {
|
|
227
|
-
try {
|
|
228
|
-
return await request();
|
|
229
|
-
} catch (e) {
|
|
230
|
-
if (retryCount < this.retries) {
|
|
231
|
-
// Implement a slightly aggressive exponential backoff to account for fierce parallelism.
|
|
232
|
-
const baseDelay = this.retryDelaySeconds * Math.pow(2, retryCount); // ms; attempt = [0, 1, 2, ...]
|
|
233
|
-
const delayS = baseDelay + baseDelay * Math.random();
|
|
234
|
-
this.logger.debug({
|
|
235
|
-
at: "ArweaveClient:retryRequest",
|
|
236
|
-
message: `Arweave request failed, retrying after waiting ${delayS} seconds: ${e}`,
|
|
237
|
-
retryCount,
|
|
238
|
-
});
|
|
239
|
-
await delay(delayS);
|
|
240
|
-
return this._retryRequest(request, retryCount + 1);
|
|
241
|
-
} else {
|
|
242
|
-
throw e;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
295
|
+
return this.gateways[0].client.wallets.jwkToAddress(this.arweaveJWT);
|
|
245
296
|
}
|
|
246
297
|
|
|
247
298
|
/**
|
|
@@ -250,9 +301,9 @@ export class ArweaveClient {
|
|
|
250
301
|
*/
|
|
251
302
|
async getBalance(): Promise<BigNumber> {
|
|
252
303
|
const address = await this.getAddress();
|
|
253
|
-
|
|
254
|
-
const balanceInFloat = await
|
|
255
|
-
// @dev The reason we add in the BN.from
|
|
304
|
+
return this._raceGateways("getBalance", async ({ client }) => {
|
|
305
|
+
const balanceInFloat = await client.wallets.getBalance(address);
|
|
306
|
+
// @dev The reason we add in the BN.from here is because the client.getBalance call
|
|
256
307
|
// does not correctly throw an error if the request fails, instead it will return the error string as the
|
|
257
308
|
// balanceInFloat.
|
|
258
309
|
// Sometimes the balance is returned in scientific notation, so we need to
|
|
@@ -264,7 +315,6 @@ export class ArweaveClient {
|
|
|
264
315
|
} else {
|
|
265
316
|
return BigNumber.from(balanceInFloat);
|
|
266
317
|
}
|
|
267
|
-
};
|
|
268
|
-
return await this._retryRequest(request, 0);
|
|
318
|
+
});
|
|
269
319
|
}
|
|
270
320
|
}
|
|
@@ -401,7 +401,10 @@ export class Coingecko {
|
|
|
401
401
|
};
|
|
402
402
|
|
|
403
403
|
// Note: If a pro API key is configured, there is no need to retry as the Pro API will act as the basic's fall back.
|
|
404
|
-
return retry(sendRequest,
|
|
404
|
+
return retry(sendRequest, {
|
|
405
|
+
retries: this.apiKey === undefined ? this.numRetries : 0,
|
|
406
|
+
delaySeconds: this.retryDelay,
|
|
407
|
+
});
|
|
405
408
|
}
|
|
406
409
|
|
|
407
410
|
protected getPriceCache(currency: string, platform_id: string): { [addr: string]: CoinGeckoPrice } {
|
package/src/utils/common.ts
CHANGED
|
@@ -221,20 +221,47 @@ export function delay(seconds: number) {
|
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
/**
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
|
|
224
|
+
* Configures {@link retry}. Retries always use exponential backoff
|
|
225
|
+
* (`delaySeconds * 2 ** attempt + random()` seconds) to play nicely with upstream
|
|
226
|
+
* rate-limits; callers that want tighter spacing should lower {@link delaySeconds}.
|
|
227
|
+
*/
|
|
228
|
+
export type RetryOptions = {
|
|
229
|
+
/** Maximum number of retry attempts after the initial call (total attempts = retries + 1). Defaults to 2 (3 total tries). */
|
|
230
|
+
retries?: number;
|
|
231
|
+
/** Base delay in seconds for the exponential backoff. Defaults to 1. */
|
|
232
|
+
delaySeconds?: number;
|
|
233
|
+
/** Predicate evaluated against the thrown error to decide whether to retry. Defaults to retrying every error. */
|
|
234
|
+
isRetryable?: (err: unknown) => boolean;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const DEFAULT_RETRY_OPTIONS: Required<RetryOptions> = {
|
|
238
|
+
retries: 2,
|
|
239
|
+
delaySeconds: 1,
|
|
240
|
+
isRetryable: () => true,
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Attempt to retry a function call with exponential backoff and a retryability predicate.
|
|
245
|
+
* @param call The function to call.
|
|
246
|
+
* @param options Retry configuration — see {@link RetryOptions}. All fields are optional; omitted fields inherit the SDK defaults.
|
|
228
247
|
* @returns The result of the function call.
|
|
229
248
|
*/
|
|
230
|
-
export function retry<T>(call: () => Promise<T>,
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
249
|
+
export function retry<T>(call: () => Promise<T>, options: RetryOptions = {}): Promise<T> {
|
|
250
|
+
const resolved: Required<RetryOptions> = { ...DEFAULT_RETRY_OPTIONS, ...options };
|
|
251
|
+
const backoffSeconds = (attempt: number): number => resolved.delaySeconds * 2 ** attempt + Math.random();
|
|
252
|
+
|
|
253
|
+
const attempt = async (nAttempts: number): Promise<T> => {
|
|
254
|
+
try {
|
|
235
255
|
return await call();
|
|
236
|
-
})
|
|
237
|
-
|
|
256
|
+
} catch (err) {
|
|
257
|
+
if (nAttempts >= resolved.retries || !resolved.isRetryable(err)) {
|
|
258
|
+
throw err;
|
|
259
|
+
}
|
|
260
|
+
await delay(backoffSeconds(nAttempts));
|
|
261
|
+
return attempt(nAttempts + 1);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
return attempt(0);
|
|
238
265
|
}
|
|
239
266
|
|
|
240
267
|
export type TransactionCostEstimate = {
|