suidouble 2.5.0 → 2.17.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.
Files changed (57) hide show
  1. package/.claude/settings.local.json +7 -0
  2. package/README.md +222 -131
  3. package/index.js +0 -2
  4. package/lib/SuiCliCommands.js +18 -25
  5. package/lib/SuiCoin.js +79 -137
  6. package/lib/SuiCoins.js +41 -29
  7. package/lib/SuiCommonMethods.js +40 -3
  8. package/lib/SuiEvent.js +54 -6
  9. package/lib/SuiInBrowser.js +143 -15
  10. package/lib/SuiInBrowserAdapter.js +185 -40
  11. package/lib/SuiLocalTestValidator.js +76 -14
  12. package/lib/SuiMaster.js +335 -139
  13. package/lib/SuiMemoryObjectStorage.js +66 -73
  14. package/lib/SuiObject.js +128 -153
  15. package/lib/SuiPackage.js +292 -187
  16. package/lib/SuiPackageModule.js +176 -221
  17. package/lib/SuiPaginatedResponse.js +288 -25
  18. package/lib/SuiPseudoRandomAddress.js +29 -2
  19. package/lib/SuiTransaction.js +115 -70
  20. package/lib/SuiUtils.js +179 -127
  21. package/package.json +29 -13
  22. package/test/build_modules.test.js +41 -0
  23. package/test/coins.test.js +17 -16
  24. package/test/custom_transaction.test.js +167 -0
  25. package/test/event_listeners.test.js +171 -0
  26. package/test/failed_transaction.test.js +184 -0
  27. package/test/name_service.test.js +28 -0
  28. package/test/owned_objects.test.js +148 -0
  29. package/test/rpc.test.js +3 -6
  30. package/test/sui_in_browser.test.js +2 -2
  31. package/test/sui_master_basic.test.js +4 -5
  32. package/test/sui_master_onlocal.test.js +84 -22
  33. package/test/sui_object_properties.test.js +85 -0
  34. package/test/test_move_contracts/different_types/Move.lock +18 -0
  35. package/test/test_move_contracts/suidouble_chat/Move.lock +18 -0
  36. package/tsconfig.json +15 -0
  37. package/types/index.d.ts +15 -0
  38. package/types/lib/SuiCliCommands.d.ts +6 -0
  39. package/types/lib/SuiCoin.d.ts +183 -0
  40. package/types/lib/SuiCoins.d.ts +93 -0
  41. package/types/lib/SuiCommonMethods.d.ts +37 -0
  42. package/types/lib/SuiEvent.d.ts +95 -0
  43. package/types/lib/SuiInBrowser.d.ts +189 -0
  44. package/types/lib/SuiInBrowserAdapter.d.ts +171 -0
  45. package/types/lib/SuiLocalTestValidator.d.ts +92 -0
  46. package/types/lib/SuiMaster.d.ts +333 -0
  47. package/types/lib/SuiMemoryObjectStorage.d.ts +96 -0
  48. package/types/lib/SuiObject.d.ts +135 -0
  49. package/types/lib/SuiPackage.d.ts +233 -0
  50. package/types/lib/SuiPackageModule.d.ts +139 -0
  51. package/types/lib/SuiPaginatedResponse.d.ts +148 -0
  52. package/types/lib/SuiPseudoRandomAddress.d.ts +33 -0
  53. package/types/lib/SuiTransaction.d.ts +92 -0
  54. package/types/lib/SuiUtils.d.ts +152 -0
  55. package/types/lib/data/icons.d.ts +12 -0
  56. package/lib/SuiTestScenario.js +0 -169
  57. package/test/sui_test_scenario.test.js +0 -61
package/lib/SuiEvent.js CHANGED
@@ -1,27 +1,57 @@
1
+ /**
2
+ * @typedef {import("./SuiMaster.js").default} SuiMaster
3
+ *
4
+ * @typedef SuiEventData
5
+ * @type {object}
6
+ * @property {string} [type] - Fully-qualified Move type of the event, e.g. `<pkg>::<module>::<EventName>`
7
+ * @property {Object} [parsedJson] - JSON representation of the event's Move struct contents
8
+ * @property {string|number} [timestampMs] - Timestamp (ms since epoch) of the checkpoint the event's tx was finalized in
9
+ * @property {string} [sender] - SuiAddress of the sender of the emitting transaction
10
+ * @property {string} [transactionDigest] - Digest of the emitting transaction
11
+ * @property {string|number} [sequenceNumber] - Event position within its transaction
12
+ * @property {string|Uint8Array} [bcs] - BCS bytes (base64 string from GraphQL, Uint8Array from gRPC)
13
+ */
1
14
 
2
-
15
+ /**
16
+ * Wrapper over a Sui event. Extends the native `Event` so it can be dispatched through
17
+ * `EventTarget`-style listeners; also exposes the common Sui event fields directly.
18
+ */
3
19
  export default class SuiEvent extends Event {
4
- constructor(params = {}) {
20
+ /**
21
+ * @param {Object} params - Initialization parameters
22
+ * @param {SuiMaster} params.suiMaster - instance of SuiMaster
23
+ * @param {SuiEventData} [params.data] - raw event data
24
+ * @param {boolean} [params.debug] - enable debug logging
25
+ */
26
+ constructor(params) {
5
27
  const typeName = params.data ? ((''+params.data.type).split('<')[0].split('::').pop()) : null;
6
28
  super(typeName, {});
7
29
 
8
30
  this._debug = !!params.debug;
31
+ /** @type {SuiMaster} */
9
32
  this._suiMaster = params.suiMaster;
10
33
  if (!this._suiMaster) {
11
- throw new Error('suiMaster is requried for suiPackage');
34
+ throw new Error('suiMaster is required for SuiEvent');
12
35
  }
13
36
 
37
+ /** @type {SuiEventData} */
14
38
  this._data = params.data || {};
15
39
 
16
40
  this.detail = this; // quick backward support as this is the instance of CustomEvent
17
41
  }
18
42
 
43
+ /**
44
+ * Debug logger. No-op unless the instance was constructed with `debug: true`.
45
+ * Prefixes messages with the SuiMaster instance number and the class name.
46
+ * @param {...any} args
47
+ */
19
48
  log(...args) {
20
49
  if (!this._debug) {
21
50
  return;
22
51
  }
23
52
 
24
- let prefix = (this._suiMaster ? (''+this._suiMaster.instanceN+' |') : (this.instanceN ? ''+this.instanceN+' |' : '') );
53
+ const self = /** @type {any} */ (this);
54
+ let prefix = (self._suiMaster ? (''+self._suiMaster.instanceN+' |') : (self.instanceN ? ''+self.instanceN+' |' : '') );
25
55
  // prefix += this.constructor.name+' | ';
26
56
 
27
57
  args.unshift(this.constructor.name+' |');
@@ -29,25 +59,39 @@ export default class SuiEvent extends Event {
29
59
  console.info.apply(null, args);
30
60
  }
31
61
 
62
+ /** Brand property for duck-typing. @returns {true} */
32
63
  get isSuiEvent() {
33
64
  return true;
34
65
  }
35
66
 
36
67
  /**
37
- * In module type name, without package and module prefix and without <T..> suffix
68
+ * In-module type name last segment of `package::module::Name<...>`, without package/module prefix or `<T..>` suffix.
69
+ * @returns {?string}
38
70
  */
39
71
  get typeName() {
40
72
  return this._data ? (''+this._data.type).split('<')[0].split('::').pop() : null;
41
73
  }
42
74
 
75
+ /**
76
+ * Fully-qualified Move type of the event, e.g. `<pkg>::<module>::<EventName>[<type-params>]`.
77
+ * @returns {?string}
78
+ */
43
79
  get type() {
44
80
  return this._data ? (''+this._data.type) : null;
45
81
  }
46
82
 
83
+ /**
84
+ * Raw event data as received from the client.
85
+ * @returns {SuiEventData}
86
+ */
47
87
  get data() {
48
88
  return this._data;
49
89
  }
50
90
 
91
+ /**
92
+ * JSON representation of the event's Move struct contents.
93
+ * @returns {?Object}
94
+ */
51
95
  get parsedJson() {
52
96
  if (this._data.parsedJson) {
53
97
  return this._data.parsedJson;
@@ -55,9 +99,13 @@ export default class SuiEvent extends Event {
55
99
  return null;
56
100
  }
57
101
 
102
+ /**
103
+ * Timestamp (milliseconds since epoch) of the checkpoint the emitting transaction was finalized in.
104
+ * @returns {?number}
105
+ */
58
106
  get timestampMs() {
59
107
  if (this._data.timestampMs) {
60
- return parseInt(this._data.timestampMs, 10);
108
+ return parseInt('' + this._data.timestampMs, 10);
61
109
  } else {
62
110
  return null;
63
111
  }
@@ -2,28 +2,62 @@ import SuiCommonMethods from './SuiCommonMethods.js';
2
2
  import SuiInBrowserAdapter from './SuiInBrowserAdapter.js';
3
3
  import { getWallets } from '@wallet-standard/core';
4
4
  import SuiMaster from './SuiMaster.js';
5
+ import { toBase64 } from '@mysten/bcs';
5
6
 
6
7
  import icons from './data/icons.js';
7
8
 
9
+ /**
10
+ * @typedef {import("@mysten/sui/grpc").SuiGrpcClient} SuiGrpcClient
11
+ * @typedef {import("@mysten/sui/cryptography").SignatureWithBytes} SignatureWithBytes
12
+ */
13
+
14
+
8
15
  const DEFAULT_CHAIN = 'sui:devnet';
9
16
 
17
+ /**
18
+ * Browser-side entry point for wallet integration. Manages a registry of SuiInBrowserAdapter
19
+ * instances (one per known wallet), tracks the active connection, and creates a SuiMaster
20
+ * pointing at the connected chain.
21
+ *
22
+ * Starts initialising itself 50 ms after construction so callers have time to attach listeners.
23
+ * Use SuiInBrowser.getSingleton({ defaultChain }) for the typical single-page-app pattern.
24
+ *
25
+ * Events emitted:
26
+ * - 'adapter' — a new SuiInBrowserAdapter was registered (detail: adapter)
27
+ * - 'connected' — the active adapter connected
28
+ * - 'disconnected' — the active adapter disconnected
29
+ * - 'rpc' — the RPC endpoint was changed via setRPC
30
+ */
10
31
  export default class SuiInBrowser extends SuiCommonMethods {
32
+ /**
33
+ * @param {Object} [params]
34
+ * @param {string} [params.defaultChain='sui:devnet'] - chain used before any wallet connects
35
+ * @param {boolean} [params.debug]
36
+ */
11
37
  constructor(params = {}) {
12
38
  super(params);
13
39
 
40
+ /** @type {Object.<string, SuiInBrowserAdapter>} */
14
41
  this._adapters = {};
15
42
 
43
+ /** @type {string} */
16
44
  this._defaultChain = params.defaultChain || DEFAULT_CHAIN;
17
45
 
46
+ /** @type {?SuiInBrowserAdapter} */
18
47
  this._activeAdapter = null;
48
+ /** @type {?string} */
19
49
  this._connectedAddress = null;
50
+ /** @type {?string} */
20
51
  this._connectedChain = null;
52
+ /** @type {boolean} */
21
53
  this._isConnected = false;
54
+ /** @type {boolean} */
22
55
  this._isConnecting = false;
23
56
 
24
57
  this._client = null;
25
58
  this._suiMaster = null;
26
59
 
60
+ /** @type {?Object} */
27
61
  this._rpcSettings = null;
28
62
 
29
63
  setTimeout(()=>{
@@ -31,53 +65,103 @@ export default class SuiInBrowser extends SuiCommonMethods {
31
65
  }, 50);
32
66
  }
33
67
 
68
+ /** @returns {?SuiInBrowserAdapter} The currently active wallet adapter, or null. */
34
69
  get activeAdapter() {
35
70
  return this._activeAdapter;
36
71
  }
37
72
 
73
+ /**
74
+ * Returns the currently connected wallet address, or null if not connected.
75
+ * @returns {?string}
76
+ */
38
77
  getAddress() {
39
78
  return this._connectedAddress;
40
79
  }
41
80
 
81
+ /**
82
+ * Sign a transaction using the current `sui:signTransaction` feature.
83
+ * @param {Object} params
84
+ * @returns {Promise<SignatureWithBytes>}
85
+ */
86
+ async signTransaction(params) {
87
+ const enriched = { account: { address: this._connectedAddress }, chain: this._connectedChain, ...params };
88
+ return await this._activeAdapter.signTransaction(enriched);
89
+ }
90
+
91
+ /**
92
+ * Delegates to the active adapter using the legacy transaction-block API.
93
+ * @param {Object} params
94
+ * @returns {Promise<*>}
95
+ */
42
96
  async signAndExecuteTransactionBlock(params) {
43
97
  return await this._activeAdapter.signAndExecuteTransactionBlock(params);
44
98
  }
45
99
 
100
+ /**
101
+ * Delegates to the active adapter using the current transaction API.
102
+ * @param {Object} params
103
+ * @returns {Promise<*>}
104
+ */
46
105
  async signAndExecuteTransaction(params) {
47
- return await this._activeAdapter.signAndExecuteTransaction(params);
106
+ const enriched = { account: { address: this._connectedAddress }, chain: this._connectedChain, ...params };
107
+ return await this._activeAdapter.signAndExecuteTransaction(enriched);
48
108
  }
49
109
 
110
+ /** @returns {*} The gRPC client instance, or null if not yet initialised. */
50
111
  get client() {
51
112
  return this._client;
52
113
  }
53
114
 
115
+ toSuiAddress() {
116
+ return this._connectedAddress;
117
+ }
118
+
119
+ /**
120
+ * Ensures the client is initialised, then returns it.
121
+ * @returns {Promise<*>}
122
+ */
54
123
  async getClient() {
55
124
  await this.initClient();
56
125
  return this._client;
57
126
  }
58
127
 
128
+ /**
129
+ * Ensures the client is initialised, then returns the SuiMaster instance.
130
+ * @returns {Promise<SuiMaster>}
131
+ */
59
132
  async getSuiMaster() {
60
133
  await this.initClient();
61
134
  return this._suiMaster;
62
135
  }
63
136
 
137
+ /** @returns {?SuiMaster} The current SuiMaster instance, or null if not yet initialised. */
64
138
  get suiMaster() {
65
139
  return this._suiMaster;
66
140
  }
67
141
 
142
+ /** @returns {boolean} True if a wallet adapter is currently connected. */
68
143
  get isConnected() {
69
144
  return this._isConnected;
70
145
  }
71
146
 
147
+ /** @returns {?string} The active wallet address, or null if not connected. */
72
148
  get connectedAddress() {
73
149
  return this._connectedAddress;
74
150
  }
75
151
 
152
+ /** @returns {?string} The chain the active adapter is connected to (e.g. 'sui:mainnet'), or null. */
76
153
  get connectedChain() {
77
154
  return this._connectedChain;
78
155
  }
79
156
 
80
157
  static _singleInstances = {};
158
+ /**
159
+ * Returns the singleton SuiInBrowser instance for the given defaultChain, creating it if
160
+ * necessary. Ideal for single-page-app usage where one instance is shared across the app.
161
+ * @param {Object} [params]
162
+ * @param {string} [params.defaultChain]
163
+ * @returns {SuiInBrowser}
164
+ */
81
165
  static getSingleton(params = {}) {
82
166
  let defaultChainKey = params.defaultChain || DEFAULT_CHAIN;
83
167
 
@@ -89,15 +173,20 @@ export default class SuiInBrowser extends SuiCommonMethods {
89
173
  return SuiInBrowser._singleInstances[defaultChainKey];
90
174
  }
91
175
 
176
+ /** @returns {Object.<string, SuiInBrowserAdapter>} Map of adapter name to SuiInBrowserAdapter. */
92
177
  get adapters() {
93
178
  return this._adapters;
94
179
  }
95
180
 
181
+ /**
182
+ * Connects to a named adapter or an adapter instance. Returns false if the adapter is not
183
+ * found in the registry.
184
+ * @param {string|SuiInBrowserAdapter} adapterOrAdapterName
185
+ * @returns {Promise<boolean|void>}
186
+ */
96
187
  async connect(adapterOrAdapterName) {
97
- let adapterName = adapterOrAdapterName;
98
- if (adapterOrAdapterName.name) {
99
- adapterName = adapterOrAdapterName.name;
100
- }
188
+ const a = /** @type {any} */ (adapterOrAdapterName);
189
+ const adapterName = a.name ?? a;
101
190
 
102
191
  if (!this._adapters[adapterName]) {
103
192
  return false;
@@ -113,11 +202,15 @@ export default class SuiInBrowser extends SuiCommonMethods {
113
202
  this._isConnecting = false;
114
203
  }
115
204
 
205
+ /**
206
+ * Called when an adapter fires its 'connected' event. Updates the active adapter, connected
207
+ * address and chain, rebuilds the gRPC client and SuiMaster, and emits 'connected'.
208
+ * @param {SuiInBrowserAdapter} suiInBrowserAdapter
209
+ */
116
210
  adapterConnected(suiInBrowserAdapter) {
117
211
  this._activeAdapter = suiInBrowserAdapter;
118
212
  this._isConnected = suiInBrowserAdapter.isConnected;
119
213
  this._connectedAddress = suiInBrowserAdapter.connectedAddress;
120
- const wasConnectedToChain = this._connectedChain;
121
214
  this._connectedChain = suiInBrowserAdapter.connectedChain;
122
215
 
123
216
  if (this._connectedChain == 'sui:unknown') {
@@ -127,9 +220,9 @@ export default class SuiInBrowser extends SuiCommonMethods {
127
220
  this._client = null;
128
221
  this._suiMaster = null;
129
222
 
130
- this.initClient();
131
-
132
- this.emit('connected');
223
+ this.initClient().then(() => {
224
+ this.emit('connected');
225
+ });
133
226
  }
134
227
 
135
228
  /**
@@ -139,7 +232,7 @@ export default class SuiInBrowser extends SuiCommonMethods {
139
232
  * @param {Object} params.rpc rpc settings
140
233
  * @param {Object.<string, string>} params.rpc.headers rpc headers
141
234
  */
142
- async setRPC(params = {}) {
235
+ async setRPC(params) {
143
236
  this._rpcSettings = params;
144
237
  this._client = null;
145
238
  this._suiMaster = null;
@@ -149,13 +242,29 @@ export default class SuiInBrowser extends SuiCommonMethods {
149
242
  this.emit('rpc');
150
243
  }
151
244
 
152
- adapterDisconnected(suiInBrowserAdapter) {
245
+ /**
246
+ * Called when an adapter fires its 'disconnected' event. Resets connection state and emits
247
+ * 'disconnected'.
248
+ * @param {SuiInBrowserAdapter} [_suiInBrowserAdapter]
249
+ */
250
+ adapterDisconnected(_suiInBrowserAdapter) {
153
251
  this._isConnected = false;
154
252
  this._connectedAddress = null;
155
253
 
156
254
  this.emit('disconnected');
157
255
  }
158
256
 
257
+ /**
258
+ * Registers a wallet adapter in the registry. If the adapter is new, creates a
259
+ * SuiInBrowserAdapter instance, wires up its events, and emits 'adapter'. If the adapter
260
+ * already exists and a standartAdapter is provided, updates it instead.
261
+ * @param {Object} adapterParams
262
+ * @param {string} [adapterParams.name]
263
+ * @param {Object} [adapterParams.standartAdapter]
264
+ * @param {string} [adapterParams.icon]
265
+ * @param {Object} [adapterParams.downloadUrls]
266
+ * @returns {void|false}
267
+ */
159
268
  attachAdapter(adapterParams) {
160
269
  let adapterName = adapterParams.name;
161
270
  if (adapterParams.standartAdapter && adapterParams.standartAdapter.name) {
@@ -178,29 +287,38 @@ export default class SuiInBrowser extends SuiCommonMethods {
178
287
  } else {
179
288
  this._adapters[adapterName] = adapter;
180
289
  this._adapters[adapterName].addEventListener('connected', (e)=>{
181
- this.adapterConnected(e.detail);
290
+ this.adapterConnected(/** @type {any} */ (e).detail);
182
291
  });
183
292
  this._adapters[adapterName].addEventListener('disconnected', (e)=>{
184
- this.adapterDisconnected(e.detail);
293
+ this.adapterDisconnected(/** @type {any} */ (e).detail);
185
294
  });
186
295
  this.emit('adapter', adapter);
187
296
  }
188
297
  }
189
298
 
299
+ /**
300
+ * Returns the currently connected chain, or the default chain if no wallet is connected.
301
+ * @returns {string}
302
+ */
190
303
  getCurrentChain() {
191
304
  return this._connectedChain ? this._connectedChain : this._defaultChain;
192
305
  }
193
306
 
307
+ /**
308
+ * Builds the gRPC client and SuiMaster for the current chain. No-op if already built.
309
+ * Uses _rpcSettings when a custom RPC endpoint has been set via setRPC.
310
+ * @returns {Promise<void>}
311
+ */
194
312
  async initClient() {
195
313
  if (this._client) {
196
- return true;
314
+ return;
197
315
  }
198
316
 
199
317
  let chainName = this.getCurrentChain();
200
318
 
201
319
  if (this._rpcSettings) {
202
320
  this._rpcSettings.providerName = chainName.split('sui:').join('');
203
- this._client = SuiMaster.SuiUtils.suiClientForRPC(this._rpcSettings);
321
+ this._client = SuiMaster.SuiUtils.suiClientForRPC(this._rpcSettings.providerName ?? chainName, this._rpcSettings);
204
322
  } else if (!chainName) {
205
323
  this.log('error', 'invalid chain', chainName);
206
324
  throw new Error('invalid chain: '+chainName);
@@ -215,6 +333,11 @@ export default class SuiInBrowser extends SuiCommonMethods {
215
333
  });
216
334
  }
217
335
 
336
+ /**
337
+ * Seeds the adapter registry from the known-wallets list and the live Wallet Standard
338
+ * registry, then subscribes to future 'register' events for dynamically installed wallets.
339
+ * @returns {Promise<void>}
340
+ */
218
341
  async initialize() {
219
342
  await this.initClient(); // set default client
220
343
 
@@ -241,6 +364,11 @@ export default class SuiInBrowser extends SuiCommonMethods {
241
364
  });
242
365
  }
243
366
 
367
+ /**
368
+ * Returns the list of known wallet placeholder params used to pre-populate the adapter
369
+ * registry before the Wallet Standard registry is queried.
370
+ * @returns {Array<{name: string, icon: string, downloadUrls: Object}>}
371
+ */
244
372
  static getPossibleWallets() {
245
373
  return [
246
374
  {