suidouble 1.45.2 → 2.16.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/.claude/settings.local.json +7 -0
- package/README.md +222 -131
- package/index.js +1 -3
- package/lib/SuiCliCommands.js +18 -25
- package/lib/SuiCoin.js +86 -138
- package/lib/SuiCoins.js +70 -31
- package/lib/SuiCommonMethods.js +40 -3
- package/lib/SuiEvent.js +54 -6
- package/lib/SuiInBrowser.js +145 -46
- package/lib/SuiInBrowserAdapter.js +164 -37
- package/lib/SuiLocalTestValidator.js +78 -25
- package/lib/SuiMaster.js +351 -126
- package/lib/SuiMemoryObjectStorage.js +66 -73
- package/lib/SuiObject.js +128 -153
- package/lib/SuiPackage.js +292 -187
- package/lib/SuiPackageModule.js +176 -221
- package/lib/SuiPaginatedResponse.js +288 -25
- package/lib/SuiPseudoRandomAddress.js +29 -2
- package/lib/SuiTransaction.js +115 -70
- package/lib/SuiUtils.js +179 -124
- package/package.json +30 -14
- package/test/build_modules.test.js +41 -0
- package/test/coins.test.js +17 -16
- package/test/custom_transaction.test.js +167 -0
- package/test/event_listeners.test.js +171 -0
- package/test/failed_transaction.test.js +184 -0
- package/test/name_service.test.js +28 -0
- package/test/owned_objects.test.js +148 -0
- package/test/rpc.test.js +3 -6
- package/test/sui_in_browser.test.js +2 -2
- package/test/sui_master_basic.test.js +4 -5
- package/test/sui_master_onlocal.test.js +84 -22
- package/test/sui_object_properties.test.js +85 -0
- package/test/test_move_contracts/different_types/Move.lock +18 -21
- package/test/test_move_contracts/different_types/sources/different_types.move +12 -12
- package/test/test_move_contracts/suidouble_chat/Move.lock +18 -22
- package/test/test_move_contracts/suidouble_chat/sources/suidouble_chat.move +9 -8
- package/tsconfig.json +15 -0
- package/types/index.d.ts +15 -0
- package/types/lib/SuiCliCommands.d.ts +6 -0
- package/types/lib/SuiCoin.d.ts +183 -0
- package/types/lib/SuiCoins.d.ts +93 -0
- package/types/lib/SuiCommonMethods.d.ts +37 -0
- package/types/lib/SuiEvent.d.ts +95 -0
- package/types/lib/SuiInBrowser.d.ts +189 -0
- package/types/lib/SuiInBrowserAdapter.d.ts +167 -0
- package/types/lib/SuiLocalTestValidator.d.ts +92 -0
- package/types/lib/SuiMaster.d.ts +333 -0
- package/types/lib/SuiMemoryObjectStorage.d.ts +96 -0
- package/types/lib/SuiObject.d.ts +135 -0
- package/types/lib/SuiPackage.d.ts +233 -0
- package/types/lib/SuiPackageModule.d.ts +139 -0
- package/types/lib/SuiPaginatedResponse.d.ts +148 -0
- package/types/lib/SuiPseudoRandomAddress.d.ts +33 -0
- package/types/lib/SuiTransaction.d.ts +92 -0
- package/types/lib/SuiUtils.d.ts +152 -0
- package/types/lib/data/icons.d.ts +12 -0
- package/lib/SuiTestScenario.js +0 -169
- package/test/sui_test_scenario.test.js +0 -61
package/lib/SuiCliCommands.js
CHANGED
|
@@ -9,9 +9,13 @@ const loadModule = async (modulePath) => {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* Load a node.js built-in on the server side. Returns null in environments where the module is not available (e.g. browser).
|
|
13
|
+
*
|
|
14
|
+
* For 'execSync' and 'spawn' resolves to the named function from `child_process`.
|
|
15
|
+
* For 'fs', 'path', 'net' resolves to the whole module's default export.
|
|
16
|
+
*
|
|
17
|
+
* @param {'execSync' | 'spawn' | 'fs' | 'path' | 'net'} name
|
|
18
|
+
* @returns {Promise<Function | object | null>} the function or module, or null if unavailable
|
|
15
19
|
*/
|
|
16
20
|
const getSystemFunction = async (name) => {
|
|
17
21
|
try {
|
|
@@ -64,7 +68,7 @@ export default class SuiCliCommands {
|
|
|
64
68
|
__waitPortPromiseResolver(false);
|
|
65
69
|
});
|
|
66
70
|
|
|
67
|
-
socket.connect(port, "
|
|
71
|
+
socket.connect(port, "127.0.0.1");
|
|
68
72
|
|
|
69
73
|
const portIsThere = await __waitPortPromise;
|
|
70
74
|
socket.destroy();
|
|
@@ -76,7 +80,7 @@ export default class SuiCliCommands {
|
|
|
76
80
|
const doSpawn = await getSystemFunction('spawn');
|
|
77
81
|
|
|
78
82
|
if (!doSpawn) {
|
|
79
|
-
throw new Error('can not spawn a
|
|
83
|
+
throw new Error('can not spawn a process in this env');
|
|
80
84
|
}
|
|
81
85
|
|
|
82
86
|
return await new Promise((res,rej)=>{
|
|
@@ -84,7 +88,8 @@ export default class SuiCliCommands {
|
|
|
84
88
|
let e = null;
|
|
85
89
|
const proc = doSpawn(command, params, {
|
|
86
90
|
env: {
|
|
87
|
-
|
|
91
|
+
// @ts-ignore — process is a Node.js global; not available in TypeScript without @types/node
|
|
92
|
+
...(process ? process.env : {}),
|
|
88
93
|
...envVars,
|
|
89
94
|
}
|
|
90
95
|
});
|
|
@@ -101,25 +106,13 @@ export default class SuiCliCommands {
|
|
|
101
106
|
}
|
|
102
107
|
}, 100);
|
|
103
108
|
});
|
|
104
|
-
|
|
105
|
-
// const proc = doSpawn(command, [], {
|
|
106
|
-
// env: {
|
|
107
|
-
// ...process.env,
|
|
108
|
-
// ...envVars,
|
|
109
|
-
// }
|
|
110
|
-
// });
|
|
111
|
-
// proc.on('error', function(err) {
|
|
112
|
-
// console.log('Oh noez, teh errurz: ' + err);
|
|
113
|
-
// });
|
|
114
|
-
|
|
115
|
-
// return proc;
|
|
116
109
|
}
|
|
117
110
|
|
|
118
111
|
static async exec(command) {
|
|
119
112
|
const doExecSync = await getSystemFunction('execSync');
|
|
120
113
|
|
|
121
114
|
if (!doExecSync) {
|
|
122
|
-
throw new Error('can not exec a
|
|
115
|
+
throw new Error('can not exec a process in this env');
|
|
123
116
|
}
|
|
124
117
|
|
|
125
118
|
return doExecSync(
|
|
@@ -137,19 +130,19 @@ export default class SuiCliCommands {
|
|
|
137
130
|
}
|
|
138
131
|
|
|
139
132
|
try {
|
|
140
|
-
const buildPathContent = await doFs.promises.readdir(
|
|
141
|
-
|
|
133
|
+
const buildPathContent = await doFs.promises.readdir(doPath.join(path, 'build'));
|
|
134
|
+
|
|
142
135
|
// @todo: there may be some junk folders and we'd have to get project name from Move.toml ?
|
|
143
136
|
const buildPath = buildPathContent[0];
|
|
144
|
-
|
|
145
|
-
const dirents = await doFs.promises.readdir(doPath.join(
|
|
137
|
+
|
|
138
|
+
const dirents = await doFs.promises.readdir(doPath.join(path, 'build', buildPath, 'bytecode_modules'), { withFileTypes: true });
|
|
146
139
|
const names = dirents
|
|
147
140
|
.filter(dirent => dirent.isFile())
|
|
148
141
|
.map(dirent => dirent.name.split('.mv').join(''));
|
|
149
|
-
|
|
142
|
+
|
|
150
143
|
return names;
|
|
151
144
|
} catch (e) {
|
|
152
|
-
throw new Error('can not get modules names from local package path');
|
|
145
|
+
throw new Error('can not get modules names from local package path: ' + (e && e.message ? e.message : e));
|
|
153
146
|
}
|
|
154
147
|
}
|
|
155
148
|
};
|
package/lib/SuiCoin.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Transaction } from '@mysten/sui/transactions';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @typedef {import("@mysten/sui/transactions").TransactionObjectArgument} TransactionObjectArgument
|
|
5
5
|
*
|
|
6
|
+
* @import { SuiClientTypes } from "@mysten/sui/client"
|
|
6
7
|
*
|
|
7
8
|
* @typedef CoinMeta
|
|
8
9
|
* @type {object}
|
|
@@ -15,17 +16,16 @@ import { Commands, Transaction } from '@mysten/sui/transactions';
|
|
|
15
16
|
* @property {string} [type] - Coin type string
|
|
16
17
|
*
|
|
17
18
|
*
|
|
18
|
-
* @typedef SuidoubleCoinBalance
|
|
19
|
-
* @
|
|
20
|
-
* @
|
|
21
|
-
* @property {string} coinType
|
|
22
|
-
* @property {number} coinObjectCount
|
|
23
|
-
* @property {bigint} totalBalance
|
|
24
|
-
* @property {Object.<string,string>} lockedBalance
|
|
25
|
-
*
|
|
19
|
+
* @typedef {SuiClientTypes.Balance & { coin: SuiCoin, totalBalance: bigint }} SuidoubleCoinBalance
|
|
20
|
+
* @typedef {import("./SuiMaster.js").default} SuiMaster
|
|
21
|
+
* @typedef {import("./SuiCoins.js").default} SuiCoins
|
|
26
22
|
*/
|
|
27
23
|
|
|
28
|
-
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Handle for a single coin type (e.g. `0x2::sui::SUI`). Caches metadata, formats amounts
|
|
27
|
+
* via decimals, and builds `Coin<T>` arguments for transactions.
|
|
28
|
+
*/
|
|
29
29
|
export default class SuiCoin {
|
|
30
30
|
|
|
31
31
|
/**
|
|
@@ -34,7 +34,7 @@ export default class SuiCoin {
|
|
|
34
34
|
* @param {string} params.coinType - sui object type for a coin, without Coin<...>, only the inside type
|
|
35
35
|
* @param {SuiCoins} params.suiCoins - instance of SuiCoins
|
|
36
36
|
*/
|
|
37
|
-
constructor(params
|
|
37
|
+
constructor(params) {
|
|
38
38
|
this._coinType = params.coinType;
|
|
39
39
|
this._suiCoins = params.suiCoins;
|
|
40
40
|
|
|
@@ -54,7 +54,7 @@ export default class SuiCoin {
|
|
|
54
54
|
throw new Error('Please load coin metadata first');
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
return BigInt(Math.floor(parseFloat(amount
|
|
57
|
+
return BigInt(Math.floor(parseFloat(amount) * Math.pow(10, this.decimals)));
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
return BigInt(amount);
|
|
@@ -71,13 +71,13 @@ export default class SuiCoin {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
|
-
* Get readable representation of amount
|
|
75
|
-
* based on coin decimals metadata (
|
|
76
|
-
*
|
|
77
|
-
* @param {
|
|
78
|
-
* @param {
|
|
79
|
-
* @param {boolean
|
|
80
|
-
*
|
|
74
|
+
* Get readable representation of a raw amount (bigint-native, NOT a pre-formatted "1.99" string),
|
|
75
|
+
* based on coin decimals metadata (requires metadata to be loaded or set).
|
|
76
|
+
*
|
|
77
|
+
* @param {bigint|number|string} amount - raw amount in the coin's base units
|
|
78
|
+
* @param {Object} [options] - format options
|
|
79
|
+
* @param {boolean} [options.withAbbr] - append K/M/B/T for large amounts (Suiet-style)
|
|
80
|
+
* @param {boolean|string} [options.separateThousands] - separate thousands by `,` (true) or by a custom delimiter
|
|
81
81
|
* @returns {string}
|
|
82
82
|
*/
|
|
83
83
|
amountToString(amount, options = {}) {
|
|
@@ -123,14 +123,14 @@ export default class SuiCoin {
|
|
|
123
123
|
|
|
124
124
|
|
|
125
125
|
/**
|
|
126
|
-
* Format large amounts
|
|
127
|
-
*
|
|
126
|
+
* Format large amounts.
|
|
127
|
+
*
|
|
128
128
|
* thanks @bruceeewong and @suiet
|
|
129
|
-
*
|
|
130
|
-
* @param {bigint} amount
|
|
131
|
-
* @param {bigint} measureUnit
|
|
132
|
-
* @param {string} abbrSymbol
|
|
133
|
-
*
|
|
129
|
+
*
|
|
130
|
+
* @param {bigint} amount
|
|
131
|
+
* @param {bigint} measureUnit - divisor for the unit (1_000n for K, 1_000_000n for M, …)
|
|
132
|
+
* @param {string} abbrSymbol - suffix to append (K, M, B, T)
|
|
133
|
+
* @param {boolean|string} [separateThousands=false] - separate thousands by `,` (true) or by a custom delimiter
|
|
134
134
|
* @returns {string}
|
|
135
135
|
*/
|
|
136
136
|
formatWithAbbr(amount, measureUnit, abbrSymbol, separateThousands = false) {
|
|
@@ -150,10 +150,14 @@ export default class SuiCoin {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
|
|
153
|
+
/**
|
|
154
|
+
* @type {SuiMaster}
|
|
155
|
+
*/
|
|
153
156
|
get suiMaster() {
|
|
154
157
|
return this._suiCoins.suiMaster;
|
|
155
158
|
}
|
|
156
159
|
|
|
160
|
+
/** @returns {string} coin type string, always prefixed with `0x` */
|
|
157
161
|
get coinType() {
|
|
158
162
|
if (this._coinType.indexOf('0x') === 0) {
|
|
159
163
|
return this._coinType;
|
|
@@ -171,6 +175,7 @@ export default class SuiCoin {
|
|
|
171
175
|
return '0x2::coin::Coin<'+this.coinType+'>';
|
|
172
176
|
}
|
|
173
177
|
|
|
178
|
+
/** @returns {number | undefined} decimals from loaded metadata, or `undefined` if not yet loaded */
|
|
174
179
|
get decimals() {
|
|
175
180
|
if (this.metadata) {
|
|
176
181
|
return this.metadata.decimals;
|
|
@@ -178,10 +183,12 @@ export default class SuiCoin {
|
|
|
178
183
|
return undefined;
|
|
179
184
|
}
|
|
180
185
|
|
|
186
|
+
/** @returns {?CoinMeta} loaded coin metadata, or null if not loaded */
|
|
181
187
|
get metadata() {
|
|
182
188
|
return this._metadata;
|
|
183
189
|
}
|
|
184
190
|
|
|
191
|
+
/** @returns {?string} coin symbol from metadata, or null if metadata isn't loaded */
|
|
185
192
|
get symbol() {
|
|
186
193
|
if (this.metadata) {
|
|
187
194
|
return this.metadata.symbol;
|
|
@@ -190,10 +197,19 @@ export default class SuiCoin {
|
|
|
190
197
|
return null;
|
|
191
198
|
}
|
|
192
199
|
|
|
200
|
+
/** @returns {?string} coin name from metadata, or null if metadata isn't loaded */
|
|
193
201
|
get name() {
|
|
194
|
-
|
|
202
|
+
if (this.metadata) {
|
|
203
|
+
return this.metadata.name;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return null;
|
|
195
207
|
}
|
|
196
208
|
|
|
209
|
+
/**
|
|
210
|
+
* True if this coin's type is the normalized SUI type (`0x2::sui::SUI` expanded to 64 hex chars).
|
|
211
|
+
* @returns {boolean}
|
|
212
|
+
*/
|
|
197
213
|
isSUI() {
|
|
198
214
|
const lc = this._coinType.toLowerCase();
|
|
199
215
|
if (lc == '0x0000000000000000000000000000000000000000000000000000000000000002::sui::sui') { // as it's normalized
|
|
@@ -209,20 +225,31 @@ export default class SuiCoin {
|
|
|
209
225
|
* @returns {boolean} if processed ok
|
|
210
226
|
*/
|
|
211
227
|
setMetadata(meta) {
|
|
212
|
-
if (
|
|
228
|
+
if (SuiCoin.isValidMetadata(meta)) {
|
|
213
229
|
this._metadata = meta;
|
|
214
230
|
this._exists = true;
|
|
215
231
|
return true;
|
|
216
232
|
}
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
217
235
|
|
|
236
|
+
/**
|
|
237
|
+
* Returns true if the object looks like a usable CoinMeta (has positive `decimals`, `name`, `symbol`).
|
|
238
|
+
* @param {CoinMeta} meta
|
|
239
|
+
* @returns {boolean}
|
|
240
|
+
*/
|
|
241
|
+
static isValidMetadata(meta) {
|
|
242
|
+
if (meta && meta.decimals && meta.decimals > 0 && meta.name && meta.symbol) {
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
218
245
|
return false;
|
|
219
246
|
}
|
|
220
247
|
|
|
221
248
|
/**
|
|
222
|
-
* Load coin metadata from the blockchain.
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
* @returns {Promise
|
|
249
|
+
* Load coin metadata from the blockchain. Concurrent callers share the same in-flight request.
|
|
250
|
+
* As a good pattern, prefer hard-coded / cached metadata set via `suiMaster.suiCoins.setCoinMetas(...)`.
|
|
251
|
+
*
|
|
252
|
+
* @returns {Promise<?CoinMeta>} resolved metadata, or `null` if the coin type has none on chain
|
|
226
253
|
*/
|
|
227
254
|
async getMetadata() {
|
|
228
255
|
if (this._metadata) {
|
|
@@ -237,25 +264,24 @@ export default class SuiCoin {
|
|
|
237
264
|
this.__getMetadataResolver = null;
|
|
238
265
|
this.__getMetadataPromise = new Promise((res)=>{ this.__getMetadataResolver = res; });
|
|
239
266
|
|
|
240
|
-
let result = null;
|
|
241
267
|
try {
|
|
242
|
-
result = await this.suiMaster.client.getCoinMetadata({
|
|
268
|
+
const result = await this.suiMaster.client.getCoinMetadata({
|
|
243
269
|
coinType: this.coinType,
|
|
244
270
|
});
|
|
271
|
+
if (result && result.coinMetadata && result.coinMetadata.decimals) {
|
|
272
|
+
this._exists = true;
|
|
273
|
+
this._metadata = result.coinMetadata;
|
|
274
|
+
} else {
|
|
275
|
+
this._exists = false;
|
|
276
|
+
}
|
|
245
277
|
} catch (e) {
|
|
246
278
|
console.error(e);
|
|
247
|
-
result = null;
|
|
248
|
-
}
|
|
249
|
-
if (!result) {
|
|
250
279
|
this._exists = false;
|
|
251
|
-
} else {
|
|
252
|
-
this._metadata = result;
|
|
253
|
-
this._exists = true;
|
|
254
280
|
}
|
|
255
281
|
|
|
256
|
-
this.__getMetadataResolver(
|
|
282
|
+
this.__getMetadataResolver(this._metadata);
|
|
257
283
|
|
|
258
|
-
return
|
|
284
|
+
return this._metadata;
|
|
259
285
|
}
|
|
260
286
|
|
|
261
287
|
/**
|
|
@@ -265,111 +291,33 @@ export default class SuiCoin {
|
|
|
265
291
|
* @returns {Promise.<bigint>}
|
|
266
292
|
*/
|
|
267
293
|
async getBalance(owner) {
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
cursor: cursor,
|
|
277
|
-
});
|
|
278
|
-
coins.push(...result.data);
|
|
279
|
-
|
|
280
|
-
cursor = result.nextCursor;
|
|
281
|
-
} while (result.hasNextPage);
|
|
282
|
-
|
|
283
|
-
let totalAmount = BigInt(0);
|
|
284
|
-
for (const coin of coins) {
|
|
285
|
-
totalAmount = totalAmount + BigInt(coin.balance);
|
|
294
|
+
const coinBalanceResponse = await this.suiMaster.client.getBalance({
|
|
295
|
+
owner: owner,
|
|
296
|
+
coinType: this.coinType,
|
|
297
|
+
});
|
|
298
|
+
if (coinBalanceResponse?.balance?.balance) {
|
|
299
|
+
return BigInt(coinBalanceResponse.balance.balance);
|
|
300
|
+
} else {
|
|
301
|
+
return BigInt(0);
|
|
286
302
|
}
|
|
287
|
-
|
|
288
|
-
return totalAmount;
|
|
289
303
|
}
|
|
290
304
|
|
|
291
305
|
|
|
292
306
|
/**
|
|
293
|
-
*
|
|
294
|
-
*
|
|
307
|
+
* Return a TransactionObjectArgument with a Coin of the requested amount, ready to use in moveCall.
|
|
308
|
+
* Delegates to the v2 SDK's `tx.coinWithBalance` intent, which at build time fetches all owned
|
|
309
|
+
* coins of this type, merges them (including zero-balance dust), and splits off the requested amount.
|
|
310
|
+
*
|
|
295
311
|
* @param {Transaction} txb - Native SUI SDK Transaction
|
|
296
|
-
* @param {string}
|
|
297
|
-
* @param {BigInt|string} amount - amount of coin.
|
|
298
|
-
* @param {boolean} addEmptyCoins - attach coins == 0 to the list
|
|
299
|
-
*
|
|
312
|
+
* @param {string} _owner - unused; kept for signature compatibility. The resolver picks coins owned by the tx sender.
|
|
313
|
+
* @param {BigInt|string} amount - amount of coin. BigInt or string normalized via Coin decimals ("0.05" → 0.05 SUI)
|
|
300
314
|
* @returns {Promise.<TransactionObjectArgument>}
|
|
301
315
|
*/
|
|
302
|
-
async coinOfAmountToTxCoin(txb,
|
|
316
|
+
async coinOfAmountToTxCoin(txb, _owner, amount) {
|
|
303
317
|
const normalizedAmount = await this.lazyNormalizeAmount(amount);
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
if (!coinIds || !coinIds.length) {
|
|
309
|
-
throw new Error('you do not have enough coins of type '+this.coinType);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (coinIds.length == 1) {
|
|
313
|
-
// only one coin object enough to cover the expense
|
|
314
|
-
if (this.isSUI()) {
|
|
315
|
-
const coinInput = txb.add(Commands.SplitCoins(txb.gas, [txb.pure.u64(expectedAmountAsBigInt)]));
|
|
316
|
-
return coinInput;
|
|
317
|
-
} else {
|
|
318
|
-
// some other coin
|
|
319
|
-
const coinInput = txb.add(Commands.SplitCoins(txb.object(coinIds[0]), [txb.pure.u64(expectedAmountAsBigInt)]));
|
|
320
|
-
return coinInput;
|
|
321
|
-
}
|
|
322
|
-
} else {
|
|
323
|
-
// few coin objects to merge
|
|
324
|
-
const coinIdToMergeIn = coinIds.shift();
|
|
325
|
-
txb.add(Commands.MergeCoins(txb.object(coinIdToMergeIn), coinIds.map((id)=>{return txb.object(id);})));
|
|
326
|
-
const coinInputSplet = txb.add(Commands.SplitCoins(txb.object(coinIdToMergeIn), [txb.pure.u64(expectedAmountAsBigInt)]));
|
|
327
|
-
|
|
328
|
-
return coinInputSplet;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
async coinObjectsEnoughForAmount(owner, expectedAmount, addEmptyCoins = false) {
|
|
333
|
-
const expectedAmountAsBigInt = BigInt(expectedAmount);
|
|
334
|
-
|
|
335
|
-
const coinIds = [];
|
|
336
|
-
const coins = [];
|
|
337
|
-
|
|
338
|
-
let result = null;
|
|
339
|
-
let cursor = null;
|
|
340
|
-
do {
|
|
341
|
-
result = await this.suiMaster.client.getCoins({
|
|
342
|
-
owner: owner,
|
|
343
|
-
coinType: this.coinType,
|
|
344
|
-
limit: 50,
|
|
345
|
-
cursor: cursor,
|
|
346
|
-
});
|
|
347
|
-
coins.push(...result.data);
|
|
348
|
-
|
|
349
|
-
cursor = result.nextCursor;
|
|
350
|
-
} while (result.hasNextPage);
|
|
351
|
-
|
|
352
|
-
coins.sort((a, b) => {
|
|
353
|
-
// From big to small
|
|
354
|
-
return Number(b.balance) - Number(a.balance);
|
|
318
|
+
return txb.coin({
|
|
319
|
+
type: this.coinType,
|
|
320
|
+
balance: BigInt(normalizedAmount),
|
|
355
321
|
});
|
|
356
|
-
|
|
357
|
-
let totalAmount = BigInt(0);
|
|
358
|
-
for (const coin of coins) {
|
|
359
|
-
if (totalAmount <= expectedAmountAsBigInt) {
|
|
360
|
-
coinIds.push(coin.coinObjectId);
|
|
361
|
-
totalAmount = totalAmount + BigInt(coin.balance);// totalAmount.add(coin.balance);
|
|
362
|
-
} else {
|
|
363
|
-
if (addEmptyCoins && BigInt(coin.balance) == 0n) {
|
|
364
|
-
coinIds.push(coin.coinObjectId);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
if (totalAmount >= expectedAmountAsBigInt) {
|
|
370
|
-
return coinIds;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
return null;
|
|
374
322
|
}
|
|
375
323
|
};
|
package/lib/SuiCoins.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import SuiCoin from './SuiCoin.js';
|
|
2
2
|
import SuiCommonMethods from './SuiCommonMethods.js';
|
|
3
|
-
import { allCoinMetas } from '@polymedia/coinmeta';
|
|
4
3
|
import { normalizeStructTag } from "@mysten/sui/utils";
|
|
5
|
-
|
|
4
|
+
import { allCoinMetas } from '@polymedia/coinmeta';
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* @typedef {import("./SuiCoin.js").CoinMeta} CoinMeta
|
|
@@ -22,10 +21,11 @@ export default class SuiCoins extends SuiCommonMethods {
|
|
|
22
21
|
* SuiCoins constructor
|
|
23
22
|
* @param {Object} params - Initialization parameters
|
|
24
23
|
* @param {SuiMaster} params.suiMaster - instance of SuiMaster
|
|
24
|
+
* @param {boolean} [params.debug]
|
|
25
25
|
*/
|
|
26
|
-
constructor(params
|
|
26
|
+
constructor(params) {
|
|
27
27
|
super(params);
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
/** @type {SuiMaster} */
|
|
30
30
|
this._suiMaster = params.suiMaster;
|
|
31
31
|
if (!this._suiMaster) {
|
|
@@ -41,17 +41,22 @@ export default class SuiCoins extends SuiCommonMethods {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* @type {SuiMaster}
|
|
46
|
+
*/
|
|
44
47
|
get suiMaster() {
|
|
45
48
|
return this._suiMaster;
|
|
46
49
|
}
|
|
47
50
|
|
|
51
|
+
/** @returns {Object.<string, SuiCoin>} map of coinType → SuiCoin instance */
|
|
48
52
|
get coins() {
|
|
49
53
|
return this._coins;
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
57
|
+
* Set predefined coin metas so they will not need to be fetched from RPC.
|
|
58
|
+
*
|
|
59
|
+
* @param {Object.<string, CoinMeta> | Array.<CoinMeta>} coinMetas - keyed map by coin type, or an array of CoinMeta with `type` field
|
|
55
60
|
* @returns {number} count of processed items
|
|
56
61
|
*/
|
|
57
62
|
setCoinMetas(coinMetas) {
|
|
@@ -82,12 +87,45 @@ export default class SuiCoins extends SuiCommonMethods {
|
|
|
82
87
|
}
|
|
83
88
|
|
|
84
89
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* @
|
|
89
|
-
|
|
90
|
-
|
|
90
|
+
* Fetch the mainnet coin metadata bundle from `coinmeta.polymedia.app` and cache the promise so
|
|
91
|
+
* concurrent callers share a single request. Failures are swallowed and resolve to an empty array.
|
|
92
|
+
*
|
|
93
|
+
* @returns {Promise<Array.<CoinMeta>>} list of CoinMeta records, or an empty array on failure
|
|
94
|
+
*/
|
|
95
|
+
static async prefetchMainnetCoinMetas() {
|
|
96
|
+
if (this.__preloadPromise) {
|
|
97
|
+
return this.__preloadPromise;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
this.__preloadPromiseResolver = null;
|
|
101
|
+
this.__preloadPromise = new Promise((resolve) => {
|
|
102
|
+
this.__preloadPromiseResolver = resolve;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const preloadUrl = "https://coinmeta.polymedia.app/api/data.json";
|
|
106
|
+
const fetched = [];
|
|
107
|
+
try {
|
|
108
|
+
const response = await fetch(preloadUrl);
|
|
109
|
+
if (response.ok) {
|
|
110
|
+
const coinMetas = await response.json();
|
|
111
|
+
if (Array.isArray(coinMetas) && coinMetas.length) {
|
|
112
|
+
fetched.push(...coinMetas);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
} catch (e) {
|
|
116
|
+
// ignore
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
this.__preloadPromiseResolver(fetched);
|
|
120
|
+
return fetched;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get all CoinBalance[] for the specific address or connected address.
|
|
125
|
+
*
|
|
126
|
+
* @param {Object} [params]
|
|
127
|
+
* @param {?string} [params.owner] - owner address; if omitted, uses the connected SuiMaster address
|
|
128
|
+
* @returns {Promise<Array<SuidoubleCoinBalance>>}
|
|
91
129
|
*/
|
|
92
130
|
async getAllBalances(params = {}) {
|
|
93
131
|
let owner = params.owner;
|
|
@@ -98,21 +136,22 @@ export default class SuiCoins extends SuiCommonMethods {
|
|
|
98
136
|
/** @type {Array.<SuidoubleCoinBalance>} */
|
|
99
137
|
const ret = [];
|
|
100
138
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
139
|
+
let cursor = null;
|
|
140
|
+
do {
|
|
141
|
+
const response = await this._suiMaster.client.listBalances({ owner, cursor });
|
|
142
|
+
for (const responseItem of (response?.balances ?? [])) {
|
|
143
|
+
/** @type {SuidoubleCoinBalance} */
|
|
144
|
+
ret.push({
|
|
145
|
+
coin: this.get(responseItem.coinType),
|
|
146
|
+
coinType: responseItem.coinType,
|
|
147
|
+
balance: responseItem.balance,
|
|
148
|
+
coinBalance: responseItem.balance,
|
|
149
|
+
addressBalance: responseItem.addressBalance,
|
|
150
|
+
totalBalance: BigInt(responseItem.balance),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
cursor = response?.hasNextPage ? response.cursor : null;
|
|
154
|
+
} while (cursor);
|
|
116
155
|
|
|
117
156
|
return ret;
|
|
118
157
|
}
|
|
@@ -141,10 +180,10 @@ export default class SuiCoins extends SuiCommonMethods {
|
|
|
141
180
|
}
|
|
142
181
|
|
|
143
182
|
/**
|
|
144
|
-
* Return instance of SuiCoin of specific type
|
|
145
|
-
*
|
|
183
|
+
* Return instance of SuiCoin of specific type. The result is cached per normalized coin type,
|
|
184
|
+
* so repeated calls with equivalent inputs return the same instance.
|
|
185
|
+
*
|
|
146
186
|
* @param {string} coinType - MoveType, or 'SUI' as helper
|
|
147
|
-
*
|
|
148
187
|
* @returns {SuiCoin}
|
|
149
188
|
*/
|
|
150
189
|
get(coinType) {
|
|
@@ -173,7 +212,7 @@ export default class SuiCoins extends SuiCommonMethods {
|
|
|
173
212
|
* @param {SuiMaster} params.suiMaster - instance of SuiMaster
|
|
174
213
|
* @returns {SuiCoins}
|
|
175
214
|
*/
|
|
176
|
-
static getSingleton(params
|
|
215
|
+
static getSingleton(params) {
|
|
177
216
|
const suiMaster = params.suiMaster;
|
|
178
217
|
const connectedChain = suiMaster.connectedChain;
|
|
179
218
|
|