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.
Files changed (59) hide show
  1. package/.claude/settings.local.json +7 -0
  2. package/README.md +222 -131
  3. package/index.js +1 -3
  4. package/lib/SuiCliCommands.js +18 -25
  5. package/lib/SuiCoin.js +86 -138
  6. package/lib/SuiCoins.js +70 -31
  7. package/lib/SuiCommonMethods.js +40 -3
  8. package/lib/SuiEvent.js +54 -6
  9. package/lib/SuiInBrowser.js +145 -46
  10. package/lib/SuiInBrowserAdapter.js +164 -37
  11. package/lib/SuiLocalTestValidator.js +78 -25
  12. package/lib/SuiMaster.js +351 -126
  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 -124
  21. package/package.json +30 -14
  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 -21
  35. package/test/test_move_contracts/different_types/sources/different_types.move +12 -12
  36. package/test/test_move_contracts/suidouble_chat/Move.lock +18 -22
  37. package/test/test_move_contracts/suidouble_chat/sources/suidouble_chat.move +9 -8
  38. package/tsconfig.json +15 -0
  39. package/types/index.d.ts +15 -0
  40. package/types/lib/SuiCliCommands.d.ts +6 -0
  41. package/types/lib/SuiCoin.d.ts +183 -0
  42. package/types/lib/SuiCoins.d.ts +93 -0
  43. package/types/lib/SuiCommonMethods.d.ts +37 -0
  44. package/types/lib/SuiEvent.d.ts +95 -0
  45. package/types/lib/SuiInBrowser.d.ts +189 -0
  46. package/types/lib/SuiInBrowserAdapter.d.ts +167 -0
  47. package/types/lib/SuiLocalTestValidator.d.ts +92 -0
  48. package/types/lib/SuiMaster.d.ts +333 -0
  49. package/types/lib/SuiMemoryObjectStorage.d.ts +96 -0
  50. package/types/lib/SuiObject.d.ts +135 -0
  51. package/types/lib/SuiPackage.d.ts +233 -0
  52. package/types/lib/SuiPackageModule.d.ts +139 -0
  53. package/types/lib/SuiPaginatedResponse.d.ts +148 -0
  54. package/types/lib/SuiPseudoRandomAddress.d.ts +33 -0
  55. package/types/lib/SuiTransaction.d.ts +92 -0
  56. package/types/lib/SuiUtils.d.ts +152 -0
  57. package/types/lib/data/icons.d.ts +12 -0
  58. package/lib/SuiTestScenario.js +0 -169
  59. package/test/sui_test_scenario.test.js +0 -61
@@ -1,37 +1,59 @@
1
1
  import SuiCommonMethods from './SuiCommonMethods.js';
2
2
 
3
+ /**
4
+ * @import { SuiClientTypes } from "@mysten/sui/client"
5
+ * @typedef {import("./SuiMaster.js").default} SuiMaster
6
+ * @typedef {import("./SuiObject.js").default} SuiObject
7
+ * @typedef {import("./SuiEvent.js").default} SuiEvent
8
+ */
9
+
10
+ /**
11
+ * Wrapper over a Sui transaction result. Lazily parses `effects.changedObjects` into
12
+ * typed `created`, `mutated`, `deleted` lists and wraps events in `SuiEvent` instances.
13
+ */
3
14
  export default class SuiTransaction extends SuiCommonMethods {
4
- constructor(params = {}) {
15
+ /**
16
+ * @param {Object} params
17
+ * @param {SuiMaster} params.suiMaster
18
+ * @param {SuiClientTypes.Transaction} params.data - raw transaction data
19
+ * @param {boolean} [params.debug]
20
+ */
21
+ constructor(params) {
5
22
  super(params);
6
23
 
7
24
  this._debug = !!params.debug;
25
+ /** @type {SuiMaster} */
8
26
  this._suiMaster = params.suiMaster;
9
27
  if (!this._suiMaster) {
10
28
  throw new Error('suiMaster is requried');
11
29
  }
12
30
 
13
- this._data = params.data || {};
31
+ /** @type {?SuiClientTypes.Transaction} */
32
+ this._data = params.data;
14
33
 
34
+ /** @type {?{created: SuiObject[], mutated: SuiObject[], deleted: SuiObject[], objects: SuiObject[]}} */
15
35
  this._results = null;
36
+ /** @type {?SuiEvent[]} */
16
37
  this._events = null;
17
38
  }
18
39
 
40
+ /** @returns {?number} epoch the transaction executed in, or null if unavailable */
19
41
  get executedEpoch() {
20
- if (this._data && this._data.effects && this._data.effects.executedEpoch) {
21
- return BigInt(this._data.effects.executedEpoch);
42
+ if (this._data && this._data.epoch) {
43
+ return Number(this._data.epoch);
22
44
  }
23
-
24
45
  return null;
25
46
  }
26
47
 
48
+ /** @returns {?string} Base58-encoded transaction digest, or null if data isn't set */
27
49
  get digest() {
28
50
  if (this._data) {
29
51
  return this._data.digest;
30
52
  }
31
-
32
53
  return null;
33
54
  }
34
55
 
56
+ /** @returns {?bigint} net gas cost in MIST (computationCost + storageCost − storageRebate), or null */
35
57
  get gasUsed() {
36
58
  try {
37
59
  if (this._data && this._data.effects && this._data.effects.gasUsed) {
@@ -44,43 +66,41 @@ export default class SuiTransaction extends SuiCommonMethods {
44
66
  return null;
45
67
  }
46
68
 
69
+ /** @returns {SuiObject[]} objects deleted by this transaction */
47
70
  get deleted() {
48
71
  const results = this.results;
49
72
  return results.deleted;
50
73
  }
51
74
 
75
+ /** @returns {SuiObject[]} objects mutated (version changed) by this transaction */
52
76
  get mutated() {
53
77
  const results = this.results;
54
78
  return results.mutated;
55
79
  }
56
80
 
81
+ /** @returns {SuiObject[]} objects created by this transaction */
57
82
  get created() {
58
83
  const results = this.results;
59
84
  return results.created;
60
85
  }
61
86
 
87
+ /** @returns {?SuiClientTypes.Transaction} raw transaction data as received from the client */
62
88
  get data() {
63
89
  return this._data;
64
90
  }
65
91
 
66
- get status() {
67
- let status = null;
68
- if (this.data && this.data.effects && this.data.effects.status && this.data.effects.status.status) {
69
- status = this.data.effects.status.status;
70
- }
71
- return status;
72
- }
73
-
92
+ /**
93
+ * Returns true if the transaction's status indicates success.
94
+ * @returns {boolean}
95
+ */
74
96
  isSuccessful() {
75
- if (this.data && this.data.effects && this.data.effects.status && this.data.effects.status.status) {
76
- if (this.data.effects.status.status == 'success') {
77
- return true;
78
- }
97
+ if (this.data && this.data.status && this.data.status.success) {
98
+ return true;
79
99
  }
80
-
81
100
  return false;
82
101
  }
83
102
 
103
+ /** @returns {SuiEvent[]} events emitted by this transaction, lazily built and cached */
84
104
  get events() {
85
105
  if (this._events) {
86
106
  return this._events;
@@ -93,7 +113,13 @@ export default class SuiTransaction extends SuiCommonMethods {
93
113
  const suiEvent = new this._suiMaster.SuiEvent({
94
114
  suiMaster: this._suiMaster,
95
115
  debug: this._debug,
96
- data: event,
116
+ data: {
117
+ // map gRPC SuiClientTypes.Event shape → SuiEventData shape
118
+ type: event.eventType,
119
+ parsedJson: event.json,
120
+ sender: event.sender,
121
+ bcs: event.bcs,
122
+ },
97
123
  });
98
124
 
99
125
  events.push(suiEvent);
@@ -104,70 +130,52 @@ export default class SuiTransaction extends SuiCommonMethods {
104
130
  return this._events;
105
131
  }
106
132
 
133
+ /**
134
+ * Parsed object-change results, lazily built from `effects.changedObjects` and cached.
135
+ * @returns {{created: SuiObject[], mutated: SuiObject[], deleted: SuiObject[], objects: SuiObject[]}}
136
+ */
107
137
  get results() {
108
138
  if (this._results) {
109
139
  return this._results;
110
140
  }
111
141
 
142
+ /** @type {Object.<string, SuiObject>} */
112
143
  const objects = {};
113
144
 
114
145
  const listCreated = [];
115
146
  const listMutated = [];
116
147
  const listDeleted = [];
117
148
 
118
- if (this.data.objectChanges) {
119
- for (const objectChange of this.data.objectChanges) {
149
+ if (this.data?.effects?.changedObjects) {
150
+ for (const objectChange of this.data.effects.changedObjects) {
120
151
  if (objectChange.objectId) {
121
- if (objects[objectChange.objectId]) {
122
-
123
- } else {
152
+ const objectId = objectChange.objectId;
153
+ const idOperation = objectChange.idOperation;
154
+ const typeGuess =
155
+ (this.data.objectTypes && this.data.objectTypes[objectChange.objectId]) ?
156
+ this.data.objectTypes[objectChange.objectId] : undefined;
157
+
158
+ if (!objects[objectId]) {
124
159
  const obj = new this._suiMaster.SuiObject({
125
160
  suiMaster: this._suiMaster,
126
161
  debug: this._debug,
127
- objectChange: objectChange,
162
+ id: objectChange.objectId,
163
+ version: objectChange.outputVersion,
164
+ type: typeGuess,
128
165
  });
129
- if (obj.address) {
130
- objects[obj.address] = obj;
131
- }
132
- }
133
- }
134
- }
135
- }
136
-
137
- if (this.data.effects) {
138
- const events = ['created', 'mutated']; // events names are the same as properties in result.effects
139
-
140
- for (const eventName of events) {
141
- if ( this.data.effects[eventName] && this.data.effects[eventName].length) {
142
- for (const effect of this.data.effects[eventName]) {
143
- if (effect.reference && effect.reference.objectId) {
144
- if (objects[effect.reference.objectId]) {
145
- if (eventName === 'created') {
146
- listCreated.push(objects[effect.reference.objectId]);
147
- } else if (eventName === 'mutated') {
148
- listMutated.push(objects[effect.reference.objectId]);
149
- }
150
- }
151
- }
166
+ objects[objectId] = obj;
152
167
  }
153
- }
154
- }
155
-
156
- if (this.data.effects.deleted) {
157
- for (const effect of this.data.effects.deleted) {
158
- if (effect.objectId) {
159
- if (objects[effect.objectId]) {
160
-
161
- } else {
162
- const obj = new this._suiMaster.SuiObject({
163
- suiMaster: this._suiMaster,
164
- debug: this._debug,
165
- objectChange: effect,
166
- });
167
- objects[effect.objectId] = obj;
168
- }
169
- objects[effect.objectId].markAsDeleted();
170
- listDeleted.push(objects[effect.objectId]);
168
+
169
+
170
+ if (idOperation == 'Created') {
171
+ listCreated.push(objects[objectId]);
172
+ } else if (idOperation == 'Deleted') {
173
+ listDeleted.push(objects[objectId]);
174
+ objects[objectId].markAsDeleted();
175
+ } else if (objectChange.inputVersion && objectChange.outputVersion
176
+ && objectChange.inputVersion != objectChange.outputVersion
177
+ ) {
178
+ listMutated.push(objects[objectId]);
171
179
  }
172
180
  }
173
181
  }
@@ -178,15 +186,52 @@ export default class SuiTransaction extends SuiCommonMethods {
178
186
  mutated: listMutated,
179
187
  deleted: listDeleted,
180
188
  objects: Object.values(objects),
181
- status: this.status,
182
189
  };
183
190
 
184
191
  return this._results;
185
192
  }
186
193
 
194
+ /**
195
+ * Wait for this transaction to be finalised on chain, then refresh `_data` with the full result.
196
+ * Clears the cached `_results` and `_events` so getters recompute from the fresh data.
197
+ *
198
+ * @param {SuiClientTypes.WaitForTransactionOptions} [input] - extra options forwarded to `client.waitForTransaction`
199
+ * @returns {Promise<this>}
200
+ * @throws if `digest` is not set
201
+ */
202
+ async waitForTransaction(input = /** @type {SuiClientTypes.WaitForTransactionOptions<{}>} */ ({})) {
203
+ if (!this.digest) {
204
+ throw new Error('can not waitForTransaction: digest is not set');
205
+ }
206
+
207
+ const result = await this._suiMaster.client.waitForTransaction(
208
+ /** @type {SuiClientTypes.WaitForTransactionOptions<{}>} */ ({
209
+ ...input,
210
+ digest: this.digest,
211
+ })
212
+ );
213
+
214
+ this._data = result.Transaction;
215
+ this._results = null;
216
+ this._events = null;
217
+
218
+ return this;
219
+ }
220
+
221
+ /**
222
+ * Checkpoint timestamp in milliseconds since epoch, or null if unavailable.
223
+ *
224
+ * Only populated when the transaction was fetched via the GraphQL path
225
+ * (`SuiMaster.fetchTransactions`). Transactions returned from `signAndExecuteTransaction`
226
+ * or `waitForTransaction` (gRPC) do not carry a timestamp in their data shape and will
227
+ * always return null here.
228
+ *
229
+ * @returns {?number}
230
+ */
187
231
  get timestampMs() {
188
- if (this.data.timestampMs) {
189
- return parseInt(''+this.data.timestampMs, 10);
232
+ const ts = /** @type {any} */ (this.data)?.timestampMs;
233
+ if (ts) {
234
+ return parseInt(''+ts, 10);
190
235
  } else {
191
236
  return null;
192
237
  }
package/lib/SuiUtils.js CHANGED
@@ -2,14 +2,17 @@ import SuiCommonMethods from './SuiCommonMethods.js';
2
2
  import { Inputs, Transaction } from '@mysten/sui/transactions';
3
3
  import { bcs } from '@mysten/sui/bcs';
4
4
  import { fromBase64 } from '@mysten/bcs';
5
- import { SuiClient, getFullnodeUrl, SuiHTTPTransport } from '@mysten/sui/client';
5
+ import { SuiGrpcClient, GrpcWebFetchTransport } from '@mysten/sui/grpc';
6
+ import { SuiGraphQLClient } from '@mysten/sui/graphql';
6
7
  import { normalizeSuiAddress } from '@mysten/sui/utils';
7
- import WebSocket from 'websocket';
8
-
9
- const WebSocketClient = WebSocket.w3cwebsocket;
10
8
 
11
9
  /**
10
+ * @import { SuiClientTypes } from "@mysten/sui/client"
12
11
  * @typedef {import("@mysten/sui/transactions").Argument} Argument
12
+ * @typedef {import("@mysten/sui/transactions").CallArg} CallArg
13
+ * @typedef {{ Pure: { bytes: string } }} PureInput
14
+ * @typedef {import("@mysten/sui/grpc").SuiGrpcClientOptions} SuiGrpcClientOptions
15
+ * @typedef {import("@mysten/sui/grpc").GrpcWebOptions} GrpcWebOptions
13
16
  */
14
17
 
15
18
  export { normalizeSuiAddress };
@@ -21,16 +24,16 @@ export default class SuiUtils extends SuiCommonMethods {
21
24
  * Attach the parameter input into transaction, to be used for moveCall
22
25
  * accepts an Inputs.Pure (result of .pureInput) or type + value directly
23
26
  *
24
- * @param {Transaction} tx
25
- * @param {string | Inputs.Pure} typeOrInput
26
- * @param {?Argument} value
27
- * @returns Argument
27
+ * @param {Transaction} tx
28
+ * @param {string | PureInput} typeOrInput - string type name, or already-serialised Pure input `{ Pure: { bytes } }`
29
+ * @param {*} [value]
30
+ * @returns {Argument}
28
31
  */
29
32
  static txInput(tx, typeOrInput, value = null) {
30
- if (typeOrInput && typeOrInput.Pure && typeOrInput.Pure.bytes) {
31
- return tx.pure(SuiUtils.pureInputToBytes(typeOrInput));
33
+ if (typeOrInput && /** @type {any} */ (typeOrInput).Pure && /** @type {any} */ (typeOrInput).Pure.bytes) {
34
+ return tx.pure(SuiUtils.pureInputToBytes(/** @type {PureInput} */ (typeOrInput)));
32
35
  } else {
33
- return tx.pure(SuiUtils.pureInputToBytes(SuiUtils.pureInput(typeOrInput, value)));
36
+ return tx.pure(SuiUtils.pureInputToBytes(/** @type {any} */ (SuiUtils.pureInput(/** @type {string} */ (typeOrInput), value))));
34
37
  }
35
38
  }
36
39
 
@@ -47,9 +50,9 @@ export default class SuiUtils extends SuiCommonMethods {
47
50
  *
48
51
  * if you are going to construct tx yourself, you'd better use SuiUtils.txInput static method
49
52
  *
50
- * @param {string} type
51
- * @param {value} value
52
- * @returns Inputs.Pure
53
+ * @param {string} type
54
+ * @param {*} value
55
+ * @returns {CallArg}
53
56
  */
54
57
  static pureInput(type, value) {
55
58
  let typeNormalized = type;
@@ -76,16 +79,43 @@ export default class SuiUtils extends SuiCommonMethods {
76
79
  }
77
80
  }
78
81
  }
82
+
83
+ throw new Error(`pureInput: unsupported type "${typeNormalized}"`);
79
84
  }
80
85
 
81
86
  /**
82
87
  * Convert sui's PureInput into bcs serialized bytes
83
- * @param {Inputs.Pure} pureInput
88
+ * @param {PureInput} pureInput
84
89
  */
85
90
  static pureInputToBytes(pureInput) {
86
91
  return fromBase64(pureInput.Pure.bytes);
87
92
  }
88
93
 
94
+ /**
95
+ * Decode a Move `vector<u8>` field into a UTF-8 string.
96
+ *
97
+ * Accepts the shapes SUI returns across representations:
98
+ * - base64 string (v2 gRPC/GraphQL JSON representation for byte vectors)
99
+ * - Uint8Array
100
+ * - array of numbers (legacy JSON-RPC shape)
101
+ *
102
+ * @param {string | Uint8Array | number[]} value
103
+ * @returns {string}
104
+ */
105
+ static bytesFieldToString(value) {
106
+ if (value == null) return '';
107
+ if (typeof value === 'string') {
108
+ return new TextDecoder().decode(fromBase64(value));
109
+ }
110
+ if (value instanceof Uint8Array) {
111
+ return new TextDecoder().decode(value);
112
+ }
113
+ if (Array.isArray(value)) {
114
+ return new TextDecoder().decode(new Uint8Array(value));
115
+ }
116
+ throw new Error('bytesFieldToString: unsupported value type');
117
+ }
118
+
89
119
  /**
90
120
  * Wrapper for sui's utils normalizeSuiAddress
91
121
  * Perform the following operations:
@@ -103,136 +133,161 @@ export default class SuiUtils extends SuiCommonMethods {
103
133
  }
104
134
 
105
135
  /**
106
- * As SUI removed websocket dependency for a node, we'll have it here as constructor wrapper
107
- * returning native WebSocket in browser and websocket's w3cwebsocket in node
108
- * @returns WebSocketClient
136
+ * Makes an instance of SuiGrpcClient pointed at a custom gRPC-web endpoint.
137
+ *
138
+ * `transportOptions` is passed through to GrpcWebFetchTransport — the same shape
139
+ * as `@protobuf-ts/grpcweb-transport`'s GrpcWebOptions:
140
+ * - baseUrl : string — required, the gRPC-web endpoint
141
+ * - format : 'text' | 'binary' — wire format, defaults to 'text'
142
+ * - fetchInit : RequestInit-ish — extra fetch options (credentials, cache, ...); `body`/`headers`/`method`/`signal` are reserved
143
+ * - fetch : typeof fetch — custom fetch implementation (useful for proxies, auth, logging)
144
+ *
145
+ * @example
146
+ * const client = SuiUtils.suiClientForRPC('mainnet', {
147
+ * baseUrl: 'https://fullnode.mainnet.sui.io:443',
148
+ * format: 'binary',
149
+ * fetchInit: { credentials: 'include' },
150
+ * fetch: (url, init) => fetch(url, { ...init, headers: { ...init.headers, 'x-api-key': 'xxx' } }),
151
+ * });
152
+ *
153
+ * @param {SuiClientTypes.Network} network
154
+ * @param {GrpcWebOptions} transportOptions
155
+ * @returns {SuiGrpcClient}
109
156
  */
110
- static WebSocketConstructor() {
111
- return WebSocketClient;
157
+ static suiClientForRPC(network, transportOptions) {
158
+ const transport = new GrpcWebFetchTransport(transportOptions);
159
+ return new SuiGrpcClient({ network, transport });
112
160
  }
113
161
 
114
162
  /**
115
- * Makes an instance for SuiClient for a specific chain using RPC
116
- * @param {Object} params parameters
117
- * @param {string} params.chainname 'mainnet', 'devnet', 'testnet', 'localnet'
118
- * @param {string} params.url rpc url
119
- * @param {Object} params.rpc rpc settings
120
- * @param {Object.<string, string>} params.rpc.headers rpc headers
121
- * @returns {SuiClient}
163
+ * Build a SuiGraphQLClient reusing the network of an existing SuiGrpcClient.
164
+ *
165
+ * Picks the default Sui GraphQL endpoint for the network unless `overrides.url` is set.
166
+ * `overrides` is forwarded to SuiGraphQLClient (fetch, headers, queries, mvr, url).
167
+ *
168
+ * @param {SuiGrpcClient} grpcClient
169
+ * @param {Object} [overrides]
170
+ * @param {?string} [overrides.url]
171
+ * @param {?typeof fetch} [overrides.fetch]
172
+ * @param {?Object.<string, string>} [overrides.headers]
173
+ * @returns {SuiGraphQLClient}
122
174
  */
123
- static suiClientForRPC(params = {}) {
124
- const providerName = params.providerName || params.chainname || params.chain;
125
- delete params.providerName;
126
- delete params.chainName;
127
- delete params.chain;
128
- params.WebSocketConstructor = SuiUtils.WebSocketConstructor();
175
+ static suiGraphQLClientFromGrpc(grpcClient, overrides = {}) {
176
+ if (!grpcClient || !grpcClient.network) {
177
+ throw new Error('grpcClient with a network is required');
178
+ }
129
179
 
130
- const transport = new SuiHTTPTransport(params);
131
- const client = new SuiClient({ transport: transport });
180
+ const defaultUrls = {
181
+ mainnet: 'https://sui-mainnet.mystenlabs.com/graphql',
182
+ testnet: 'https://graphql.testnet.sui.io/graphql',
183
+ devnet: 'https://graphql.devnet.sui.io/graphql',
184
+ localnet: 'http://127.0.0.1:9125/graphql',
185
+ };
132
186
 
133
- client.providerName = providerName;
187
+ const url = overrides.url || defaultUrls[grpcClient.network];
188
+ if (!url) {
189
+ throw new Error(`no default GraphQL url for network "${grpcClient.network}" — pass overrides.url`);
190
+ }
134
191
 
135
- return client;
192
+ return new SuiGraphQLClient({
193
+ ...overrides,
194
+ url,
195
+ network: grpcClient.network,
196
+ });
136
197
  }
137
198
 
138
199
  /**
139
- * Makes an instance for SuiClient for a specific chain, eg: 'mainnet'
140
- * @param {string} chainname
141
- * @returns {SuiClient}
200
+ * Makes an instance for SuiGrpcClient for a specific chain, eg: 'mainnet' | 'testnet' | 'devnet' | 'localnet'
201
+ * @param {SuiClientTypes.Network} network
202
+ * @returns {SuiGrpcClient}
142
203
  */
143
- static suiClientFor(chainname) {
144
- return new SuiClient({
145
- transport: new SuiHTTPTransport({
146
- url: getFullnodeUrl(chainname),
147
- WebSocketConstructor: SuiUtils.WebSocketConstructor(),
148
- }),
149
- });
204
+ static suiClientFor(network) {
205
+ // strip optional 'sui:' prefix so both 'mainnet' and 'sui:mainnet' work
206
+ const normalized = SuiUtils.normalizeNetworkString(network) ?? network;
207
+ const baseUrls = {
208
+ mainnet: 'https://fullnode.mainnet.sui.io:443',
209
+ testnet: 'https://fullnode.testnet.sui.io:443',
210
+ devnet: 'https://fullnode.devnet.sui.io:443',
211
+ localnet: 'http://127.0.0.1:9000',
212
+ };
213
+ return SuiUtils.suiClientForRPC(normalized, { baseUrl: baseUrls[normalized] });
150
214
  }
151
215
 
152
216
  /**
153
- * Normalize SuiClient parameter, accepting:
154
- * - different previous versions of sui.js library,
155
- * - object of SuiClient class
156
- * - object of SuiLocalTestValidator class
157
- * - string of the chain name to connect to
158
- *
159
- * @returns {SuiClient}
217
+ * Normalize client parameter into a SuiGrpcClient, accepting:
218
+ * - an existing SuiGrpcClient instance
219
+ * - a SuiLocalTestValidator instance
220
+ * - a string chain name: 'local' | 'localnet' | 'dev' | 'devnet' | 'test' | 'testnet' | 'main' | 'mainnet'
221
+ * - a legacy client object (sniffs network from endpoint / connection.fullnode / providerName)
222
+ *
223
+ * @param {SuiGrpcClient | string | Object} clientParam
224
+ * @returns {SuiGrpcClient | null}
160
225
  */
161
226
  static normalizeClient(clientParam) {
162
- let client = null;
163
- let providerName = null;
164
-
165
- if (clientParam) {
166
- if (clientParam == 'local' || (clientParam.constructor && clientParam.constructor.name && clientParam.constructor.name == 'SuiLocalTestValidator')) {
167
- if (clientParam == 'local') {
168
- client = SuiUtils.suiClientFor('localnet');
169
- providerName = 'sui:localnet';
170
- } else {
171
- // SuiLocalTestValidator
172
- providerName = clientParam.providerName;
173
- client = clientParam.client;
174
- }
175
- } else if (clientParam == 'test' || clientParam == 'testnet') {
176
- client = SuiUtils.suiClientFor('testnet');
177
- providerName = 'sui:testnet';
178
- } else if (clientParam == 'dev' || clientParam == 'devnet') {
179
- client = SuiUtils.suiClientFor('devnet');
180
- providerName = 'sui:devnet';
181
- } else if (clientParam == 'main' || clientParam == 'mainnet') {
182
- client = SuiUtils.suiClientFor('mainnet');
183
- providerName = 'sui:mainnet';
184
- } else {
185
- if (clientParam && clientParam.constructor && (clientParam.endpoint || clientParam.transport)) {
186
- client = clientParam;
187
- let url = '';
188
- if (clientParam.endpoint) {
189
- // workaround set in SuiInBrowserAdapter
190
- url = clientParam.endpoint;
191
- } else if (clientParam.transport && clientParam.transport.websocketClient && clientParam.transport.websocketClient.endpoint) {
192
- url = clientParam.transport.websocketClient.endpoint;
193
- }
194
-
195
- if (clientParam.providerName) {
196
- providerName = clientParam.providerName;
197
- if (['devnet', 'mainnet', 'testnet', 'localnet'].indexOf(clientParam.providerName) != -1) {
198
- providerName = 'sui:'+clientParam.providerName; // no prefix - add prefix
199
- }
200
- } else if (url.indexOf('devnet') !== -1) {
201
- providerName = 'sui:devnet';
202
- } else if (url.indexOf('testnet') !== -1) {
203
- providerName = 'sui:testnet';
204
- } else if (url.indexOf('mainnet') !== -1) {
205
- providerName = 'sui:mainnet';
206
- } else if (url.indexOf('127.0.0.1') !== -1) {
207
- providerName = 'sui:localnet';
208
- } else {
209
- // just keep provider name as unique to fullnode URL to keep separate ObjectStorage instances
210
- providerName = url.split('//')[1];
211
- }
212
- } else if (clientParam && clientParam.connection && clientParam.connection.fullnode) {
213
- client = clientParam;
214
-
215
- if (clientParam.connection.fullnode.indexOf('devnet') !== -1) {
216
- providerName = 'sui:devnet';
217
- } else if (clientParam.connection.fullnode.indexOf('testnet') !== -1) {
218
- providerName = 'sui:testnet';
219
- } else if (clientParam.connection.fullnode.indexOf('mainnet') !== -1) {
220
- providerName = 'sui:mainnet';
221
- } else if (clientParam.connection.fullnode.indexOf('127.0.0.1') !== -1) {
222
- providerName = 'sui:localnet';
223
- } else {
224
- // just keep provider name as unique to fullnode URL to keep separate ObjectStorage instances
225
- providerName = clientParam.connection.fullnode;
226
- }
227
- }
228
- }
227
+ if (!clientParam) {
228
+ return null;
229
+ }
230
+
231
+ if (clientParam instanceof SuiGrpcClient) {
232
+ return clientParam;
229
233
  }
230
234
 
231
- if (client) {
232
- client.providerName = providerName;
233
- return client;
235
+ const network = SuiUtils._sniffNetwork(clientParam);
236
+ if (!network) {
237
+ return null;
234
238
  }
235
239
 
240
+ return SuiUtils.suiClientFor(network);
241
+ }
242
+
243
+ /**
244
+ * Strip an optional "sui:" prefix and map short aliases ('local', 'dev', 'test', 'main')
245
+ * to full network names. Returns null if the string doesn't match a known network.
246
+ *
247
+ * @param {string} s
248
+ * @returns {SuiClientTypes.Network | null}
249
+ */
250
+ static normalizeNetworkString(s) {
251
+ if (typeof s !== 'string') return null;
252
+ const normalized = s.toLowerCase().replace(/^sui:/, '');
253
+ if (normalized === 'local' || normalized === 'localnet') return 'localnet';
254
+ if (normalized === 'dev' || normalized === 'devnet') return 'devnet';
255
+ if (normalized === 'test' || normalized === 'testnet') return 'testnet';
256
+ if (normalized === 'main' || normalized === 'mainnet') return 'mainnet';
257
+ return null;
258
+ }
259
+
260
+ /**
261
+ * Extracts a SuiClientTypes.Network string from various inputs used historically by suidouble.
262
+ *
263
+ * @param {string | Object} clientParam
264
+ * @returns {SuiClientTypes.Network | null}
265
+ */
266
+ static _sniffNetwork(clientParam) {
267
+ if (typeof clientParam === 'string') {
268
+ return SuiUtils.normalizeNetworkString(clientParam);
269
+ }
270
+
271
+ if (clientParam.constructor && clientParam.constructor.name === 'SuiLocalTestValidator') {
272
+ return SuiUtils.normalizeNetworkString(clientParam.providerName) || 'localnet';
273
+ }
274
+
275
+ const fromNetwork = SuiUtils.normalizeNetworkString(clientParam.network);
276
+ if (fromNetwork) return fromNetwork;
277
+
278
+ const fromProvider = SuiUtils.normalizeNetworkString(clientParam.providerName);
279
+ if (fromProvider) return fromProvider;
280
+
281
+ const url = clientParam.endpoint
282
+ || (clientParam.transport && clientParam.transport.websocketClient && clientParam.transport.websocketClient.endpoint)
283
+ || (clientParam.connection && clientParam.connection.fullnode)
284
+ || '';
285
+
286
+ if (url.indexOf('devnet') !== -1) return 'devnet';
287
+ if (url.indexOf('testnet') !== -1) return 'testnet';
288
+ if (url.indexOf('mainnet') !== -1) return 'mainnet';
289
+ if (url.indexOf('127.0.0.1') !== -1 || url.indexOf('localhost') !== -1) return 'localnet';
290
+
236
291
  return null;
237
292
  }
238
293