Skip to content

Mvm #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 16, 2022
Merged

Mvm #10

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions example/mvm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const { Client, extraGeneratorByInfo, getMvmTransaction, getAssetIDByAddress, getContractByUserIDOrAssetID } = require('mixin-node-sdk')
const fs = require('fs')
const keystore = JSON.parse(fs.readFileSync(__dirname + '/../config.json', 'utf8'))
const client = new Client(keystore)

async function main() {
// 1. 使用参数进行生成 extra
const extra = extraGeneratorByInfo(
'0x7c15d0D2faA1b63862880Bed982bd3020e1f1A9A', // 要调用的合约地址
'addLiquidity', // 上述合约里的方法名称
['address', 'uint256'], // 上述方法里的参数类型
['0xA95664B451dE7E85e5E743AFD0F8f4d2A57eD11a', '20000'] // 上述方法里的参数的值
)
// 注意, mvm 里的资产 decimal 是 8.
// 所以, 如果要转账 0.0002 那么上述构建 extra 要转账的金额就是 0.0002 * 1e8 = 20000
// 2. 使用生成的 extra 来构建交易
const transactionInput = getMvmTransaction({
amount: '0.0002',
asset: '965e5c6e-434c-3fa9-b780-c50f43cd955c', // cnb 的 asset_id
trace: 'uuid', // uuid 可以为空,
extra,
})
// 3. 可以直接调用 /transaction 付款,
// 也可以生成一个 code_id 来调起客户端的付款
const res = await client.verifyPayment(transactionInput)
console.log(res)
console.log(`mixin://codes/${res.code_id}`)


const cnbAssetID = await getAssetIDByAddress('0xA95664B451dE7E85e5E743AFD0F8f4d2A57eD11a') // cnb 的地址
console.log(cnbAssetID) // 965e5c6e-434c-3fa9-b780-c50f43cd955c

const btcAssetContract = await getContractByUserIDOrAssetID('c6d0c728-2624-429b-8e0d-d9d19b6592fa')
console.log(btcAssetContract)
}

main()
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mixin-node-sdk",
"version": "3.0.14",
"version": "3.0.15",
"license": "MIT",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
Expand Down Expand Up @@ -77,6 +77,7 @@
"@types/uuid": "^8.3.1",
"axios": "^0.21.1",
"bignumber.js": "^9.0.1",
"ethers": "^5.6.0",
"int64-buffer": "^1.0.1",
"jsonwebtoken": "^8.5.1",
"node-forge": "^0.10.0",
Expand Down
34 changes: 20 additions & 14 deletions src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { AxiosInstance } from 'axios';
import { mixinRequest, request } from '../services/request';
import { UserClient } from './user';
import { AddressClient } from './address';

import {
AddressClientRequest,
AddressCreateParams,
Expand Down Expand Up @@ -78,22 +77,29 @@ import { PINClient } from './pin';
import { SnapshotClient } from './snapshot';
import { TransferClient } from './transfer';
import { CollectiblesClient } from './collectibles';
export {
getMvmTransaction,
abiParamsGenerator,
extraGeneratByInfo,
getContractByUserIDOrAssetID,
getAssetIDByAddress,
getUserIDByAddress,
} from './mvm'

export class Client
implements
AddressClientRequest,
AppClientRequest,
AssetClientRequest,
AttachmentClientRequest,
CollectiblesClient,
ConversationClientRequest,
MessageClientRequest,
MultisigClientRequest,
PINClientRequest,
SnapshotClientRequest,
TransferClientRequest,
UserClientRequest
{
AddressClientRequest,
AppClientRequest,
AssetClientRequest,
AttachmentClientRequest,
CollectiblesClient,
ConversationClientRequest,
MessageClientRequest,
MultisigClientRequest,
PINClientRequest,
SnapshotClientRequest,
TransferClientRequest,
UserClientRequest {
request: AxiosInstance;
keystore: Keystore;

Expand Down
3 changes: 2 additions & 1 deletion src/client/message.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AxiosInstance } from 'axios';
import { base64url } from '../mixin/sign';
import {
AcknowledgementRequest,
Keystore,
Expand Down Expand Up @@ -51,7 +52,7 @@ export class MessageClient implements MessageClientRequest {
recipient_id
),
message_id: this.newUUID(),
data: Buffer.from(data).toString('base64'),
data: base64url(Buffer.from(data)),
});
}

Expand Down
4 changes: 2 additions & 2 deletions src/client/multisigs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AxiosInstance } from 'axios';
import { getSignPIN } from '../mixin/sign';
import { base64url, getSignPIN } from '../mixin/sign';
import { BigNumber } from 'bignumber.js';
import {
Keystore,
Expand Down Expand Up @@ -70,7 +70,7 @@ export class MultisigsClient implements MultisigClientRequest {
const tx: Transaction = {
version: TxVersion,
asset: newHash(inputs[0].asset_id),
extra: Buffer.from(memo).toString('base64'),
extra: base64url(Buffer.from(memo)),
inputs: [],
outputs: [],
};
Expand Down
107 changes: 107 additions & 0 deletions src/client/mvm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { TransactionInput, InvokeCodeParams } from "../types"
import { v4 as newUUID, parse, stringify } from 'uuid'
import { Encoder } from "../mixin/encoder"
import { base64url } from "../mixin/sign"
import { ethers, utils } from "ethers"
import { JsonFragment } from "@ethersproject/abi"
import { registryAbi, registryAddress, registryProcess } from "../mixin/mvm_registry"

// const OperationPurposeUnknown = 0
const OperationPurposeGroupEvent = 1
// const OperationPurposeAddProcess = 11
// const OperationPurposeCreditProcess = 12

const members = [
"a15e0b6d-76ed-4443-b83f-ade9eca2681a",
"b9126674-b07d-49b6-bf4f-48d965b2242b",
"15141fe4-1cfd-40f8-9819-71e453054639",
"3e72ca0c-1bab-49ad-aa0a-4d8471d375e7"
]

const threshold = 3


// 获取 mvm 的交易输入
export const getMvmTransaction = (params: InvokeCodeParams): TransactionInput => {
return {
asset_id: params.asset,
amount: params.amount,
trace_id: params.trace || newUUID(),
opponent_multisig: {
receivers: members,
threshold
},
memo: encodeMemo(params.extra, params.process || registryProcess),
}
}

// 根据 abi 获取 extra
export const abiParamsGenerator = (contractAddress: string, abi: JsonFragment[]): { [method: string]: Function } => {
const res: { [method: string]: Function } = {}
if (contractAddress.startsWith('0x')) contractAddress = contractAddress.slice(2)
abi.forEach(item => {
if (item.type === 'function') {
const params = item.inputs?.map(v => v.type!) || []
const methodID = getMethodIdByAbi(item, params)
res[item.name!] = function () {
if (arguments.length != params.length) throw new Error('params length error')
const abiCoder = new utils.AbiCoder()
return (contractAddress + methodID + abiCoder.encode(params, Array.from(arguments)).slice(2)).toLowerCase()
}
}
})
return res
}

// 根据调用信息获取 extra
export const extraGeneratByInfo = (contractAddress: string, methodName: string, types?: string[], values?: any[]): string => {
if (contractAddress.startsWith('0x')) contractAddress = contractAddress.slice(2)
if (!types || !values) return (contractAddress + utils.id(methodName + '()').slice(2, 10)).toLowerCase()
if (types.length != values.length) throw new Error('params length error')
const methodId = utils.id(methodName + '(' + types.join(',') + ')').slice(2, 10)
const abiCoder = new utils.AbiCoder()
return (contractAddress + methodId + abiCoder.encode(types, values).slice(2)).toLowerCase()
}

export const getContractByUserIDOrAssetID = (id: string): Promise<string> => {
const registry = getRegistryContract()
const _idx = '0x' + Buffer.from(parse(id) as Buffer).toString('hex')
return registry.contracts(_idx)
}

export const getAssetIDByAddress = async (contract_address: string): Promise<string> => {
const registry = getRegistryContract()
let res = await registry.assets(contract_address)
if (res.isZero()) return ""
res = res._hex.slice(2)
return stringify(Buffer.from(res, 'hex'))
}

export const getUserIDByAddress = async (contract_address: string): Promise<string> => {
const registry = getRegistryContract()
let res = await registry.users(contract_address)
if (res.isZero()) return ""
res = res._hex.slice(2)
return stringify(Buffer.from(res, 'hex'))
}

const getRegistryContract = () =>
new ethers.Contract(registryAddress, registryAbi, new ethers.providers.JsonRpcProvider('http://104.197.245.214:8545'))



function getMethodIdByAbi(abi: JsonFragment, params: string[]): string {
let res = abi.name + '('
res += (params.join(',') || '') + ')'
return utils.id(res).slice(2, 10)
}

const encodeMemo = (extra: string, process: string): string => {
const enc = new Encoder(Buffer.from([]))
enc.writeInt(OperationPurposeGroupEvent)
enc.write(parse(process) as Buffer)
enc.writeBytes(Buffer.from([]))
enc.writeBytes(Buffer.from([]))
enc.writeBytes(Buffer.from(extra, 'hex'))
return base64url(enc.buf)
}
11 changes: 11 additions & 0 deletions src/mixin/encoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ export class Encoder {
write(buf: Buffer) {
this.buf = Buffer.concat([this.buf, buf]);
}

writeBytes(buf: Buffer) {
const len = buf.byteLength;
if (len > 65 * 21) {
throw new Error('bytes too long. max length is 21 * 65, current length is ' + len);
}

this.writeInt(len);
this.write(buf);
}

writeInt(i: number) {
if (i > maxEcodingInt) {
throw new Error('int overflow');
Expand Down
Loading