@scallop-io/sui-scallop-sdk 2.0.1 → 2.0.2-switchboard-alpha.2
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 +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +30 -28
- package/dist/index.mjs +6 -4
- package/package.json +6 -3
- package/src/builders/oracles/index.ts +17 -1
- package/src/builders/oracles/switchboard.ts +233 -0
- package/src/models/scallopAddress.ts +1 -1
- package/src/models/scallopCache.ts +3 -7
- package/src/models/suiKit.ts +19 -4
- package/src/types/model.ts +3 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scallop-io/sui-scallop-sdk",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2-switchboard-alpha.2",
|
|
4
4
|
"description": "Typescript sdk for interacting with Scallop contract on SUI",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sui",
|
|
@@ -44,8 +44,9 @@
|
|
|
44
44
|
"@noble/hashes": "^1.3.2",
|
|
45
45
|
"@pythnetwork/price-service-client": "^1.8.2",
|
|
46
46
|
"@pythnetwork/pyth-sui-js": "2.1.0",
|
|
47
|
-
"@scallop-io/sui-kit": "1.3.
|
|
47
|
+
"@scallop-io/sui-kit": "1.3.5",
|
|
48
48
|
"@scure/bip39": "^1.2.1",
|
|
49
|
+
"@switchboard-xyz/sui-sdk": "0.0.22-beta.1",
|
|
49
50
|
"@tanstack/query-core": "5.51.15",
|
|
50
51
|
"axios": "^1.6.0",
|
|
51
52
|
"bech32": "^2.0.0",
|
|
@@ -82,7 +83,7 @@
|
|
|
82
83
|
"peerDependencies": {
|
|
83
84
|
"@mysten/bcs": "^1.2.0",
|
|
84
85
|
"@mysten/sui": "1.3.1",
|
|
85
|
-
"@scallop-io/sui-kit": "1.3.
|
|
86
|
+
"@scallop-io/sui-kit": "1.3.5",
|
|
86
87
|
"bn.js": "^5.2.1"
|
|
87
88
|
},
|
|
88
89
|
"lint-staged": {
|
|
@@ -149,6 +150,8 @@
|
|
|
149
150
|
"scripts": {
|
|
150
151
|
"clean": "rm -rf tsconfig.tsbuildinfo ./dist",
|
|
151
152
|
"build": "pnpm run build:tsup",
|
|
153
|
+
"build-sourcemap": "pnpm run build:sourcemap",
|
|
154
|
+
"build:sourcemap": "tsup ./src/index.ts --format esm,cjs --splitting --minify --treeshake --dts --sourcemap",
|
|
152
155
|
"build:tsup": "tsup ./src/index.ts --format esm,cjs --splitting --minify --treeshake --dts",
|
|
153
156
|
"watch:tsup": "tsup ./src/index.ts --format esm,cjs --clean --splitting --watch",
|
|
154
157
|
"watch:types": "tsc --watch",
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
} from 'src/types';
|
|
10
10
|
import { xOracleList as X_ORACLE_LIST } from 'src/constants';
|
|
11
11
|
import { updatePythPriceFeeds } from './pyth';
|
|
12
|
+
import { updateSwitchboardAggregators } from './switchboard';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Update the price of the oracle for multiple coin.
|
|
@@ -62,7 +63,22 @@ export const updateOracles = async (
|
|
|
62
63
|
filterAssetCoinNames(assetCoinName, 'pyth')
|
|
63
64
|
);
|
|
64
65
|
if (pythAssetCoinNames.length > 0)
|
|
65
|
-
await updatePythPriceFeeds(builder,
|
|
66
|
+
await updatePythPriceFeeds(builder, pythAssetCoinNames, txBlock);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (flattenedRules.has('switchboard')) {
|
|
70
|
+
const switchboardAssetCoinNames = assetCoinNames.filter((assetCoinName) =>
|
|
71
|
+
filterAssetCoinNames(assetCoinName, 'switchboard')
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (switchboardAssetCoinNames.length > 0) {
|
|
75
|
+
await updateSwitchboardAggregators(
|
|
76
|
+
builder,
|
|
77
|
+
switchboardAssetCoinNames,
|
|
78
|
+
txBlock,
|
|
79
|
+
builder.params.switchboardSolanaRpc
|
|
80
|
+
);
|
|
81
|
+
}
|
|
66
82
|
}
|
|
67
83
|
|
|
68
84
|
// Remove duplicate coin names.
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { ScallopBuilder } from 'src/models';
|
|
2
|
+
import { type SuiTxBlock as SuiKitTxBlock } from '@scallop-io/sui-kit';
|
|
3
|
+
import {
|
|
4
|
+
Aggregator,
|
|
5
|
+
AggregatorData,
|
|
6
|
+
ObjectParsingHelper,
|
|
7
|
+
// suiQueueCache,
|
|
8
|
+
// QueueData,
|
|
9
|
+
SwitchboardClient,
|
|
10
|
+
} from '@switchboard-xyz/sui-sdk';
|
|
11
|
+
import { queryMultipleObjects } from 'src/queries';
|
|
12
|
+
import { SuiParsedData } from '@mysten/sui/client';
|
|
13
|
+
import { toHex } from '@mysten/bcs';
|
|
14
|
+
|
|
15
|
+
const getFieldsFromObject = (
|
|
16
|
+
response: SuiParsedData | null | undefined
|
|
17
|
+
): any => {
|
|
18
|
+
// Check if 'data' and 'content' exist and are of the expected type
|
|
19
|
+
if (response && response.dataType === 'moveObject') {
|
|
20
|
+
// Safely return 'fields' from 'content'
|
|
21
|
+
return response.fields as any;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
throw new Error('Invalid response data');
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const parseFeedConfigs = (responses: SuiParsedData[]): AggregatorData[] => {
|
|
28
|
+
return responses.map(getFieldsFromObject).map((aggregatorData) => {
|
|
29
|
+
const currentResult = (aggregatorData.current_result as any).fields;
|
|
30
|
+
const updateState = (aggregatorData.update_state as any).fields;
|
|
31
|
+
|
|
32
|
+
// build the data object
|
|
33
|
+
const data: AggregatorData = {
|
|
34
|
+
id: ObjectParsingHelper.asId(aggregatorData.id),
|
|
35
|
+
authority: ObjectParsingHelper.asString(aggregatorData.authority),
|
|
36
|
+
createdAtMs: ObjectParsingHelper.asNumber(aggregatorData.created_at_ms),
|
|
37
|
+
currentResult: {
|
|
38
|
+
maxResult: ObjectParsingHelper.asBN(currentResult.max_result),
|
|
39
|
+
maxTimestamp: ObjectParsingHelper.asNumber(
|
|
40
|
+
currentResult.max_timestamp_ms
|
|
41
|
+
),
|
|
42
|
+
mean: ObjectParsingHelper.asBN(currentResult.mean),
|
|
43
|
+
minResult: ObjectParsingHelper.asBN(currentResult.min_result),
|
|
44
|
+
minTimestamp: ObjectParsingHelper.asNumber(
|
|
45
|
+
currentResult.min_timestamp_ms
|
|
46
|
+
),
|
|
47
|
+
range: ObjectParsingHelper.asBN(currentResult.range),
|
|
48
|
+
result: ObjectParsingHelper.asBN(currentResult.result),
|
|
49
|
+
stdev: ObjectParsingHelper.asBN(currentResult.stdev),
|
|
50
|
+
},
|
|
51
|
+
feedHash: toHex(
|
|
52
|
+
ObjectParsingHelper.asUint8Array(aggregatorData.feed_hash)
|
|
53
|
+
),
|
|
54
|
+
maxStalenessSeconds: ObjectParsingHelper.asNumber(
|
|
55
|
+
aggregatorData.max_staleness_seconds
|
|
56
|
+
),
|
|
57
|
+
maxVariance: ObjectParsingHelper.asNumber(aggregatorData.max_variance),
|
|
58
|
+
minResponses: ObjectParsingHelper.asNumber(aggregatorData.min_responses),
|
|
59
|
+
minSampleSize: ObjectParsingHelper.asNumber(
|
|
60
|
+
aggregatorData.min_sample_size
|
|
61
|
+
),
|
|
62
|
+
name: ObjectParsingHelper.asString(aggregatorData.name),
|
|
63
|
+
queue: ObjectParsingHelper.asString(aggregatorData.queue),
|
|
64
|
+
updateState: {
|
|
65
|
+
currIdx: ObjectParsingHelper.asNumber(updateState.curr_idx),
|
|
66
|
+
results: updateState.results.map((r: any) => {
|
|
67
|
+
const oracleId = r.fields.oracle;
|
|
68
|
+
const value = ObjectParsingHelper.asBN(r.fields.result.fields);
|
|
69
|
+
const timestamp = parseInt(r.fields.timestamp_ms);
|
|
70
|
+
return {
|
|
71
|
+
oracle: oracleId,
|
|
72
|
+
value,
|
|
73
|
+
timestamp,
|
|
74
|
+
};
|
|
75
|
+
}),
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return data;
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// const parseQueueAddresses = async (
|
|
84
|
+
// cache: ScallopCache,
|
|
85
|
+
// queueAddresses: string[]
|
|
86
|
+
// ): Promise<void> => {
|
|
87
|
+
// const queueParsedDatas = (
|
|
88
|
+
// await queryMultipleObjects(cache, queueAddresses)
|
|
89
|
+
// ).map((q) => getFieldsFromObject(q.content));
|
|
90
|
+
|
|
91
|
+
// // Get the existing_oracles table ids
|
|
92
|
+
// const existingOracleTableIds = queueParsedDatas.map((q: any) => {
|
|
93
|
+
// return q.existing_oracles.fields.id.id;
|
|
94
|
+
// });
|
|
95
|
+
|
|
96
|
+
// for (let i = 0; i < existingOracleTableIds.length; i++) {
|
|
97
|
+
// const parentId = existingOracleTableIds[i];
|
|
98
|
+
// const queueAddress = queueAddresses[i];
|
|
99
|
+
// const queueResponse = queueParsedDatas[i];
|
|
100
|
+
// const queueCachedData = await suiQueueCache.get(queueAddress);
|
|
101
|
+
// if (queueCachedData) {
|
|
102
|
+
// return queueCachedData;
|
|
103
|
+
// }
|
|
104
|
+
|
|
105
|
+
// let cursor = null;
|
|
106
|
+
// let nextPage = true;
|
|
107
|
+
|
|
108
|
+
// const existingOraclesIds = [];
|
|
109
|
+
// while (nextPage) {
|
|
110
|
+
// const resp = await cache.queryGetDynamicFields({
|
|
111
|
+
// parentId,
|
|
112
|
+
// limit: 25,
|
|
113
|
+
// cursor,
|
|
114
|
+
// });
|
|
115
|
+
|
|
116
|
+
// if (!resp) break;
|
|
117
|
+
// const { nextCursor, data, hasNextPage } = resp;
|
|
118
|
+
// nextPage = hasNextPage;
|
|
119
|
+
// cursor = nextCursor;
|
|
120
|
+
|
|
121
|
+
// existingOraclesIds.push(...data.map((t) => t.objectId));
|
|
122
|
+
// }
|
|
123
|
+
|
|
124
|
+
// const existingOracles = await queryMultipleObjects(
|
|
125
|
+
// cache,
|
|
126
|
+
// existingOraclesIds
|
|
127
|
+
// ).then((res) => {
|
|
128
|
+
// return res.map((t) => {
|
|
129
|
+
// const fields: any = getFieldsFromObject(t.content);
|
|
130
|
+
// return {
|
|
131
|
+
// oracleId: ObjectParsingHelper.asString(fields.value.fields.oracle_id),
|
|
132
|
+
// oracleKey: toBase58(
|
|
133
|
+
// ObjectParsingHelper.asUint8Array(fields.value.fields.oracle_key)
|
|
134
|
+
// ),
|
|
135
|
+
// };
|
|
136
|
+
// });
|
|
137
|
+
// });
|
|
138
|
+
|
|
139
|
+
// const queueData = {
|
|
140
|
+
// authority: ObjectParsingHelper.asString(queueResponse.authority),
|
|
141
|
+
// existingOracles,
|
|
142
|
+
// // get fee number (though encoded as string)
|
|
143
|
+
// fee: ObjectParsingHelper.asNumber(queueResponse.fee),
|
|
144
|
+
|
|
145
|
+
// // fee recipient address
|
|
146
|
+
// feeRecipient: ObjectParsingHelper.asString(queueResponse.fee_recipient),
|
|
147
|
+
|
|
148
|
+
// // accepted fee coin types
|
|
149
|
+
// feeTypes: ObjectParsingHelper.asArray(queueResponse.fee_types).map(
|
|
150
|
+
// (ft: any) => ft.fields.name
|
|
151
|
+
// ),
|
|
152
|
+
|
|
153
|
+
// // guardian queue id
|
|
154
|
+
// guardianQueueId: ObjectParsingHelper.asString(
|
|
155
|
+
// queueResponse.guardian_queue_id
|
|
156
|
+
// ),
|
|
157
|
+
|
|
158
|
+
// // queue id
|
|
159
|
+
// id: ObjectParsingHelper.asId(queueResponse.id),
|
|
160
|
+
|
|
161
|
+
// // last queue override ms
|
|
162
|
+
// lastQueueOverrideMs: ObjectParsingHelper.asNumber(
|
|
163
|
+
// queueResponse.last_queue_override_ms
|
|
164
|
+
// ),
|
|
165
|
+
|
|
166
|
+
// // minimum attestations
|
|
167
|
+
// minAttestations: ObjectParsingHelper.asNumber(
|
|
168
|
+
// queueResponse.min_attestations
|
|
169
|
+
// ),
|
|
170
|
+
|
|
171
|
+
// // queue name
|
|
172
|
+
// name: ObjectParsingHelper.asString(queueResponse.name),
|
|
173
|
+
// oracleValidityLengthMs: ObjectParsingHelper.asNumber(
|
|
174
|
+
// queueResponse.oracle_validity_length_ms
|
|
175
|
+
// ),
|
|
176
|
+
|
|
177
|
+
// // get source queue key
|
|
178
|
+
// queueKey: toBase58(
|
|
179
|
+
// ObjectParsingHelper.asUint8Array(queueResponse.queue_key) as Uint8Array
|
|
180
|
+
// ),
|
|
181
|
+
// } as QueueData;
|
|
182
|
+
|
|
183
|
+
// suiQueueCache.set(queueAddress, queueData);
|
|
184
|
+
// }
|
|
185
|
+
// };
|
|
186
|
+
|
|
187
|
+
export const updateSwitchboardAggregators = async (
|
|
188
|
+
builder: ScallopBuilder,
|
|
189
|
+
assetCoinNames: string[],
|
|
190
|
+
txBlock: SuiKitTxBlock,
|
|
191
|
+
solanaRPCUrl: string = 'https://crossbar.switchboard.xyz/rpc/mainnet'
|
|
192
|
+
) => {
|
|
193
|
+
const switchboardClient = new SwitchboardClient(builder.suiKit.client());
|
|
194
|
+
const onDemandAggObjects = await queryMultipleObjects(
|
|
195
|
+
builder.cache,
|
|
196
|
+
await builder.query.getSwitchboardOnDemandAggregatorObjectIds(
|
|
197
|
+
assetCoinNames
|
|
198
|
+
)
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const feedConfigs = parseFeedConfigs(
|
|
202
|
+
onDemandAggObjects.map((t) => t.content) as SuiParsedData[]
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Get queue addresses
|
|
206
|
+
// const queueAddresses = feedConfigs.map((fc) => fc.queue);
|
|
207
|
+
|
|
208
|
+
// await parseQueueAddresses(builder.cache, queueAddresses);
|
|
209
|
+
|
|
210
|
+
for (let idx = 0; idx < assetCoinNames.length; idx++) {
|
|
211
|
+
// console.log({ coinName: assetCoinNames[idx] });
|
|
212
|
+
const switchboardAgg = new Aggregator(
|
|
213
|
+
switchboardClient,
|
|
214
|
+
onDemandAggObjects[idx].objectId
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
const { responses, failures } = await switchboardAgg.fetchUpdateTx(
|
|
218
|
+
txBlock.txBlock,
|
|
219
|
+
{
|
|
220
|
+
feedConfigs: feedConfigs[idx],
|
|
221
|
+
solanaRPCUrl,
|
|
222
|
+
}
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
console.log({ responses, failures });
|
|
226
|
+
if (failures.length > 0) {
|
|
227
|
+
throw new Error(
|
|
228
|
+
`Failed to update aggregator for ${assetCoinNames[idx]}: ${failures.join(',')}`
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return;
|
|
233
|
+
};
|
|
@@ -443,7 +443,7 @@ export class ScallopAddress {
|
|
|
443
443
|
instance?: ScallopAddressInstanceParams
|
|
444
444
|
) {
|
|
445
445
|
const { addressId, auth, network, forceAddressesInterface } = params;
|
|
446
|
-
this.cache = instance?.cache ?? new ScallopCache(
|
|
446
|
+
this.cache = instance?.cache ?? new ScallopCache(params);
|
|
447
447
|
|
|
448
448
|
this._requestClient = axios.create({
|
|
449
449
|
baseURL: API_BASE_URL,
|
|
@@ -246,7 +246,7 @@ export class ScallopCache {
|
|
|
246
246
|
return query;
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
-
|
|
249
|
+
private async queryGetNormalizedMoveFunction(target: string) {
|
|
250
250
|
const { address, module, name } = parseStructTag(target);
|
|
251
251
|
return this.queryClient.fetchQuery({
|
|
252
252
|
queryKey: queryKeys.rpc.getNormalizedMoveFunction(target),
|
|
@@ -460,11 +460,7 @@ export class ScallopCache {
|
|
|
460
460
|
input: GetBalanceParams
|
|
461
461
|
): Promise<CoinBalance | null> {
|
|
462
462
|
if (!input.coinType) return null;
|
|
463
|
-
|
|
464
|
-
return (
|
|
465
|
-
((await this.queryGetAllCoinBalances(input.owner)) ?? {})[
|
|
466
|
-
normalizeStructTag(input.coinType)
|
|
467
|
-
] ?? '0'
|
|
468
|
-
);
|
|
463
|
+
const coinBalances = await this.queryGetAllCoinBalances(input.owner);
|
|
464
|
+
return coinBalances[normalizeStructTag(input.coinType)] ?? null;
|
|
469
465
|
}
|
|
470
466
|
}
|
package/src/models/suiKit.ts
CHANGED
|
@@ -1,11 +1,26 @@
|
|
|
1
1
|
import { SuiKit, SuiKitParams } from '@scallop-io/sui-kit';
|
|
2
2
|
import { RPC_PROVIDERS } from 'src/constants/rpc';
|
|
3
3
|
|
|
4
|
-
export const newSuiKit = (params: SuiKitParams) => {
|
|
4
|
+
export const newSuiKit = (params: Partial<SuiKitParams>) => {
|
|
5
|
+
let initParams;
|
|
6
|
+
if (
|
|
7
|
+
'suiClients' in params &&
|
|
8
|
+
params.suiClients &&
|
|
9
|
+
params.suiClients?.length > 0
|
|
10
|
+
) {
|
|
11
|
+
initParams = {
|
|
12
|
+
suiClients: params.suiClients,
|
|
13
|
+
};
|
|
14
|
+
} else {
|
|
15
|
+
initParams = {
|
|
16
|
+
fullnodeUrls:
|
|
17
|
+
'fullnodeUrls' in params
|
|
18
|
+
? (params?.fullnodeUrls ?? RPC_PROVIDERS)
|
|
19
|
+
: RPC_PROVIDERS,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
5
22
|
return new SuiKit({
|
|
6
23
|
...params,
|
|
7
|
-
|
|
8
|
-
new Set([...(params.fullnodeUrls ?? []), ...RPC_PROVIDERS])
|
|
9
|
-
),
|
|
24
|
+
...initParams,
|
|
10
25
|
});
|
|
11
26
|
};
|
package/src/types/model.ts
CHANGED
|
@@ -74,7 +74,7 @@ export type ScallopCacheParams = {
|
|
|
74
74
|
walletAddress?: string;
|
|
75
75
|
cacheOptions?: QueryClientConfig;
|
|
76
76
|
config?: ScallopCacheConfig;
|
|
77
|
-
} & SuiKitParams
|
|
77
|
+
} & Partial<SuiKitParams>;
|
|
78
78
|
|
|
79
79
|
export type ScallopIndexerParams = ScallopCacheParams & {
|
|
80
80
|
indexerApiUrl?: string;
|
|
@@ -99,6 +99,7 @@ export type ScallopConstantsParams = ScallopAddressParams & {
|
|
|
99
99
|
export type ScallopUtilsParams = ScallopAddressParams &
|
|
100
100
|
ScallopConstantsParams & {
|
|
101
101
|
pythEndpoints?: string[];
|
|
102
|
+
switchboardSolanaRpc?: string;
|
|
102
103
|
};
|
|
103
104
|
|
|
104
105
|
export type ScallopQueryParams = ScallopUtilsParams & ScallopIndexerParams;
|
|
@@ -109,7 +110,7 @@ export type ScallopBuilderParams = ScallopQueryParams & {
|
|
|
109
110
|
};
|
|
110
111
|
|
|
111
112
|
export type ScallopClientParams = ScallopBuilderParams;
|
|
112
|
-
export type ScallopParams = SuiKitParams &
|
|
113
|
+
export type ScallopParams = Partial<SuiKitParams> &
|
|
113
114
|
ScallopAddressParams &
|
|
114
115
|
ScallopConstantsParams & {
|
|
115
116
|
walletAddress?: string;
|