@mysten/sui 1.8.0 → 1.9.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 (53) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/cjs/transactions/Transaction.d.ts +6 -0
  3. package/dist/cjs/transactions/Transaction.js +20 -8
  4. package/dist/cjs/transactions/Transaction.js.map +2 -2
  5. package/dist/cjs/transactions/index.d.ts +2 -0
  6. package/dist/cjs/transactions/index.js +2 -0
  7. package/dist/cjs/transactions/index.js.map +2 -2
  8. package/dist/cjs/transactions/plugins/NamedPackagesPlugin.d.ts +49 -0
  9. package/dist/cjs/transactions/plugins/NamedPackagesPlugin.js +78 -0
  10. package/dist/cjs/transactions/plugins/NamedPackagesPlugin.js.map +7 -0
  11. package/dist/cjs/transactions/plugins/utils.d.ts +27 -0
  12. package/dist/cjs/transactions/plugins/utils.js +96 -0
  13. package/dist/cjs/transactions/plugins/utils.js.map +7 -0
  14. package/dist/cjs/utils/index.d.ts +1 -0
  15. package/dist/cjs/utils/index.js +3 -0
  16. package/dist/cjs/utils/index.js.map +2 -2
  17. package/dist/cjs/utils/move-registry.d.ts +6 -0
  18. package/dist/cjs/utils/move-registry.js +36 -0
  19. package/dist/cjs/utils/move-registry.js.map +7 -0
  20. package/dist/cjs/version.d.ts +2 -2
  21. package/dist/cjs/version.js +2 -2
  22. package/dist/cjs/version.js.map +1 -1
  23. package/dist/esm/transactions/Transaction.d.ts +6 -0
  24. package/dist/esm/transactions/Transaction.js +20 -8
  25. package/dist/esm/transactions/Transaction.js.map +2 -2
  26. package/dist/esm/transactions/index.d.ts +2 -0
  27. package/dist/esm/transactions/index.js +4 -0
  28. package/dist/esm/transactions/index.js.map +2 -2
  29. package/dist/esm/transactions/plugins/NamedPackagesPlugin.d.ts +49 -0
  30. package/dist/esm/transactions/plugins/NamedPackagesPlugin.js +58 -0
  31. package/dist/esm/transactions/plugins/NamedPackagesPlugin.js.map +7 -0
  32. package/dist/esm/transactions/plugins/utils.d.ts +27 -0
  33. package/dist/esm/transactions/plugins/utils.js +76 -0
  34. package/dist/esm/transactions/plugins/utils.js.map +7 -0
  35. package/dist/esm/utils/index.d.ts +1 -0
  36. package/dist/esm/utils/index.js +3 -0
  37. package/dist/esm/utils/index.js.map +2 -2
  38. package/dist/esm/utils/move-registry.d.ts +6 -0
  39. package/dist/esm/utils/move-registry.js +16 -0
  40. package/dist/esm/utils/move-registry.js.map +7 -0
  41. package/dist/esm/version.d.ts +2 -2
  42. package/dist/esm/version.js +2 -2
  43. package/dist/esm/version.js.map +1 -1
  44. package/dist/tsconfig.esm.tsbuildinfo +1 -1
  45. package/dist/tsconfig.tsbuildinfo +1 -1
  46. package/package.json +1 -1
  47. package/src/transactions/Transaction.ts +43 -13
  48. package/src/transactions/index.ts +7 -0
  49. package/src/transactions/plugins/NamedPackagesPlugin.ts +129 -0
  50. package/src/transactions/plugins/utils.ts +118 -0
  51. package/src/utils/index.ts +2 -0
  52. package/src/utils/move-registry.ts +24 -0
  53. package/src/version.ts +2 -2
@@ -99,19 +99,23 @@ export function isTransaction(obj: unknown): obj is Transaction {
99
99
 
100
100
  export type TransactionObjectInput = string | CallArg | TransactionObjectArgument;
101
101
 
102
- const modulePluginRegistry = {
103
- buildPlugins: [] as TransactionPlugin[],
104
- serializationPlugins: [] as TransactionPlugin[],
102
+ interface TransactionPluginRegistry {
103
+ // eslint-disable-next-line @typescript-eslint/ban-types
104
+ buildPlugins: Map<string | Function, TransactionPlugin>;
105
+ // eslint-disable-next-line @typescript-eslint/ban-types
106
+ serializationPlugins: Map<string | Function, TransactionPlugin>;
107
+ }
108
+
109
+ const modulePluginRegistry: TransactionPluginRegistry = {
110
+ buildPlugins: new Map(),
111
+ serializationPlugins: new Map(),
105
112
  };
106
113
 
107
114
  const TRANSACTION_REGISTRY_KEY = Symbol.for('@mysten/transaction/registry');
108
115
  function getGlobalPluginRegistry() {
109
116
  try {
110
117
  const target = globalThis as {
111
- [TRANSACTION_REGISTRY_KEY]?: {
112
- buildPlugins: TransactionPlugin[];
113
- serializationPlugins: TransactionPlugin[];
114
- };
118
+ [TRANSACTION_REGISTRY_KEY]?: TransactionPluginRegistry;
115
119
  };
116
120
 
117
121
  if (!target[TRANSACTION_REGISTRY_KEY]) {
@@ -168,12 +172,38 @@ export class Transaction {
168
172
  return newTransaction;
169
173
  }
170
174
 
171
- static registerGlobalSerializationPlugin(step: TransactionPlugin) {
172
- getGlobalPluginRegistry().serializationPlugins.push(step);
175
+ /** @deprecated global plugins should be registered with a name */
176
+ static registerGlobalSerializationPlugin(step: TransactionPlugin): void;
177
+ static registerGlobalSerializationPlugin(name: string, step: TransactionPlugin): void;
178
+ static registerGlobalSerializationPlugin(
179
+ stepOrStep: TransactionPlugin | string,
180
+ step?: TransactionPlugin,
181
+ ) {
182
+ getGlobalPluginRegistry().serializationPlugins.set(
183
+ stepOrStep,
184
+ step ?? (stepOrStep as TransactionPlugin),
185
+ );
186
+ }
187
+
188
+ static unregisterGlobalSerializationPlugin(name: string) {
189
+ getGlobalPluginRegistry().serializationPlugins.delete(name);
190
+ }
191
+
192
+ /** @deprecated global plugins should be registered with a name */
193
+ static registerGlobalBuildPlugin(step: TransactionPlugin): void;
194
+ static registerGlobalBuildPlugin(name: string, step: TransactionPlugin): void;
195
+ static registerGlobalBuildPlugin(
196
+ stepOrStep: TransactionPlugin | string,
197
+ step?: TransactionPlugin,
198
+ ) {
199
+ getGlobalPluginRegistry().buildPlugins.set(
200
+ stepOrStep,
201
+ step ?? (stepOrStep as TransactionPlugin),
202
+ );
173
203
  }
174
204
 
175
- static registerGlobalBuildPlugin(step: TransactionPlugin) {
176
- getGlobalPluginRegistry().buildPlugins.push(step);
205
+ static unregisterGlobalBuildPlugin(name: string) {
206
+ getGlobalPluginRegistry().buildPlugins.delete(name);
177
207
  }
178
208
 
179
209
  addSerializationPlugin(step: TransactionPlugin) {
@@ -277,8 +307,8 @@ export class Transaction {
277
307
  constructor() {
278
308
  const globalPlugins = getGlobalPluginRegistry();
279
309
  this.#data = new TransactionDataBuilder();
280
- this.#buildPlugins = [...globalPlugins.buildPlugins];
281
- this.#serializationPlugins = [...globalPlugins.serializationPlugins];
310
+ this.#buildPlugins = [...globalPlugins.buildPlugins.values()];
311
+ this.#serializationPlugins = [...globalPlugins.serializationPlugins.values()];
282
312
  }
283
313
 
284
314
  /** Returns an argument for the gas coin, to be used in a transaction. */
@@ -47,3 +47,10 @@ export type {
47
47
  } from './json-rpc-resolver.js';
48
48
 
49
49
  export { Arguments } from './Arguments.js';
50
+
51
+ export {
52
+ namedPackagesPlugin,
53
+ type NamedPackagesPluginOptions,
54
+ } from './plugins/NamedPackagesPlugin.js';
55
+
56
+ export { type NamedPackagesPluginCache } from './plugins/utils.js';
@@ -0,0 +1,129 @@
1
+ // Copyright (c) Mysten Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import type { SuiGraphQLClient } from '../../graphql/client.js';
5
+ import type { BuildTransactionOptions } from '../json-rpc-resolver.js';
6
+ import type { TransactionDataBuilder } from '../TransactionData.js';
7
+ import type { NamedPackagesPluginCache, NameResolutionRequest } from './utils.js';
8
+ import { findTransactionBlockNames, listToRequests, replaceNames } from './utils.js';
9
+
10
+ export type NamedPackagesPluginOptions = {
11
+ /**
12
+ * The SuiGraphQLClient to use for resolving names.
13
+ * The endpoint should be the GraphQL endpoint of the network you are targeting.
14
+ * For non-mainnet networks, if the plugin doesn't work as expected, you need to validate that the
15
+ * RPC provider has support for the `packageByName` and `typeByName` queries (using external resolver).
16
+ */
17
+ suiGraphQLClient: SuiGraphQLClient;
18
+ /**
19
+ * The number of names to resolve in each batch request.
20
+ * Needs to be calculated based on the GraphQL query limits.
21
+ */
22
+ pageSize?: number;
23
+ /**
24
+ * Local overrides for the resolution plugin. Pass this to pre-populate
25
+ * the cache with known packages / types (especially useful for local or CI testing).
26
+ *
27
+ * Expected format example:
28
+ * {
29
+ * packages: {
30
+ * 'std@framework': '0x1234',
31
+ * },
32
+ * types: {
33
+ * 'std@framework::string::String': '0x1234::string::String',
34
+ * },
35
+ * }
36
+ *
37
+ */
38
+ overrides?: NamedPackagesPluginCache;
39
+ };
40
+
41
+ /**
42
+ * @experimental This plugin is in experimental phase and there might be breaking changes in the future
43
+ *
44
+ * Adds named resolution so that you can use .move names in your transactions.
45
+ * e.g. `app@org::type::Type` will be resolved to `0x1234::type::Type`.
46
+ * This plugin will resolve all names & types in the transaction block.
47
+ *
48
+ * To install this plugin globally in your app, use:
49
+ * ```
50
+ * Transaction.registerGlobalSerializationPlugin("namedPackagesPlugin", namedPackagesPlugin({ suiGraphQLClient }));
51
+ * ```
52
+ *
53
+ * You can also define `overrides` to pre-populate name resolutions locally (removes the GraphQL request).
54
+ */
55
+ export const namedPackagesPlugin = ({
56
+ suiGraphQLClient,
57
+ pageSize = 10,
58
+ overrides = { packages: {}, types: {} },
59
+ }: NamedPackagesPluginOptions) => {
60
+ const cache = {
61
+ packages: { ...overrides.packages },
62
+ types: { ...overrides.types },
63
+ };
64
+
65
+ return async (
66
+ transactionData: TransactionDataBuilder,
67
+ _buildOptions: BuildTransactionOptions,
68
+ next: () => Promise<void>,
69
+ ) => {
70
+ const names = findTransactionBlockNames(transactionData);
71
+ const batches = listToRequests(
72
+ {
73
+ packages: names.packages.filter((x) => !cache.packages[x]),
74
+ types: names.types.filter((x) => !cache.types[x]),
75
+ },
76
+ pageSize,
77
+ );
78
+
79
+ // now we need to bulk resolve all the names + types, and replace them in the transaction data.
80
+ (await Promise.all(batches.map((batch) => query(suiGraphQLClient, batch)))).forEach((res) => {
81
+ Object.assign(cache.types, res.types);
82
+ Object.assign(cache.packages, res.packages);
83
+ });
84
+
85
+ replaceNames(transactionData, cache);
86
+
87
+ await next();
88
+ };
89
+
90
+ async function query(client: SuiGraphQLClient, requests: NameResolutionRequest[]) {
91
+ const results: NamedPackagesPluginCache = { packages: {}, types: {} };
92
+ // avoid making a request if there are no names to resolve.
93
+ if (requests.length === 0) return results;
94
+
95
+ // Create multiple queries for each name / type we need to resolve
96
+ // TODO: Replace with bulk APIs when available.
97
+ const gqlQuery = `{
98
+ ${requests.map((req) => {
99
+ const request = req.type === 'package' ? 'packageByName' : 'typeByName';
100
+ const fields = req.type === 'package' ? 'address' : 'repr';
101
+
102
+ return `${gqlQueryKey(req.id)}: ${request}(name:"${req.name}") {
103
+ ${fields}
104
+ }`;
105
+ })}
106
+ }`;
107
+
108
+ const result = await client.query({
109
+ query: gqlQuery,
110
+ variables: undefined,
111
+ });
112
+
113
+ if (result.errors) throw new Error(JSON.stringify({ query: gqlQuery, errors: result.errors }));
114
+
115
+ // Parse the results and create a map of `<name|type> -> <address|repr>`
116
+ for (const req of requests) {
117
+ const key = gqlQueryKey(req.id);
118
+ if (!result.data || !result.data[key]) throw new Error(`No result found for: ${req.name}`);
119
+ const data = result.data[key] as { address?: string; repr?: string };
120
+
121
+ if (req.type === 'package') results.packages[req.name] = data.address!;
122
+ if (req.type === 'moveType') results.types[req.name] = data.repr!;
123
+ }
124
+
125
+ return results;
126
+ }
127
+ };
128
+
129
+ const gqlQueryKey = (idx: number) => `key_${idx}`;
@@ -0,0 +1,118 @@
1
+ // Copyright (c) Mysten Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { isValidNamedPackage, isValidNamedType } from '../../utils/move-registry.js';
5
+ import type { TransactionDataBuilder } from '../TransactionData.js';
6
+
7
+ export type NamedPackagesPluginCache = {
8
+ packages: Record<string, string>;
9
+ types: Record<string, string>;
10
+ };
11
+
12
+ const NAME_SEPARATOR = '@';
13
+
14
+ export type NameResolutionRequest = {
15
+ id: number;
16
+ type: 'package' | 'moveType';
17
+ name: string;
18
+ };
19
+
20
+ /**
21
+ * Looks up all `.move` names in a transaction block.
22
+ * Returns a list of all the names found.
23
+ */
24
+ export const findTransactionBlockNames = (
25
+ builder: TransactionDataBuilder,
26
+ ): { packages: string[]; types: string[] } => {
27
+ const packages: Set<string> = new Set();
28
+ const types: Set<string> = new Set();
29
+
30
+ for (const command of builder.commands) {
31
+ if (!('MoveCall' in command)) continue;
32
+ const tx = command.MoveCall;
33
+
34
+ if (!tx) continue;
35
+
36
+ const pkg = tx.package.split('::')[0];
37
+ if (pkg.includes(NAME_SEPARATOR)) {
38
+ if (!isValidNamedPackage(pkg)) throw new Error(`Invalid package name: ${pkg}`);
39
+ packages.add(pkg);
40
+ }
41
+
42
+ for (const type of tx.typeArguments ?? []) {
43
+ if (type.includes(NAME_SEPARATOR)) {
44
+ if (!isValidNamedType(type)) throw new Error(`Invalid type with names: ${type}`);
45
+ types.add(type);
46
+ }
47
+ }
48
+ }
49
+
50
+ return {
51
+ packages: [...packages],
52
+ types: [...types],
53
+ };
54
+ };
55
+
56
+ /**
57
+ * Replace all names & types in a transaction block
58
+ * with their resolved names/types.
59
+ */
60
+ export const replaceNames = (builder: TransactionDataBuilder, cache: NamedPackagesPluginCache) => {
61
+ for (const command of builder.commands) {
62
+ const tx = command.MoveCall;
63
+ if (!tx) continue;
64
+
65
+ const nameParts = tx.package.split('::');
66
+ const name = nameParts[0];
67
+
68
+ if (name.includes(NAME_SEPARATOR) && !cache.packages[name])
69
+ throw new Error(`No address found for package: ${name}`);
70
+
71
+ nameParts[0] = cache.packages[name];
72
+ tx.package = nameParts.join('::');
73
+
74
+ const types = tx.typeArguments;
75
+ if (!types) continue;
76
+
77
+ for (let i = 0; i < types.length; i++) {
78
+ if (!types[i].includes(NAME_SEPARATOR)) continue;
79
+
80
+ if (!cache.types[types[i]]) throw new Error(`No resolution found for type: ${types[i]}`);
81
+ types[i] = cache.types[types[i]];
82
+ }
83
+
84
+ tx.typeArguments = types;
85
+ }
86
+ };
87
+
88
+ export const listToRequests = (
89
+ names: { packages: string[]; types: string[] },
90
+ batchSize: number,
91
+ ): NameResolutionRequest[][] => {
92
+ const results: NameResolutionRequest[] = [];
93
+ const uniqueNames = deduplicate(names.packages);
94
+ const uniqueTypes = deduplicate(names.types);
95
+
96
+ for (const [idx, name] of uniqueNames.entries()) {
97
+ results.push({ id: idx, type: 'package', name } as NameResolutionRequest);
98
+ }
99
+ for (const [idx, type] of uniqueTypes.entries()) {
100
+ results.push({
101
+ id: idx + uniqueNames.length,
102
+ type: 'moveType',
103
+ name: type,
104
+ } as NameResolutionRequest);
105
+ }
106
+
107
+ return batch(results, batchSize);
108
+ };
109
+
110
+ const deduplicate = <T>(arr: T[]): T[] => [...new Set(arr)];
111
+
112
+ const batch = <T>(arr: T[], size: number): T[][] => {
113
+ const batches = [];
114
+ for (let i = 0; i < arr.length; i += size) {
115
+ batches.push(arr.slice(i, i + size));
116
+ }
117
+ return batches;
118
+ };
@@ -27,3 +27,5 @@ export {
27
27
  SUI_TYPE_ARG,
28
28
  SUI_SYSTEM_STATE_OBJECT_ID,
29
29
  } from './constants.js';
30
+
31
+ export { isValidNamedPackage, isValidNamedType } from './move-registry.js';
@@ -0,0 +1,24 @@
1
+ // Copyright (c) Mysten Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ /** The pattern to find an optionally versioned name */
5
+ const NAME_PATTERN = /^([a-z0-9]+(?:-[a-z0-9]+)*)@([a-z0-9]+(?:-[a-z0-9]+)*)(?:\/v(\d+))?$/;
6
+
7
+ export const isValidNamedPackage = (name: string): boolean => {
8
+ return NAME_PATTERN.test(name);
9
+ };
10
+
11
+ /**
12
+ * Checks if a type contains valid named packages.
13
+ * This DOES NOT check if the type is a valid Move type.
14
+ */
15
+ export const isValidNamedType = (type: string): boolean => {
16
+ // split our type by all possible type delimeters.
17
+ const splitType = type.split(/::|<|>|,/);
18
+ for (const t of splitType) {
19
+ if (t.includes('@') && !isValidNamedPackage(t)) return false;
20
+ }
21
+ // TODO: Add `isValidStructTag` check once
22
+ // it's generally introduced.
23
+ return true;
24
+ };
package/src/version.ts CHANGED
@@ -3,5 +3,5 @@
3
3
 
4
4
  // This file is generated by genversion.mjs. Do not edit it directly.
5
5
 
6
- export const PACKAGE_VERSION = '1.8.0';
7
- export const TARGETED_RPC_VERSION = '1.33.0';
6
+ export const PACKAGE_VERSION = '1.9.0';
7
+ export const TARGETED_RPC_VERSION = '1.34.0';