@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,95 @@
|
|
|
1
|
+
import { ModelConfig } from '@msafe/sui3-model/common';
|
|
2
|
+
|
|
3
|
+
export enum MSafeEnv {
|
|
4
|
+
local = 'local',
|
|
5
|
+
unit = 'unit',
|
|
6
|
+
dev = 'dev',
|
|
7
|
+
prev = 'prev',
|
|
8
|
+
prod = 'prod',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface MSafeConfig {
|
|
12
|
+
suiClient: {
|
|
13
|
+
url: string;
|
|
14
|
+
};
|
|
15
|
+
backend: DBConfig;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface MSafeConfigOptions {
|
|
19
|
+
suiClient?: {
|
|
20
|
+
url?: string;
|
|
21
|
+
};
|
|
22
|
+
backend?: DBConfig;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Use pseudo backend for now.
|
|
26
|
+
export type DBConfig = ModelConfig;
|
|
27
|
+
|
|
28
|
+
export const LOCAL_DATABASE_CONFIG: DBConfig = {
|
|
29
|
+
type: 'mysql',
|
|
30
|
+
host: '127.0.0.1',
|
|
31
|
+
port: 3306,
|
|
32
|
+
username: 'msafe',
|
|
33
|
+
password: 'msafe',
|
|
34
|
+
database: 'msafe_sui_local',
|
|
35
|
+
logging: false,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const DEV_DATABASE_CONFIG: DBConfig = {
|
|
39
|
+
type: 'mysql',
|
|
40
|
+
host: 'msafe-dev-database.cluster-caos3ssocrx6.us-west-1.rds.amazonaws.com',
|
|
41
|
+
port: 3306,
|
|
42
|
+
username: 'msafe',
|
|
43
|
+
password: 'Momentum.Safe2022',
|
|
44
|
+
database: 'msafe_sui_dev',
|
|
45
|
+
logging: false,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const TESTNET_RPC_URL = 'https://sui-testnet.blockvision.org/v1/2Sgk89ivT64MnKdcGzjmyjY2ndD';
|
|
49
|
+
export const MAINNET_RPC_URL = 'https://sui-mainnet.blockvision.org/v1/2Sgk7NPvqkd7mESYkxF01yX15l7';
|
|
50
|
+
|
|
51
|
+
export const ENV_CONFIGS = new Map<MSafeEnv, MSafeConfig>([
|
|
52
|
+
[
|
|
53
|
+
MSafeEnv.unit,
|
|
54
|
+
{
|
|
55
|
+
suiClient: {
|
|
56
|
+
url: TESTNET_RPC_URL,
|
|
57
|
+
},
|
|
58
|
+
backend: LOCAL_DATABASE_CONFIG,
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
[
|
|
62
|
+
MSafeEnv.local,
|
|
63
|
+
{
|
|
64
|
+
suiClient: {
|
|
65
|
+
url: TESTNET_RPC_URL,
|
|
66
|
+
},
|
|
67
|
+
backend: LOCAL_DATABASE_CONFIG,
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
[
|
|
71
|
+
MSafeEnv.dev,
|
|
72
|
+
{
|
|
73
|
+
suiClient: {
|
|
74
|
+
url: TESTNET_RPC_URL,
|
|
75
|
+
},
|
|
76
|
+
backend: DEV_DATABASE_CONFIG,
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
export function getMSafeConfig(env: MSafeEnv, options?: MSafeConfigOptions) {
|
|
82
|
+
const config = ENV_CONFIGS.get(env);
|
|
83
|
+
if (!config) {
|
|
84
|
+
throw new Error('Unknown environment');
|
|
85
|
+
}
|
|
86
|
+
if (options?.suiClient?.url) {
|
|
87
|
+
config.suiClient.url = options.suiClient.url;
|
|
88
|
+
}
|
|
89
|
+
if (options?.backend) {
|
|
90
|
+
config.backend = options.backend;
|
|
91
|
+
}
|
|
92
|
+
return config;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const AUTH_SIGN_MESSAGE = 'Welcome to MSafe';
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { SuiClient } from '@mysten/sui.js/client';
|
|
2
|
+
import { TransactionBlock } from '@mysten/sui.js/transactions';
|
|
3
|
+
|
|
4
|
+
import { Formatter } from '@/utils/format';
|
|
5
|
+
import { getAllCoins, SUI_COIN } from '@/utils/sui';
|
|
6
|
+
|
|
7
|
+
export interface IntentionCoinTransfer {
|
|
8
|
+
txType: 'CoinTransfer';
|
|
9
|
+
recipient: string;
|
|
10
|
+
coinType: string;
|
|
11
|
+
amount: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function buildCoinTransferTxb(input: {
|
|
15
|
+
suiClient: SuiClient;
|
|
16
|
+
sender: string;
|
|
17
|
+
intention: IntentionCoinTransfer;
|
|
18
|
+
}) {
|
|
19
|
+
if (Formatter.isSuiStructEqual(input.intention.coinType, SUI_COIN)) {
|
|
20
|
+
return buildSuiCoinTransferTxb(input);
|
|
21
|
+
}
|
|
22
|
+
return buildOtherCoinTransferTxb(input);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function buildSuiCoinTransferTxb(input: { sender: string; intention: IntentionCoinTransfer }): TransactionBlock {
|
|
26
|
+
const txb = new TransactionBlock();
|
|
27
|
+
const [coin] = txb.splitCoins(txb.gas, [txb.pure(input.intention.amount)]);
|
|
28
|
+
txb.transferObjects([coin], txb.pure(input.intention.recipient));
|
|
29
|
+
txb.setSender(input.sender);
|
|
30
|
+
return txb;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function buildOtherCoinTransferTxb(input: {
|
|
34
|
+
suiClient: SuiClient;
|
|
35
|
+
sender: string;
|
|
36
|
+
intention: IntentionCoinTransfer;
|
|
37
|
+
}) {
|
|
38
|
+
const { suiClient, sender, intention } = input;
|
|
39
|
+
|
|
40
|
+
const objs = await getAllCoins({
|
|
41
|
+
suiClient,
|
|
42
|
+
owner: sender,
|
|
43
|
+
coinType: intention.coinType,
|
|
44
|
+
});
|
|
45
|
+
if (objs.length === 0) {
|
|
46
|
+
throw new Error('No valid coin found to send');
|
|
47
|
+
}
|
|
48
|
+
const totalBal = objs.reduce((sum, coin) => sum + BigInt(coin.balance), 0n);
|
|
49
|
+
if (totalBal < BigInt(intention.amount)) {
|
|
50
|
+
throw new Error('Not enough balance');
|
|
51
|
+
}
|
|
52
|
+
const txb = new TransactionBlock();
|
|
53
|
+
const primary = txb.object(objs[0].coinObjectId);
|
|
54
|
+
if (objs.length > 1) {
|
|
55
|
+
txb.mergeCoins(
|
|
56
|
+
primary,
|
|
57
|
+
objs.slice(1).map((obj) => txb.object(obj.coinObjectId)),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
const [coin] = txb.splitCoins(primary, [txb.pure(intention.amount)]);
|
|
61
|
+
txb.transferObjects([coin], txb.pure(intention.recipient));
|
|
62
|
+
txb.setSender(input.sender);
|
|
63
|
+
return txb;
|
|
64
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './intention';
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { SuiClient } from '@mysten/sui.js/client';
|
|
2
|
+
|
|
3
|
+
import { buildCoinTransferTxb, IntentionCoinTransfer } from '@/transactions/coin-transfer';
|
|
4
|
+
import { buildObjectTransferTxb, IntentionObjectTransfer } from '@/transactions/object-transfer';
|
|
5
|
+
import { buildRejectTxb } from '@/transactions/reject';
|
|
6
|
+
|
|
7
|
+
export type TxIntention = IntentionObjectTransfer | IntentionCoinTransfer;
|
|
8
|
+
|
|
9
|
+
// TODO: Refactor later
|
|
10
|
+
export class IntentionHelper {
|
|
11
|
+
static ser(intention: TxIntention) {
|
|
12
|
+
return JSON.stringify(intention);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static de(val: string): TxIntention {
|
|
16
|
+
const intention = JSON.parse(val);
|
|
17
|
+
if (typeof intention !== 'object' || !('txType' in intention)) {
|
|
18
|
+
throw new Error(`Failed to deserialize intention: ${val}`);
|
|
19
|
+
}
|
|
20
|
+
return JSON.parse(val);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// TODO: Add gas option here.
|
|
24
|
+
static buildTxb(input: { suiClient: SuiClient; intention: TxIntention; sender: string }) {
|
|
25
|
+
switch (input.intention.txType) {
|
|
26
|
+
case 'CoinTransfer':
|
|
27
|
+
return buildCoinTransferTxb({
|
|
28
|
+
suiClient: input.suiClient,
|
|
29
|
+
sender: input.sender,
|
|
30
|
+
intention: input.intention as IntentionCoinTransfer,
|
|
31
|
+
});
|
|
32
|
+
case 'ObjectTransfer':
|
|
33
|
+
return buildObjectTransferTxb({
|
|
34
|
+
suiClient: input.suiClient,
|
|
35
|
+
sender: input.sender,
|
|
36
|
+
intention: input.intention as IntentionObjectTransfer,
|
|
37
|
+
});
|
|
38
|
+
default:
|
|
39
|
+
throw new Error(`Unknown tx type: ${input.intention}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static getTxType(intention: TxIntention) {
|
|
44
|
+
switch (intention.txType) {
|
|
45
|
+
case 'CoinTransfer':
|
|
46
|
+
return {
|
|
47
|
+
txType: 'CoinTransfer',
|
|
48
|
+
txSubType: 'CoinTransfer',
|
|
49
|
+
};
|
|
50
|
+
case 'ObjectTransfer':
|
|
51
|
+
return {
|
|
52
|
+
txType: 'ObjectTransfer',
|
|
53
|
+
txSubType: 'ObjectTransfer',
|
|
54
|
+
};
|
|
55
|
+
default:
|
|
56
|
+
throw new Error('Unknown intention type');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
static buildRejectTransaction(input: { msafeAddress: string; payloadToReject: string }) {
|
|
61
|
+
return buildRejectTxb({ sender: input.msafeAddress, payloadToReject: input.payloadToReject });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { SuiClient } from '@mysten/sui.js/client';
|
|
2
|
+
import type { SuiObjectResponse } from '@mysten/sui.js/src/client/types';
|
|
3
|
+
import { TransactionBlock } from '@mysten/sui.js/transactions';
|
|
4
|
+
|
|
5
|
+
import { Formatter } from '@/utils/format';
|
|
6
|
+
|
|
7
|
+
export interface IntentionObjectTransfer {
|
|
8
|
+
txType: 'ObjectTransfer';
|
|
9
|
+
receiver: string;
|
|
10
|
+
objectId: string;
|
|
11
|
+
objectType: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function buildObjectTransferTxb(input: {
|
|
15
|
+
suiClient: SuiClient;
|
|
16
|
+
sender: string;
|
|
17
|
+
intention: IntentionObjectTransfer;
|
|
18
|
+
}) {
|
|
19
|
+
await validateObjectTransfer(input);
|
|
20
|
+
|
|
21
|
+
const txb = new TransactionBlock();
|
|
22
|
+
txb.transferObjects([txb.object(input.intention.objectId)], txb.pure(input.intention.receiver));
|
|
23
|
+
txb.setSender(input.sender);
|
|
24
|
+
|
|
25
|
+
return txb;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function validateObjectTransfer(input: {
|
|
29
|
+
suiClient: SuiClient;
|
|
30
|
+
sender: string;
|
|
31
|
+
intention: IntentionObjectTransfer;
|
|
32
|
+
}) {
|
|
33
|
+
const { suiClient, sender, intention } = input;
|
|
34
|
+
const obj = await suiClient.getObject({
|
|
35
|
+
id: intention.objectId,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (obj.data === undefined) {
|
|
39
|
+
throw new Error('Object not found');
|
|
40
|
+
}
|
|
41
|
+
if (!obj.data?.type) {
|
|
42
|
+
throw new Error('Object type is null');
|
|
43
|
+
}
|
|
44
|
+
if (!Formatter.isSuiStructEqual(obj.data.type, intention.objectType)) {
|
|
45
|
+
throw new Error('Object type not expected');
|
|
46
|
+
}
|
|
47
|
+
if (Formatter.isCoinObjectType(obj.data.type)) {
|
|
48
|
+
throw new Error('Can not transfer coin object in Object Transfer transactions');
|
|
49
|
+
}
|
|
50
|
+
const addressOwner = getAddressOwner(obj);
|
|
51
|
+
if (!Formatter.isSuiAddressEqual(addressOwner, sender)) {
|
|
52
|
+
throw new Error('Object owner not match');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getAddressOwner(object: SuiObjectResponse) {
|
|
57
|
+
const owner = object.data?.owner;
|
|
58
|
+
if (!owner) {
|
|
59
|
+
throw new Error('Object Owner not found');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (typeof owner !== 'object' || !('AddressOwner' in owner)) {
|
|
63
|
+
throw new Error('Invalid object owner');
|
|
64
|
+
}
|
|
65
|
+
return owner.AddressOwner;
|
|
66
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { TransactionBlock } from '@mysten/sui.js/transactions';
|
|
2
|
+
|
|
3
|
+
import { HexToUint8Array } from '@/utils/buffer';
|
|
4
|
+
|
|
5
|
+
export async function buildRejectTxb(input: { sender: string; payloadToReject: string }) {
|
|
6
|
+
const approveTxb = TransactionBlock.from(HexToUint8Array(input.payloadToReject) as Uint8Array);
|
|
7
|
+
const gasPayment = approveTxb.blockData.gasConfig.payment;
|
|
8
|
+
if (!gasPayment) {
|
|
9
|
+
throw new Error('No gas payment found for approve payload');
|
|
10
|
+
}
|
|
11
|
+
// Reject transaction is an empty transaction block with the same gas payment as the
|
|
12
|
+
// transaction to be rejected.
|
|
13
|
+
const txb = new TransactionBlock();
|
|
14
|
+
txb.setGasPayment(gasPayment);
|
|
15
|
+
txb.setSender(input.sender);
|
|
16
|
+
return txb;
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// TODO: Stream related transactions.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface CreateMSafeAccountInfo {
|
|
2
|
+
ownerWithWeight: {
|
|
3
|
+
address: string;
|
|
4
|
+
weight: number;
|
|
5
|
+
}[];
|
|
6
|
+
threshold: number;
|
|
7
|
+
name: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
creationNonce: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface CreatePermissionInfo {
|
|
13
|
+
ownerWithWeight: {
|
|
14
|
+
address: string;
|
|
15
|
+
weight: number;
|
|
16
|
+
}[];
|
|
17
|
+
threshold: number;
|
|
18
|
+
creationNonce: number;
|
|
19
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { PublicKey } from '@mysten/sui.js/src/cryptography';
|
|
2
|
+
|
|
3
|
+
import { TxIntention } from '@/transactions/intention';
|
|
4
|
+
|
|
5
|
+
export interface MSafeAccountInfo {
|
|
6
|
+
address: string;
|
|
7
|
+
ownersWithWeightPK: OwnerWithWeightPK[];
|
|
8
|
+
threshold: number;
|
|
9
|
+
name: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
creationNonce: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type OwnerWithWeightPK = OwnerWithWeight & {
|
|
15
|
+
publicKey: PublicKey;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export interface OwnerWithWeight {
|
|
19
|
+
address: string;
|
|
20
|
+
weight: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface PendingTx {
|
|
24
|
+
digest: string;
|
|
25
|
+
payload: string;
|
|
26
|
+
msafeAddress: string;
|
|
27
|
+
isRejectTx: boolean;
|
|
28
|
+
intention?: TxIntention;
|
|
29
|
+
creator: string;
|
|
30
|
+
createdAt: Date;
|
|
31
|
+
sequenceNumber: number;
|
|
32
|
+
votes: PendingVote[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface PendingVote {
|
|
36
|
+
userAddress: string;
|
|
37
|
+
signature: string;
|
|
38
|
+
timestamp: Date;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Transaction sent by the MSafe account.
|
|
42
|
+
export interface HistorySendTx {
|
|
43
|
+
digest: string;
|
|
44
|
+
payload: string;
|
|
45
|
+
msafeAddress: string;
|
|
46
|
+
sequenceNumber: number;
|
|
47
|
+
isRejectTx: boolean;
|
|
48
|
+
status: string;
|
|
49
|
+
creator: string;
|
|
50
|
+
createdAt: Date;
|
|
51
|
+
votes: HistoryVote[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// TODO: Add ReceiveTransaction in backend code.
|
|
55
|
+
export interface ReceiveTransaction {
|
|
56
|
+
digest: string;
|
|
57
|
+
payload: string;
|
|
58
|
+
txType: string;
|
|
59
|
+
txSubType: string;
|
|
60
|
+
timestamp: Date;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface HistoryVote {
|
|
64
|
+
userAddress: string;
|
|
65
|
+
timestamp: Date;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface FutureIntention {
|
|
69
|
+
intention: TxIntention;
|
|
70
|
+
sequenceNumber: number;
|
|
71
|
+
msafeAddress: string;
|
|
72
|
+
creator: string;
|
|
73
|
+
txType: string;
|
|
74
|
+
txSubType: string;
|
|
75
|
+
rawData: string;
|
|
76
|
+
status: string;
|
|
77
|
+
statusRemark?: string;
|
|
78
|
+
createdAt: Date;
|
|
79
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ExecuteTransactionRequestType,
|
|
3
|
+
SuiTransactionBlockResponseOptions,
|
|
4
|
+
SuiTransactionBlockResponse,
|
|
5
|
+
} from '@mysten/sui.js/client';
|
|
6
|
+
import { SignatureWithBytes } from '@mysten/sui.js/src/cryptography/keypair';
|
|
7
|
+
import { TransactionBlock } from '@mysten/sui.js/transactions';
|
|
8
|
+
|
|
9
|
+
export interface IWallet {
|
|
10
|
+
walletType: string;
|
|
11
|
+
|
|
12
|
+
address(): Promise<string>;
|
|
13
|
+
|
|
14
|
+
signPersonalMessage(input: { messageStr: string }): Promise<SignatureWithBytes>;
|
|
15
|
+
|
|
16
|
+
signTransactionBlock(input: { transactionBlock: TransactionBlock | Uint8Array }): Promise<SignatureWithBytes>;
|
|
17
|
+
|
|
18
|
+
signAndExecuteTransactionBlock(input: {
|
|
19
|
+
transactionBlock: TransactionBlock;
|
|
20
|
+
requestType?: ExecuteTransactionRequestType;
|
|
21
|
+
options?: SuiTransactionBlockResponseOptions;
|
|
22
|
+
}): Promise<SuiTransactionBlockResponse>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function stringToBuffer(s: string) {
|
|
2
|
+
return Buffer.from(s, 'utf-8');
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function Uint8ArrayToHex(b: Uint8Array): string {
|
|
6
|
+
return `0x${Array.prototype.map.call(b, (x) => `0${x.toString(16)}`.slice(-2)).join('')}`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function HexToUint8Array(hex: string): Uint8Array {
|
|
10
|
+
return Uint8Array.from(Buffer.from(hex.startsWith('0x') ? hex.slice(2) : hex, 'hex'));
|
|
11
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { CoinMetadata, SuiClient, SuiObjectData } from '@mysten/sui.js/client';
|
|
2
|
+
import { normalizeStructTag } from '@mysten/sui.js/utils';
|
|
3
|
+
|
|
4
|
+
// CoinHelper is the coin helper to query for coin metadata.
|
|
5
|
+
export class CoinHelper {
|
|
6
|
+
private _client: SuiClient;
|
|
7
|
+
|
|
8
|
+
private _coinMetaReg: Map<string, CoinMetadata>;
|
|
9
|
+
|
|
10
|
+
constructor(client: SuiClient) {
|
|
11
|
+
this._client = client;
|
|
12
|
+
this._coinMetaReg = new Map();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async getCoinMeta(coinType: string): Promise<CoinMetadata | undefined> {
|
|
16
|
+
const normalized = normalizeStructTag(coinType);
|
|
17
|
+
if (this._coinMetaReg.has(normalized)) {
|
|
18
|
+
return this._coinMetaReg.get(normalized);
|
|
19
|
+
}
|
|
20
|
+
const meta = await this.queryCoinMeta(normalized);
|
|
21
|
+
if (meta) {
|
|
22
|
+
this._coinMetaReg.set(normalized, meta);
|
|
23
|
+
}
|
|
24
|
+
return meta;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private async queryCoinMeta(coinType: string): Promise<CoinMetadata | undefined> {
|
|
28
|
+
const res = await this._client.getCoinMetadata({ coinType });
|
|
29
|
+
return res || undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Copied from sui/sdk/typescript/src/framework/framework.ts
|
|
34
|
+
|
|
35
|
+
export const COIN_TYPE_ARG_REGEX =
|
|
36
|
+
/^0x0000000000000000000000000000000000000000000000000000000000000002::coin::Coin<(.+)>$/;
|
|
37
|
+
|
|
38
|
+
export class Coin {
|
|
39
|
+
static isCoin(type: string | undefined | null): boolean {
|
|
40
|
+
if (!type) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
return normalizeStructTag(type).match(COIN_TYPE_ARG_REGEX) != null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static getCoinType(type: string) {
|
|
47
|
+
const [, res] = normalizeStructTag(type).match(COIN_TYPE_ARG_REGEX) ?? [];
|
|
48
|
+
return res || null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static getBalance(data: SuiObjectData): bigint | undefined {
|
|
52
|
+
if (!Coin.isCoin(data.type)) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
if (data.content?.dataType !== 'moveObject') {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
const { balance } = data.content?.fields as any;
|
|
59
|
+
if (balance === undefined) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
return BigInt(balance);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PublicKey,
|
|
3
|
+
SerializedSignature,
|
|
4
|
+
SIGNATURE_FLAG_TO_SCHEME,
|
|
5
|
+
SignatureFlag,
|
|
6
|
+
SignatureScheme,
|
|
7
|
+
} from '@mysten/sui.js/cryptography';
|
|
8
|
+
import { Ed25519PublicKey } from '@mysten/sui.js/keypairs/ed25519';
|
|
9
|
+
import { Secp256k1PublicKey } from '@mysten/sui.js/keypairs/secp256k1';
|
|
10
|
+
import { Secp256r1PublicKey } from '@mysten/sui.js/keypairs/secp256r1';
|
|
11
|
+
import { verifyPersonalMessage, verifyTransactionBlock } from '@mysten/sui.js/verify';
|
|
12
|
+
|
|
13
|
+
import { stringToBuffer } from '@/utils/buffer';
|
|
14
|
+
import { Formatter } from '@/utils/format';
|
|
15
|
+
|
|
16
|
+
export class SignatureVerifier {
|
|
17
|
+
static async getPublicKeyFromSignature(input: {
|
|
18
|
+
message: Uint8Array;
|
|
19
|
+
messageType: 'TransactionBlock' | 'Personal';
|
|
20
|
+
signature: SerializedSignature;
|
|
21
|
+
}) {
|
|
22
|
+
if (input.messageType === 'TransactionBlock') {
|
|
23
|
+
return verifyTransactionBlock(input.message, input.signature);
|
|
24
|
+
}
|
|
25
|
+
return verifyPersonalMessage(input.message, input.signature);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static async getPublicKeyFromPersonalSignature(input: { messageStr: string; signature: SerializedSignature }) {
|
|
29
|
+
const message = stringToBuffer(input.messageStr);
|
|
30
|
+
return this.getPublicKeyFromSignature({
|
|
31
|
+
message,
|
|
32
|
+
messageType: 'Personal',
|
|
33
|
+
signature: input.signature,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static async verifySignature(input: {
|
|
38
|
+
message: Uint8Array;
|
|
39
|
+
messageType: 'TransactionBlock' | 'Personal';
|
|
40
|
+
signature: SerializedSignature;
|
|
41
|
+
targetAddress: string;
|
|
42
|
+
}) {
|
|
43
|
+
const publicKey = await SignatureVerifier.getPublicKeyFromSignature(input);
|
|
44
|
+
return Formatter.isSuiAddressEqual(publicKey.toSuiAddress(), input.targetAddress);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static async verifyPersonalSignature(input: {
|
|
48
|
+
messageStr: string;
|
|
49
|
+
signature: SerializedSignature;
|
|
50
|
+
targetAddress: string;
|
|
51
|
+
}) {
|
|
52
|
+
const message = stringToBuffer(input.messageStr);
|
|
53
|
+
return this.verifySignature({
|
|
54
|
+
message,
|
|
55
|
+
messageType: 'Personal',
|
|
56
|
+
signature: input.signature,
|
|
57
|
+
targetAddress: input.targetAddress,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static async verifyTransactionSignature(input: {
|
|
62
|
+
payload: Uint8Array;
|
|
63
|
+
signature: SerializedSignature;
|
|
64
|
+
targetAddress: string;
|
|
65
|
+
}): Promise<boolean> {
|
|
66
|
+
return this.verifySignature({
|
|
67
|
+
messageType: 'TransactionBlock',
|
|
68
|
+
message: input.payload,
|
|
69
|
+
signature: input.signature,
|
|
70
|
+
targetAddress: input.targetAddress,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export class PublicKeySerde {
|
|
76
|
+
static ser(publicKey: PublicKey) {
|
|
77
|
+
return {
|
|
78
|
+
publicKey: publicKey.toBase64(),
|
|
79
|
+
scheme: SIGNATURE_FLAG_TO_SCHEME[publicKey.flag() as SignatureFlag],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
static de(input: { publicKey: string | Uint8Array; scheme: SignatureScheme }): PublicKey {
|
|
84
|
+
switch (input.scheme) {
|
|
85
|
+
case 'ED25519':
|
|
86
|
+
return new Ed25519PublicKey(input.publicKey);
|
|
87
|
+
case 'Secp256k1':
|
|
88
|
+
return new Secp256k1PublicKey(input.publicKey);
|
|
89
|
+
case 'Secp256r1':
|
|
90
|
+
return new Secp256r1PublicKey(input.publicKey);
|
|
91
|
+
default:
|
|
92
|
+
throw new Error('Unsupported signature scheme: $input.scheme');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { normalizeSuiAddress, normalizeStructTag } from '@mysten/sui.js/utils';
|
|
2
|
+
|
|
3
|
+
import { Coin } from '@/utils/coin';
|
|
4
|
+
|
|
5
|
+
export class Formatter {
|
|
6
|
+
static normalizeSuiAddress(addr: string) {
|
|
7
|
+
return normalizeSuiAddress(addr);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
static normalizeStructTag(struct: string) {
|
|
11
|
+
return normalizeStructTag(struct);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static isSuiAddressEqual(addr1: string, addr2: string) {
|
|
15
|
+
return normalizeSuiAddress(addr1) === normalizeSuiAddress(addr2);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static isSuiStructEqual(struct1: string, struct2: string) {
|
|
19
|
+
return normalizeStructTag(struct1) === normalizeStructTag(struct2);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static isCoinObjectType(struct: string) {
|
|
23
|
+
return Coin.isCoin(struct);
|
|
24
|
+
}
|
|
25
|
+
}
|