@scallop-io/sui-scallop-sdk 2.3.1 → 2.3.4
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/index.d.mts +15 -15
- package/dist/index.d.ts +15 -15
- package/dist/index.js +5 -5
- package/dist/index.mjs +2 -2
- package/package.json +1 -1
- package/src/builders/oracles/pyth.ts +11 -3
- package/src/constants/queryKeys.ts +11 -1
- package/src/models/scallopQuery.ts +1 -1
- package/src/models/scallopSuiKit.ts +1 -56
- package/src/models/scallopUtils.ts +49 -33
- package/src/types/constant/queryKeys.ts +1 -1
package/package.json
CHANGED
|
@@ -95,8 +95,12 @@ export const updatePythPriceFeeds = async (
|
|
|
95
95
|
'0xa8b8dcc9880166edb57b53e05f8df7364d31b5d9b7d107fd27f0b69cf338b687',
|
|
96
96
|
}
|
|
97
97
|
);
|
|
98
|
-
const priceIds =
|
|
99
|
-
|
|
98
|
+
const priceIds = Array.from(
|
|
99
|
+
new Set(
|
|
100
|
+
assetCoinNames.map((assetCoinName) =>
|
|
101
|
+
builder.address.get(`core.coins.${assetCoinName}.oracle.pyth.feed`)
|
|
102
|
+
)
|
|
103
|
+
)
|
|
100
104
|
);
|
|
101
105
|
|
|
102
106
|
const endpoints = builder.utils.pythEndpoints ?? [
|
|
@@ -106,7 +110,11 @@ export const updatePythPriceFeeds = async (
|
|
|
106
110
|
// iterate through the endpoints
|
|
107
111
|
for (const endpoint of endpoints) {
|
|
108
112
|
try {
|
|
109
|
-
const pythConnection = new SuiPriceServiceConnection(endpoint
|
|
113
|
+
const pythConnection = new SuiPriceServiceConnection(endpoint, {
|
|
114
|
+
priceFeedRequestConfig: {
|
|
115
|
+
binary: true,
|
|
116
|
+
},
|
|
117
|
+
});
|
|
110
118
|
const priceUpdateData =
|
|
111
119
|
await pythConnection.getPriceFeedsUpdateData(priceIds);
|
|
112
120
|
|
|
@@ -74,6 +74,16 @@ export const queryKeys = {
|
|
|
74
74
|
},
|
|
75
75
|
},
|
|
76
76
|
oracle: {
|
|
77
|
-
getPythLatestPriceFeeds: () => [
|
|
77
|
+
getPythLatestPriceFeeds: (priceIds: string[], endpoint: string) => [
|
|
78
|
+
'oracle',
|
|
79
|
+
'getPythPriceIds',
|
|
80
|
+
priceIds,
|
|
81
|
+
endpoint,
|
|
82
|
+
],
|
|
83
|
+
getCoinPrices: (priceIds: string[]) => [
|
|
84
|
+
'oracle',
|
|
85
|
+
'getCoinPrices',
|
|
86
|
+
priceIds,
|
|
87
|
+
],
|
|
78
88
|
},
|
|
79
89
|
};
|
|
@@ -60,7 +60,7 @@ import {
|
|
|
60
60
|
queryObligation,
|
|
61
61
|
queryVeScaKeyIdFromReferralBindings,
|
|
62
62
|
} from 'src/queries';
|
|
63
|
-
import { SuiObjectRef, SuiObjectData } from '@mysten/sui/
|
|
63
|
+
import { SuiObjectRef, SuiObjectData } from '@mysten/sui/client';
|
|
64
64
|
import { SuiObjectArg } from '@scallop-io/sui-kit';
|
|
65
65
|
import { ScallopQueryInterface } from './interface';
|
|
66
66
|
|
|
@@ -26,6 +26,7 @@ import { QueryKey } from '@tanstack/query-core';
|
|
|
26
26
|
import ScallopQueryClient, {
|
|
27
27
|
ScallopQueryClientParams,
|
|
28
28
|
} from './scallopQueryClient';
|
|
29
|
+
import { RateLimiter } from './rateLimiter';
|
|
29
30
|
|
|
30
31
|
type QueryInspectTxnParams = {
|
|
31
32
|
queryTarget: string;
|
|
@@ -65,62 +66,6 @@ const deepMergeObject = <T>(curr: T, update: T): T => {
|
|
|
65
66
|
return result;
|
|
66
67
|
};
|
|
67
68
|
|
|
68
|
-
export class RateLimiter {
|
|
69
|
-
private tokens: number;
|
|
70
|
-
private lastRefillTime: number;
|
|
71
|
-
private readonly refillRate: number; // tokens per millisecond
|
|
72
|
-
|
|
73
|
-
constructor(private readonly capacity: number = 10) {
|
|
74
|
-
this.refillRate = this.capacity / 1000; // 10 tokens per second = 0.01 tokens/ms
|
|
75
|
-
this.tokens = this.capacity;
|
|
76
|
-
this.lastRefillTime = Date.now();
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
private refill() {
|
|
80
|
-
const now = Date.now();
|
|
81
|
-
const elapsed = now - this.lastRefillTime;
|
|
82
|
-
const newTokens = elapsed * this.refillRate;
|
|
83
|
-
|
|
84
|
-
this.tokens = Math.min(this.capacity, this.tokens + newTokens);
|
|
85
|
-
this.lastRefillTime = now;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
private getTimeToNextToken(): number {
|
|
89
|
-
this.refill();
|
|
90
|
-
|
|
91
|
-
if (this.tokens >= 1) {
|
|
92
|
-
return 0;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Calculate exact milliseconds needed for 1 full token
|
|
96
|
-
const deficit = 1 - this.tokens;
|
|
97
|
-
return Math.ceil(deficit / this.refillRate);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async acquireToken(): Promise<void> {
|
|
101
|
-
// eslint-disable-next-line no-constant-condition
|
|
102
|
-
while (true) {
|
|
103
|
-
const waitTime = this.getTimeToNextToken();
|
|
104
|
-
|
|
105
|
-
if (waitTime === 0) {
|
|
106
|
-
if (this.tokens >= 1) {
|
|
107
|
-
this.tokens -= 1;
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
114
|
-
this.refill();
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async execute<T>(fn: () => Promise<T>): Promise<T> {
|
|
119
|
-
await this.acquireToken();
|
|
120
|
-
return await fn();
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
69
|
class ScallopSuiKit extends ScallopQueryClient {
|
|
125
70
|
public readonly suiKit: SuiKit;
|
|
126
71
|
private _walletAddress: string;
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
} from 'src/constants';
|
|
17
17
|
import { PriceFeed, SuiPriceServiceConnection } from '@pythnetwork/pyth-sui-js';
|
|
18
18
|
import ScallopSuiKit, { ScallopSuiKitParams } from './scallopSuiKit';
|
|
19
|
-
import { SuiObjectData } from '@mysten/sui/
|
|
19
|
+
import { SuiObjectData } from '@mysten/sui/client';
|
|
20
20
|
import { queryObligation } from 'src/queries';
|
|
21
21
|
import { ScallopUtilsInterface } from './interface';
|
|
22
22
|
|
|
@@ -521,58 +521,74 @@ class ScallopUtils implements ScallopUtilsInterface {
|
|
|
521
521
|
]),
|
|
522
522
|
] as string[]
|
|
523
523
|
) {
|
|
524
|
-
|
|
524
|
+
const priceIdsMap = new Map(
|
|
525
|
+
coinNames.map((coinName) => [
|
|
526
|
+
coinName,
|
|
527
|
+
this.address.get(`core.coins.${coinName}.oracle.pyth.feed`),
|
|
528
|
+
])
|
|
529
|
+
);
|
|
525
530
|
|
|
526
|
-
const
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
const priceIdPairs = Array.from(failedRequests.values()).reduce(
|
|
531
|
-
(acc, coinName) => {
|
|
532
|
-
const priceId =
|
|
533
|
-
this.address.get(`core.coins.${coinName}.oracle.pyth.feed`) ??
|
|
534
|
-
this.constants.poolAddresses[coinName]?.pythFeed;
|
|
535
|
-
acc.push([coinName, priceId]);
|
|
536
|
-
return acc;
|
|
537
|
-
},
|
|
538
|
-
[] as [string, string][]
|
|
539
|
-
);
|
|
540
|
-
if (priceIdPairs.length === 0) break;
|
|
531
|
+
const priceIds = Array.from(priceIdsMap.values());
|
|
532
|
+
const state = this.queryClient.getQueryState(
|
|
533
|
+
queryKeys.oracle.getCoinPrices(priceIds)
|
|
534
|
+
);
|
|
541
535
|
|
|
542
|
-
|
|
536
|
+
if (state && state && Date.now() - (state.dataUpdatedAt ?? 0) < 30_000) {
|
|
537
|
+
return state.data as CoinPrices;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
let coinPrices: CoinPrices = {};
|
|
541
|
+
for (const endpoint of this.pythEndpoints) {
|
|
543
542
|
const pythConnection = new SuiPriceServiceConnection(endpoint, {
|
|
544
543
|
timeout: this.timeout,
|
|
544
|
+
httpRetries: 0,
|
|
545
545
|
});
|
|
546
546
|
|
|
547
547
|
try {
|
|
548
548
|
const feeds = await this.queryClient.fetchQuery({
|
|
549
|
-
queryKey: queryKeys.oracle.getPythLatestPriceFeeds(
|
|
549
|
+
queryKey: queryKeys.oracle.getPythLatestPriceFeeds(
|
|
550
|
+
priceIds,
|
|
551
|
+
endpoint
|
|
552
|
+
),
|
|
550
553
|
queryFn: async () => {
|
|
551
554
|
return await pythConnection.getLatestPriceFeeds(priceIds);
|
|
552
555
|
},
|
|
553
|
-
|
|
554
|
-
|
|
556
|
+
retry: false,
|
|
557
|
+
staleTime: 30_000,
|
|
558
|
+
gcTime: 30_000,
|
|
555
559
|
});
|
|
556
|
-
if (feeds)
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
560
|
+
if (!feeds) throw new Error('No feeds returned from pyth');
|
|
561
|
+
|
|
562
|
+
if (feeds.length !== priceIds.length)
|
|
563
|
+
throw new Error('Incomplete feeds returned from pyth');
|
|
564
|
+
|
|
565
|
+
feeds.forEach((feed, idx) => {
|
|
566
|
+
const coinName = coinNames[idx] as string;
|
|
567
|
+
const data = this.parseDataFromPythPriceFeed(feed);
|
|
568
|
+
coinPrices[coinName as string] = data.price;
|
|
569
|
+
});
|
|
570
|
+
this.queryClient.setQueryData(
|
|
571
|
+
queryKeys.oracle.getCoinPrices(priceIds),
|
|
572
|
+
coinPrices
|
|
573
|
+
);
|
|
564
574
|
} catch (e: any) {
|
|
575
|
+
if ('status' in e && e.status === 403) {
|
|
576
|
+
console.log(`trying next pyth endpoint`);
|
|
577
|
+
continue; // try next endpoint
|
|
578
|
+
}
|
|
565
579
|
console.error(e.message);
|
|
566
580
|
}
|
|
567
|
-
if (failedRequests.size === 0) break;
|
|
568
581
|
}
|
|
569
582
|
|
|
570
|
-
if (
|
|
583
|
+
if (Object.keys(coinPrices).length === 0) {
|
|
571
584
|
coinPrices = {
|
|
572
585
|
...coinPrices,
|
|
573
|
-
...(await this.getPythPrices(Array.from(
|
|
586
|
+
...(await this.getPythPrices(Array.from(coinNames))),
|
|
574
587
|
};
|
|
575
|
-
|
|
588
|
+
this.queryClient.setQueryData(
|
|
589
|
+
queryKeys.oracle.getCoinPrices(priceIds),
|
|
590
|
+
coinPrices
|
|
591
|
+
);
|
|
576
592
|
}
|
|
577
593
|
|
|
578
594
|
return coinPrices;
|