@msafe/sui3-sdk 0.0.90 → 1.0.6

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/src/utils/coin.ts CHANGED
@@ -1,13 +1,15 @@
1
- import { CoinMetadata, SuiClient, SuiObjectData } from '@mysten/sui.js/client';
2
- import { normalizeStructTag } from '@mysten/sui.js/utils';
1
+ import type { SuiGrpcClient } from '@mysten/sui/grpc';
2
+ import { normalizeStructTag } from '@mysten/sui/utils';
3
+
4
+ import type { CoinMetadata } from '@/types/assets';
3
5
 
4
6
  // CoinHelper is the coin helper to query for coin metadata.
5
7
  export class CoinHelper {
6
- private _client: SuiClient;
8
+ private _client: SuiGrpcClient;
7
9
 
8
10
  private _coinMetaReg: Map<string, CoinMetadata>;
9
11
 
10
- constructor(client: SuiClient) {
12
+ constructor(client: SuiGrpcClient) {
11
13
  this._client = client;
12
14
  this._coinMetaReg = new Map();
13
15
  }
@@ -26,7 +28,7 @@ export class CoinHelper {
26
28
 
27
29
  private async queryCoinMeta(coinType: string): Promise<CoinMetadata | undefined> {
28
30
  const res = await this._client.getCoinMetadata({ coinType });
29
- return res || undefined;
31
+ return res.coinMetadata ?? undefined;
30
32
  }
31
33
  }
32
34
 
@@ -48,14 +50,25 @@ export class Coin {
48
50
  return res || null;
49
51
  }
50
52
 
51
- static getBalance(data: SuiObjectData): bigint | undefined {
53
+ static getBalance(data: {
54
+ type?: string | null;
55
+ json?: Record<string, unknown> | null;
56
+ content?: { dataType?: string; fields?: { balance?: string } };
57
+ }): bigint | undefined {
52
58
  if (!Coin.isCoin(data.type)) {
53
59
  return undefined;
54
60
  }
61
+ if (data.json && typeof data.json === 'object' && 'balance' in data.json) {
62
+ const { balance } = data.json as { balance?: string };
63
+ if (balance === undefined) {
64
+ return undefined;
65
+ }
66
+ return BigInt(balance);
67
+ }
55
68
  if (data.content?.dataType !== 'moveObject') {
56
69
  return undefined;
57
70
  }
58
- const { balance } = data.content?.fields as any;
71
+ const { balance } = data.content?.fields as { balance?: string };
59
72
  if (balance === undefined) {
60
73
  return undefined;
61
74
  }
@@ -1,5 +1,4 @@
1
- import { SerializedSignature } from '@mysten/sui.js/cryptography';
2
- import { verifyPersonalMessage, verifyTransactionBlock } from '@mysten/sui.js/verify';
1
+ import { verifyPersonalMessageSignature, verifyTransactionSignature } from '@mysten/sui/verify';
3
2
 
4
3
  import { stringToBuffer } from '@/utils/buffer';
5
4
  import { Formatter } from '@/utils/format';
@@ -8,15 +7,15 @@ export class SignatureVerifier {
8
7
  static async getPublicKeyFromSignature(input: {
9
8
  message: Uint8Array;
10
9
  messageType: 'TransactionBlock' | 'Personal';
11
- signature: SerializedSignature;
10
+ signature: string;
12
11
  }) {
13
12
  if (input.messageType === 'TransactionBlock') {
14
- return verifyTransactionBlock(input.message, input.signature);
13
+ return verifyTransactionSignature(input.message, input.signature);
15
14
  }
16
- return verifyPersonalMessage(input.message, input.signature);
15
+ return verifyPersonalMessageSignature(input.message, input.signature);
17
16
  }
18
17
 
19
- static async getPublicKeyFromPersonalSignature(input: { messageStr: string; signature: SerializedSignature }) {
18
+ static async getPublicKeyFromPersonalSignature(input: { messageStr: string; signature: string }) {
20
19
  const message = stringToBuffer(input.messageStr);
21
20
  return this.getPublicKeyFromSignature({
22
21
  message,
@@ -28,18 +27,14 @@ export class SignatureVerifier {
28
27
  static async verifySignature(input: {
29
28
  message: Uint8Array;
30
29
  messageType: 'TransactionBlock' | 'Personal';
31
- signature: SerializedSignature;
30
+ signature: string;
32
31
  targetAddress: string;
33
32
  }) {
34
33
  const publicKey = await SignatureVerifier.getPublicKeyFromSignature(input);
35
34
  return Formatter.isSuiAddressEqual(publicKey.toSuiAddress(), input.targetAddress);
36
35
  }
37
36
 
38
- static async verifyPersonalSignature(input: {
39
- messageStr: string;
40
- signature: SerializedSignature;
41
- targetAddress: string;
42
- }) {
37
+ static async verifyPersonalSignature(input: { messageStr: string; signature: string; targetAddress: string }) {
43
38
  const message = stringToBuffer(input.messageStr);
44
39
  return this.verifySignature({
45
40
  message,
@@ -51,7 +46,7 @@ export class SignatureVerifier {
51
46
 
52
47
  static async verifyTransactionSignature(input: {
53
48
  payload: Uint8Array;
54
- signature: SerializedSignature;
49
+ signature: string;
55
50
  targetAddress: string;
56
51
  }): Promise<boolean> {
57
52
  return this.verifySignature({
@@ -1,4 +1,4 @@
1
- import { normalizeSuiAddress, normalizeStructTag } from '@mysten/sui.js/utils';
1
+ import { normalizeSuiAddress, normalizeStructTag } from '@mysten/sui/utils';
2
2
 
3
3
  import { Coin } from '@/utils/coin';
4
4
 
@@ -3,3 +3,4 @@ export * from './crypto';
3
3
  export * from './format';
4
4
  export * from './sui';
5
5
  export * from './coin';
6
+ export * from './transaction';
@@ -1,11 +1,15 @@
1
- import { SuiObjectResponse, SuiObjectDataOptions, SuiObjectData, SuiClient } from '@mysten/sui.js/client';
1
+ import type { SuiClientTypes } from '@mysten/sui/client';
2
+ import type { SuiGrpcClient } from '@mysten/sui/grpc';
2
3
 
3
4
  import { EntryIterator, getAllFromIterator, REQUEST_PAGE_SIZE, SuiIterator } from './iterator';
4
5
  import { PagedData, Requester } from './requester';
5
6
 
6
7
  // TODO: setup testnet faucet and test this file
7
8
 
8
- export type ObjectFilter = (objRes: SuiObjectResponse) => boolean;
9
+ /** Owned object row with Move JSON content (for helpers like Coin.getBalance). */
10
+ export type OwnedObjectData = SuiClientTypes.Object<{ json: true }>;
11
+
12
+ export type ObjectFilter = (obj: OwnedObjectData) => boolean;
9
13
 
10
14
  // OidIter is the iterator to give the list of object ids
11
15
  export type OidIter = SuiIterator<string>;
@@ -13,20 +17,27 @@ export type OidIter = SuiIterator<string>;
13
17
  export interface BatchObjectOptions {
14
18
  filter?: ObjectFilter;
15
19
  pageSize?: number;
16
- objectOptions?: SuiObjectDataOptions;
20
+ /** Extra fields to request from the node; defaults include Move JSON for struct fields. */
21
+ objectInclude?: SuiClientTypes.ObjectInclude;
22
+ }
23
+
24
+ const defaultObjectInclude: SuiClientTypes.ObjectInclude = { json: true };
25
+
26
+ function mergeInclude(options?: BatchObjectOptions): SuiClientTypes.ObjectInclude {
27
+ return { ...defaultObjectInclude, ...options?.objectInclude };
17
28
  }
18
29
 
19
30
  // getObjectsById get the list of objects by id.
20
31
  // Compared with the multiGetObject method defined by SUI, this method will do the pagination
21
32
  // for get object requests.
22
33
  export async function getObjectsById(
23
- provider: SuiClient,
34
+ provider: SuiGrpcClient,
24
35
  ids: string[],
25
36
  options?: BatchObjectOptions,
26
- ): Promise<(SuiObjectData | undefined)[]> {
37
+ ): Promise<(OwnedObjectData | undefined)[]> {
27
38
  const oidIter = new ListOidIterator(ids);
28
39
  const iter = new ObjectBatchIterator(provider, oidIter, options);
29
- return (await getAllFromIterator(iter)) as (SuiObjectData | undefined)[];
40
+ return (await getAllFromIterator(iter)) as (OwnedObjectData | undefined)[];
30
41
  }
31
42
 
32
43
  // ListOidIterator is the iterator that iterate through a list of ids.
@@ -53,9 +64,9 @@ export class ListOidIterator implements OidIter {
53
64
  }
54
65
  }
55
66
 
56
- export class ObjectBatchIterator extends EntryIterator<SuiObjectData> {
67
+ export class ObjectBatchIterator extends EntryIterator<OwnedObjectData> {
57
68
  constructor(
58
- public readonly provider: SuiClient,
69
+ public readonly provider: SuiGrpcClient,
59
70
  public readonly idIter: OidIter,
60
71
  public readonly options?: BatchObjectOptions,
61
72
  ) {
@@ -64,27 +75,24 @@ export class ObjectBatchIterator extends EntryIterator<SuiObjectData> {
64
75
  }
65
76
 
66
77
  // TODO: Test with customize objects
67
- export class ObjectBatchRequester implements Requester<SuiObjectData> {
78
+ export class ObjectBatchRequester implements Requester<OwnedObjectData> {
68
79
  filter: ObjectFilter | undefined;
69
80
 
70
81
  pageSize: number;
71
82
 
72
- objectOptions: SuiObjectDataOptions;
83
+ objectInclude: SuiClientTypes.ObjectInclude;
73
84
 
74
85
  constructor(
75
- public readonly provider: SuiClient,
86
+ public readonly provider: SuiGrpcClient,
76
87
  public readonly stringIter: OidIter,
77
88
  public options?: BatchObjectOptions,
78
89
  ) {
79
90
  this.filter = options?.filter;
80
91
  this.pageSize = options?.pageSize || REQUEST_PAGE_SIZE;
81
- this.objectOptions = options?.objectOptions || {
82
- showType: true,
83
- showContent: true,
84
- };
92
+ this.objectInclude = mergeInclude(options);
85
93
  }
86
94
 
87
- async doNextRequest(): Promise<PagedData<SuiObjectData>> {
95
+ async doNextRequest(): Promise<PagedData<OwnedObjectData>> {
88
96
  const requestPage: string[] = [];
89
97
  while (requestPage.length < this.pageSize) {
90
98
  const hasNext = await this.stringIter.hasNext();
@@ -96,36 +104,44 @@ export class ObjectBatchRequester implements Requester<SuiObjectData> {
96
104
  requestPage.push(objId);
97
105
  }
98
106
  }
99
- const res: SuiObjectResponse[] = await this.provider.multiGetObjects({
100
- ids: requestPage,
101
- options: this.objectOptions,
107
+ const { objects } = await this.provider.getObjects({
108
+ objectIds: requestPage,
109
+ include: this.objectInclude,
102
110
  });
103
- let filtered: SuiObjectResponse[];
111
+ const rows: OwnedObjectData[] = [];
112
+ // eslint-disable-next-line no-restricted-syntax
113
+ for (const entry of objects) {
114
+ if (entry instanceof Error) {
115
+ continue;
116
+ }
117
+ rows.push(entry as OwnedObjectData);
118
+ }
119
+ let filtered: OwnedObjectData[];
104
120
  if (this.filter) {
105
121
  const { filter } = this;
106
- filtered = res.filter((r: SuiObjectResponse) => filter?.(r));
122
+ filtered = rows.filter((r) => filter(r));
107
123
  } else {
108
- filtered = res;
124
+ filtered = rows;
109
125
  }
110
126
  return {
111
- data: filtered.map((objRes) => objRes.data).filter((data) => data) as SuiObjectData[],
127
+ data: filtered,
112
128
  hasNext: await this.stringIter.hasNext(),
113
129
  };
114
130
  }
115
131
  }
116
132
 
117
133
  export async function getAllOwnedObjects(
118
- provider: SuiClient,
134
+ provider: SuiGrpcClient,
119
135
  owner: string,
120
136
  options?: BatchObjectOptions,
121
- ): Promise<SuiObjectData[]> {
137
+ ): Promise<OwnedObjectData[]> {
122
138
  const iter = new OwnedObjectIterator(provider, owner, options);
123
- return (await getAllFromIterator(iter)) as SuiObjectData[];
139
+ return (await getAllFromIterator(iter)) as OwnedObjectData[];
124
140
  }
125
141
 
126
- export class OwnedObjectIterator extends EntryIterator<SuiObjectData> implements SuiIterator<SuiObjectData> {
142
+ export class OwnedObjectIterator extends EntryIterator<OwnedObjectData> implements SuiIterator<OwnedObjectData> {
127
143
  constructor(
128
- public readonly provider: SuiClient,
144
+ public readonly provider: SuiGrpcClient,
129
145
  public readonly owner: string,
130
146
  public readonly options?: BatchObjectOptions,
131
147
  ) {
@@ -133,46 +149,43 @@ export class OwnedObjectIterator extends EntryIterator<SuiObjectData> implements
133
149
  }
134
150
  }
135
151
 
136
- export class OwnedObjectRequester implements Requester<SuiObjectData> {
152
+ export class OwnedObjectRequester implements Requester<OwnedObjectData> {
137
153
  nextCursor: string | null;
138
154
 
139
155
  public filter: ObjectFilter | undefined;
140
156
 
141
157
  public pageSize: number;
142
158
 
143
- public objectOptions: SuiObjectDataOptions;
159
+ public objectInclude: SuiClientTypes.ObjectInclude;
144
160
 
145
161
  constructor(
146
- public readonly provider: SuiClient,
162
+ public readonly provider: SuiGrpcClient,
147
163
  public readonly owner: string,
148
164
  public readonly options?: BatchObjectOptions,
149
165
  ) {
150
166
  this.nextCursor = null;
151
167
  this.filter = options?.filter;
152
168
  this.pageSize = options?.pageSize || REQUEST_PAGE_SIZE;
153
- this.objectOptions = options?.objectOptions || {
154
- showType: true,
155
- showContent: true,
156
- };
169
+ this.objectInclude = mergeInclude(options);
157
170
  }
158
171
 
159
- async doNextRequest(): Promise<PagedData<SuiObjectData>> {
160
- const res = await this.provider.getOwnedObjects({
172
+ async doNextRequest(): Promise<PagedData<OwnedObjectData>> {
173
+ const res = await this.provider.listOwnedObjects({
161
174
  owner: this.owner,
162
- options: this.objectOptions,
175
+ include: this.objectInclude,
163
176
  cursor: this.nextCursor,
164
177
  limit: this.pageSize,
165
178
  });
166
- this.nextCursor = res.nextCursor as string;
167
- let filtered: SuiObjectResponse[];
179
+ this.nextCursor = res.cursor;
180
+ let filtered: OwnedObjectData[];
168
181
  if (this.filter) {
169
182
  const { filter } = this;
170
- filtered = res.data.filter((obj: SuiObjectResponse) => filter?.(obj));
183
+ filtered = res.objects.filter((obj) => filter(obj as OwnedObjectData));
171
184
  } else {
172
- filtered = res.data;
185
+ filtered = res.objects as OwnedObjectData[];
173
186
  }
174
187
  return {
175
- data: filtered.map((r: SuiObjectResponse) => r.data).filter((data) => data) as SuiObjectData[],
188
+ data: filtered,
176
189
  hasNext: res.hasNextPage,
177
190
  };
178
191
  }
package/src/utils/sui.ts CHANGED
@@ -1,21 +1,64 @@
1
1
  import { PublicKeySerde } from '@msafe/sui3-utils';
2
- import { SuiClient, PaginatedTransactionResponse, PaginatedCoins, CoinStruct } from '@mysten/sui.js/client';
3
- import { parseSerializedSignature, PublicKey, SerializedSignature } from '@mysten/sui.js/cryptography';
4
- import { MultiSigPublicKey } from '@mysten/sui.js/multisig';
2
+ import type { SuiClientTypes } from '@mysten/sui/client';
3
+ import { parseSerializedSignature, type PublicKey } from '@mysten/sui/cryptography';
4
+ import { SuiGraphQLClient } from '@mysten/sui/graphql';
5
+ import type { SuiGrpcClient } from '@mysten/sui/grpc';
6
+ import { MultiSigPublicKey } from '@mysten/sui/multisig';
5
7
 
6
8
  import { Formatter } from '@/utils/format';
7
9
 
8
10
  export const SUI_COIN = '0x2::sui::SUI';
9
11
 
10
- export async function getPublicKeyFromChain(suiClient: SuiClient, address: string): Promise<PublicKey | undefined> {
11
- let txs: PaginatedTransactionResponse;
12
+ const GRAPHQL_URL_BY_NETWORK: Record<string, string> = {
13
+ mainnet: 'https://sui-mainnet.mystenlabs.com/graphql',
14
+ testnet: 'https://sui-testnet.mystenlabs.com/graphql',
15
+ devnet: 'https://sui-devnet.mystenlabs.com/graphql',
16
+ };
17
+
18
+ function graphqlClientForGrpc(suiClient: SuiGrpcClient) {
19
+ const url = GRAPHQL_URL_BY_NETWORK[suiClient.network] ?? GRAPHQL_URL_BY_NETWORK.testnet;
20
+ return new SuiGraphQLClient({ url, network: suiClient.network });
21
+ }
22
+
23
+ function collectSignatureStrings(value: unknown): string[] {
24
+ if (typeof value === 'string') {
25
+ return [value];
26
+ }
27
+ if (!value || typeof value !== 'object') {
28
+ return [];
29
+ }
30
+ if ('base64' in value && typeof (value as { base64: unknown }).base64 === 'string') {
31
+ return [(value as { base64: string }).base64];
32
+ }
33
+ if ('scheme' in value && 'base64' in value) {
34
+ const b64 = (value as { base64?: unknown }).base64;
35
+ return typeof b64 === 'string' ? [b64] : [];
36
+ }
37
+ return [];
38
+ }
39
+
40
+ const TX_SIG_QUERY = `
41
+ query PublicKeyTxSigs($sender: SuiAddress!, $first: Int!) {
42
+ transactions(first: $first, filter: { sentAddress: $sender }) {
43
+ nodes {
44
+ signatures
45
+ }
46
+ }
47
+ }
48
+ `;
49
+
50
+ export async function getPublicKeyFromChain(suiClient: SuiGrpcClient, address: string): Promise<PublicKey | undefined> {
51
+ const graphql = graphqlClientForGrpc(suiClient);
52
+ let txs: { transactions?: { nodes?: unknown[] } };
12
53
  try {
13
- txs = await suiClient.queryTransactionBlocks({
14
- // Disable naming rule since the variable is defined by Mysten
15
- filter: { FromAddress: address },
16
- options: { showInput: true },
17
- limit: 2,
54
+ const res = await graphql.query({
55
+ query: TX_SIG_QUERY,
56
+ variables: { sender: address, first: 2 },
18
57
  });
58
+ if (res.errors?.length) {
59
+ return undefined;
60
+ }
61
+ txs = (res.data ?? {}) as { transactions?: { nodes?: unknown[] } };
19
62
  } catch (e) {
20
63
  // Currently when there is no history transaction, will report an error:
21
64
  // Error: byte deserialization failed, cause by: Odd number of digits
@@ -23,11 +66,18 @@ export async function getPublicKeyFromChain(suiClient: SuiClient, address: strin
23
66
  return undefined;
24
67
  }
25
68
 
26
- if (txs.data.length === 0 || !txs.data[0].transaction?.txSignatures) {
69
+ const nodes = txs.transactions?.nodes ?? [];
70
+ if (nodes.length === 0) {
71
+ return undefined;
72
+ }
73
+
74
+ const first = nodes[0] as { signatures?: unknown };
75
+ const rawSigs = first.signatures;
76
+ const signatures: string[] = Array.isArray(rawSigs) ? rawSigs.flatMap((item) => collectSignatureStrings(item)) : [];
77
+
78
+ if (signatures.length === 0) {
27
79
  return undefined;
28
80
  }
29
- const tx = txs.data[0];
30
- const signatures = tx.transaction?.txSignatures as string[];
31
81
 
32
82
  for (let i = 0; i !== signatures.length; i++) {
33
83
  const serializedSig = signatures[i];
@@ -40,7 +90,7 @@ export async function getPublicKeyFromChain(suiClient: SuiClient, address: strin
40
90
  return undefined;
41
91
  }
42
92
 
43
- function getAddressFromSignatures(serializedSig: SerializedSignature, targetAddress: string) {
93
+ function getAddressFromSignatures(serializedSig: string, targetAddress: string) {
44
94
  const decoded = parseSerializedSignature(serializedSig);
45
95
  switch (decoded.signatureScheme) {
46
96
  case 'MultiSig': {
@@ -56,7 +106,7 @@ function getAddressFromSignatures(serializedSig: SerializedSignature, targetAddr
56
106
  case 'Secp256r1': {
57
107
  const pk = PublicKeySerde.de({ publicKeyEncoded: decoded.publicKey, schema: decoded.signatureScheme });
58
108
  if (Formatter.isSuiAddressEqual(pk.toSuiAddress(), targetAddress)) {
59
- return pk;
109
+ return pk as unknown as PublicKey;
60
110
  }
61
111
  return undefined;
62
112
  }
@@ -66,19 +116,19 @@ function getAddressFromSignatures(serializedSig: SerializedSignature, targetAddr
66
116
  }
67
117
  }
68
118
 
69
- export async function getAllCoins(input: { suiClient: SuiClient; owner: string; coinType: string | undefined }) {
119
+ export async function getAllCoins(input: { suiClient: SuiGrpcClient; owner: string; coinType: string | undefined }) {
70
120
  let hasNext = true;
71
- let cursor: string | undefined | null;
72
- const res: CoinStruct[] = [];
121
+ let cursor: string | null | undefined;
122
+ const res: SuiClientTypes.Coin[] = [];
73
123
  while (hasNext) {
74
- const currentPage: PaginatedCoins = await input.suiClient.getCoins({
124
+ const currentPage = await input.suiClient.listCoins({
75
125
  owner: input.owner,
76
126
  coinType: input.coinType,
77
127
  cursor,
78
128
  });
79
- res.push(...currentPage.data);
129
+ res.push(...currentPage.objects);
80
130
  hasNext = currentPage.hasNextPage;
81
- cursor = currentPage.nextCursor;
131
+ cursor = currentPage.cursor;
82
132
  }
83
133
  return res;
84
134
  }
@@ -0,0 +1,12 @@
1
+ import { Transaction, isTransaction } from '@mysten/sui/transactions';
2
+
3
+ /**
4
+ * Normalize a transaction value to {@link Transaction} (handles plain Transaction or serialized legacy shapes).
5
+ */
6
+ export function toSuiTransaction(txb: Transaction | unknown): Transaction {
7
+ if (isTransaction(txb)) {
8
+ return txb as Transaction;
9
+ }
10
+ const legacy = txb as { serialize: () => string };
11
+ return Transaction.from(legacy.serialize()) as Transaction;
12
+ }
package/tsconfig.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
7
7
  "module": "ES2022", /* Specify what module code is generated. */
8
8
  "rootDir": "./", /* Specify the root folder within your source files. */
9
- "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
9
+ "moduleResolution": "bundler", /* Resolve @mysten/sui subpath exports (ESM). */
10
10
  "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
11
11
  "paths": { "@/*": ["./src/*"] }, /* Specify a set of entries that re-map imports to additional lookup locations. */
12
12
  "resolveJsonModule": true, /* Enable importing .json files. */