@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.
- package/CHANGELOG.md +7 -0
- package/dist/cjs/transactions/Transaction.d.ts +6 -0
- package/dist/cjs/transactions/Transaction.js +20 -8
- package/dist/cjs/transactions/Transaction.js.map +2 -2
- package/dist/cjs/transactions/index.d.ts +2 -0
- package/dist/cjs/transactions/index.js +2 -0
- package/dist/cjs/transactions/index.js.map +2 -2
- package/dist/cjs/transactions/plugins/NamedPackagesPlugin.d.ts +49 -0
- package/dist/cjs/transactions/plugins/NamedPackagesPlugin.js +78 -0
- package/dist/cjs/transactions/plugins/NamedPackagesPlugin.js.map +7 -0
- package/dist/cjs/transactions/plugins/utils.d.ts +27 -0
- package/dist/cjs/transactions/plugins/utils.js +96 -0
- package/dist/cjs/transactions/plugins/utils.js.map +7 -0
- package/dist/cjs/utils/index.d.ts +1 -0
- package/dist/cjs/utils/index.js +3 -0
- package/dist/cjs/utils/index.js.map +2 -2
- package/dist/cjs/utils/move-registry.d.ts +6 -0
- package/dist/cjs/utils/move-registry.js +36 -0
- package/dist/cjs/utils/move-registry.js.map +7 -0
- package/dist/cjs/version.d.ts +2 -2
- package/dist/cjs/version.js +2 -2
- package/dist/cjs/version.js.map +1 -1
- package/dist/esm/transactions/Transaction.d.ts +6 -0
- package/dist/esm/transactions/Transaction.js +20 -8
- package/dist/esm/transactions/Transaction.js.map +2 -2
- package/dist/esm/transactions/index.d.ts +2 -0
- package/dist/esm/transactions/index.js +4 -0
- package/dist/esm/transactions/index.js.map +2 -2
- package/dist/esm/transactions/plugins/NamedPackagesPlugin.d.ts +49 -0
- package/dist/esm/transactions/plugins/NamedPackagesPlugin.js +58 -0
- package/dist/esm/transactions/plugins/NamedPackagesPlugin.js.map +7 -0
- package/dist/esm/transactions/plugins/utils.d.ts +27 -0
- package/dist/esm/transactions/plugins/utils.js +76 -0
- package/dist/esm/transactions/plugins/utils.js.map +7 -0
- package/dist/esm/utils/index.d.ts +1 -0
- package/dist/esm/utils/index.js +3 -0
- package/dist/esm/utils/index.js.map +2 -2
- package/dist/esm/utils/move-registry.d.ts +6 -0
- package/dist/esm/utils/move-registry.js +16 -0
- package/dist/esm/utils/move-registry.js.map +7 -0
- package/dist/esm/version.d.ts +2 -2
- package/dist/esm/version.js +2 -2
- package/dist/esm/version.js.map +1 -1
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/transactions/Transaction.ts +43 -13
- package/src/transactions/index.ts +7 -0
- package/src/transactions/plugins/NamedPackagesPlugin.ts +129 -0
- package/src/transactions/plugins/utils.ts +118 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/move-registry.ts +24 -0
- 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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
172
|
-
|
|
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
|
|
176
|
-
getGlobalPluginRegistry().buildPlugins.
|
|
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
|
+
};
|
package/src/utils/index.ts
CHANGED
|
@@ -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