0%

构建Near Dapp Orderly 流程

上面我们通过构建Near Dapp了解了如何connect wallet以及如何调用合约方法,现在来看看如何进行Orderly的flow。

Orderly 介绍

Orderly Network,一个基于Near的无需许可的去中心化交易协议和模块化生态系统,下一代去中心化交易基础设施,Orderly Network由Woo Network和Near团队共同孵化。WooFi Dex为其推出的第一个Dapp。

Orderly Network的使命是为任何DApp创建最强大的流动性层基础设施以供使用和构建,它提供具有CeFi级执行的无需许可的现货和期货订单簿交易基础设施。

要让DApp接入Orderly Flow,只需要三个步骤:

  1. 创建账号
  2. 设置OrderlyKey
  3. 设置tradingKey

创建账号

首先需要检查当前连接的钱包的accountId在合约中是否已经存在。

因为接下来要涉及到好多查询方法,所以我们来封装一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import {AccessKeyViewRaw, AccountView, CodeResult} from "near-api-js/lib/providers/provider";

function callViewFunction(params: { contractName: string; methodName: string; args: { [key: string]: any } }) {
const provider = new providers.JsonRpcProvider({ url: environment.nearWalletConfig.nodeUrl });
const b64 = Buffer.from(JSON.stringify(params.args)).toString('base64');
return provider
.query<CodeResult>({
request_type: 'call_function',
account_id: params.contractName,
method_name: params.methodName,
args_base64: b64,
finality: 'optimistic',
})
.then((res) => JSON.parse(Buffer.from(res.result).toString()));
}

然后查询accountId是否在合约中存在:

1
2
3
4
5
6
7
8
export const checkUserAccountIsExist = (accountId: string) =>
callViewFunction({
contractName: environment.nearWalletConfig.contractName,
methodName: 'user_account_exists',
args: {
user: accountId,
},
});

当我们调用这个checkUserAccountIsExist,可以得到结果:true或false。

这里我们为了测试,重新创建了个新的测试钱包地址:neardapp-t1.testnet。用这个新账号连接后调用这个方法,得到false。

然后去创建账号。

在之前是要调用create_user来创建的,但现在只需要进行storage_deposit就可以了。

需要先查询下需要storage_balance的区间:

1
2
3
4
5
6
7
8
export const storageBalanceBounds = (tokenContractAddress: string, accountId: string) =>
callViewFunction({
contractName: tokenContractAddress,
methodName: 'storage_balance_bounds',
args: {
account_id: accountId,
},
});

这里的tokenContractAddress就是我们的合约地址。

调用这个方法可以得到结果:

1
2
3
4
{
max: null,
min: "14930000000000000000000",
}

这里我们知道了我们至少要storage_deposit 0.01493 near。

然后需要调用storage_depsoit方法去存储这个min数值的near。

我们来使用requestSignTransactions的形式来调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import {Account, Contract, KeyPair, providers, utils, WalletConnection} from "near-api-js";
import {serialize} from "near-api-js/lib/utils";
const BOATLOAD_OF_GAS = utils.format.parseNearAmount('0.00000000003')!;

export const getAccessKeyInfo = async (accountId: string, keyPair: KeyPair): Promise<AccessKeyViewRaw> => {
const provider = new providers.JsonRpcProvider({url: environment.nearWalletConfig.nodeUrl});

const publicKey = keyPair.getPublicKey();
return provider.query<AccessKeyViewRaw>(
`access_key/${accountId}/${publicKey.toString()}`, ''
);

}
export const storageDeposit = async (wallet: WalletConnection, depositValue: string) => {
const accountId = wallet.getAccountId();
const keyPair = await environment.nearWalletConfig.keyStore.getKey(environment.nearWalletConfig.networkId, accountId);
const publicKey = keyPair.getPublicKey();

const accessKeyInfo = await getAccessKeyInfo(accountId, keyPair);
const nonce = ++accessKeyInfo.nonce;
const recentBlockHash = serialize.base_decode(accessKeyInfo.block_hash);
const transactions: Transaction[] = [];
transactions.push(createTransaction(
accountId,
publicKey,
environment.nearWalletConfig.contractName,
nonce,
[
functionCall(
'storage_deposit',
{
receiver_id: environment.nearWalletConfig.contractName,
msg: '',
},
BOATLOAD_OF_GAS,
depositValue,
)
],
recentBlockHash,
))
return wallet.requestSignTransactions({
transactions,
})

};

调用这个方法的时候,会调转到myNearWallet的页面去签名,授权签名成功后会返回到我们的页面,然后我们检查账号是否存在的时候,就得到了true,说明账号已经创建完毕了,在合约中可以查到这个accountId。

设置OrderlyKey

首先我们需要检测当前的keyPair是否已经设置过:

1
2
3
4
5
6
7
8
9
export const isOrderlyKeyAnnounced = (accountId: string, orderlyKeyPair: KeyPair) =>
callViewFunction({
contractName: environment.nearWalletConfig.contractName,
methodName: 'is_orderly_key_announced',
args: {
user: accountId,
orderly_key: orderlyKeyPair.getPublicKey().toString(),
},
});

当前的OrderlyKeyPair可以这样获得:

1
2
3
const keyPair = await environment.nearWalletConfig.keyStore.getKey(environment.nearWalletConfig.networkId, accountId);
const isAnnounced = await isOrderlyKeyAnnounced(accountId, keyPair!);
console.log('is announced', isAnnounced)

这里返回结果isAnnounce是false,那么接下来开始设置:

1
2
3
4
5
6
7
8
export const setAnnounceKey = async (account: Account): Promise<any> => {
return account.functionCall({
contractId: environment.nearWalletConfig.contractName,
methodName: 'user_announce_key',
args: {},
gas: MAX_GAS,
});
}

调用这个方法,发现合约报了个错:

1
Error: {"index":0,"kind":{"ExecutionError":"Smart contract panicked: Not enough deposit, call storage_balance_bounds to query required min amount"}}

这是预存的消费额度不够了,我们需要重新检测一下。

首先需要获取下当前用户存储的余额(storage_balance)有多少:

1
2
3
4
5
6
7
8
export const storageBalanceOf = (tokenContractAddress: string, accountId: string) =>
callViewFunction({
contractName: tokenContractAddress,
methodName: 'storage_balance_of',
args: {
account_id: accountId,
},
});

这个返回:

1
2
3
4
{
available: "0",
total: "14930000000000000000000"
}

然后看下存储用户账号的金额(user_storage_usage)用了多少:

1
2
3
4
5
6
7
8
export const userStorageUsage = (accountId: string) =>
callViewFunction({
contractName: environment.nearWalletConfig.contractName,
methodName: 'user_storage_usage',
args: {
user: accountId,
},
});

这个返回:14930000000000000000000.

然后看下announceKey的时候花费多少:

1
2
3
4
5
6
export const storageCostOfAnnounceKey = () =>
callViewFunction({
contractName: environment.nearWalletConfig.contractName,
methodName: 'storage_cost_of_announce_key',
args: {},
});

这个返回4930000000000000000000

然后需要计算看是否需要storage_deposit:
value = storage_cost_of_announce_key + user_storage_usage - storage_balance_of
如果value大于0, 说明需要storage_deposit.

1
2
3
4
5
6
7
const storageUsage = await userStorageUsage(accountId);
const balanceOf = await storageBalanceOf(environment.nearWalletConfig.contractName, accountId);
const storageCost = await storageCostOfAnnounceKey()
const value = new BigNumber(storageUsage).plus(new BigNumber(storageCost)).minus(new BigNumber(balanceOf.total));
if (value.isGreaterThan(0)) {
const storageDepositRes = await storageDeposit(walletConnection!, value.toFixed());
}

然后再调用announceKey方法:

1
2
3
4
if (!isAnnounced) {
const setOrderlyKeyRes = await setAnnounceKey(nearAccount!);
console.log('set orderlyKey res', setOrderlyKeyRes);
}

这次可以看到正确的输出,并再次刷新检测orderlyKey是否announce,可以得到true。说明设置成功。

设置tradingKey

逻辑是这样的,我们需要自己生成tradingKey keyPair,然后调用user_request_set_trading_key设置到合约之后,将这个tradingKeyPair的私钥存储起来,这样再次刷新的时候就读取这个私钥,然后还原keyPair就行了,要不然会多次调用这个设置方法。

生成tradingKey我们需要引入两个包:

1
2
npm i keccak256
npm i elliptic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import keccak256 from "keccak256";
const EC = require('elliptic').ec;

export const generateTradingKeyPair = () => {
const ec = new EC('secp256k1');
const keyPair = ec.genKeyPair();

return {
privateKey: keyPair.getPrivate().toString('hex'),
publicKey: keyPair.getPublic().encode('hex'),
keyPair,
};
};

export const getTradingKeyPair = () => {
const secretKey= localStorage.getItem('TradingKeySecret');
if (! secretKey) {
return generateTradingKeyPair();
}
const ec = new EC('secp256k1');
const keyPair = ec.keyFromPrivate(secretKey);
return {
privateKey: keyPair.getPrivate().toString('hex'),
publicKey: keyPair.getPublic().encode('hex'),
keyPair,
};

}

在设置tradingKey之前,需要清楚tradingKey和OrderlyKey的关系。

目前是,一个OrderlyKey只能设置一个TradingKey。

所以,在设置之前也需要检查下是否已经设置了TradingKey:

1
2
3
4
5
6
7
8
9
export const isTradingKeySet = async (accountId: string, orderlyKeyPair: KeyPair) =>
callViewFunction({
contractName: environment.nearWalletConfig.contractName,
methodName: 'is_trading_key_set',
args: {
user: accountId,
orderly_key: orderlyKeyPair.getPublicKey().toString(),
},
});

可以看到,这里检查TradingKey是否设置的时候,传递是OrderlyKey的公钥。

然后可以setTradingKey了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export const userRequestSetTradingKey = (account: Account, tradingKeyPair: any) => {
const pubKeyAsHex = tradingKeyPair.publicKey.replace('04', '');
const normalizeTradingKey = window.btoa(keccak256(pubKeyAsHex).toString('hex'));
return account.functionCall({
contractId: environment.nearWalletConfig.contractName,
methodName: 'user_request_set_trading_key',
args: {
key: normalizeTradingKey,
},
gas: MAX_GAS,
attachedDeposit: utils.format.parseNearAmount('0'),
});

}

然后调用:

1
2
3
4
5
6
const tradingKeyPair = getTradingKeyPair();
const isSet = await isTradingKeySet(accountId, orderlyKeyPair);
if (!isSet) {
const setTradingKeyRes = await userRequestSetTradingKey(nearAccount, tradingKeyPair);
localStorage.setItem('TradingKeySecret', tradingKeyPair.privateKey);
}

可以看到输出正确的,并再次刷新的时候得到当前的TradingKeyPair。


结合之前的connect钱包,然后设置OrderlyKey和TradingKey,基本上一大步已经跨进了Orderly了。

码字辛苦,打赏个咖啡☕️可好?💘