@sign-global/tokentable-core 1.14.2 → 2.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sign-global/tokentable-core",
3
- "version": "1.14.2",
3
+ "version": "2.0.0",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",
@@ -36,9 +36,8 @@
36
36
  "bignumber.js": "^9.1.2",
37
37
  "dayjs": "^1.11.13",
38
38
  "nanoid": "^5.0.9",
39
- "@mysten/dapp-kit": "^0.16.3",
40
- "@mysten/sui": "^1.29.1",
41
- "@mysten/wallet-standard": "^0.16.10",
39
+ "@mysten/sui": "^2.28.0",
40
+ "@mysten/wallet-standard": "^0.21.21",
42
41
  "@aptos-labs/ts-sdk": "^4.0.0",
43
42
  "@aptos-labs/wallet-adapter-react": "^7.0.4"
44
43
  },
@@ -1,4 +1,3 @@
1
- import { getFullnodeUrl } from '@mysten/sui/client';
2
1
  import { CHAIN } from '@tonconnect/sdk';
3
2
  import { defineChain } from 'viem';
4
3
 
@@ -163,10 +162,10 @@ export const suiTestNet = {
163
162
  },
164
163
  rpcUrls: {
165
164
  public: {
166
- http: [getFullnodeUrl('testnet')]
165
+ http: ['https://fullnode.testnet.sui.io:443']
167
166
  },
168
167
  default: {
169
- http: [getFullnodeUrl('testnet')]
168
+ http: ['https://fullnode.testnet.sui.io:443']
170
169
  }
171
170
  },
172
171
  blockExplorers: {
@@ -189,10 +188,10 @@ export const suiMainNet = {
189
188
  },
190
189
  rpcUrls: {
191
190
  public: {
192
- http: [getFullnodeUrl('mainnet')]
191
+ http: ['https://fullnode.mainnet.sui.io:443']
193
192
  },
194
193
  default: {
195
- http: [getFullnodeUrl('mainnet')]
194
+ http: ['https://fullnode.mainnet.sui.io:443']
196
195
  }
197
196
  },
198
197
  blockExplorers: {
@@ -1,6 +1,7 @@
1
1
  import { SuiContractClientBase, SuiContractInfo } from './SuiContractClient';
2
2
  import { Transaction } from '@mysten/sui/transactions';
3
3
  import { bcs } from '@mysten/sui/bcs';
4
+ import { fromBase64 } from '@mysten/sui/utils';
4
5
 
5
6
  export class SuiBaseDistributorClient extends SuiContractClientBase {
6
7
  constructor(info: SuiContractInfo) {
@@ -20,19 +21,14 @@ export class SuiBaseDistributorClient extends SuiContractClientBase {
20
21
 
21
22
  const result = await this.signAndExecute(tx);
22
23
 
23
- let distributorId: string | undefined;
24
- if (result.effects?.created) {
25
- const createdObj = result.effects.created.find(
26
- (obj: any) => obj.owner === 'Shared' || obj.owner?.Shared // 某些版本字段是对象
27
- );
28
- if (createdObj) {
29
- distributorId = createdObj.reference.objectId;
30
- }
31
- }
24
+ // 新建的 shared object 即 distributor
25
+ const createdObj = result.effects?.changedObjects.find(
26
+ (obj) => obj.idOperation === 'Created' && obj.outputOwner?.$kind === 'Shared'
27
+ );
32
28
 
33
29
  return {
34
30
  txResult: result,
35
- distributorId
31
+ distributorId: createdObj?.objectId
36
32
  };
37
33
  }
38
34
 
@@ -69,20 +65,20 @@ export class SuiBaseDistributorClient extends SuiContractClientBase {
69
65
  }
70
66
 
71
67
  async getDistributorInfo() {
72
- const res = await this.client.getObject({
73
- id: this.distributorId!,
74
- options: { showContent: true }
68
+ const { object } = await this.client.getObject({
69
+ objectId: this.distributorId!,
70
+ include: { json: true }
75
71
  });
76
- const content: any = (res as any).data?.content as any;
77
- const fields = content?.fields as any;
78
- const match = content?.type.match(/<(.+)>$/);
72
+ const fields = object.json as any;
73
+ const match = object.type.match(/<(.+)>$/);
79
74
  const coinType = match ? match[1] : '';
80
75
  return {
81
76
  startTime: fields?.start_time,
82
77
  endTime: fields?.end_time,
83
- signer: fields?.authorized_signer,
78
+ signer: fields?.authorized_public_key,
84
79
  ownerCapId: fields?.owner_cap_id,
85
- version: fields?.version ? String.fromCharCode(...(fields.version as number[])) : undefined,
80
+ // gRPC JSON vector<u8> base64 返回
81
+ version: fields?.version ? new TextDecoder().decode(fromBase64(fields.version)) : undefined,
86
82
  feeCollector: fields?.fee_collector_config,
87
83
  token: coinType,
88
84
  tokenBalance: fields?.token_balance,
@@ -113,42 +109,30 @@ export class SuiBaseDistributorClient extends SuiContractClientBase {
113
109
 
114
110
  async batchFetchClaimed(coinType: string, claimIds: string[]): Promise<boolean[]> {
115
111
  const claimIdsBytes = claimIds.map((id) => Array.from(new TextEncoder().encode(id)));
116
- const res = await this.readMoveFunction<[number[], string]>(
117
- 'get_claim_status',
118
- [coinType],
119
- [this.distributorId!, claimIdsBytes]
120
- );
121
-
122
- const [data, type] = res;
112
+ const res = await this.readMoveFunction('get_claim_status', [coinType], [this.distributorId!, claimIdsBytes]);
123
113
 
124
114
  // 使用 BCS 解码 vector<bool>
125
- if (type === 'vector<bool>' && data) {
126
- // data 通常是 Uint8Array 格式
127
- const uint8Array = new Uint8Array(data);
128
- return Array.from(bcs.vector(bcs.bool()).parse(uint8Array));
129
- }
130
- return [];
115
+ return bcs.vector(bcs.bool()).parse(res);
131
116
  }
132
117
 
133
118
  async getDistributorId(projectId: string, projectRegistryId: string) {
134
- const distributorId = await this.readMoveFunction<{ Some?: string; None?: null }>(
119
+ const res = await this.readMoveFunction(
135
120
  'get_distributor_by_project_id',
136
121
  [],
137
122
  [projectRegistryId, Array.from(Buffer.from(projectId, 'utf8'))]
138
123
  );
139
- return distributorId.Some;
124
+ // 使用 BCS 解码 Option<ID>
125
+ return bcs.option(bcs.Address).parse(res);
140
126
  }
141
127
 
142
128
  async getOwnerCapId() {
143
- const objects = await this.client.getOwnedObjects({
129
+ const { objects } = await this.client.listOwnedObjects({
144
130
  owner: this.wallet.accounts[0].address,
145
- filter: {
146
- StructType: `${this.packageId}::ownable::OwnerCap`
147
- }
131
+ type: `${this.packageId}::ownable::OwnerCap`
148
132
  });
149
133
 
150
134
  // 获取 OwnerCap 对象ID
151
- const ownerCapId = objects.data[0]?.data?.objectId;
135
+ const ownerCapId = objects[0]?.objectId;
152
136
 
153
137
  return ownerCapId;
154
138
  }
@@ -12,27 +12,27 @@ export class SuiDistributorWithFeeClient extends SuiContractClientBase {
12
12
  const totalFees = BigInt(totalFeesStr);
13
13
 
14
14
  // 获取用户的 coins
15
- const coins = await this.client.getCoins({
15
+ const coins = await this.client.listCoins({
16
16
  owner: this.wallet.accounts[0].address,
17
17
  coinType: feeTokenType
18
18
  });
19
19
 
20
20
  // 找到足够余额的单个 coin
21
- const suitableCoin = coins.data.find((coin) => BigInt(coin.balance) >= totalFees);
21
+ const suitableCoin = coins.objects.find((coin) => BigInt(coin.balance) >= totalFees);
22
22
 
23
23
  if (suitableCoin) {
24
24
  // 如果coin的余额正好等于所需费用,直接使用
25
25
  if (BigInt(suitableCoin.balance) === totalFees) {
26
- return tx.object(suitableCoin.coinObjectId);
26
+ return tx.object(suitableCoin.objectId);
27
27
  }
28
28
  // 如果coin的余额大于所需费用,需要分割coin
29
- const [feeCoin] = tx.splitCoins(tx.object(suitableCoin.coinObjectId), [tx.pure(bcs.u64().serialize(totalFees))]);
29
+ const [feeCoin] = tx.splitCoins(tx.object(suitableCoin.objectId), [tx.pure(bcs.u64().serialize(totalFees))]);
30
30
  return feeCoin;
31
31
  }
32
32
 
33
33
  // 如果没有单个足够的 coin,需要合并多个 coins
34
34
  // 这里可以实现 coin 合并逻辑或抛出错误
35
- throw new Error(`Insufficient balance. Required: ${totalFeesStr}, Available coins: ${coins.data.length}`);
35
+ throw new Error(`Insufficient balance. Required: ${totalFeesStr}, Available coins: ${coins.objects.length}`);
36
36
  }
37
37
 
38
38
  async claimWithFee(coinType: string, feeCollector: string, data: ISignatureClaimData[]) {
@@ -73,13 +73,13 @@ export class SuiDistributorWithFeeClient extends SuiContractClientBase {
73
73
  const feeTokenType = '0x2::sui::SUI'; // 或其他费用代币类型
74
74
 
75
75
  // 在splitCoins之前添加余额检查
76
- const gasBalance = await this.client.getBalance({
76
+ const { balance: gasBalance } = await this.client.getBalance({
77
77
  owner: this.wallet.accounts[0].address,
78
78
  coinType: feeTokenType
79
79
  });
80
80
 
81
- if (BigInt(gasBalance.totalBalance) < totalFees) {
82
- throw new Error(`Insufficient SUI balance. Required: ${totalFees}, Available: ${gasBalance.totalBalance}`);
81
+ if (BigInt(gasBalance.balance) < totalFees) {
82
+ throw new Error(`Insufficient SUI balance. Required: ${totalFees}, Available: ${gasBalance.balance}`);
83
83
  }
84
84
 
85
85
  const [feeCoin] = tx.splitCoins(
@@ -1,6 +1,7 @@
1
- import { SuiClient, getFullnodeUrl } from '@mysten/sui/client';
1
+ import { SuiGrpcClient } from '@mysten/sui/grpc';
2
2
  import { bcs } from '@mysten/sui/bcs';
3
3
  import { Transaction } from '@mysten/sui/transactions';
4
+ import { fromBase64 } from '@mysten/sui/utils';
4
5
  import { signTransaction, Wallet } from '@mysten/wallet-standard';
5
6
 
6
7
  export interface SuiContractInfo {
@@ -9,6 +10,7 @@ export interface SuiContractInfo {
9
10
  walletClient: Wallet;
10
11
  coinType: string;
11
12
  distributorId?: string;
13
+ /** gRPC endpoint, e.g. https://fullnode.mainnet.sui.io:443 */
12
14
  chainRpc?: string;
13
15
  }
14
16
 
@@ -18,7 +20,7 @@ export abstract class SuiContractClientBase {
18
20
  protected moduleName: string;
19
21
  protected distributorId?: string;
20
22
  protected wallet: Wallet;
21
- protected client: SuiClient;
23
+ protected client: SuiGrpcClient;
22
24
 
23
25
  constructor(contractInfo: SuiContractInfo & { moduleName: string }) {
24
26
  this.packageId = contractInfo.packageId;
@@ -26,7 +28,10 @@ export abstract class SuiContractClientBase {
26
28
  this.moduleName = contractInfo.moduleName;
27
29
  this.distributorId = contractInfo.distributorId;
28
30
  this.wallet = contractInfo.walletClient;
29
- this.client = new SuiClient({ network: this.chainId, url: contractInfo.chainRpc || getFullnodeUrl(this.chainId) });
31
+ this.client = new SuiGrpcClient({
32
+ network: this.chainId,
33
+ baseUrl: contractInfo.chainRpc || `https://fullnode.${this.chainId}.sui.io:443`
34
+ });
30
35
  }
31
36
 
32
37
  async signAndExecute(tx: Transaction) {
@@ -77,35 +82,36 @@ export abstract class SuiContractClientBase {
77
82
  });
78
83
 
79
84
  // 发送交易
80
- const result = await this.client.executeTransactionBlock({
81
- transactionBlock: bytes,
82
- signature,
83
- options: {
84
- showEffects: true,
85
- showEvents: true
86
- }
85
+ const result = await this.client.executeTransaction({
86
+ transaction: fromBase64(bytes),
87
+ signatures: [signature],
88
+ include: { effects: true, events: true }
87
89
  });
88
90
 
91
+ if (result.$kind === 'FailedTransaction') {
92
+ throw new Error(result.FailedTransaction.status.error?.message ?? 'Transaction failed');
93
+ }
94
+
89
95
  // 等待交易确认
90
- await this.waitForTx(result.digest);
96
+ await this.waitForTx(result.Transaction.digest);
91
97
 
92
- return result;
98
+ return result.Transaction;
93
99
  }
94
100
 
95
101
  protected async waitForTx(digest: string) {
96
102
  return this.client.waitForTransaction({
97
103
  digest,
98
- options: { showEffects: true, showEvents: true }
104
+ include: { effects: true, events: true }
99
105
  });
100
106
  }
101
107
 
102
108
  /**
103
- * 读取合约返回值(只读调用)
109
+ * 读取合约返回值(只读调用),返回第一个返回值的 BCS 字节
104
110
  * @param func Move function 名
105
111
  * @param typeArgs 类型参数
106
112
  * @param args Move function 参数
107
113
  */
108
- async readMoveFunction<T = any>(func: string, typeArgs: string[] = [], args: any[] = []): Promise<T> {
114
+ async readMoveFunction(func: string, typeArgs: string[] = [], args: any[] = []): Promise<Uint8Array> {
109
115
  const DEFAULT_SENDER = '0x0000000000000000000000000000000000000000000000000000000000000001';
110
116
  const sender = this.wallet?.accounts?.[0]?.address || DEFAULT_SENDER;
111
117
  if (!sender) throw new Error('No wallet account selected');
@@ -122,55 +128,55 @@ export abstract class SuiContractClientBase {
122
128
  return tx.pure(arg); // 纯量 u64、address 等
123
129
  })
124
130
  });
131
+ tx.setSender(sender);
125
132
 
126
- // devInspectTransactionBlock 不消耗 Gas
127
- const res = await this.client.devInspectTransactionBlock({
128
- sender,
129
- transactionBlock: tx // ✅ 这里传 Transaction,不再报 TS 类型错误
133
+ // simulateTransaction 不消耗 Gas;关闭 checks 以允许调用非 entry 函数
134
+ const res = await this.client.simulateTransaction({
135
+ transaction: tx,
136
+ checksEnabled: false,
137
+ include: { commandResults: true }
130
138
  });
131
139
 
132
140
  console.log(res, 'res');
133
141
 
134
- if (!res || !res.results?.[0]?.returnValues) {
142
+ if (res.$kind === 'FailedTransaction') {
143
+ throw new Error(`Failed to read ${func}: ${res.FailedTransaction.status.error?.message}`);
144
+ }
145
+
146
+ const returnValue = res.commandResults?.[0]?.returnValues?.[0]?.bcs;
147
+ if (!returnValue) {
135
148
  throw new Error(`Failed to read ${func}`);
136
149
  }
137
150
 
138
- return res.results[0].returnValues[0] as T;
151
+ return returnValue;
139
152
  }
140
153
 
141
154
  /**
142
155
  * 查询 Distributor MoveObject 的 fields
143
156
  */
144
157
  async getObjectFields(objectId: string) {
145
- const res = await this.client.getObject({
146
- id: objectId,
147
- options: { showContent: true }
158
+ const { object } = await this.client.getObject({
159
+ objectId,
160
+ include: { json: true }
148
161
  });
149
162
 
150
- if (res.data?.content?.dataType !== 'moveObject') {
163
+ if (!object.json) {
151
164
  throw new Error(`Object ${objectId} is not a moveObject`);
152
165
  }
153
166
 
154
- return res.data.content.fields;
167
+ return object.json;
155
168
  }
156
169
 
157
170
  async getSharedObjectRef(tx: Transaction, objectId: string, mutable: boolean) {
158
- const obj = await this.client.getObject({
159
- id: objectId,
160
- options: { showOwner: true }
161
- });
171
+ const { object } = await this.client.getObject({ objectId });
162
172
 
163
- console.log(obj, 'obj');
164
-
165
- if (!obj.data) {
166
- throw new Error(`Object ${objectId} not found`);
167
- }
173
+ console.log(object, 'obj');
168
174
 
169
- if (!(obj.data.owner as any)?.Shared) {
175
+ if (object.owner.$kind !== 'Shared') {
170
176
  throw new Error(`Object ${objectId} is not a shared object`);
171
177
  }
172
178
 
173
- const initialSharedVersion = (obj.data.owner as any).Shared.initial_shared_version;
179
+ const initialSharedVersion = object.owner.Shared.initialSharedVersion;
174
180
 
175
181
  return tx.sharedObjectRef({
176
182
  objectId,
@@ -2,13 +2,12 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
 
3
3
  const suiClientArgs = vi.fn();
4
4
 
5
- vi.mock('@mysten/sui/client', () => ({
6
- SuiClient: class {
7
- constructor(args: { network: string; url: string }) {
5
+ vi.mock('@mysten/sui/grpc', () => ({
6
+ SuiGrpcClient: class {
7
+ constructor(args: { network: string; baseUrl: string }) {
8
8
  suiClientArgs(args);
9
9
  }
10
- },
11
- getFullnodeUrl: (network: string) => `https://fullnode.${network}.sui.io:443`
10
+ }
12
11
  }));
13
12
 
14
13
  import { SuiContractClientBase, SuiContractInfo } from '../SuiContractClient';
@@ -29,11 +28,11 @@ describe('SuiContractClientBase RPC selection', () => {
29
28
  });
30
29
 
31
30
  it('uses chainRpc when provided', () => {
32
- new TestClient({ ...baseInfo, chainRpc: 'https://sui-mainnet.g.alchemy.com/v2/key' });
31
+ new TestClient({ ...baseInfo, chainRpc: 'https://sui-mainnet.grpc.example.com:443' });
33
32
 
34
33
  expect(suiClientArgs).toHaveBeenCalledWith({
35
34
  network: 'mainnet',
36
- url: 'https://sui-mainnet.g.alchemy.com/v2/key'
35
+ baseUrl: 'https://sui-mainnet.grpc.example.com:443'
37
36
  });
38
37
  });
39
38
 
@@ -42,7 +41,16 @@ describe('SuiContractClientBase RPC selection', () => {
42
41
 
43
42
  expect(suiClientArgs).toHaveBeenCalledWith({
44
43
  network: 'mainnet',
45
- url: 'https://fullnode.mainnet.sui.io:443'
44
+ baseUrl: 'https://fullnode.mainnet.sui.io:443'
45
+ });
46
+ });
47
+
48
+ it('uses the testnet fullnode for testnet', () => {
49
+ new TestClient({ ...baseInfo, chainId: 'testnet' });
50
+
51
+ expect(suiClientArgs).toHaveBeenCalledWith({
52
+ network: 'testnet',
53
+ baseUrl: 'https://fullnode.testnet.sui.io:443'
46
54
  });
47
55
  });
48
56
  });