@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,57 @@
|
|
|
1
|
+
import { PublicKey, SerializedSignature } from '@mysten/sui.js/cryptography';
|
|
2
|
+
|
|
3
|
+
import { CreateMSafeParams, JWTToken, UserWithOwnedMSafe } from '@/backend/types';
|
|
4
|
+
import { TxIntention } from '@/transactions/intention';
|
|
5
|
+
import { FutureIntention, HistorySendTx, MSafeAccountInfo, PendingTx } from '@/types/msafe';
|
|
6
|
+
|
|
7
|
+
export interface IBackend {
|
|
8
|
+
isJWTTokenValid(jwt: JWTToken): Promise<boolean>;
|
|
9
|
+
authSign(input: {
|
|
10
|
+
address: string;
|
|
11
|
+
message: string;
|
|
12
|
+
signature: SerializedSignature;
|
|
13
|
+
walletType: string;
|
|
14
|
+
}): Promise<JWTToken>;
|
|
15
|
+
setJWTToken(token: JWTToken): void;
|
|
16
|
+
|
|
17
|
+
getPublicKey(address: string): Promise<PublicKey | undefined>;
|
|
18
|
+
getPublicKeyBatch(addresses: string[]): Promise<(PublicKey | undefined)[]>;
|
|
19
|
+
getMSafeAccountInfo(msafeAddress: string): Promise<MSafeAccountInfo>;
|
|
20
|
+
getUserInfo(userAddress: string): Promise<UserWithOwnedMSafe>;
|
|
21
|
+
getPendingTransactions(msafeAddress: string): Promise<PendingTx[]>;
|
|
22
|
+
getHistoryTransactions(msafeAddress: string): Promise<HistorySendTx[]>;
|
|
23
|
+
getFutureIntentions(msafeAddress: string): Promise<FutureIntention[]>;
|
|
24
|
+
getCurrentSequenceNumber(msafeAddress: string): Promise<number>;
|
|
25
|
+
getNextSequenceNumber(msafeAddress: string): Promise<number>;
|
|
26
|
+
|
|
27
|
+
createMSafeAccount(input: CreateMSafeParams): Promise<void>;
|
|
28
|
+
proposeIntention(input: {
|
|
29
|
+
intention: TxIntention;
|
|
30
|
+
sequenceNumber: number;
|
|
31
|
+
userAddress: string;
|
|
32
|
+
msafeAddress: string;
|
|
33
|
+
signature: SerializedSignature;
|
|
34
|
+
}): Promise<void>;
|
|
35
|
+
proposePendingTransaction(input: {
|
|
36
|
+
intention: TxIntention;
|
|
37
|
+
userAddress: string;
|
|
38
|
+
msafeAddress: string;
|
|
39
|
+
digest: string;
|
|
40
|
+
signature: SerializedSignature;
|
|
41
|
+
}): Promise<void>;
|
|
42
|
+
rejectCurrentTx(input: {
|
|
43
|
+
userAddress: string;
|
|
44
|
+
msafeAddress: string;
|
|
45
|
+
digest: string;
|
|
46
|
+
signature: SerializedSignature;
|
|
47
|
+
}): Promise<void>;
|
|
48
|
+
voteForTransaction(input: {
|
|
49
|
+
txDigest: string;
|
|
50
|
+
msafeAddress: string;
|
|
51
|
+
userAddress: string;
|
|
52
|
+
signature: SerializedSignature;
|
|
53
|
+
}): Promise<void>;
|
|
54
|
+
buildNextIntentionAndAddToPending(input: { msafeAddress: string }): Promise<void>;
|
|
55
|
+
skipNextFailedIntention(input: { msafeAddress: string; userAddress: string }): Promise<void>;
|
|
56
|
+
processExecutedTransaction(digest: string): Promise<void>;
|
|
57
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { SerializedSignature } from '@mysten/sui.js/cryptography';
|
|
2
|
+
|
|
3
|
+
import { MSafeAccountInfo, OwnerWithWeightPK } from '@/types/msafe';
|
|
4
|
+
|
|
5
|
+
export type JWTToken = string;
|
|
6
|
+
|
|
7
|
+
export interface CreateMSafeParams {
|
|
8
|
+
ownerWithWeight: OwnerWithWeightPK[];
|
|
9
|
+
threshold: number;
|
|
10
|
+
name: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
creationNonce: number;
|
|
13
|
+
signature: SerializedSignature;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface UserWithOwnedMSafe {
|
|
17
|
+
address: string;
|
|
18
|
+
publicKey: string;
|
|
19
|
+
schema: string;
|
|
20
|
+
creationNonce: number;
|
|
21
|
+
ownedMSafe: MSafeAccountInfo[];
|
|
22
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { PublicKey, SerializedSignature } from '@mysten/sui.js/cryptography';
|
|
2
|
+
|
|
3
|
+
import { MessageHelper } from '@/core/MessageHelper';
|
|
4
|
+
import { PublicKeyHelper } from '@/core/PublicKeyHelper';
|
|
5
|
+
import { MSafeGlobals } from '@/globals/MSafeGlobals';
|
|
6
|
+
import { CreateMSafeAccountInfo, CreatePermissionInfo } from '@/types/creation';
|
|
7
|
+
import { MultiSigConfig, RawMultiSig, validateMultiSigConfig } from '@/utils/multi-sig';
|
|
8
|
+
|
|
9
|
+
export class CreateHelper {
|
|
10
|
+
constructor(
|
|
11
|
+
public readonly globals: MSafeGlobals,
|
|
12
|
+
public readonly pkHelper: PublicKeyHelper,
|
|
13
|
+
) {}
|
|
14
|
+
|
|
15
|
+
async getPublicKeyBatch(addresses: string[]) {
|
|
16
|
+
return this.pkHelper.getPublicKeyBatch(addresses);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async calculateMSafeAddress(info: CreatePermissionInfo) {
|
|
20
|
+
const msConfig = await this.reduceCreationInfoToRawConfig(info);
|
|
21
|
+
const ms = new RawMultiSig(msConfig);
|
|
22
|
+
return ms.suiAddress;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Validate the create info and return the msafe address.
|
|
26
|
+
async validateCreateInfo(createInfo: CreateMSafeAccountInfo) {
|
|
27
|
+
const rawConfig = await this.reduceCreationInfoToRawConfig(createInfo);
|
|
28
|
+
validateMultiSigConfig(rawConfig);
|
|
29
|
+
|
|
30
|
+
// Calculate the sui address will check the multi-sig
|
|
31
|
+
return this.calculateMSafeAddress(createInfo);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async submitMSafeCreation(creationInfo: CreateMSafeAccountInfo) {
|
|
35
|
+
const msafeAddress = await this.validateCreateInfo(creationInfo);
|
|
36
|
+
|
|
37
|
+
const signingMessage = MessageHelper.createMSafeMessage(msafeAddress);
|
|
38
|
+
const signature = await this.globals.wallet.signPersonalMessage({ messageStr: signingMessage });
|
|
39
|
+
|
|
40
|
+
await this.submitToBackend(creationInfo, signature.signature);
|
|
41
|
+
return msafeAddress;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private async reduceCreationInfoToRawConfig(info: CreatePermissionInfo): Promise<MultiSigConfig> {
|
|
45
|
+
const publicKeys = await this.getPublicKeyBatch(info.ownerWithWeight.map((oww) => oww.address));
|
|
46
|
+
publicKeys.forEach((pk, i) => {
|
|
47
|
+
if (pk === undefined) {
|
|
48
|
+
throw new Error(`Unknown public key for address: ${info.ownerWithWeight[i].address}`);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
threshold: info.threshold,
|
|
53
|
+
ownerWithWeight: info.ownerWithWeight.map((owner, i) => ({
|
|
54
|
+
publicKey: publicKeys[i] as PublicKey,
|
|
55
|
+
weight: owner.weight,
|
|
56
|
+
})),
|
|
57
|
+
creationNonce: info.creationNonce,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private async submitToBackend(createInfo: CreateMSafeAccountInfo, signature: SerializedSignature) {
|
|
62
|
+
const pks = await this.pkHelper.getPublicKeyBatch(createInfo.ownerWithWeight.map((owner) => owner.address));
|
|
63
|
+
await this.globals.backend.createMSafeAccount({
|
|
64
|
+
ownerWithWeight: createInfo.ownerWithWeight.map((owner, i) => ({
|
|
65
|
+
address: owner.address,
|
|
66
|
+
weight: owner.weight,
|
|
67
|
+
publicKey: pks[i] as PublicKey, // PublicKey has been verified.
|
|
68
|
+
})),
|
|
69
|
+
threshold: createInfo.threshold,
|
|
70
|
+
name: createInfo.name, // name validation is deferred to backend
|
|
71
|
+
description: createInfo.description, // description validation is deferred to backend
|
|
72
|
+
creationNonce: createInfo.creationNonce,
|
|
73
|
+
signature,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { SerializedSignature } from '@mysten/sui.js/cryptography';
|
|
2
|
+
|
|
3
|
+
import { MessageHelper } from '@/core/MessageHelper';
|
|
4
|
+
import { MSafeGlobals } from '@/globals/MSafeGlobals';
|
|
5
|
+
import { IntentionHelper, TxIntention } from '@/transactions/intention';
|
|
6
|
+
import { MSafeAccountInfo, PendingTx } from '@/types/msafe';
|
|
7
|
+
import { HexToUint8Array } from '@/utils/buffer';
|
|
8
|
+
import { RawMultiSig } from '@/utils/multi-sig';
|
|
9
|
+
|
|
10
|
+
export class MSafeAccount {
|
|
11
|
+
public rawMultiSig: RawMultiSig;
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
public readonly globals: MSafeGlobals,
|
|
15
|
+
public readonly info: MSafeAccountInfo,
|
|
16
|
+
) {
|
|
17
|
+
this.rawMultiSig = RawMultiSig.fromMSafeAccountInfo(info);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static async new(globals: MSafeGlobals, address: string) {
|
|
21
|
+
const info = await globals.backend.getMSafeAccountInfo(address);
|
|
22
|
+
return new MSafeAccount(globals, info);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async pendingTransactions() {
|
|
26
|
+
return this.backend.getPendingTransactions(this.address);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async historyTransaction() {
|
|
30
|
+
return this.backend.getHistoryTransactions(this.address);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async futureIntentions() {
|
|
34
|
+
return this.backend.getFutureIntentions(this.address);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async currentSequenceNumber() {
|
|
38
|
+
return this.backend.getCurrentSequenceNumber(this.address);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async nextSequenceNumber() {
|
|
42
|
+
return this.backend.getNextSequenceNumber(this.address);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async proposeIntention(intention: TxIntention, sequenceNumber: number) {
|
|
46
|
+
const message = MessageHelper.proposeIntentionMessage({ intention, sn: sequenceNumber });
|
|
47
|
+
const signature = await this.wallet.signPersonalMessage({
|
|
48
|
+
messageStr: message,
|
|
49
|
+
});
|
|
50
|
+
await this.backend.proposeIntention({
|
|
51
|
+
intention,
|
|
52
|
+
sequenceNumber,
|
|
53
|
+
msafeAddress: this.address,
|
|
54
|
+
userAddress: await this.userAddress(),
|
|
55
|
+
signature: signature.signature,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async voteForTransaction(digest: string, payload: string) {
|
|
60
|
+
const payloadBytes = HexToUint8Array(payload);
|
|
61
|
+
const signature = await this.wallet.signTransactionBlock({ transactionBlock: payloadBytes });
|
|
62
|
+
return this.backend.voteForTransaction({
|
|
63
|
+
msafeAddress: this.address,
|
|
64
|
+
userAddress: await this.userAddress(),
|
|
65
|
+
txDigest: digest,
|
|
66
|
+
signature: signature.signature,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Shortcut for proposing a transaction to be a pending transaction, and add user vote to it.
|
|
71
|
+
// Requires the multi-sig to be empty in pending transaction.
|
|
72
|
+
async proposePendingTransaction(intention: TxIntention) {
|
|
73
|
+
const txb = await IntentionHelper.buildTxb({
|
|
74
|
+
suiClient: this.suiClient,
|
|
75
|
+
intention,
|
|
76
|
+
sender: this.address,
|
|
77
|
+
});
|
|
78
|
+
const payload = await txb.build({ client: this.suiClient });
|
|
79
|
+
const digest = await txb.getDigest({ client: this.suiClient });
|
|
80
|
+
const signature = await this.wallet.signTransactionBlock({ transactionBlock: payload });
|
|
81
|
+
return this.backend.proposePendingTransaction({
|
|
82
|
+
msafeAddress: this.address,
|
|
83
|
+
userAddress: await this.userAddress(),
|
|
84
|
+
intention,
|
|
85
|
+
digest,
|
|
86
|
+
signature: signature.signature,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async rejectCurrentTx(pending?: PendingTx) {
|
|
91
|
+
let payloadToReject: string;
|
|
92
|
+
if (pending) {
|
|
93
|
+
if (pending.isRejectTx) {
|
|
94
|
+
throw new Error('Pending not reject transaction');
|
|
95
|
+
}
|
|
96
|
+
payloadToReject = pending.payload;
|
|
97
|
+
} else {
|
|
98
|
+
const pendings = await this.pendingTransactions();
|
|
99
|
+
if (pendings.length !== 1 || pendings[0].isRejectTx) {
|
|
100
|
+
throw new Error('Already rejected');
|
|
101
|
+
}
|
|
102
|
+
payloadToReject = pendings[0].payload;
|
|
103
|
+
}
|
|
104
|
+
const rejectTxb = await IntentionHelper.buildRejectTransaction({
|
|
105
|
+
msafeAddress: this.address,
|
|
106
|
+
payloadToReject,
|
|
107
|
+
});
|
|
108
|
+
const digest = await rejectTxb.getDigest({ client: this.suiClient });
|
|
109
|
+
const payload = await rejectTxb.build({ client: this.suiClient });
|
|
110
|
+
const signature = await this.wallet.signTransactionBlock({ transactionBlock: payload });
|
|
111
|
+
return this.backend.rejectCurrentTx({
|
|
112
|
+
msafeAddress: this.address,
|
|
113
|
+
userAddress: await this.userAddress(),
|
|
114
|
+
digest,
|
|
115
|
+
signature: signature.signature,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async buildNextIntentionAndAddToPending() {
|
|
120
|
+
return this.backend.buildNextIntentionAndAddToPending({ msafeAddress: this.address });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async skipNextFailedIntention() {
|
|
124
|
+
return this.backend.skipNextFailedIntention({ msafeAddress: this.address, userAddress: await this.userAddress() });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async executePendingTx(pending: PendingTx) {
|
|
128
|
+
if (pending.votes.length < this.info.threshold) {
|
|
129
|
+
throw new Error('Not enough signatures');
|
|
130
|
+
}
|
|
131
|
+
const sigs: SerializedSignature[] = [];
|
|
132
|
+
const gotSigs = new Map(pending.votes.map((vote) => [vote.userAddress, vote.signature]));
|
|
133
|
+
for (let i = 0; i < this.info.ownersWithWeightPK.length; i++) {
|
|
134
|
+
const owner = this.info.ownersWithWeightPK[i];
|
|
135
|
+
const signature = gotSigs.get(owner.publicKey.toSuiAddress());
|
|
136
|
+
if (signature) {
|
|
137
|
+
sigs.push(signature);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const multiSignature = this.rawMultiSig.combinePartialSignatures(sigs);
|
|
141
|
+
return this.suiClient.executeTransactionBlock({
|
|
142
|
+
transactionBlock: HexToUint8Array(pending.payload),
|
|
143
|
+
signature: multiSignature,
|
|
144
|
+
options: { showEffects: true },
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
get address() {
|
|
149
|
+
return this.info.address;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private get backend() {
|
|
153
|
+
return this.globals.backend;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private get wallet() {
|
|
157
|
+
return this.globals.wallet;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private async userAddress() {
|
|
161
|
+
return this.globals.wallet.address();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private get suiClient() {
|
|
165
|
+
return this.globals.suiClient;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { JWTToken } from '@/backend/types';
|
|
2
|
+
import { CreateHelper } from '@/core/CreateHelper';
|
|
3
|
+
import { MessageHelper } from '@/core/MessageHelper';
|
|
4
|
+
import { MSafeAccount } from '@/core/MSafeAccount';
|
|
5
|
+
import { PublicKeyHelper } from '@/core/PublicKeyHelper';
|
|
6
|
+
import { MSafeConfigOptions, MSafeEnv } from '@/globals/const';
|
|
7
|
+
import { MSafeGlobals } from '@/globals/MSafeGlobals';
|
|
8
|
+
import { CreateMSafeAccountInfo } from '@/types/creation';
|
|
9
|
+
import { MSafeAccountInfo } from '@/types/msafe';
|
|
10
|
+
import { IWallet } from '@/types/wallet';
|
|
11
|
+
|
|
12
|
+
export class MSafeClient {
|
|
13
|
+
private _creationHelper: CreateHelper | undefined;
|
|
14
|
+
|
|
15
|
+
private _publicKeyHelper: PublicKeyHelper | undefined;
|
|
16
|
+
|
|
17
|
+
constructor(public readonly globals: MSafeGlobals) {}
|
|
18
|
+
|
|
19
|
+
static async New(env: MSafeEnv, options?: MSafeConfigOptions) {
|
|
20
|
+
const globals = await MSafeGlobals.New(env, options);
|
|
21
|
+
return new MSafeClient(globals);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async connectWallet(input: { wallet: IWallet; jwtToken?: JWTToken }): Promise<JWTToken> {
|
|
25
|
+
this.globals.wallet = input.wallet;
|
|
26
|
+
const isValidJWT = input.jwtToken && (await this.backend.isJWTTokenValid(input.jwtToken));
|
|
27
|
+
if (isValidJWT) {
|
|
28
|
+
this.backend.setJWTToken(input.jwtToken as JWTToken);
|
|
29
|
+
return input.jwtToken as JWTToken;
|
|
30
|
+
}
|
|
31
|
+
const messageStr = MessageHelper.welcomeMessage(new Date().toUTCString());
|
|
32
|
+
const sig = await input.wallet.signPersonalMessage({
|
|
33
|
+
messageStr,
|
|
34
|
+
});
|
|
35
|
+
return this.backend.authSign({
|
|
36
|
+
address: await input.wallet.address(),
|
|
37
|
+
message: messageStr,
|
|
38
|
+
signature: sig.signature,
|
|
39
|
+
walletType: input.wallet.walletType,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async userInfo() {
|
|
44
|
+
return this.globals.backend.getUserInfo(await this.walletAddress());
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async createAccount(info: CreateMSafeAccountInfo) {
|
|
48
|
+
return this.creationHelper.submitMSafeCreation(info);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
getMSafeAccount(info: MSafeAccountInfo) {
|
|
52
|
+
return new MSafeAccount(this.globals, info);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async getMSafeAccountFromAddress(msafeAddress: string) {
|
|
56
|
+
const info = await this.backend.getMSafeAccountInfo(msafeAddress);
|
|
57
|
+
return new MSafeAccount(this.globals, info);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Get the creation helper to calculate the msafe account address,
|
|
61
|
+
// validation, and more.
|
|
62
|
+
get creationHelper() {
|
|
63
|
+
if (!this._creationHelper) {
|
|
64
|
+
this._creationHelper = new CreateHelper(this.globals, this.publicKeyHelper);
|
|
65
|
+
}
|
|
66
|
+
return this._creationHelper;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get publicKeyHelper() {
|
|
70
|
+
if (!this._publicKeyHelper) {
|
|
71
|
+
this._publicKeyHelper = new PublicKeyHelper(this.globals);
|
|
72
|
+
}
|
|
73
|
+
return this._publicKeyHelper;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get config() {
|
|
77
|
+
return this.globals.config;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
get backend() {
|
|
81
|
+
return this.globals.backend;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
get wallet() {
|
|
85
|
+
return this.globals.wallet;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private async walletAddress() {
|
|
89
|
+
return this.wallet.address();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { IntentionHelper, TxIntention } from '@/transactions/intention';
|
|
2
|
+
|
|
3
|
+
export class MessageHelper {
|
|
4
|
+
// Message used for MSafe creation
|
|
5
|
+
static createMSafeMessage(msafeAddress: string): string {
|
|
6
|
+
return `Create MSafe Account: ${msafeAddress}`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
static deCreateMSafeMessage(msg: string) {
|
|
10
|
+
const regex = /Create MSafe Account: (.+)/;
|
|
11
|
+
const matches = msg.match(regex);
|
|
12
|
+
return matches ? matches[1] : undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Message to be used when user login. The timestamp string
|
|
16
|
+
// is used to for extra validation.
|
|
17
|
+
static welcomeMessage(timestamp: string): string {
|
|
18
|
+
return `Welcome to MSafe. ${timestamp}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static deWelcomeMessage(msg: string): string | undefined {
|
|
22
|
+
const regex = /Welcome to MSafe. (.+)/;
|
|
23
|
+
const matches = msg.match(regex);
|
|
24
|
+
return matches ? matches[1] : undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static proposeIntentionMessage(data: ProposeIntentionData) {
|
|
28
|
+
const { intention, sn } = data;
|
|
29
|
+
const intentionData = IntentionHelper.ser(intention);
|
|
30
|
+
const msg: ProposeIntentionMessage = {
|
|
31
|
+
intentionData,
|
|
32
|
+
sequenceNumber: sn,
|
|
33
|
+
};
|
|
34
|
+
return JSON.stringify(msg);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static deProposeIntentionMessage(s: string): ProposeIntentionData {
|
|
38
|
+
const de = JSON.parse(s);
|
|
39
|
+
if (
|
|
40
|
+
!('intentionData' in de) ||
|
|
41
|
+
typeof de.intentionData !== 'string' ||
|
|
42
|
+
!('sequenceNumber' in de) ||
|
|
43
|
+
typeof de.sequenceNumber !== 'number'
|
|
44
|
+
) {
|
|
45
|
+
throw new Error('Invalid intention data');
|
|
46
|
+
}
|
|
47
|
+
const { sequenceNumber, intentionData } = de;
|
|
48
|
+
return {
|
|
49
|
+
sn: sequenceNumber,
|
|
50
|
+
intention: IntentionHelper.de(intentionData),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface ProposeIntentionData {
|
|
56
|
+
intention: TxIntention;
|
|
57
|
+
sn: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ProposeIntentionMessage {
|
|
61
|
+
intentionData: string;
|
|
62
|
+
sequenceNumber: number;
|
|
63
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { PublicKey } from '@mysten/sui.js/src/cryptography';
|
|
2
|
+
|
|
3
|
+
import { MSafeGlobals } from '@/globals/MSafeGlobals';
|
|
4
|
+
import { getPublicKeyFromChain } from '@/utils/sui';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* PublicKeyHelper is the helper to query for public keys from
|
|
8
|
+
* backend and blockchain.
|
|
9
|
+
*/
|
|
10
|
+
export class PublicKeyHelper {
|
|
11
|
+
private knownPublicKeys: Map<string, PublicKey>;
|
|
12
|
+
|
|
13
|
+
constructor(public readonly globals: MSafeGlobals) {
|
|
14
|
+
this.knownPublicKeys = new Map<string, PublicKey>();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async getPublicKey(address: string) {
|
|
18
|
+
const cached = this.knownPublicKeys.get(address);
|
|
19
|
+
if (cached) {
|
|
20
|
+
return cached;
|
|
21
|
+
}
|
|
22
|
+
const pk = await this._getPublicKey(address);
|
|
23
|
+
if (pk) {
|
|
24
|
+
this.knownPublicKeys.set(address, pk);
|
|
25
|
+
}
|
|
26
|
+
return pk;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async getPublicKeyBatch(addresses: string[]): Promise<(PublicKey | undefined)[]> {
|
|
30
|
+
const results: (PublicKey | undefined)[] = new Array(addresses.length).fill(undefined);
|
|
31
|
+
|
|
32
|
+
// Query local cache first
|
|
33
|
+
for (let i = 0; i < addresses.length; i++) {
|
|
34
|
+
const address = addresses[i];
|
|
35
|
+
results[i] = this.knownPublicKeys.get(address);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Query backend in batch
|
|
39
|
+
const emptyIndexes = results
|
|
40
|
+
.map((elem, index) => (elem === undefined ? index : -1))
|
|
41
|
+
.filter((index) => index !== -1);
|
|
42
|
+
const backendResult = await this.globals.backend.getPublicKeyBatch(emptyIndexes.map((index) => addresses[index]));
|
|
43
|
+
for (let i = 0; i < emptyIndexes.length; i++) {
|
|
44
|
+
const index = emptyIndexes[i];
|
|
45
|
+
results[index] = backendResult[i];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Query the public key from blockchain
|
|
49
|
+
for (let i = 0; i < results.length; i++) {
|
|
50
|
+
if (results[i] === undefined) {
|
|
51
|
+
results[i] = await this.getPublicKeyFromChain(addresses[i]);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Finally, write the public keys to the local cache
|
|
56
|
+
for (let i = 0; i < addresses.length; i++) {
|
|
57
|
+
if (results[i]) {
|
|
58
|
+
this.knownPublicKeys.set(addresses[i], results[i] as PublicKey);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return results;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private async _getPublicKey(address: string) {
|
|
65
|
+
const pkBackend = await this.getPublicKeyFromBackend(address);
|
|
66
|
+
if (pkBackend) {
|
|
67
|
+
return pkBackend;
|
|
68
|
+
}
|
|
69
|
+
const pkChain = await this.getPublicKeyFromChain(address);
|
|
70
|
+
if (pkChain) {
|
|
71
|
+
return pkChain;
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private async getPublicKeyFromBackend(address: string): Promise<PublicKey | undefined> {
|
|
77
|
+
try {
|
|
78
|
+
const pk = await this.globals.backend.getPublicKey(address);
|
|
79
|
+
return pk;
|
|
80
|
+
} catch (_) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private async getPublicKeyFromChain(address: string): Promise<PublicKey | undefined> {
|
|
86
|
+
return getPublicKeyFromChain(this.globals.suiClient, address);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { SuiClient } from '@mysten/sui.js/client';
|
|
2
|
+
|
|
3
|
+
import { IBackend } from '@/backend/interface';
|
|
4
|
+
import { PseudoBackend } from '@/backend/PseudoBackend';
|
|
5
|
+
import { getMSafeConfig, MSafeConfig, MSafeConfigOptions, MSafeEnv } from '@/globals/const';
|
|
6
|
+
import { IWallet } from '@/types/wallet';
|
|
7
|
+
|
|
8
|
+
export class MSafeGlobals {
|
|
9
|
+
public readonly backend: IBackend;
|
|
10
|
+
|
|
11
|
+
public readonly suiClient: SuiClient;
|
|
12
|
+
|
|
13
|
+
public readonly config: MSafeConfig;
|
|
14
|
+
|
|
15
|
+
_wallet: IWallet | undefined;
|
|
16
|
+
|
|
17
|
+
constructor(input: { backend: IBackend; suiClient: SuiClient; config: MSafeConfig }) {
|
|
18
|
+
this.backend = input.backend;
|
|
19
|
+
this.suiClient = input.suiClient;
|
|
20
|
+
this.config = input.config;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static async New(env: MSafeEnv, options?: MSafeConfigOptions) {
|
|
24
|
+
const config = getMSafeConfig(env, options);
|
|
25
|
+
const suiClient = new SuiClient(config.suiClient);
|
|
26
|
+
const backend = await PseudoBackend.New(config.backend, suiClient);
|
|
27
|
+
return new MSafeGlobals({
|
|
28
|
+
backend,
|
|
29
|
+
suiClient,
|
|
30
|
+
config,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
connectWallet(wallet: IWallet) {
|
|
35
|
+
this._wallet = wallet;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get wallet() {
|
|
39
|
+
if (!this._wallet) {
|
|
40
|
+
throw new Error('wallet not connected');
|
|
41
|
+
}
|
|
42
|
+
return this._wallet;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
set wallet(val: IWallet) {
|
|
46
|
+
this._wallet = val;
|
|
47
|
+
}
|
|
48
|
+
}
|