上面我们通过构建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,只需要三个步骤:
- 创建账号
- 设置OrderlyKey
- 设置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了。