@msafe/sui3-sdk 0.0.2-pre-9f39791.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/.eslintignore +3 -0
- package/.eslintrc +87 -0
- package/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/.idea/jsLinters/eslint.xml +6 -0
- package/.idea/misc.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/msafe-sui3-sdk.iml +9 -0
- package/.idea/vcs.xml +6 -0
- package/.prettierrc +22 -0
- package/README.md +3 -0
- package/jest.config.ts +63 -0
- package/package.json +54 -0
- package/scripts/prerelease.sh +5 -0
- package/src/backend/CoreDatabase.ts +55 -0
- package/src/backend/PseudoBackend.ts +851 -0
- package/src/backend/interface.ts +57 -0
- package/src/backend/types.ts +22 -0
- package/src/core/CreateHelper.ts +76 -0
- package/src/core/MSafeAccount.ts +167 -0
- package/src/core/MSafeClient.ts +91 -0
- package/src/core/MessageHelper.ts +63 -0
- package/src/core/PublicKeyHelper.ts +88 -0
- package/src/core/index.ts +4 -0
- package/src/globals/MSafeGlobals.ts +48 -0
- package/src/globals/const.ts +95 -0
- package/src/globals/index.ts +2 -0
- package/src/index.ts +5 -0
- package/src/transactions/coin-transfer.ts +64 -0
- package/src/transactions/index.ts +1 -0
- package/src/transactions/intention.ts +63 -0
- package/src/transactions/object-transfer.ts +66 -0
- package/src/transactions/reject.ts +17 -0
- package/src/transactions/stream.ts +1 -0
- package/src/types/creation.ts +19 -0
- package/src/types/index.ts +3 -0
- package/src/types/msafe.ts +79 -0
- package/src/types/wallet.ts +23 -0
- package/src/utils/buffer.ts +11 -0
- package/src/utils/coin.ts +64 -0
- package/src/utils/crypto.ts +95 -0
- package/src/utils/format.ts +25 -0
- package/src/utils/index.ts +6 -0
- package/src/utils/multi-sig.ts +113 -0
- package/src/utils/sui.ts +90 -0
- package/temp_package.json +54 -0
- package/test/lib/TestHelper.ts +94 -0
- package/test/lib/account.ts +87 -0
- package/test/lib/config.ts +9 -0
- package/test/lib/faucet.ts +49 -0
- package/test/unit/backend/backend.test.ts +367 -0
- package/test/unit/core/CreateHelper.test.ts +121 -0
- package/test/unit/core/MessageHelper.test.ts +32 -0
- package/test/unit/core/PublicKeyHelper.test.ts +80 -0
- package/test/unit/core/msafe.test.ts +298 -0
- package/test/unit/utils/buffer.test.ts +17 -0
- package/test/unit/utils/crypto.test.ts +32 -0
- package/test/unit/utils/multi-sig.test.ts +47 -0
- package/test/unit/utils/sui.test.ts +25 -0
- package/tsconfig.json +37 -0
- package/tsup.config.ts +9 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { SuiClient } from '@mysten/sui.js/client';
|
|
2
|
+
|
|
3
|
+
import { PseudoBackend } from '@/backend/PseudoBackend';
|
|
4
|
+
import { MSafeAccount } from '@/core/MSafeAccount';
|
|
5
|
+
import { MSafeClient } from '@/core/MSafeClient';
|
|
6
|
+
import { MSafeEnv } from '@/globals/const';
|
|
7
|
+
import { MSafeGlobals } from '@/globals/MSafeGlobals';
|
|
8
|
+
import { IntentionCoinTransfer } from '@/transactions/coin-transfer';
|
|
9
|
+
import { IWallet } from '@/types/wallet';
|
|
10
|
+
import { SUI_COIN } from '@/utils/sui';
|
|
11
|
+
|
|
12
|
+
import { LocalWallet } from '../../lib/account';
|
|
13
|
+
import { TestHelper } from '../../lib/TestHelper';
|
|
14
|
+
|
|
15
|
+
describe('MSafe integration', () => {
|
|
16
|
+
let testHelper: TestHelper;
|
|
17
|
+
let suiClient: SuiClient;
|
|
18
|
+
const wallets: IWallet[] = [];
|
|
19
|
+
const clients: MSafeClient[] = [];
|
|
20
|
+
let backend: PseudoBackend;
|
|
21
|
+
const userAddresses: string[] = [];
|
|
22
|
+
|
|
23
|
+
beforeAll(async () => {
|
|
24
|
+
const globals = await MSafeGlobals.New(MSafeEnv.unit);
|
|
25
|
+
suiClient = globals.suiClient;
|
|
26
|
+
testHelper = new TestHelper(globals);
|
|
27
|
+
await testHelper.init();
|
|
28
|
+
for (let i = 0; i !== 3; i++) {
|
|
29
|
+
const userGlobals = await MSafeGlobals.New(MSafeEnv.unit);
|
|
30
|
+
const wallet = new LocalWallet(userGlobals.suiClient);
|
|
31
|
+
const client = new MSafeClient(userGlobals);
|
|
32
|
+
await client.connectWallet({ wallet });
|
|
33
|
+
clients.push(client);
|
|
34
|
+
wallets.push(wallet);
|
|
35
|
+
userAddresses.push(await wallet.address());
|
|
36
|
+
}
|
|
37
|
+
backend = globals.backend as PseudoBackend;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
afterAll(async () => {
|
|
41
|
+
(testHelper.globals.backend as PseudoBackend).db.coreModel.close();
|
|
42
|
+
for (let i = 0; i < clients.length; i++) {
|
|
43
|
+
await (clients[i].globals.backend as PseudoBackend).db.coreModel.close();
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
async function createMSafe() {
|
|
48
|
+
return clients[0].creationHelper.submitMSafeCreation({
|
|
49
|
+
ownerWithWeight: userAddresses.map((addr) => ({
|
|
50
|
+
address: addr,
|
|
51
|
+
weight: 1,
|
|
52
|
+
})),
|
|
53
|
+
threshold: wallets.length,
|
|
54
|
+
name: 'Test MSafe',
|
|
55
|
+
description: 'Some description',
|
|
56
|
+
creationNonce: 0,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function getMSafeAccountForTest() {
|
|
61
|
+
const userInfo = await clients[0].userInfo();
|
|
62
|
+
let msafe: MSafeAccount;
|
|
63
|
+
if (userInfo.ownedMSafe.length > 0) {
|
|
64
|
+
msafe = clients[0].getMSafeAccount(userInfo.ownedMSafe[0]);
|
|
65
|
+
} else {
|
|
66
|
+
const msafeAddress = await createMSafe();
|
|
67
|
+
msafe = await clients[0].getMSafeAccountFromAddress(msafeAddress);
|
|
68
|
+
}
|
|
69
|
+
await faucetAccount(msafe.info.address);
|
|
70
|
+
return msafe;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function faucetAccount(addr: string) {
|
|
74
|
+
const bal = await suiClient.getBalance({ owner: addr });
|
|
75
|
+
if (bal.totalBalance === '0') {
|
|
76
|
+
await testHelper.faucetWallet(addr, 40000000n);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
it('create MSafe', async () => {
|
|
81
|
+
await createMSafe();
|
|
82
|
+
|
|
83
|
+
for (let i = 0; i !== clients.length; i++) {
|
|
84
|
+
const userInfo = await clients[i].userInfo();
|
|
85
|
+
expect(userInfo.creationNonce).toBe(i === 0 ? 1 : 0);
|
|
86
|
+
expect(userInfo.ownedMSafe.length).toBe(1);
|
|
87
|
+
expect(userInfo.address).toBe(userAddresses[i]);
|
|
88
|
+
expect(userInfo.ownedMSafe.length).toBe(1);
|
|
89
|
+
expect(userInfo.ownedMSafe[0].threshold).toBe(userAddresses.length);
|
|
90
|
+
expect(userInfo.ownedMSafe[0].creationNonce).toBe(0);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('Propose and execute one transaction', async () => {
|
|
95
|
+
const msafeAccount = await getMSafeAccountForTest();
|
|
96
|
+
|
|
97
|
+
const initBal = await suiClient.getBalance({ owner: userAddresses[0] });
|
|
98
|
+
|
|
99
|
+
const intention: IntentionCoinTransfer = {
|
|
100
|
+
txType: 'CoinTransfer',
|
|
101
|
+
recipient: userAddresses[0],
|
|
102
|
+
amount: '100',
|
|
103
|
+
coinType: SUI_COIN,
|
|
104
|
+
};
|
|
105
|
+
const sn = await msafeAccount.currentSequenceNumber();
|
|
106
|
+
await msafeAccount.proposeIntention(intention, sn);
|
|
107
|
+
|
|
108
|
+
const futureIntentions = await msafeAccount.futureIntentions();
|
|
109
|
+
expect(futureIntentions.length).toBe(1);
|
|
110
|
+
expect(futureIntentions[0].sequenceNumber).toBe(0);
|
|
111
|
+
expect(futureIntentions[0].txType).toBe('CoinTransfer');
|
|
112
|
+
expect(futureIntentions[0].creator).toBe(userAddresses[0]);
|
|
113
|
+
|
|
114
|
+
const pendings0 = await msafeAccount.pendingTransactions();
|
|
115
|
+
expect(pendings0.length).toBe(0);
|
|
116
|
+
|
|
117
|
+
await msafeAccount.buildNextIntentionAndAddToPending();
|
|
118
|
+
const pendings = await msafeAccount.pendingTransactions();
|
|
119
|
+
expect(pendings.length).toBe(1);
|
|
120
|
+
|
|
121
|
+
for (let i = 0; i < userAddresses.length; i++) {
|
|
122
|
+
const msafe = clients[i].getMSafeAccount(msafeAccount.info);
|
|
123
|
+
await msafe.voteForTransaction(pendings[0].digest, pendings[0].payload);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const votedPendings = await msafeAccount.pendingTransactions();
|
|
127
|
+
expect(votedPendings.length).toBe(1);
|
|
128
|
+
expect(votedPendings[0].votes.length).toBe(userAddresses.length);
|
|
129
|
+
|
|
130
|
+
const res = await msafeAccount.executePendingTx(votedPendings[0]);
|
|
131
|
+
expect(res.effects?.status.status).toBe('success');
|
|
132
|
+
|
|
133
|
+
await backend.processExecutedTransaction(pendings[0].digest);
|
|
134
|
+
const pendingsExecuted = await msafeAccount.pendingTransactions();
|
|
135
|
+
expect(pendingsExecuted.length).toBe(0);
|
|
136
|
+
|
|
137
|
+
const history = await msafeAccount.historyTransaction();
|
|
138
|
+
const relatedHistory = history.find(
|
|
139
|
+
(tx) => tx.msafeAddress === msafeAccount.info.address && tx.digest === votedPendings[0].digest,
|
|
140
|
+
);
|
|
141
|
+
expect(relatedHistory).toBeDefined();
|
|
142
|
+
expect(relatedHistory?.status).toBe('success');
|
|
143
|
+
|
|
144
|
+
// Check execution result
|
|
145
|
+
const bal = await suiClient.getBalance({ owner: userAddresses[0] });
|
|
146
|
+
expect(bal.totalBalance).not.toBe(initBal.totalBalance);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('proposePendingTransaction and reject transaction', async () => {
|
|
150
|
+
const msafeAccount = await getMSafeAccountForTest();
|
|
151
|
+
|
|
152
|
+
const initBal = await suiClient.getBalance({ owner: userAddresses[0] });
|
|
153
|
+
|
|
154
|
+
const intention: IntentionCoinTransfer = {
|
|
155
|
+
txType: 'CoinTransfer',
|
|
156
|
+
recipient: userAddresses[0],
|
|
157
|
+
amount: '200',
|
|
158
|
+
coinType: SUI_COIN,
|
|
159
|
+
};
|
|
160
|
+
await msafeAccount.proposePendingTransaction(intention);
|
|
161
|
+
|
|
162
|
+
let pendings = await msafeAccount.pendingTransactions();
|
|
163
|
+
expect(pendings.length).toBe(1);
|
|
164
|
+
expect(pendings[0].creator).toBe(userAddresses[0]);
|
|
165
|
+
expect(pendings[0].votes.length).toBe(1);
|
|
166
|
+
expect(pendings[0].votes[0].userAddress).toBe(userAddresses[0]);
|
|
167
|
+
|
|
168
|
+
// Reject the transaction
|
|
169
|
+
await msafeAccount.rejectCurrentTx();
|
|
170
|
+
|
|
171
|
+
pendings = await msafeAccount.pendingTransactions();
|
|
172
|
+
expect(pendings.length).toBe(2);
|
|
173
|
+
const approvePending = pendings.find((pending) => !pending.isRejectTx);
|
|
174
|
+
const rejectPending = pendings.find((pending) => pending.isRejectTx);
|
|
175
|
+
expect(approvePending).toBeDefined();
|
|
176
|
+
expect(approvePending?.intention?.txType).toBe('CoinTransfer');
|
|
177
|
+
expect(approvePending?.votes.length).toBe(0);
|
|
178
|
+
|
|
179
|
+
expect(rejectPending).toBeDefined();
|
|
180
|
+
expect(rejectPending?.intention).toBeUndefined();
|
|
181
|
+
expect(rejectPending?.votes.length).toBe(1);
|
|
182
|
+
|
|
183
|
+
for (let i = 1; i < clients.length; i++) {
|
|
184
|
+
const msafe = clients[i].getMSafeAccount(msafeAccount.info);
|
|
185
|
+
await msafe.voteForTransaction(rejectPending!.digest, rejectPending!.payload!);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
pendings = await msafeAccount.pendingTransactions();
|
|
189
|
+
expect(pendings.length).toBe(2);
|
|
190
|
+
const rejectPending2 = pendings.find((pending) => pending.isRejectTx);
|
|
191
|
+
|
|
192
|
+
expect(rejectPending2).toBeDefined();
|
|
193
|
+
expect(rejectPending2?.intention).toBeUndefined();
|
|
194
|
+
expect(rejectPending2?.votes.length).toBe(clients.length);
|
|
195
|
+
|
|
196
|
+
const res = await msafeAccount.executePendingTx(rejectPending2!);
|
|
197
|
+
expect(res.effects?.status.status).toBe('success');
|
|
198
|
+
|
|
199
|
+
await backend.processExecutedTransaction(rejectPending2!.digest);
|
|
200
|
+
pendings = await msafeAccount.pendingTransactions();
|
|
201
|
+
expect(pendings.length).toBe(0);
|
|
202
|
+
|
|
203
|
+
const history = await msafeAccount.historyTransaction();
|
|
204
|
+
const approveHistory = history.find((hs) => hs.digest === approvePending?.digest);
|
|
205
|
+
expect(approveHistory).toBeDefined();
|
|
206
|
+
expect(approveHistory?.status).toBe('rejected');
|
|
207
|
+
|
|
208
|
+
const rejectHistory = history.find((hs) => hs.digest === rejectPending?.digest);
|
|
209
|
+
expect(rejectHistory).toBeDefined();
|
|
210
|
+
expect(rejectHistory?.status).toBe('success');
|
|
211
|
+
|
|
212
|
+
const bal = await suiClient.getBalance({ owner: userAddresses[0] });
|
|
213
|
+
expect(bal.totalBalance).toBe(initBal.totalBalance);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('Propose multiple intentions, and the second intention fails', async () => {
|
|
217
|
+
const msafeAccount = await getMSafeAccountForTest();
|
|
218
|
+
|
|
219
|
+
const intention: IntentionCoinTransfer = {
|
|
220
|
+
txType: 'CoinTransfer',
|
|
221
|
+
recipient: userAddresses[0],
|
|
222
|
+
amount: '300',
|
|
223
|
+
coinType: SUI_COIN,
|
|
224
|
+
};
|
|
225
|
+
const invalidIntention: IntentionCoinTransfer = {
|
|
226
|
+
txType: 'CoinTransfer',
|
|
227
|
+
recipient: userAddresses[0],
|
|
228
|
+
amount: '18446744073709551614',
|
|
229
|
+
coinType: SUI_COIN,
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
let pendings = await msafeAccount.pendingTransactions();
|
|
233
|
+
expect(pendings.length).toBe(0);
|
|
234
|
+
let futures = await msafeAccount.futureIntentions();
|
|
235
|
+
expect(futures.length).toBe(0);
|
|
236
|
+
|
|
237
|
+
let nextSN = await msafeAccount.nextSequenceNumber();
|
|
238
|
+
await msafeAccount.proposeIntention(intention, nextSN);
|
|
239
|
+
pendings = await msafeAccount.pendingTransactions();
|
|
240
|
+
expect(pendings.length).toBe(0);
|
|
241
|
+
futures = await msafeAccount.futureIntentions();
|
|
242
|
+
expect(futures.length).toBe(1);
|
|
243
|
+
|
|
244
|
+
// Propose multiple intentions
|
|
245
|
+
nextSN = await msafeAccount.nextSequenceNumber();
|
|
246
|
+
await msafeAccount.proposeIntention(invalidIntention, nextSN);
|
|
247
|
+
pendings = await msafeAccount.pendingTransactions();
|
|
248
|
+
expect(pendings.length).toBe(0);
|
|
249
|
+
futures = await msafeAccount.futureIntentions();
|
|
250
|
+
expect(futures.length).toBe(2);
|
|
251
|
+
|
|
252
|
+
// Add the first intention to pendings
|
|
253
|
+
await msafeAccount.buildNextIntentionAndAddToPending();
|
|
254
|
+
pendings = await msafeAccount.pendingTransactions();
|
|
255
|
+
expect(pendings.length).toBe(1);
|
|
256
|
+
|
|
257
|
+
for (let i = 0; i < clients.length; i++) {
|
|
258
|
+
const msafe = clients[i].getMSafeAccount(msafeAccount.info);
|
|
259
|
+
await msafe.voteForTransaction(pendings[0].digest, pendings[0].payload);
|
|
260
|
+
}
|
|
261
|
+
pendings = await msafeAccount.pendingTransactions();
|
|
262
|
+
expect(pendings.length).toBe(1);
|
|
263
|
+
|
|
264
|
+
const res1 = await msafeAccount.executePendingTx(pendings[0]);
|
|
265
|
+
expect(res1.effects?.status.status).toBe('success');
|
|
266
|
+
|
|
267
|
+
// Process the transaction in the backend
|
|
268
|
+
const tx1Digest = pendings[0].digest;
|
|
269
|
+
await backend.processExecutedTransaction(pendings[0].digest);
|
|
270
|
+
pendings = await msafeAccount.pendingTransactions();
|
|
271
|
+
expect(pendings.length).toBe(0);
|
|
272
|
+
futures = await msafeAccount.futureIntentions();
|
|
273
|
+
expect(futures.length).toBe(1);
|
|
274
|
+
let history = await msafeAccount.historyTransaction();
|
|
275
|
+
const tx1History = history.find((hs) => hs.digest === tx1Digest);
|
|
276
|
+
expect(tx1History).toBeDefined();
|
|
277
|
+
expect(tx1History?.status).toBe('success');
|
|
278
|
+
|
|
279
|
+
// Build the next intention will fail. Then user can trigger the skip logic
|
|
280
|
+
await expect(msafeAccount.buildNextIntentionAndAddToPending()).rejects.toThrow();
|
|
281
|
+
futures = await msafeAccount.futureIntentions();
|
|
282
|
+
expect(futures.length).toBe(1);
|
|
283
|
+
expect(futures[0].status).toBe('failed');
|
|
284
|
+
expect(futures[0].statusRemark).toBeDefined();
|
|
285
|
+
|
|
286
|
+
// Skip the build failed transaction
|
|
287
|
+
await msafeAccount.skipNextFailedIntention();
|
|
288
|
+
futures = await msafeAccount.futureIntentions();
|
|
289
|
+
expect(futures.length).toBe(0);
|
|
290
|
+
history = await msafeAccount.historyTransaction();
|
|
291
|
+
const skipped = history.find((hs) => hs.sequenceNumber === nextSN);
|
|
292
|
+
expect(skipped).toBeDefined();
|
|
293
|
+
expect(skipped?.status).toBe('build-failed');
|
|
294
|
+
|
|
295
|
+
const nextSNAfter = await msafeAccount.nextSequenceNumber();
|
|
296
|
+
expect(nextSNAfter).toBe(nextSN + 1);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Uint8ArrayToHex, HexToUint8Array } from '@/utils/buffer';
|
|
2
|
+
|
|
3
|
+
describe('Hex Buffer conversion', () => {
|
|
4
|
+
it('should convert forth and back', () => {
|
|
5
|
+
const hex = '0x01020304050607080910aaff';
|
|
6
|
+
const buf = HexToUint8Array(hex);
|
|
7
|
+
const got = Uint8ArrayToHex(buf);
|
|
8
|
+
expect(hex).toEqual(got);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should convert back and forth', () => {
|
|
12
|
+
const buf = new Uint8Array([0x12, 0x34, 0x56]);
|
|
13
|
+
const hexValue = Uint8ArrayToHex(buf);
|
|
14
|
+
const got = new Uint8Array(HexToUint8Array(hexValue));
|
|
15
|
+
expect(got).toEqual(buf);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Ed25519Keypair } from '@mysten/sui.js/keypairs/ed25519';
|
|
2
|
+
|
|
3
|
+
import { PublicKeySerde, SignatureVerifier } from '@/utils/crypto';
|
|
4
|
+
|
|
5
|
+
import { LocalWallet } from '../../lib/account';
|
|
6
|
+
import { TESTNET_SUI_CLIENT } from '../../lib/config';
|
|
7
|
+
|
|
8
|
+
describe('verify', () => {
|
|
9
|
+
it('message', async () => {
|
|
10
|
+
const wallet = new LocalWallet(TESTNET_SUI_CLIENT);
|
|
11
|
+
const message = 'User login';
|
|
12
|
+
const sig = await wallet.signPersonalMessage({
|
|
13
|
+
messageStr: message,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const result = await SignatureVerifier.verifyPersonalSignature({
|
|
17
|
+
messageStr: message,
|
|
18
|
+
signature: sig.signature,
|
|
19
|
+
targetAddress: await wallet.address(),
|
|
20
|
+
});
|
|
21
|
+
expect(result).toBeTruthy();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('public key', () => {
|
|
26
|
+
it('ed25519', () => {
|
|
27
|
+
const publicKey = new Ed25519Keypair().getPublicKey();
|
|
28
|
+
const ser = PublicKeySerde.ser(publicKey);
|
|
29
|
+
const de = PublicKeySerde.de(ser);
|
|
30
|
+
expect(de.toSuiAddress()).toEqual(publicKey.toSuiAddress());
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { SignatureWithBytes } from '@mysten/sui.js/cryptography';
|
|
2
|
+
|
|
3
|
+
import { RawMultiSig } from '@/utils/multi-sig';
|
|
4
|
+
|
|
5
|
+
import { LocalWallet } from '../../lib/account';
|
|
6
|
+
import { TESTNET_SUI_CLIENT } from '../../lib/config';
|
|
7
|
+
|
|
8
|
+
describe('RawMultiSig', () => {
|
|
9
|
+
it('9/9 wallet', async () => {
|
|
10
|
+
const wallets = LocalWallet.createBatch(TESTNET_SUI_CLIENT, 9);
|
|
11
|
+
const ms = await createTestMultiSig(wallets, 9);
|
|
12
|
+
|
|
13
|
+
const message = 'Hello world';
|
|
14
|
+
const signatures = await Promise.all(wallets.map((wallet) => wallet.signPersonalMessage({ messageStr: message })));
|
|
15
|
+
const combinedSig = ms.combinePartialSignatures(signatures.map((sig) => sig.signature));
|
|
16
|
+
const result = await ms.verifyPersonalMessage(message, combinedSig);
|
|
17
|
+
expect(result).toBeTruthy();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('4/9 wallet', async () => {
|
|
21
|
+
const wallets = LocalWallet.createBatch(TESTNET_SUI_CLIENT, 9);
|
|
22
|
+
const ms = await createTestMultiSig(wallets, 4);
|
|
23
|
+
|
|
24
|
+
const message = 'Hello world';
|
|
25
|
+
const signatures: SignatureWithBytes[] = await Promise.all(
|
|
26
|
+
[wallets[1], wallets[3], wallets[5], wallets[7]].map((wallet) =>
|
|
27
|
+
wallet.signPersonalMessage({ messageStr: message }),
|
|
28
|
+
),
|
|
29
|
+
);
|
|
30
|
+
const combinedSig = ms.combinePartialSignatures(signatures.map((sig) => sig.signature));
|
|
31
|
+
const result = await ms.verifyPersonalMessage(message, combinedSig);
|
|
32
|
+
expect(result).toBeTruthy();
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export async function createTestMultiSig(wallets: LocalWallet[], threshold: number) {
|
|
37
|
+
const publicKeys = await Promise.all(wallets.map((wallet) => wallet.publicKey()));
|
|
38
|
+
const msConfig = {
|
|
39
|
+
threshold,
|
|
40
|
+
ownerWithWeight: publicKeys.map((pk) => ({
|
|
41
|
+
publicKey: pk,
|
|
42
|
+
weight: 1,
|
|
43
|
+
})),
|
|
44
|
+
creationNonce: 0,
|
|
45
|
+
};
|
|
46
|
+
return new RawMultiSig(msConfig);
|
|
47
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { getPublicKeyFromChain } from '@/utils/sui';
|
|
2
|
+
|
|
3
|
+
import { TESTNET_SUI_CLIENT } from '../../lib/config';
|
|
4
|
+
|
|
5
|
+
describe('getPublicKeyFromChain', () => {
|
|
6
|
+
it('single signer', async () => {
|
|
7
|
+
const address = '0x25404f7631419c291644218c198af7e2599a8dbbeee248c82781db889534141b';
|
|
8
|
+
const pk = await getPublicKeyFromChain(TESTNET_SUI_CLIENT, address);
|
|
9
|
+
expect(pk).toBeDefined();
|
|
10
|
+
expect(pk?.toSuiAddress()).toBe(address);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('not found', async () => {
|
|
14
|
+
const address = '0x123123123';
|
|
15
|
+
const pk = await getPublicKeyFromChain(TESTNET_SUI_CLIENT, address);
|
|
16
|
+
expect(pk).toBeUndefined();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('multi-sig', async () => {
|
|
20
|
+
const address = '0xeabab6aa83af760fec754acbab0f10cf878651ac8705c295c88171b7a4260e36';
|
|
21
|
+
await expect(getPublicKeyFromChain(TESTNET_SUI_CLIENT, address)).rejects.toThrow(
|
|
22
|
+
'multi-sig wallet cannot be owner',
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2022",
|
|
4
|
+
"lib": ["ES2022", "esnext"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
|
5
|
+
"experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
|
6
|
+
"emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
|
7
|
+
"module": "ES2022", /* Specify what module code is generated. */
|
|
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. */
|
|
10
|
+
"baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
|
11
|
+
"paths": { "@/*": ["./src/*"] }, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
|
12
|
+
"resolveJsonModule": true, /* Enable importing .json files. */
|
|
13
|
+
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
|
14
|
+
"declarationMap": true, /* Create sourcemaps for d.ts files. */
|
|
15
|
+
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
|
16
|
+
"outDir": "./dist", /* Specify an output folder for all emitted files. */
|
|
17
|
+
"allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
|
18
|
+
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
|
19
|
+
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
|
20
|
+
|
|
21
|
+
"strict": true, /* Enable all strict type-checking options. */
|
|
22
|
+
"strictPropertyInitialization": false, /* Check for class properties that are declared but not set in the constructor. */
|
|
23
|
+
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
|
24
|
+
},
|
|
25
|
+
"ts-node": {
|
|
26
|
+
"esm": true,
|
|
27
|
+
"experimentalSpecifierResolution": "node",
|
|
28
|
+
},
|
|
29
|
+
"include": [
|
|
30
|
+
"src/**/*",
|
|
31
|
+
"test/**/*",
|
|
32
|
+
"scripts/**/*",
|
|
33
|
+
"./tsconfig.json",
|
|
34
|
+
"./jest.config.ts",
|
|
35
|
+
"./tsup.config.ts"
|
|
36
|
+
]
|
|
37
|
+
}
|