suidouble 2.5.0 → 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 +0 -2
- package/lib/SuiCliCommands.js +18 -25
- package/lib/SuiCoin.js +79 -137
- package/lib/SuiCoins.js +41 -29
- package/lib/SuiCommonMethods.js +40 -3
- package/lib/SuiEvent.js +54 -6
- package/lib/SuiInBrowser.js +143 -15
- package/lib/SuiInBrowserAdapter.js +164 -37
- package/lib/SuiLocalTestValidator.js +76 -14
- package/lib/SuiMaster.js +335 -139
- 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 -127
- package/package.json +29 -13
- 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/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 { TransactionCommands as Commands, Transaction } from '@mysten/sui/transa
|
|
|
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
|
|
@@ -217,6 +233,11 @@ export default class SuiCoin {
|
|
|
217
233
|
return false;
|
|
218
234
|
}
|
|
219
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
|
+
*/
|
|
220
241
|
static isValidMetadata(meta) {
|
|
221
242
|
if (meta && meta.decimals && meta.decimals > 0 && meta.name && meta.symbol) {
|
|
222
243
|
return true;
|
|
@@ -225,10 +246,10 @@ export default class SuiCoin {
|
|
|
225
246
|
}
|
|
226
247
|
|
|
227
248
|
/**
|
|
228
|
-
* Load coin metadata from the blockchain.
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
* @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
|
|
232
253
|
*/
|
|
233
254
|
async getMetadata() {
|
|
234
255
|
if (this._metadata) {
|
|
@@ -243,25 +264,24 @@ export default class SuiCoin {
|
|
|
243
264
|
this.__getMetadataResolver = null;
|
|
244
265
|
this.__getMetadataPromise = new Promise((res)=>{ this.__getMetadataResolver = res; });
|
|
245
266
|
|
|
246
|
-
let result = null;
|
|
247
267
|
try {
|
|
248
|
-
result = await this.suiMaster.client.getCoinMetadata({
|
|
268
|
+
const result = await this.suiMaster.client.getCoinMetadata({
|
|
249
269
|
coinType: this.coinType,
|
|
250
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
|
+
}
|
|
251
277
|
} catch (e) {
|
|
252
278
|
console.error(e);
|
|
253
|
-
result = null;
|
|
254
|
-
}
|
|
255
|
-
if (!result) {
|
|
256
279
|
this._exists = false;
|
|
257
|
-
} else {
|
|
258
|
-
this._metadata = result;
|
|
259
|
-
this._exists = true;
|
|
260
280
|
}
|
|
261
281
|
|
|
262
|
-
this.__getMetadataResolver(
|
|
282
|
+
this.__getMetadataResolver(this._metadata);
|
|
263
283
|
|
|
264
|
-
return
|
|
284
|
+
return this._metadata;
|
|
265
285
|
}
|
|
266
286
|
|
|
267
287
|
/**
|
|
@@ -271,111 +291,33 @@ export default class SuiCoin {
|
|
|
271
291
|
* @returns {Promise.<bigint>}
|
|
272
292
|
*/
|
|
273
293
|
async getBalance(owner) {
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
cursor: cursor,
|
|
283
|
-
});
|
|
284
|
-
coins.push(...result.data);
|
|
285
|
-
|
|
286
|
-
cursor = result.nextCursor;
|
|
287
|
-
} while (result.hasNextPage);
|
|
288
|
-
|
|
289
|
-
let totalAmount = BigInt(0);
|
|
290
|
-
for (const coin of coins) {
|
|
291
|
-
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);
|
|
292
302
|
}
|
|
293
|
-
|
|
294
|
-
return totalAmount;
|
|
295
303
|
}
|
|
296
304
|
|
|
297
305
|
|
|
298
306
|
/**
|
|
299
|
-
*
|
|
300
|
-
*
|
|
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
|
+
*
|
|
301
311
|
* @param {Transaction} txb - Native SUI SDK Transaction
|
|
302
|
-
* @param {string}
|
|
303
|
-
* @param {BigInt|string} amount - amount of coin.
|
|
304
|
-
* @param {boolean} addEmptyCoins - attach coins == 0 to the list
|
|
305
|
-
*
|
|
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)
|
|
306
314
|
* @returns {Promise.<TransactionObjectArgument>}
|
|
307
315
|
*/
|
|
308
|
-
async coinOfAmountToTxCoin(txb,
|
|
316
|
+
async coinOfAmountToTxCoin(txb, _owner, amount) {
|
|
309
317
|
const normalizedAmount = await this.lazyNormalizeAmount(amount);
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (!coinIds || !coinIds.length) {
|
|
315
|
-
throw new Error('you do not have enough coins of type '+this.coinType);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
if (coinIds.length == 1) {
|
|
319
|
-
// only one coin object enough to cover the expense
|
|
320
|
-
if (this.isSUI()) {
|
|
321
|
-
const coinInput = txb.add(Commands.SplitCoins(txb.gas, [txb.pure.u64(expectedAmountAsBigInt)]));
|
|
322
|
-
return coinInput;
|
|
323
|
-
} else {
|
|
324
|
-
// some other coin
|
|
325
|
-
const coinInput = txb.add(Commands.SplitCoins(txb.object(coinIds[0]), [txb.pure.u64(expectedAmountAsBigInt)]));
|
|
326
|
-
return coinInput;
|
|
327
|
-
}
|
|
328
|
-
} else {
|
|
329
|
-
// few coin objects to merge
|
|
330
|
-
const coinIdToMergeIn = coinIds.shift();
|
|
331
|
-
txb.add(Commands.MergeCoins(txb.object(coinIdToMergeIn), coinIds.map((id)=>{return txb.object(id);})));
|
|
332
|
-
const coinInputSplet = txb.add(Commands.SplitCoins(txb.object(coinIdToMergeIn), [txb.pure.u64(expectedAmountAsBigInt)]));
|
|
333
|
-
|
|
334
|
-
return coinInputSplet;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
async coinObjectsEnoughForAmount(owner, expectedAmount, addEmptyCoins = false) {
|
|
339
|
-
const expectedAmountAsBigInt = BigInt(expectedAmount);
|
|
340
|
-
|
|
341
|
-
const coinIds = [];
|
|
342
|
-
const coins = [];
|
|
343
|
-
|
|
344
|
-
let result = null;
|
|
345
|
-
let cursor = null;
|
|
346
|
-
do {
|
|
347
|
-
result = await this.suiMaster.client.getCoins({
|
|
348
|
-
owner: owner,
|
|
349
|
-
coinType: this.coinType,
|
|
350
|
-
limit: 50,
|
|
351
|
-
cursor: cursor,
|
|
352
|
-
});
|
|
353
|
-
coins.push(...result.data);
|
|
354
|
-
|
|
355
|
-
cursor = result.nextCursor;
|
|
356
|
-
} while (result.hasNextPage);
|
|
357
|
-
|
|
358
|
-
coins.sort((a, b) => {
|
|
359
|
-
// From big to small
|
|
360
|
-
return Number(b.balance) - Number(a.balance);
|
|
318
|
+
return txb.coin({
|
|
319
|
+
type: this.coinType,
|
|
320
|
+
balance: BigInt(normalizedAmount),
|
|
361
321
|
});
|
|
362
|
-
|
|
363
|
-
let totalAmount = BigInt(0);
|
|
364
|
-
for (const coin of coins) {
|
|
365
|
-
if (totalAmount <= expectedAmountAsBigInt) {
|
|
366
|
-
coinIds.push(coin.coinObjectId);
|
|
367
|
-
totalAmount = totalAmount + BigInt(coin.balance);// totalAmount.add(coin.balance);
|
|
368
|
-
} else {
|
|
369
|
-
if (addEmptyCoins && BigInt(coin.balance) == 0n) {
|
|
370
|
-
coinIds.push(coin.coinObjectId);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
if (totalAmount >= expectedAmountAsBigInt) {
|
|
376
|
-
return coinIds;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
return null;
|
|
380
322
|
}
|
|
381
323
|
};
|
package/lib/SuiCoins.js
CHANGED
|
@@ -21,10 +21,11 @@ export default class SuiCoins extends SuiCommonMethods {
|
|
|
21
21
|
* SuiCoins constructor
|
|
22
22
|
* @param {Object} params - Initialization parameters
|
|
23
23
|
* @param {SuiMaster} params.suiMaster - instance of SuiMaster
|
|
24
|
+
* @param {boolean} [params.debug]
|
|
24
25
|
*/
|
|
25
|
-
constructor(params
|
|
26
|
+
constructor(params) {
|
|
26
27
|
super(params);
|
|
27
|
-
|
|
28
|
+
|
|
28
29
|
/** @type {SuiMaster} */
|
|
29
30
|
this._suiMaster = params.suiMaster;
|
|
30
31
|
if (!this._suiMaster) {
|
|
@@ -40,17 +41,22 @@ export default class SuiCoins extends SuiCommonMethods {
|
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
/**
|
|
45
|
+
* @type {SuiMaster}
|
|
46
|
+
*/
|
|
43
47
|
get suiMaster() {
|
|
44
48
|
return this._suiMaster;
|
|
45
49
|
}
|
|
46
50
|
|
|
51
|
+
/** @returns {Object.<string, SuiCoin>} map of coinType → SuiCoin instance */
|
|
47
52
|
get coins() {
|
|
48
53
|
return this._coins;
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
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
|
|
54
60
|
* @returns {number} count of processed items
|
|
55
61
|
*/
|
|
56
62
|
setCoinMetas(coinMetas) {
|
|
@@ -80,6 +86,12 @@ export default class SuiCoins extends SuiCommonMethods {
|
|
|
80
86
|
return processedCount;
|
|
81
87
|
}
|
|
82
88
|
|
|
89
|
+
/**
|
|
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
|
+
*/
|
|
83
95
|
static async prefetchMainnetCoinMetas() {
|
|
84
96
|
if (this.__preloadPromise) {
|
|
85
97
|
return this.__preloadPromise;
|
|
@@ -109,12 +121,11 @@ export default class SuiCoins extends SuiCommonMethods {
|
|
|
109
121
|
}
|
|
110
122
|
|
|
111
123
|
/**
|
|
112
|
-
* Get all CoinBalance[] for the specific address or connected address
|
|
113
|
-
*
|
|
114
|
-
* @param {Object} params
|
|
115
|
-
* @param {string
|
|
116
|
-
*
|
|
117
|
-
* @returns {Promise.<Array.<SuidoubleCoinBalance>>}
|
|
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>>}
|
|
118
129
|
*/
|
|
119
130
|
async getAllBalances(params = {}) {
|
|
120
131
|
let owner = params.owner;
|
|
@@ -125,21 +136,22 @@ export default class SuiCoins extends SuiCommonMethods {
|
|
|
125
136
|
/** @type {Array.<SuidoubleCoinBalance>} */
|
|
126
137
|
const ret = [];
|
|
127
138
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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);
|
|
143
155
|
|
|
144
156
|
return ret;
|
|
145
157
|
}
|
|
@@ -168,10 +180,10 @@ export default class SuiCoins extends SuiCommonMethods {
|
|
|
168
180
|
}
|
|
169
181
|
|
|
170
182
|
/**
|
|
171
|
-
* Return instance of SuiCoin of specific type
|
|
172
|
-
*
|
|
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
|
+
*
|
|
173
186
|
* @param {string} coinType - MoveType, or 'SUI' as helper
|
|
174
|
-
*
|
|
175
187
|
* @returns {SuiCoin}
|
|
176
188
|
*/
|
|
177
189
|
get(coinType) {
|
|
@@ -200,7 +212,7 @@ export default class SuiCoins extends SuiCommonMethods {
|
|
|
200
212
|
* @param {SuiMaster} params.suiMaster - instance of SuiMaster
|
|
201
213
|
* @returns {SuiCoins}
|
|
202
214
|
*/
|
|
203
|
-
static getSingleton(params
|
|
215
|
+
static getSingleton(params) {
|
|
204
216
|
const suiMaster = params.suiMaster;
|
|
205
217
|
const connectedChain = suiMaster.connectedChain;
|
|
206
218
|
|
package/lib/SuiCommonMethods.js
CHANGED
|
@@ -1,37 +1,74 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Minimal `CustomEvent` polyfill — `globalThis.CustomEvent` is not universally available
|
|
3
|
+
* in Node runtimes, so suidouble dispatches its own class with a `detail` field.
|
|
4
|
+
*/
|
|
3
5
|
class CustomEvent extends Event {
|
|
4
6
|
#detail;
|
|
5
7
|
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} type
|
|
10
|
+
* @param {EventInit & { detail?: * }} [options]
|
|
11
|
+
*/
|
|
6
12
|
constructor(type, options) {
|
|
7
13
|
super(type, options);
|
|
8
14
|
this.#detail = options?.detail ?? null;
|
|
9
15
|
}
|
|
10
16
|
|
|
17
|
+
/** @returns {*} the payload passed via `options.detail` at construction time, or null */
|
|
11
18
|
get detail() {
|
|
12
19
|
return this.#detail;
|
|
13
20
|
}
|
|
14
21
|
}
|
|
15
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Base class for suidouble domain objects. Extends `EventTarget` so subclasses can emit
|
|
25
|
+
* events via `this.emit(...)` and consumers can subscribe via `addEventListener(...)`.
|
|
26
|
+
* Also provides a debug-gated `log(...)` helper.
|
|
27
|
+
*/
|
|
16
28
|
export default class SuiCommonMethods extends EventTarget {
|
|
29
|
+
/**
|
|
30
|
+
* @param {Object} [params]
|
|
31
|
+
* @param {boolean} [params.debug] - enable debug logging via `log(...)`
|
|
32
|
+
*/
|
|
17
33
|
constructor(params = {}) {
|
|
18
34
|
super();
|
|
19
35
|
|
|
20
36
|
this._debug = !!params.debug;
|
|
21
37
|
}
|
|
22
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Debug logger. No-op unless the instance was constructed with `debug: true`.
|
|
41
|
+
* Prefixes messages with the owning SuiMaster's instance number (if available) and the class name.
|
|
42
|
+
* @param {...any} args
|
|
43
|
+
*/
|
|
23
44
|
log(...args) {
|
|
24
45
|
if (!this._debug) {
|
|
25
46
|
return;
|
|
26
47
|
}
|
|
27
48
|
|
|
28
|
-
|
|
49
|
+
const self = /** @type {any} */ (this);
|
|
50
|
+
let prefix = (self._suiMaster ? (''+self._suiMaster.instanceN+' |') : (self.instanceN ? ''+self.instanceN+' |' : '') );
|
|
29
51
|
|
|
30
52
|
args.unshift(this.constructor.name+' |');
|
|
31
53
|
args.unshift(prefix);
|
|
32
54
|
console.info.apply(null, args);
|
|
33
55
|
}
|
|
34
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Dispatch an event to registered `addEventListener` subscribers.
|
|
59
|
+
*
|
|
60
|
+
* If `data` is a `SuiEvent` (has `isSuiEvent === true`) and `forceCustom` is falsy, it is dispatched
|
|
61
|
+
* directly so its own `.type` (the Move event name) is used — this lets consumers
|
|
62
|
+
* `addEventListener('ChatShopCreated', …)` without wrapping. Otherwise dispatches a `CustomEvent`
|
|
63
|
+
* of type `eventType` with `data` placed on the event's `detail` field.
|
|
64
|
+
*
|
|
65
|
+
* Any dispatch errors are caught and logged to `console.error` so a buggy listener can't abort the
|
|
66
|
+
* surrounding flow.
|
|
67
|
+
*
|
|
68
|
+
* @param {string} eventType - event name when wrapping in a CustomEvent (ignored for direct SuiEvent dispatch)
|
|
69
|
+
* @param {*} data - payload; SuiEvent is dispatched directly, anything else is wrapped
|
|
70
|
+
* @param {boolean} [forceCustom=false] - if true, always wrap in a CustomEvent even for SuiEvent payloads
|
|
71
|
+
*/
|
|
35
72
|
emit(eventType, data, forceCustom = false) {
|
|
36
73
|
try {
|
|
37
74
|
if (data && data.isSuiEvent && !forceCustom) {
|