0%

Near Dapp 充提

承接上一篇构建Near Dapp Orderly 流程我们知道了去生成OrderlyKey和TradingKey进入OrderlyNetwork,现在来看看如何来充值、提现以及如何查询余额。

充值 - Deposit

我们的near token在我们的钱包里面,要想在DeFi中交易,需要将token充到合约里面。

先创建一个deposit.component.tsx文件,这个组件中可以输入金额,然后点击按钮就可以deposit Near:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import {ChangeEvent, useState} from "react";

export function DepositComponent() {
const [amount, setAmount] = useState<string>('1');

const onDepositClick = () => {
console.log('value', amount);
}

const changeValue = (e: ChangeEvent) => {
setAmount((e.target as HTMLInputElement).value);
}
return (
<form className='flex justify-center gap-2 '>
<input type='number' value={amount} onChange={changeValue}/>
<div onClick={onDepositClick}>Deposit</div>
</form>
)
}

在充值之前,我们需要获得一个storage_cost_of_token_balance数值:

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

然后判断是否需要storage_deposit,然后再depsoit:

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
46
47
48
49
50
51
52
53
54
55
56
57
export const depositNear = async (wallet: WalletConnection, amount: string) => {
const accountId = wallet.getAccountId();
const keyPair = await environment.nearWalletConfig.keyStore.getKey(environment.nearWalletConfig.networkId, accountId);
const publicKey = keyPair.getPublicKey();

const [storageUsage, balanceOf, storageCost, accessKeyInfo] = await Promise.all([
userStorageUsage(accountId),
storageBalanceOf(environment.nearWalletConfig.contractName, accountId),
storageCostOfTokenBalance(),
getAccessKeyInfo(accountId, keyPair)
]);
const value = new BigNumber(storageUsage).plus(new BigNumber(storageCost)).minus(new BigNumber(balanceOf.total));

const nonce = ++accessKeyInfo.nonce;
const recentBlockHash = serialize.base_decode(accessKeyInfo.block_hash);
const transactions: Transaction[] = [];

if (value.isGreaterThan(0)) {
transactions.push(createTransaction(
accountId,
publicKey,
environment.nearWalletConfig.contractName,
nonce,
[
functionCall(
'storage_deposit',
{
receiver_id: environment.nearWalletConfig.contractName,
msg: '',

},
BOATLOAD_OF_GAS,
value.toFixed(),
)
],
recentBlockHash,
))
}
transactions.push(createTransaction(
accountId,
publicKey,
environment.nearWalletConfig.contractName,
nonce + 1,
[
functionCall(
'user_deposit_native_token',
{},
BOATLOAD_OF_GAS,
utils.format.parseNearAmount(amount),
)
],
recentBlockHash,
))
return wallet.requestSignTransactions({
transactions,
})

然后在deposit.component.tsx中调用这个方法:

1
2
3
4
5
6
7
8
9
10
import {ChangeEvent, useState} from "react";
import {useConnection} from "./ConnectionContext";
import {depositNear} from "./services/contract.service";

export function DepositComponent() {
//...
const onDepositClick = () => {
depositNear(walletConnection!, amount).then();
}
}

当点击deposit按钮后,会跳转到MyNearWallet去签名,在签名页面我们可以观察到,如果有需要storage_deposit操作的话,会两个transaction一起签名的。

在签名完毕后,可以在explorer里面查看对应的签名信息,确认是成功了的。

通过REST API 查询Balance

REST Api 我们需要增加一些配置在environment里面:

1
2
3
4
5
6
7
8
import {keyStores} from "near-api-js";

export const environment = {
//...
config: {
apiUrl: 'https://testnet-api.orderly.org',
}
}

然后通过/position/balances这个接口可以查询balance。但是调用这个接口需要用OrderlyKey签名。

首先看签名函数:

1
2
3
4
5
export const signMessageByOrderlyKey = (params: string, keyPair: KeyPair) => {
const u8 = Buffer.from(params);
const signStr = keyPair.sign(u8);
return base64url(Buffer.from(signStr.signature).toString('base64'));
}

然后我们创建asset.service.ts来写这个api请求的调用方法:

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
export const getBalance = async (): Promise<any> => {
const urlParam = '/position/balances';
const accountId = 'neardapp-t1.testnet';
const timestamp = new Date().getTime().toString();
const messageStr = [timestamp, 'GET', urlParam].join('');
const keyPair = await environment.nearWalletConfig.keyStore.getKey(environment.nearWalletConfig.networkId, accountId)
const sign = signMessageByOrderlyKey(messageStr, keyPair);

const headers = {
'Access-Control-Allow-Origin': '*',
Accept: 'application/json',
'Content-Type': 'application/json;charset=utf-8',
}
Object.assign(headers, {
'orderly-account-id': accountId,
'orderly-key': keyPair?.getPublicKey().toString(),
'orderly-timestamp': timestamp,
'orderly-signature': sign,
});
return fetch(environment.config.apiUrl + '/position/balances', {
method: 'GET',
headers,
})
.then(response => response.json());
}

调用这个方法,可以观察到我们现在的balances里面有个near,holding数值是我们前面deposit的数值。

提现 - Withdraw

充值是将钱包里面的钱放进合约进行交易。
提现是将合约账号里面的钱取出放到钱包。

同样,我们创建一个withdraw.component.tsx组件,这个组件中我们可以输入需要提现的金额,然后有个withdraw按钮:

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
import {useConnection} from "./ConnectionContext";
import {ChangeEvent, useEffect, useState} from "react";
import {withdrawNear} from "./services/contract.service";
import {getTokenList} from "./services/asset.service";

interface TokenConfigInterface{
token: string;
token_account_id: string;
}

export function WithdrawComponent() {
const [amount, setAmount] = useState<string>('');
const onWithdraw = () => {
// todo

}
const onChange = (e: ChangeEvent) => {
setAmount((e.target as HTMLInputElement).value);
}
return (
<form className='flex justify-center gap-2 mt-2'>
<input type='number' value={amount} onChange={onChange}/>
<div onClick={onWithdraw}>Withdraw</div>
</form>
)
}

提现基本和充值差不多,有个不同的地方是需要获取一下withdrawFee:

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

并且需要知道near的tokenAddress,当然这里我们只针对near原生token,不涉及到非原生代币,所以我们知道Near的tokenAddress是near

然后看withdraw方法:

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
export const withdrawNear = async (wallet: WalletConnection, tokenAddress: string, amount: string) => {
const accountId = wallet.getAccountId();
const keyPair = await environment.nearWalletConfig.keyStore.getKey(environment.nearWalletConfig.networkId, accountId);
const publicKey = keyPair.getPublicKey();

const [storageUsage, balanceOf, storageCost, withdrawFee, accessKeyInfo] = await Promise.all([
userStorageUsage(accountId),
storageBalanceOf(environment.nearWalletConfig.contractName, accountId),
storageCostOfTokenBalance(),
getWithdrawFee(),
getAccessKeyInfo(accountId, keyPair)
]);
const recentBlockHash = serialize.base_decode(accessKeyInfo.block_hash);
const value = new BigNumber(storageUsage).plus(new BigNumber(storageCost)).minus(new BigNumber(balanceOf.total));
const transactions: Transaction[] = [];
if (value.isGreaterThan(0)) {
transactions.push(createTransaction(
accountId,
publicKey,
environment.nearWalletConfig.contractName,
accessKeyInfo.nonce + 1,
[
functionCall(
'storage_deposit',
{
receiver_id: environment.nearWalletConfig.contractName,
msg: '',

},
BOATLOAD_OF_GAS,
value.toFixed(),
)
],
recentBlockHash,
))
}

transactions.push(createTransaction(
accountId,
publicKey,
environment.nearWalletConfig.contractName,
accessKeyInfo.nonce + 2,
[
functionCall(
'user_request_withdraw',
{
token: tokenAddress,
amount: utils.format.parseNearAmount(amount),
},
BOATLOAD_OF_GAS,
withdrawFee,
)
],
recentBlockHash,
))

return wallet.requestSignTransactions({
transactions,
});
}

然后在withdraw.component.tsx中完善调用:

1
2
3
4
5
6
7
8
const onWithdraw = () => {
const tokenData = tokenList.find(item => item.token === 'NEAR');
console.log('token data', tokenData);
if (!tokenData) {
return false;
}
withdrawNear(walletConnection!, 'near', amount).then();
}

当点击withdraw按钮的时候,会跳转到MyNearWallet进行签名,在签名页面,如果有需要storage_depsoit的话可以看到有两个transaction。签名完成后,我们可以再请求balance接口看看数据。

但是这个数据不是实时的,可能会有点延迟。

比如我们前面deposit了1near,现在withdraw了1near,那么这个balance里面near的holding就是0.


充提是调用合约方法,麻烦的是需要在调用之前去判断一些费用是否足够,否则合约操作会失败。

这里也顺带看了在调用REST API的时候需要先签名,如果涉及到用户数据的接口,需要判断签名的。

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