在前一篇文章 Solidity - zkSync Paymaster 程式範例說明了如何撰寫和部署 Paymaster 合約,也有使用 Paymaster 發起交易的方法。不過專案是用 zksync-cli 建立,使用的是 Ethers.js v5 的版本。這篇文章會以如何在 Dapp 中,使用現成 Paymaster 的角度來說明。包含 Ethers.js v5 和 v6 的用法。

使用 zksync 套件和連接錢包等說明,參考之前的文章使用 Ethers.js 操作 zkSync 帳戶抽象,這邊不重複說明。

產生 Paymaster 參數

使用 Paymaster 發送交易要在交易中帶入 Paymaster 相關參數,可以使用套件中提供的方法:

1
utils.getPaymasterParams(paymasterAddress: string, paymasterInput: PaymasterInput): PaymasterParams
  • paymasterAddress: Paymaster 合約地址。
  • paymasterInput: Paymaster 使用的類型和參數,目前有兩種類型,參考下面範例。
  • 回傳值:Tx 中 customDatapaymasterParams 資料格式,參考下面範例。

PaymasterInput 範例如下:

General

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// v5 使用 zksync-web3
import { utils } from 'zksync2-js';

const PAYMASTER = '0xf9340983982a75985518CEa03129381be8b20F74';

const paymasterParams = utils.getPaymasterParams(PAYMASTER, {
type: 'General',
innerInput: '0x' // Paymaster 額外客製化的參數
});

// paymasterParams
// {
// paymaster: '0xf9340983982a75985518CEa03129381be8b20F74';
// paymasterInput: '0x8c5a344500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000';
// }

ApprovalBased

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const PAYMASTER = '0xf9340983982a75985518CEa03129381be8b20F74';
const TOKEN = '0xa1987D356Af995581D296dFeA2de8e9Ff2E418dF';

const paymasterParams = utils.getPaymasterParams(PAYMASTER, {
type: 'ApprovalBased',

// 用來支付手續費的 ERC20 代幣
token: TOKEN,

// 授權的數量,可視為最多願意支付多少,預估階段要先填一個比較大的數字,否則會交易失敗無法預估
minimalAllowance: '30000000000000000000',

// Paymaster 額外客製化的參數
innerInput: '0x'
});

// paymasterParams
// {
// paymaster: '0xf9340983982a75985518CEa03129381be8b20F74';
// paymasterInput: '"0x949431dc000000000000000000000000a1987d356af995581d296dfea2de8e9ff2e418df000000000000000000000000000000000000000000000001a055690d9db8000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000"';
// }

預估手續費

v6

1
2
3
4
5
6
7
8
9
const gasLimit = await contract.method.estimateGas(
{
customData: {
paymasterParams,
gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT
}
}
);
const gasPrice = (await provider.getFeeData()).gasPrice;

如果是使用 ApprovalBased 要重新計算一次 paymasterParamsminimalAllowance

1
2
3
4
5
6
7
8
9
10
11
const tokenPrice = 2000n;  // 此 Token 對於 ETH 的價格,例如 USDC/ETH 假設是 2000

// 重新生成
paymasterParams = utils.getPaymasterParams(PAYMASTER, {
type: 'ApprovalBased',
token: TOKEN,

// 要注意 Token 和 ETH 小數位數的轉換,這裡範例使用的 Token 是 Decimals 18,所以沒有轉換
minimalAllowance: gasLimit * gasPrice * tokenPrice,
innerInput: '0x'
});

v5

1
2
3
4
5
6
7
8
9
10
// 順序不同
const gasLimit = await contract.estimateGas.method(
{
customData: {
paymasterParams,
gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT
}
}
);
const gasPrice = await provider.getGasPrice();

和上面一樣要重新計算一次 minimalAllowance,不過 v5 和 v6 使用的數字型別不同

1
gasLimit.mul(gasPrice).mul(2000)

如果沒有使用帳戶抽象的話,實際上也可以略過這一步,讓他自己計算,但 minimalAllowance 不會自己計算。不進行預估的話可能會送出比實際需要的數字還大,有可能被收取超額的手續費。

送出交易

1
2
3
4
5
6
7
8
9
10
11
12
await contract.method({
// 如果略過上一步, gasPrice 和 gasLimit 可以不填,讓他自動計算
gasPrice,
gasLimit,
customData: {
paymasterParams,
gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT,
},

// v6 要填 from
from: signer.address
});

實際發送出交易 Metamask 會看到下面畫面:
zkSync Paymaster

會發現它其實是使用和帳戶抽象一樣的簽章方式來發送。Paymaster 可以和帳戶抽象同時使用,參考使用 Ethers.js 操作 zkSync 帳戶抽象,把 paymasterParams 資料帶入即可。

延伸閱讀

Solidity - zkSync Paymaster 程式範例
使用 Ethers.js 操作 zkSync 帳戶抽象
使用 Web3.js 操作 zkSync Paymaster