@haven-fi/solauto-sdk 1.0.832 → 1.0.834
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/services/transactions/manager/transactionsManager.d.ts +1 -0
- package/dist/services/transactions/manager/transactionsManager.d.ts.map +1 -1
- package/dist/services/transactions/manager/transactionsManager.js +41 -23
- package/dist/utils/priceUtils.d.ts.map +1 -1
- package/dist/utils/priceUtils.js +33 -19
- package/package.json +1 -1
- package/src/services/transactions/manager/transactionsManager.ts +64 -37
- package/src/utils/priceUtils.ts +44 -25
- package/tests/unit/priceUtils.ts +64 -0
- package/tests/unit/transactionsManager.ts +69 -0
|
@@ -60,6 +60,7 @@ export declare class TransactionsManager<T extends TxHandler> {
|
|
|
60
60
|
private shouldProceedToSend;
|
|
61
61
|
private refreshItemSets;
|
|
62
62
|
private processTransactionsAtomically;
|
|
63
|
+
private processTransactionSetsSequentially;
|
|
63
64
|
private sendJitoBundle;
|
|
64
65
|
private processTransactionSet;
|
|
65
66
|
protected sendTransaction(tx: TransactionBuilder, name: string, attemptNum: number, priorityFeeSetting?: PriorityFeeSetting, txRunType?: TransactionRunType): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transactionsManager.d.ts","sourceRoot":"","sources":["../../../../src/services/transactions/manager/transactionsManager.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EACL,kBAAkB,EAElB,kBAAkB,EACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAIL,aAAa,EAKd,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,OAAO,EAEL,YAAY,EACZ,eAAe,EAEhB,MAAM,UAAU,CAAC;AAGlB,qBAAa,wBAAyB,SAAQ,KAAK;gBACrC,OAAO,EAAE,MAAM;CAK5B;AAED,oBAAY,iBAAiB;IAC3B,OAAO,YAAY;IACnB,UAAU,eAAe;IACzB,MAAM,WAAW;IACjB,UAAU,eAAe;IACzB,MAAM,WAAW;CAClB;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,0BAA0B,GAAG,wBAAwB,EAAE,CAAC;AAEpE,UAAU,WAAW;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,uBAAuB,CAAC,CAAC,SAAS,SAAS;IAC1D,SAAS,EAAE,CAAC,CAAC;IACb,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAChE,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,eAAe,CAAC,EAAE,eAAe,CAAC;CACnC;AAED,qBAAa,mBAAmB,CAAC,CAAC,SAAS,SAAS;IAClD,SAAS,CAAC,SAAS,EAAE,CAAC,CAAC;IACvB,SAAS,CAAC,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAC1E,SAAS,CAAC,SAAS,CAAC,EAAE,kBAAkB,CAAC;IACzC,SAAS,CAAC,kBAAkB,EAAE,kBAAkB,CAAC;IACjD,SAAS,CAAC,UAAU,EAAE,OAAO,CAAC;IAC9B,SAAS,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACxC,SAAS,CAAC,QAAQ,EAAE,0BAA0B,CAAM;IACpD,SAAS,CAAC,YAAY,EAAE,YAAY,CAAC;IACrC,SAAS,CAAC,eAAe,EAAE,MAAM,CAAC;IAClC,SAAS,CAAC,YAAY,EAAE,MAAM,CAAC;IAC/B,SAAS,CAAC,UAAU,EAAE,MAAM,CAAC;IAC7B,SAAS,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;gBAEhC,IAAI,EAAE,uBAAuB,CAAC,CAAC,CAAC;YAoB9B,uBAAuB;IAwDrC,OAAO,CAAC,YAAY;YAiCN,aAAa;IAwB3B,SAAS,CAAC,4BAA4B,CACpC,SAAS,EAAE,KAAK,GAAG,SAAS,EAC5B,UAAU,EAAE,MAAM;IAcpB,OAAO,CAAC,mBAAmB;IAkBd,IAAI,CACf,KAAK,EAAE,eAAe,EAAE,GACvB,OAAO,CAAC,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"transactionsManager.d.ts","sourceRoot":"","sources":["../../../../src/services/transactions/manager/transactionsManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -161,11 +161,7 @@ class TransactionsManager {
|
|
|
161
161
|
await this.processTransactionsAtomically(itemSets);
|
|
162
162
|
}
|
|
163
163
|
else {
|
|
164
|
-
|
|
165
|
-
while (currentIndex < itemSets.length) {
|
|
166
|
-
await this.processTransactionSet(itemSets, currentIndex);
|
|
167
|
-
currentIndex++;
|
|
168
|
-
}
|
|
164
|
+
await this.processTransactionSetsSequentially(itemSets);
|
|
169
165
|
}
|
|
170
166
|
return this.statuses;
|
|
171
167
|
}
|
|
@@ -215,27 +211,49 @@ class TransactionsManager {
|
|
|
215
211
|
return newItemSets;
|
|
216
212
|
}
|
|
217
213
|
async processTransactionsAtomically(itemSets) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
if (
|
|
228
|
-
|
|
214
|
+
let bundleSendAttempted = false;
|
|
215
|
+
try {
|
|
216
|
+
await (0, utils_1.retryWithExponentialBackoff)(async (attemptNum, prevError) => {
|
|
217
|
+
if (prevError &&
|
|
218
|
+
this.statuses.filter((x) => x.simulationSuccessful).length >
|
|
219
|
+
this.signableRetries) {
|
|
220
|
+
throw prevError;
|
|
221
|
+
}
|
|
222
|
+
this.priorityFeeSetting = this.getUpdatedPriorityFeeSetting(prevError, attemptNum);
|
|
223
|
+
if (attemptNum > 0) {
|
|
224
|
+
const refreshedSets = await this.refreshItemSets(itemSets, attemptNum, prevError);
|
|
225
|
+
if (!refreshedSets || !refreshedSets.length) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
itemSets = refreshedSets;
|
|
230
|
+
}
|
|
229
231
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
+
if (!this.shouldProceedToSend(itemSets, attemptNum)) {
|
|
233
|
+
return;
|
|
232
234
|
}
|
|
235
|
+
bundleSendAttempted = true;
|
|
236
|
+
await this.sendJitoBundle(itemSets, attemptNum);
|
|
237
|
+
}, this.totalRetries, this.retryDelay, this.errorsToThrow);
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
const explicitlyNonRetryable = this.errorsToThrow?.some((errorType) => error instanceof errorType);
|
|
241
|
+
if (!bundleSendAttempted ||
|
|
242
|
+
itemSets.length <= 1 ||
|
|
243
|
+
explicitlyNonRetryable ||
|
|
244
|
+
this.abortController?.signal.aborted) {
|
|
245
|
+
throw error;
|
|
233
246
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
247
|
+
(0, utils_1.consoleLog)("Jito bundle failed after all retries; retrying one transaction at a time");
|
|
248
|
+
await this.processTransactionSetsSequentially(itemSets);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
async processTransactionSetsSequentially(itemSets) {
|
|
252
|
+
let currentIndex = 0;
|
|
253
|
+
while (currentIndex < itemSets.length) {
|
|
254
|
+
await this.processTransactionSet(itemSets, currentIndex);
|
|
255
|
+
currentIndex++;
|
|
256
|
+
}
|
|
239
257
|
}
|
|
240
258
|
async sendJitoBundle(itemSets, attemptNum) {
|
|
241
259
|
let transactions = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"priceUtils.d.ts","sourceRoot":"","sources":["../../src/utils/priceUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,SAAS,IAAI,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAiBrE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAErD,UAAU,WAAW;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,SAAS,EAAE,EAClB,SAAS,GAAE,SAA8B,EACzC,SAAS,CAAC,EAAE,SAAS,GACpB,OAAO,CAAC,MAAM,EAAE,CAAC,CA8CnB;AAED,wBAAsB,aAAa,CACjC,KAAK,EAAE,SAAS,EAAE,GACjB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"priceUtils.d.ts","sourceRoot":"","sources":["../../src/utils/priceUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,SAAS,IAAI,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAiBrE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAErD,UAAU,WAAW;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,SAAS,EAAE,EAClB,SAAS,GAAE,SAA8B,EACzC,SAAS,CAAC,EAAE,SAAS,GACpB,OAAO,CAAC,MAAM,EAAE,CAAC,CA8CnB;AAED,wBAAsB,aAAa,CACjC,KAAK,EAAE,SAAS,EAAE,GACjB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CA0EtC;AAkBD,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,SAAS,EAAE,GACjB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAsDtC;AAED,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,SAAS,EAAE,GACjB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAsBtC;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,SAAS,GAAG,YAAY,GAAG,MAAM,GAAG,SAAS,EACnD,SAAS,GAAE,SAA8B,EACzC,SAAS,CAAC,EAAE,SAAS,GACpB,MAAM,GAAG,SAAS,CAyBpB"}
|
package/dist/utils/priceUtils.js
CHANGED
|
@@ -80,9 +80,16 @@ async function getPythPrices(mints) {
|
|
|
80
80
|
return {};
|
|
81
81
|
}
|
|
82
82
|
const priceFeedIds = mints.map((mint) => constants_1.PYTH_PRICE_FEED_IDS[mint.toString()]);
|
|
83
|
-
const
|
|
83
|
+
const pythApiKey = process.env.PYTH_API_KEY;
|
|
84
|
+
// Hermes requires authentication as of August 26, 2026. Jupiter keeps price
|
|
85
|
+
// fetching available for browser clients and services without a Pyth API key.
|
|
86
|
+
if (!pythApiKey) {
|
|
87
|
+
(0, generalUtils_1.consoleLog)("PYTH_API_KEY is not configured; using Jupiter prices");
|
|
88
|
+
return await getJupTokenPrices(mints);
|
|
89
|
+
}
|
|
90
|
+
const getReq = async () => await fetch(`https://pyth.dourolabs.app/hermes/v2/updates/price/latest?${priceFeedIds
|
|
84
91
|
.map((x) => `ids%5B%5D=${x}`)
|
|
85
|
-
.join("&")}`);
|
|
92
|
+
.join("&")}`, { headers: { Authorization: `Bearer ${pythApiKey}` } });
|
|
86
93
|
const deriveValue = (price, exponent) => {
|
|
87
94
|
if (exponent > 0) {
|
|
88
95
|
return Number((0, numberUtils_1.toBaseUnit)(Number(price), exponent));
|
|
@@ -94,23 +101,30 @@ async function getPythPrices(mints) {
|
|
|
94
101
|
return Number(price);
|
|
95
102
|
}
|
|
96
103
|
};
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
104
|
+
let prices;
|
|
105
|
+
try {
|
|
106
|
+
prices = await (0, generalUtils_1.retryWithExponentialBackoff)(async () => {
|
|
107
|
+
const resp = await getReq();
|
|
108
|
+
if (!resp.ok) {
|
|
109
|
+
const responseBody = await resp.text();
|
|
110
|
+
throw new Error(`Pyth Hermes request failed (${resp.status} ${resp.statusText}): ${responseBody}`);
|
|
111
|
+
}
|
|
112
|
+
const json = await resp.json();
|
|
113
|
+
const parsedPrices = json.parsed.map((x) => {
|
|
114
|
+
return {
|
|
115
|
+
realtimePrice: deriveValue(x.price.price, x.price.expo),
|
|
116
|
+
confInterval: deriveValue(x.price.conf, x.price.expo) * 2.12,
|
|
117
|
+
emaPrice: deriveValue(x.ema_price.price, x.ema_price.expo),
|
|
118
|
+
emaConfInterval: deriveValue(x.ema_price.conf, x.ema_price.expo) * 2.12,
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
return parsedPrices;
|
|
122
|
+
}, 5, 250);
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
(0, generalUtils_1.consoleLog)(`Failed to fetch Pyth prices; using Jupiter prices: ${error?.message ?? error}`);
|
|
126
|
+
return await getJupTokenPrices(mints);
|
|
127
|
+
}
|
|
114
128
|
return (0, generalUtils_1.createRecord)(mints.map((x) => x.toString()), prices);
|
|
115
129
|
}
|
|
116
130
|
function getSortedPriceData(prices, mints) {
|
package/package.json
CHANGED
|
@@ -279,11 +279,7 @@ export class TransactionsManager<T extends TxHandler> {
|
|
|
279
279
|
if (this.atomically) {
|
|
280
280
|
await this.processTransactionsAtomically(itemSets);
|
|
281
281
|
} else {
|
|
282
|
-
|
|
283
|
-
while (currentIndex < itemSets.length) {
|
|
284
|
-
await this.processTransactionSet(itemSets, currentIndex);
|
|
285
|
-
currentIndex++;
|
|
286
|
-
}
|
|
282
|
+
await this.processTransactionSetsSequentially(itemSets);
|
|
287
283
|
}
|
|
288
284
|
|
|
289
285
|
return this.statuses;
|
|
@@ -366,44 +362,75 @@ export class TransactionsManager<T extends TxHandler> {
|
|
|
366
362
|
}
|
|
367
363
|
|
|
368
364
|
private async processTransactionsAtomically(itemSets: TransactionSet[]) {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
prevError,
|
|
381
|
-
attemptNum
|
|
382
|
-
);
|
|
365
|
+
let bundleSendAttempted = false;
|
|
366
|
+
try {
|
|
367
|
+
await retryWithExponentialBackoff(
|
|
368
|
+
async (attemptNum, prevError) => {
|
|
369
|
+
if (
|
|
370
|
+
prevError &&
|
|
371
|
+
this.statuses.filter((x) => x.simulationSuccessful).length >
|
|
372
|
+
this.signableRetries
|
|
373
|
+
) {
|
|
374
|
+
throw prevError;
|
|
375
|
+
}
|
|
383
376
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
attemptNum,
|
|
388
|
-
prevError
|
|
377
|
+
this.priorityFeeSetting = this.getUpdatedPriorityFeeSetting(
|
|
378
|
+
prevError,
|
|
379
|
+
attemptNum
|
|
389
380
|
);
|
|
390
|
-
|
|
381
|
+
|
|
382
|
+
if (attemptNum > 0) {
|
|
383
|
+
const refreshedSets = await this.refreshItemSets(
|
|
384
|
+
itemSets,
|
|
385
|
+
attemptNum,
|
|
386
|
+
prevError
|
|
387
|
+
);
|
|
388
|
+
if (!refreshedSets || !refreshedSets.length) {
|
|
389
|
+
return;
|
|
390
|
+
} else {
|
|
391
|
+
itemSets = refreshedSets;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (!this.shouldProceedToSend(itemSets, attemptNum)) {
|
|
391
396
|
return;
|
|
392
|
-
} else {
|
|
393
|
-
itemSets = refreshedSets;
|
|
394
397
|
}
|
|
395
|
-
}
|
|
396
398
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}
|
|
399
|
+
bundleSendAttempted = true;
|
|
400
|
+
await this.sendJitoBundle(itemSets, attemptNum);
|
|
401
|
+
},
|
|
402
|
+
this.totalRetries,
|
|
403
|
+
this.retryDelay,
|
|
404
|
+
this.errorsToThrow
|
|
405
|
+
);
|
|
406
|
+
} catch (error: any) {
|
|
407
|
+
const explicitlyNonRetryable = this.errorsToThrow?.some(
|
|
408
|
+
(errorType) => error instanceof errorType
|
|
409
|
+
);
|
|
410
|
+
if (
|
|
411
|
+
!bundleSendAttempted ||
|
|
412
|
+
itemSets.length <= 1 ||
|
|
413
|
+
explicitlyNonRetryable ||
|
|
414
|
+
this.abortController?.signal.aborted
|
|
415
|
+
) {
|
|
416
|
+
throw error;
|
|
417
|
+
}
|
|
400
418
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
this.
|
|
405
|
-
|
|
406
|
-
|
|
419
|
+
consoleLog(
|
|
420
|
+
"Jito bundle failed after all retries; retrying one transaction at a time"
|
|
421
|
+
);
|
|
422
|
+
await this.processTransactionSetsSequentially(itemSets);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
private async processTransactionSetsSequentially(
|
|
427
|
+
itemSets: TransactionSet[]
|
|
428
|
+
) {
|
|
429
|
+
let currentIndex = 0;
|
|
430
|
+
while (currentIndex < itemSets.length) {
|
|
431
|
+
await this.processTransactionSet(itemSets, currentIndex);
|
|
432
|
+
currentIndex++;
|
|
433
|
+
}
|
|
407
434
|
}
|
|
408
435
|
|
|
409
436
|
private async sendJitoBundle(itemSets: TransactionSet[], attemptNum: number) {
|
package/src/utils/priceUtils.ts
CHANGED
|
@@ -88,12 +88,21 @@ export async function getPythPrices(
|
|
|
88
88
|
const priceFeedIds = mints.map(
|
|
89
89
|
(mint) => PYTH_PRICE_FEED_IDS[mint.toString()]
|
|
90
90
|
);
|
|
91
|
+
const pythApiKey = process.env.PYTH_API_KEY;
|
|
92
|
+
|
|
93
|
+
// Hermes requires authentication as of August 26, 2026. Jupiter keeps price
|
|
94
|
+
// fetching available for browser clients and services without a Pyth API key.
|
|
95
|
+
if (!pythApiKey) {
|
|
96
|
+
consoleLog("PYTH_API_KEY is not configured; using Jupiter prices");
|
|
97
|
+
return await getJupTokenPrices(mints);
|
|
98
|
+
}
|
|
91
99
|
|
|
92
100
|
const getReq = async () =>
|
|
93
101
|
await fetch(
|
|
94
|
-
`https://
|
|
102
|
+
`https://pyth.dourolabs.app/hermes/v2/updates/price/latest?${priceFeedIds
|
|
95
103
|
.map((x) => `ids%5B%5D=${x}`)
|
|
96
|
-
.join("&")}
|
|
104
|
+
.join("&")}`,
|
|
105
|
+
{ headers: { Authorization: `Bearer ${pythApiKey}` } }
|
|
97
106
|
);
|
|
98
107
|
|
|
99
108
|
const deriveValue = (price: number, exponent: number) => {
|
|
@@ -106,30 +115,40 @@ export async function getPythPrices(
|
|
|
106
115
|
}
|
|
107
116
|
};
|
|
108
117
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
118
|
+
let prices: PriceResult[];
|
|
119
|
+
try {
|
|
120
|
+
prices = await retryWithExponentialBackoff(
|
|
121
|
+
async () => {
|
|
122
|
+
const resp = await getReq();
|
|
123
|
+
if (!resp.ok) {
|
|
124
|
+
const responseBody = await resp.text();
|
|
125
|
+
throw new Error(
|
|
126
|
+
`Pyth Hermes request failed (${resp.status} ${resp.statusText}): ${responseBody}`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
116
129
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
const json = await resp.json();
|
|
131
|
+
const parsedPrices = json.parsed.map((x: any) => {
|
|
132
|
+
return {
|
|
133
|
+
realtimePrice: deriveValue(x.price.price, x.price.expo),
|
|
134
|
+
confInterval: deriveValue(x.price.conf, x.price.expo) * 2.12,
|
|
135
|
+
emaPrice: deriveValue(x.ema_price.price, x.ema_price.expo),
|
|
136
|
+
emaConfInterval:
|
|
137
|
+
deriveValue(x.ema_price.conf, x.ema_price.expo) * 2.12,
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return parsedPrices;
|
|
142
|
+
},
|
|
143
|
+
5,
|
|
144
|
+
250
|
|
145
|
+
);
|
|
146
|
+
} catch (error: any) {
|
|
147
|
+
consoleLog(
|
|
148
|
+
`Failed to fetch Pyth prices; using Jupiter prices: ${error?.message ?? error}`
|
|
149
|
+
);
|
|
150
|
+
return await getJupTokenPrices(mints);
|
|
151
|
+
}
|
|
133
152
|
|
|
134
153
|
return createRecord(
|
|
135
154
|
mints.map((x) => x.toString()),
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { describe, it } from "mocha";
|
|
3
|
+
import { PublicKey } from "@solana/web3.js";
|
|
4
|
+
import { getPythPrices, USDC } from "../../src";
|
|
5
|
+
|
|
6
|
+
describe("Pyth price fetching", () => {
|
|
7
|
+
const originalFetch = global.fetch;
|
|
8
|
+
const originalPythApiKey = process.env.PYTH_API_KEY;
|
|
9
|
+
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
global.fetch = originalFetch;
|
|
12
|
+
if (originalPythApiKey === undefined) {
|
|
13
|
+
delete process.env.PYTH_API_KEY;
|
|
14
|
+
} else {
|
|
15
|
+
process.env.PYTH_API_KEY = originalPythApiKey;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("uses Jupiter when a Pyth API key is not configured", async () => {
|
|
20
|
+
delete process.env.PYTH_API_KEY;
|
|
21
|
+
let requestUrl = "";
|
|
22
|
+
global.fetch = (async (input: string | URL | Request) => {
|
|
23
|
+
requestUrl = input.toString();
|
|
24
|
+
return {
|
|
25
|
+
json: async () => ({ [USDC]: { usdPrice: 1 } }),
|
|
26
|
+
} as Response;
|
|
27
|
+
}) as typeof fetch;
|
|
28
|
+
|
|
29
|
+
const prices = await getPythPrices([new PublicKey(USDC)]);
|
|
30
|
+
|
|
31
|
+
expect(requestUrl).to.contain("lite-api.jup.ag/price/v3");
|
|
32
|
+
expect(prices[USDC].realtimePrice).to.equal(1);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("uses authenticated upgraded Hermes when a Pyth API key is configured", async () => {
|
|
36
|
+
process.env.PYTH_API_KEY = "test-api-key";
|
|
37
|
+
let requestUrl = "";
|
|
38
|
+
let authorization = "";
|
|
39
|
+
global.fetch = (async (
|
|
40
|
+
input: string | URL | Request,
|
|
41
|
+
init?: RequestInit
|
|
42
|
+
) => {
|
|
43
|
+
requestUrl = input.toString();
|
|
44
|
+
authorization = (init?.headers as Record<string, string>).Authorization;
|
|
45
|
+
return {
|
|
46
|
+
ok: true,
|
|
47
|
+
json: async () => ({
|
|
48
|
+
parsed: [
|
|
49
|
+
{
|
|
50
|
+
price: { price: "100000000", conf: "1000", expo: -8 },
|
|
51
|
+
ema_price: { price: "100000000", conf: "1000", expo: -8 },
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
}),
|
|
55
|
+
} as Response;
|
|
56
|
+
}) as typeof fetch;
|
|
57
|
+
|
|
58
|
+
const prices = await getPythPrices([new PublicKey(USDC)]);
|
|
59
|
+
|
|
60
|
+
expect(requestUrl).to.contain("pyth.dourolabs.app/hermes");
|
|
61
|
+
expect(authorization).to.equal("Bearer test-api-key");
|
|
62
|
+
expect(prices[USDC].realtimePrice).to.equal(1);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { describe, it } from "mocha";
|
|
3
|
+
import { TransactionsManager } from "../../src";
|
|
4
|
+
|
|
5
|
+
function createManager(abortController?: AbortController) {
|
|
6
|
+
return new TransactionsManager({
|
|
7
|
+
txHandler: {
|
|
8
|
+
defaultLookupTables: () => [],
|
|
9
|
+
umi: {},
|
|
10
|
+
} as any,
|
|
11
|
+
retryConfig: { totalRetries: 1, retryDelay: 0 },
|
|
12
|
+
abortController,
|
|
13
|
+
}) as any;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe("TransactionsManager bundle fallback", () => {
|
|
17
|
+
it("retries a failed multi-transaction bundle sequentially", async () => {
|
|
18
|
+
const manager = createManager();
|
|
19
|
+
const calls: string[] = [];
|
|
20
|
+
const itemSets = [{}, {}, {}] as any[];
|
|
21
|
+
|
|
22
|
+
manager.shouldProceedToSend = () => true;
|
|
23
|
+
manager.sendJitoBundle = async () => {
|
|
24
|
+
calls.push("bundle");
|
|
25
|
+
throw new Error("bundle failed");
|
|
26
|
+
};
|
|
27
|
+
manager.processTransactionSet = async (
|
|
28
|
+
receivedSets: any[],
|
|
29
|
+
currentIndex: number
|
|
30
|
+
) => {
|
|
31
|
+
expect(receivedSets).to.equal(itemSets);
|
|
32
|
+
calls.push(`single:${currentIndex}`);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
await manager.processTransactionsAtomically(itemSets);
|
|
36
|
+
|
|
37
|
+
expect(calls).to.deep.equal([
|
|
38
|
+
"bundle",
|
|
39
|
+
"single:0",
|
|
40
|
+
"single:1",
|
|
41
|
+
"single:2",
|
|
42
|
+
]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("does not fall back after the request is aborted", async () => {
|
|
46
|
+
const abortController = new AbortController();
|
|
47
|
+
const manager = createManager(abortController);
|
|
48
|
+
let sequentialAttempts = 0;
|
|
49
|
+
|
|
50
|
+
manager.shouldProceedToSend = () => true;
|
|
51
|
+
manager.sendJitoBundle = async () => {
|
|
52
|
+
abortController.abort();
|
|
53
|
+
throw new Error("bundle aborted");
|
|
54
|
+
};
|
|
55
|
+
manager.processTransactionSet = async () => {
|
|
56
|
+
sequentialAttempts++;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
let thrownError: Error | undefined;
|
|
60
|
+
try {
|
|
61
|
+
await manager.processTransactionsAtomically([{}, {}]);
|
|
62
|
+
} catch (error: any) {
|
|
63
|
+
thrownError = error;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
expect(thrownError?.message).to.equal("bundle aborted");
|
|
67
|
+
expect(sequentialAttempts).to.equal(0);
|
|
68
|
+
});
|
|
69
|
+
});
|