Skip to content

Commit 72be86f

Browse files
author
Neo
authored
Merge pull request #10 from MixinNetwork/mvm
Mvm
2 parents 9729127 + 8c46371 commit 72be86f

File tree

14 files changed

+942
-37
lines changed

14 files changed

+942
-37
lines changed

example/mvm.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
const { Client, extraGeneratorByInfo, getMvmTransaction, getAssetIDByAddress, getContractByUserIDOrAssetID } = require('mixin-node-sdk')
2+
const fs = require('fs')
3+
const keystore = JSON.parse(fs.readFileSync(__dirname + '/../config.json', 'utf8'))
4+
const client = new Client(keystore)
5+
6+
async function main() {
7+
// 1. 使用参数进行生成 extra
8+
const extra = extraGeneratorByInfo(
9+
'0x7c15d0D2faA1b63862880Bed982bd3020e1f1A9A', // 要调用的合约地址
10+
'addLiquidity', // 上述合约里的方法名称
11+
['address', 'uint256'], // 上述方法里的参数类型
12+
['0xA95664B451dE7E85e5E743AFD0F8f4d2A57eD11a', '20000'] // 上述方法里的参数的值
13+
)
14+
// 注意, mvm 里的资产 decimal 是 8.
15+
// 所以, 如果要转账 0.0002 那么上述构建 extra 要转账的金额就是 0.0002 * 1e8 = 20000
16+
// 2. 使用生成的 extra 来构建交易
17+
const transactionInput = getMvmTransaction({
18+
amount: '0.0002',
19+
asset: '965e5c6e-434c-3fa9-b780-c50f43cd955c', // cnb 的 asset_id
20+
trace: 'uuid', // uuid 可以为空,
21+
extra,
22+
})
23+
// 3. 可以直接调用 /transaction 付款,
24+
// 也可以生成一个 code_id 来调起客户端的付款
25+
const res = await client.verifyPayment(transactionInput)
26+
console.log(res)
27+
console.log(`mixin://codes/${res.code_id}`)
28+
29+
30+
const cnbAssetID = await getAssetIDByAddress('0xA95664B451dE7E85e5E743AFD0F8f4d2A57eD11a') // cnb 的地址
31+
console.log(cnbAssetID) // 965e5c6e-434c-3fa9-b780-c50f43cd955c
32+
33+
const btcAssetContract = await getContractByUserIDOrAssetID('c6d0c728-2624-429b-8e0d-d9d19b6592fa')
34+
console.log(btcAssetContract)
35+
}
36+
37+
main()

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mixin-node-sdk",
3-
"version": "3.0.14",
3+
"version": "3.0.15",
44
"license": "MIT",
55
"main": "dist/index.js",
66
"typings": "dist/index.d.ts",
@@ -77,6 +77,7 @@
7777
"@types/uuid": "^8.3.1",
7878
"axios": "^0.21.1",
7979
"bignumber.js": "^9.0.1",
80+
"ethers": "^5.6.0",
8081
"int64-buffer": "^1.0.1",
8182
"jsonwebtoken": "^8.5.1",
8283
"node-forge": "^0.10.0",

src/client/index.ts

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { AxiosInstance } from 'axios';
44
import { mixinRequest, request } from '../services/request';
55
import { UserClient } from './user';
66
import { AddressClient } from './address';
7-
87
import {
98
AddressClientRequest,
109
AddressCreateParams,
@@ -78,22 +77,29 @@ import { PINClient } from './pin';
7877
import { SnapshotClient } from './snapshot';
7978
import { TransferClient } from './transfer';
8079
import { CollectiblesClient } from './collectibles';
80+
export {
81+
getMvmTransaction,
82+
abiParamsGenerator,
83+
extraGeneratByInfo,
84+
getContractByUserIDOrAssetID,
85+
getAssetIDByAddress,
86+
getUserIDByAddress,
87+
} from './mvm'
8188

8289
export class Client
8390
implements
84-
AddressClientRequest,
85-
AppClientRequest,
86-
AssetClientRequest,
87-
AttachmentClientRequest,
88-
CollectiblesClient,
89-
ConversationClientRequest,
90-
MessageClientRequest,
91-
MultisigClientRequest,
92-
PINClientRequest,
93-
SnapshotClientRequest,
94-
TransferClientRequest,
95-
UserClientRequest
96-
{
91+
AddressClientRequest,
92+
AppClientRequest,
93+
AssetClientRequest,
94+
AttachmentClientRequest,
95+
CollectiblesClient,
96+
ConversationClientRequest,
97+
MessageClientRequest,
98+
MultisigClientRequest,
99+
PINClientRequest,
100+
SnapshotClientRequest,
101+
TransferClientRequest,
102+
UserClientRequest {
97103
request: AxiosInstance;
98104
keystore: Keystore;
99105

src/client/message.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { AxiosInstance } from 'axios';
2+
import { base64url } from '../mixin/sign';
23
import {
34
AcknowledgementRequest,
45
Keystore,
@@ -51,7 +52,7 @@ export class MessageClient implements MessageClientRequest {
5152
recipient_id
5253
),
5354
message_id: this.newUUID(),
54-
data: Buffer.from(data).toString('base64'),
55+
data: base64url(Buffer.from(data)),
5556
});
5657
}
5758

src/client/multisigs.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { AxiosInstance } from 'axios';
2-
import { getSignPIN } from '../mixin/sign';
2+
import { base64url, getSignPIN } from '../mixin/sign';
33
import { BigNumber } from 'bignumber.js';
44
import {
55
Keystore,
@@ -70,7 +70,7 @@ export class MultisigsClient implements MultisigClientRequest {
7070
const tx: Transaction = {
7171
version: TxVersion,
7272
asset: newHash(inputs[0].asset_id),
73-
extra: Buffer.from(memo).toString('base64'),
73+
extra: base64url(Buffer.from(memo)),
7474
inputs: [],
7575
outputs: [],
7676
};

src/client/mvm.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { TransactionInput, InvokeCodeParams } from "../types"
2+
import { v4 as newUUID, parse, stringify } from 'uuid'
3+
import { Encoder } from "../mixin/encoder"
4+
import { base64url } from "../mixin/sign"
5+
import { ethers, utils } from "ethers"
6+
import { JsonFragment } from "@ethersproject/abi"
7+
import { registryAbi, registryAddress, registryProcess } from "../mixin/mvm_registry"
8+
9+
// const OperationPurposeUnknown = 0
10+
const OperationPurposeGroupEvent = 1
11+
// const OperationPurposeAddProcess = 11
12+
// const OperationPurposeCreditProcess = 12
13+
14+
const members = [
15+
"a15e0b6d-76ed-4443-b83f-ade9eca2681a",
16+
"b9126674-b07d-49b6-bf4f-48d965b2242b",
17+
"15141fe4-1cfd-40f8-9819-71e453054639",
18+
"3e72ca0c-1bab-49ad-aa0a-4d8471d375e7"
19+
]
20+
21+
const threshold = 3
22+
23+
24+
// 获取 mvm 的交易输入
25+
export const getMvmTransaction = (params: InvokeCodeParams): TransactionInput => {
26+
return {
27+
asset_id: params.asset,
28+
amount: params.amount,
29+
trace_id: params.trace || newUUID(),
30+
opponent_multisig: {
31+
receivers: members,
32+
threshold
33+
},
34+
memo: encodeMemo(params.extra, params.process || registryProcess),
35+
}
36+
}
37+
38+
// 根据 abi 获取 extra
39+
export const abiParamsGenerator = (contractAddress: string, abi: JsonFragment[]): { [method: string]: Function } => {
40+
const res: { [method: string]: Function } = {}
41+
if (contractAddress.startsWith('0x')) contractAddress = contractAddress.slice(2)
42+
abi.forEach(item => {
43+
if (item.type === 'function') {
44+
const params = item.inputs?.map(v => v.type!) || []
45+
const methodID = getMethodIdByAbi(item, params)
46+
res[item.name!] = function () {
47+
if (arguments.length != params.length) throw new Error('params length error')
48+
const abiCoder = new utils.AbiCoder()
49+
return (contractAddress + methodID + abiCoder.encode(params, Array.from(arguments)).slice(2)).toLowerCase()
50+
}
51+
}
52+
})
53+
return res
54+
}
55+
56+
// 根据调用信息获取 extra
57+
export const extraGeneratByInfo = (contractAddress: string, methodName: string, types?: string[], values?: any[]): string => {
58+
if (contractAddress.startsWith('0x')) contractAddress = contractAddress.slice(2)
59+
if (!types || !values) return (contractAddress + utils.id(methodName + '()').slice(2, 10)).toLowerCase()
60+
if (types.length != values.length) throw new Error('params length error')
61+
const methodId = utils.id(methodName + '(' + types.join(',') + ')').slice(2, 10)
62+
const abiCoder = new utils.AbiCoder()
63+
return (contractAddress + methodId + abiCoder.encode(types, values).slice(2)).toLowerCase()
64+
}
65+
66+
export const getContractByUserIDOrAssetID = (id: string): Promise<string> => {
67+
const registry = getRegistryContract()
68+
const _idx = '0x' + Buffer.from(parse(id) as Buffer).toString('hex')
69+
return registry.contracts(_idx)
70+
}
71+
72+
export const getAssetIDByAddress = async (contract_address: string): Promise<string> => {
73+
const registry = getRegistryContract()
74+
let res = await registry.assets(contract_address)
75+
if (res.isZero()) return ""
76+
res = res._hex.slice(2)
77+
return stringify(Buffer.from(res, 'hex'))
78+
}
79+
80+
export const getUserIDByAddress = async (contract_address: string): Promise<string> => {
81+
const registry = getRegistryContract()
82+
let res = await registry.users(contract_address)
83+
if (res.isZero()) return ""
84+
res = res._hex.slice(2)
85+
return stringify(Buffer.from(res, 'hex'))
86+
}
87+
88+
const getRegistryContract = () =>
89+
new ethers.Contract(registryAddress, registryAbi, new ethers.providers.JsonRpcProvider('http://104.197.245.214:8545'))
90+
91+
92+
93+
function getMethodIdByAbi(abi: JsonFragment, params: string[]): string {
94+
let res = abi.name + '('
95+
res += (params.join(',') || '') + ')'
96+
return utils.id(res).slice(2, 10)
97+
}
98+
99+
const encodeMemo = (extra: string, process: string): string => {
100+
const enc = new Encoder(Buffer.from([]))
101+
enc.writeInt(OperationPurposeGroupEvent)
102+
enc.write(parse(process) as Buffer)
103+
enc.writeBytes(Buffer.from([]))
104+
enc.writeBytes(Buffer.from([]))
105+
enc.writeBytes(Buffer.from(extra, 'hex'))
106+
return base64url(enc.buf)
107+
}

src/mixin/encoder.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,17 @@ export class Encoder {
1919
write(buf: Buffer) {
2020
this.buf = Buffer.concat([this.buf, buf]);
2121
}
22+
23+
writeBytes(buf: Buffer) {
24+
const len = buf.byteLength;
25+
if (len > 65 * 21) {
26+
throw new Error('bytes too long. max length is 21 * 65, current length is ' + len);
27+
}
28+
29+
this.writeInt(len);
30+
this.write(buf);
31+
}
32+
2233
writeInt(i: number) {
2334
if (i > maxEcodingInt) {
2435
throw new Error('int overflow');

0 commit comments

Comments
 (0)