suidouble 2.5.0 → 2.17.0-1

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 +161 -16
  10. package/lib/SuiInBrowserAdapter.js +192 -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 +195 -0
  44. package/types/lib/SuiInBrowserAdapter.d.ts +173 -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,114 @@ 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
- return await this._activeAdapter.signAndExecuteTransactionBlock(params);
97
+ const enriched = { account: { address: this._connectedAddress }, chain: this._connectedChain, ...params };
98
+ return await this._activeAdapter.signAndExecuteTransactionBlock(enriched);
44
99
  }
45
100
 
101
+ /**
102
+ * Delegates to the active adapter using the current transaction API.
103
+ * @param {Object} params
104
+ * @returns {Promise<*>}
105
+ */
46
106
  async signAndExecuteTransaction(params) {
47
- return await this._activeAdapter.signAndExecuteTransaction(params);
107
+ const enriched = { account: { address: this._connectedAddress }, chain: this._connectedChain, ...params };
108
+ return await this._activeAdapter.signAndExecuteTransaction(enriched);
109
+ }
110
+
111
+ /**
112
+ * Sign a personal/arbitrary message via the active adapter.
113
+ * @param {Object} params
114
+ * @returns {Promise<*>}
115
+ */
116
+ async signPersonalMessage(params) {
117
+ const enriched = { account: { address: this._connectedAddress }, chain: this._connectedChain, ...params };
118
+ return await this._activeAdapter.signPersonalMessage(enriched);
48
119
  }
49
120
 
121
+ /** @returns {*} The gRPC client instance, or null if not yet initialised. */
50
122
  get client() {
51
123
  return this._client;
52
124
  }
53
125
 
126
+ toSuiAddress() {
127
+ return this._connectedAddress;
128
+ }
129
+
130
+ /**
131
+ * Ensures the client is initialised, then returns it.
132
+ * @returns {Promise<*>}
133
+ */
54
134
  async getClient() {
55
135
  await this.initClient();
56
136
  return this._client;
57
137
  }
58
138
 
139
+ /**
140
+ * Ensures the client is initialised, then returns the SuiMaster instance.
141
+ * @returns {Promise<SuiMaster>}
142
+ */
59
143
  async getSuiMaster() {
60
144
  await this.initClient();
61
145
  return this._suiMaster;
62
146
  }
63
147
 
148
+ /** @returns {?SuiMaster} The current SuiMaster instance, or null if not yet initialised. */
64
149
  get suiMaster() {
65
150
  return this._suiMaster;
66
151
  }
67
152
 
153
+ /** @returns {boolean} True if a wallet adapter is currently connected. */
68
154
  get isConnected() {
69
155
  return this._isConnected;
70
156
  }
71
157
 
158
+ /** @returns {?string} The active wallet address, or null if not connected. */
72
159
  get connectedAddress() {
73
160
  return this._connectedAddress;
74
161
  }
75
162
 
163
+ /** @returns {?string} The chain the active adapter is connected to (e.g. 'sui:mainnet'), or null. */
76
164
  get connectedChain() {
77
165
  return this._connectedChain;
78
166
  }
79
167
 
80
168
  static _singleInstances = {};
169
+ /**
170
+ * Returns the singleton SuiInBrowser instance for the given defaultChain, creating it if
171
+ * necessary. Ideal for single-page-app usage where one instance is shared across the app.
172
+ * @param {Object} [params]
173
+ * @param {string} [params.defaultChain]
174
+ * @returns {SuiInBrowser}
175
+ */
81
176
  static getSingleton(params = {}) {
82
177
  let defaultChainKey = params.defaultChain || DEFAULT_CHAIN;
83
178
 
@@ -89,15 +184,20 @@ export default class SuiInBrowser extends SuiCommonMethods {
89
184
  return SuiInBrowser._singleInstances[defaultChainKey];
90
185
  }
91
186
 
187
+ /** @returns {Object.<string, SuiInBrowserAdapter>} Map of adapter name to SuiInBrowserAdapter. */
92
188
  get adapters() {
93
189
  return this._adapters;
94
190
  }
95
191
 
192
+ /**
193
+ * Connects to a named adapter or an adapter instance. Returns false if the adapter is not
194
+ * found in the registry.
195
+ * @param {string|SuiInBrowserAdapter} adapterOrAdapterName
196
+ * @returns {Promise<boolean|void>}
197
+ */
96
198
  async connect(adapterOrAdapterName) {
97
- let adapterName = adapterOrAdapterName;
98
- if (adapterOrAdapterName.name) {
99
- adapterName = adapterOrAdapterName.name;
100
- }
199
+ const a = /** @type {any} */ (adapterOrAdapterName);
200
+ const adapterName = a.name ?? a;
101
201
 
102
202
  if (!this._adapters[adapterName]) {
103
203
  return false;
@@ -111,13 +211,21 @@ export default class SuiInBrowser extends SuiCommonMethods {
111
211
  this.log('error', e);
112
212
  }
113
213
  this._isConnecting = false;
214
+
215
+ if (this._activeAdapter.isConnected && this._connectedAddress !== this._activeAdapter.connectedAddress) {
216
+ this.adapterConnected(this._activeAdapter);
217
+ }
114
218
  }
115
219
 
220
+ /**
221
+ * Called when an adapter fires its 'connected' event. Updates the active adapter, connected
222
+ * address and chain, rebuilds the gRPC client and SuiMaster, and emits 'connected'.
223
+ * @param {SuiInBrowserAdapter} suiInBrowserAdapter
224
+ */
116
225
  adapterConnected(suiInBrowserAdapter) {
117
226
  this._activeAdapter = suiInBrowserAdapter;
118
227
  this._isConnected = suiInBrowserAdapter.isConnected;
119
228
  this._connectedAddress = suiInBrowserAdapter.connectedAddress;
120
- const wasConnectedToChain = this._connectedChain;
121
229
  this._connectedChain = suiInBrowserAdapter.connectedChain;
122
230
 
123
231
  if (this._connectedChain == 'sui:unknown') {
@@ -127,9 +235,9 @@ export default class SuiInBrowser extends SuiCommonMethods {
127
235
  this._client = null;
128
236
  this._suiMaster = null;
129
237
 
130
- this.initClient();
131
-
132
- this.emit('connected');
238
+ this.initClient().then(() => {
239
+ this.emit('connected');
240
+ });
133
241
  }
134
242
 
135
243
  /**
@@ -139,7 +247,7 @@ export default class SuiInBrowser extends SuiCommonMethods {
139
247
  * @param {Object} params.rpc rpc settings
140
248
  * @param {Object.<string, string>} params.rpc.headers rpc headers
141
249
  */
142
- async setRPC(params = {}) {
250
+ async setRPC(params) {
143
251
  this._rpcSettings = params;
144
252
  this._client = null;
145
253
  this._suiMaster = null;
@@ -149,13 +257,29 @@ export default class SuiInBrowser extends SuiCommonMethods {
149
257
  this.emit('rpc');
150
258
  }
151
259
 
152
- adapterDisconnected(suiInBrowserAdapter) {
260
+ /**
261
+ * Called when an adapter fires its 'disconnected' event. Resets connection state and emits
262
+ * 'disconnected'.
263
+ * @param {SuiInBrowserAdapter} [_suiInBrowserAdapter]
264
+ */
265
+ adapterDisconnected(_suiInBrowserAdapter) {
153
266
  this._isConnected = false;
154
267
  this._connectedAddress = null;
155
268
 
156
269
  this.emit('disconnected');
157
270
  }
158
271
 
272
+ /**
273
+ * Registers a wallet adapter in the registry. If the adapter is new, creates a
274
+ * SuiInBrowserAdapter instance, wires up its events, and emits 'adapter'. If the adapter
275
+ * already exists and a standartAdapter is provided, updates it instead.
276
+ * @param {Object} adapterParams
277
+ * @param {string} [adapterParams.name]
278
+ * @param {Object} [adapterParams.standartAdapter]
279
+ * @param {string} [adapterParams.icon]
280
+ * @param {Object} [adapterParams.downloadUrls]
281
+ * @returns {void|false}
282
+ */
159
283
  attachAdapter(adapterParams) {
160
284
  let adapterName = adapterParams.name;
161
285
  if (adapterParams.standartAdapter && adapterParams.standartAdapter.name) {
@@ -170,37 +294,48 @@ export default class SuiInBrowser extends SuiCommonMethods {
170
294
  ...adapterParams,
171
295
  debug: this._debug,
172
296
  });
297
+ adapter._preferredChain = this._defaultChain;
173
298
  if (this._adapters[adapterName]) {
174
299
  // already attached
300
+ this._adapters[adapterName]._preferredChain = this._defaultChain;
175
301
  if (adapterParams.standartAdapter) {
176
302
  this._adapters[adapterName].setStandartAdapter(adapterParams.standartAdapter);
177
303
  }
178
304
  } else {
179
305
  this._adapters[adapterName] = adapter;
180
306
  this._adapters[adapterName].addEventListener('connected', (e)=>{
181
- this.adapterConnected(e.detail);
307
+ this.adapterConnected(/** @type {any} */ (e).detail);
182
308
  });
183
309
  this._adapters[adapterName].addEventListener('disconnected', (e)=>{
184
- this.adapterDisconnected(e.detail);
310
+ this.adapterDisconnected(/** @type {any} */ (e).detail);
185
311
  });
186
312
  this.emit('adapter', adapter);
187
313
  }
188
314
  }
189
315
 
316
+ /**
317
+ * Returns the currently connected chain, or the default chain if no wallet is connected.
318
+ * @returns {string}
319
+ */
190
320
  getCurrentChain() {
191
321
  return this._connectedChain ? this._connectedChain : this._defaultChain;
192
322
  }
193
323
 
324
+ /**
325
+ * Builds the gRPC client and SuiMaster for the current chain. No-op if already built.
326
+ * Uses _rpcSettings when a custom RPC endpoint has been set via setRPC.
327
+ * @returns {Promise<void>}
328
+ */
194
329
  async initClient() {
195
330
  if (this._client) {
196
- return true;
331
+ return;
197
332
  }
198
333
 
199
334
  let chainName = this.getCurrentChain();
200
335
 
201
336
  if (this._rpcSettings) {
202
337
  this._rpcSettings.providerName = chainName.split('sui:').join('');
203
- this._client = SuiMaster.SuiUtils.suiClientForRPC(this._rpcSettings);
338
+ this._client = SuiMaster.SuiUtils.suiClientForRPC(this._rpcSettings.providerName ?? chainName, this._rpcSettings);
204
339
  } else if (!chainName) {
205
340
  this.log('error', 'invalid chain', chainName);
206
341
  throw new Error('invalid chain: '+chainName);
@@ -215,6 +350,11 @@ export default class SuiInBrowser extends SuiCommonMethods {
215
350
  });
216
351
  }
217
352
 
353
+ /**
354
+ * Seeds the adapter registry from the known-wallets list and the live Wallet Standard
355
+ * registry, then subscribes to future 'register' events for dynamically installed wallets.
356
+ * @returns {Promise<void>}
357
+ */
218
358
  async initialize() {
219
359
  await this.initClient(); // set default client
220
360
 
@@ -241,6 +381,11 @@ export default class SuiInBrowser extends SuiCommonMethods {
241
381
  });
242
382
  }
243
383
 
384
+ /**
385
+ * Returns the list of known wallet placeholder params used to pre-populate the adapter
386
+ * registry before the Wallet Standard registry is queried.
387
+ * @returns {Array<{name: string, icon: string, downloadUrls: Object}>}
388
+ */
244
389
  static getPossibleWallets() {
245
390
  return [
246
391
  {