Skip to content

Commit 513e90d

Browse files
added README.md example of custom paymaster example & minor fixes
1 parent b590cd9 commit 513e90d

File tree

4 files changed

+147
-19
lines changed

4 files changed

+147
-19
lines changed

README.md

Lines changed: 137 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ For user needs there are the following contracts:
159159
* NonceHolder
160160
* ERC20Contract & ERC20FunctionEncoder
161161
* ContractDeployer
162+
* PaymasterFlowEncoder
162163

163164

164165
#### NonceHolder
@@ -234,7 +235,26 @@ Methods:
234235
| compute_l2_create_address | Address, Nonce | Address | Accepts address of deployer and current deploing nonce and returns address of contract that is going to be deployed by `encode_create` method |
235236
| compute_l2_create2_address | Address, bytecode, ctor bytecode, salt | Address | Accepts address of deployer, binary representation of contract, if needed it's constructor in binary format and salf. By default constructor can be b'0' value. Returns address of contract that is going to be deployed by `encode_create2` method |
236237

238+
#### PaymasterFlowEncoder
237239

240+
PaymasterFlowEncoder is utility contract for encoding Paymaster parameters.<br>
241+
Construction contract needs only Web3 Module object. It can be Eth or ZkSync.<br>
242+
243+
Example:
244+
```python
245+
from zksync2.manage_contracts.paymaster_utils import PaymasterFlowEncoder
246+
from zksync2.module.module_builder import ZkSyncBuilder
247+
248+
zksync_web3 = ZkSyncBuilder.build("ZKSYNC_NETWORK_URL")
249+
paymaster_encoder = PaymasterFlowEncoder(zksync_web3)
250+
```
251+
252+
This utility contract has 2 methods wrapped directly to python:
253+
254+
* encode_approval_based
255+
* encode_general
256+
257+
For example and usage, please have a look into example [section](#examples)
238258

239259

240260
### Examples
@@ -718,4 +738,120 @@ def deploy_contract_create2():
718738
if __name__ == "__main__":
719739
deploy_contract_create2()
720740

721-
```
741+
```
742+
743+
#### Custom Paymaster
744+
This example is based on the `custom_paymaster_binary` that accepts any coin with <br>
745+
the 1:1 relation to native token( 18 decimals )
746+
747+
748+
```python
749+
750+
from pathlib import Path
751+
from eth_account import Account
752+
from eth_account.signers.local import LocalAccount
753+
from eth_typing import HexStr
754+
from eth_utils import remove_0x_prefix
755+
from web3 import Web3
756+
from zksync2.manage_contracts.gas_provider import StaticGasProvider
757+
from zksync2.manage_contracts.paymaster_utils import PaymasterFlowEncoder
758+
from zksync2.signer.eth_signer import PrivateKeyEthSigner
759+
from zksync2.module.module_builder import ZkSyncBuilder
760+
from zksync2.manage_contracts.erc20_contract import ERC20Contract
761+
from zksync2.transaction.transaction712 import TxFunctionCall
762+
763+
from zksync2.core.types import EthBlockParams, PaymasterParams, Token
764+
765+
766+
def read_hex_binary(name: str) -> bytes:
767+
p = Path(f"./{name}")
768+
with p.open(mode='r') as contact_file:
769+
lines = contact_file.readlines()
770+
data = "".join(lines)
771+
return bytes.fromhex(data)
772+
773+
774+
# Contract to mint custom token under testnet:
775+
# https://goerli.explorer.zksync.io/address/0xFC174650BDEbE4D94736442307D4D7fdBe799EeC#contract
776+
777+
class PaymasterExample:
778+
SERC20_TOKEN = Token(
779+
Web3.toChecksumAddress("0x" + "0" * 40),
780+
Web3.toChecksumAddress("0xFC174650BDEbE4D94736442307D4D7fdBe799EeC"),
781+
"SERC20",
782+
18)
783+
784+
def __init__(self):
785+
self.web3 = ZkSyncBuilder.build("ZKSYNC_NETWORK_URL")
786+
self.account: LocalAccount = Account.from_key("YOUR_PRIVATE_KEY")
787+
self.chain_id = self.web3.zksync.chain_id
788+
self.signer = PrivateKeyEthSigner(self.account, self.chain_id)
789+
self.gas_provider = StaticGasProvider(Web3.toWei(1, "gwei"), 555000)
790+
791+
self.custom_paymaster_contract_bin = read_hex_binary("custom_paymaster_binary.hex")
792+
793+
@property
794+
def paymaster_address(self) -> HexStr:
795+
return self.web3.zksync.zks_get_testnet_paymaster_address()
796+
797+
def build_paymaster(self, trans: TxFunctionCall, fee: int) -> TxFunctionCall:
798+
paymaster_encoder = PaymasterFlowEncoder(self.web3.zksync)
799+
encoded_approval_base = paymaster_encoder.encode_approval_based(self.SERC20_TOKEN.l2_address,
800+
fee,
801+
b'')
802+
encoded_approval_bin = bytes.fromhex(remove_0x_prefix(encoded_approval_base))
803+
trans.tx["eip712Meta"].paymaster_params = PaymasterParams(paymaster=self.paymaster_address,
804+
paymaster_input=encoded_approval_bin)
805+
return trans
806+
807+
def run(self):
808+
gas_price = self.web3.zksync.gas_price
809+
paymaster_address = self.paymaster_address
810+
nonce = self.web3.zksync.get_transaction_count(self.account.address, EthBlockParams.PENDING.value)
811+
transaction = TxFunctionCall(self.chain_id,
812+
nonce=nonce,
813+
from_=self.account.address,
814+
to=self.account.address,
815+
gas_price=gas_price)
816+
817+
# INFO: encode paymaster params with dummy fee to estimate real one
818+
unknown_fee = 0
819+
transaction = self.build_paymaster(transaction, unknown_fee)
820+
821+
paymaster_est_gas = self.web3.zksync.eth_estimate_gas(transaction.tx)
822+
preprocessed_fee = gas_price * paymaster_est_gas
823+
824+
print(f"Paymaster fee: {preprocessed_fee}")
825+
826+
erc20 = ERC20Contract(self.web3.zksync, contract_address=self.SERC20_TOKEN.l2_address,
827+
account=self.account,
828+
gas_provider=self.gas_provider)
829+
830+
allowance = erc20.allowance(self.account.address, paymaster_address)
831+
if allowance < preprocessed_fee:
832+
is_approved = erc20.approve_deposit(paymaster_address, preprocessed_fee)
833+
print(f"pass deposite: {is_approved}")
834+
835+
# INFO: encode paymaster params with real fee
836+
transaction = self.build_paymaster(transaction, preprocessed_fee)
837+
838+
balance_before = self.web3.zksync.get_balance(self.account.address, EthBlockParams.PENDING.value)
839+
print(f"balance before : {balance_before}")
840+
841+
tx712 = transaction.tx712(paymaster_est_gas)
842+
singed_message = self.signer.sign_typed_data(tx712.to_eip712_struct())
843+
msg = tx712.encode(singed_message)
844+
tx_hash = self.web3.zksync.send_raw_transaction(msg)
845+
tx_receipt = self.web3.zksync.wait_for_transaction_receipt(tx_hash, timeout=240, poll_latency=0.5)
846+
print(f"status: {tx_receipt['status']}")
847+
848+
balance_after = self.web3.zksync.get_balance(self.account.address, EthBlockParams.PENDING.value)
849+
print(f"balance after: {balance_after}")
850+
851+
852+
if __name__ == "__main__":
853+
paymaster = PaymasterExample()
854+
paymaster.run()
855+
856+
```
857+

tests/test_paymaster.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,6 @@
33
from eth_typing import HexStr
44
from eth_utils import keccak, remove_0x_prefix
55
from web3 import Web3
6-
from web3.middleware import geth_poa_middleware
7-
8-
import zksync2.transaction.transaction712
9-
from zksync2.provider.eth_provider import EthereumProvider
10-
116
from zksync2.manage_contracts.paymaster_utils import PaymasterFlowEncoder
127
from zksync2.manage_contracts.erc20_contract import ERC20Contract
138
from zksync2.manage_contracts.gas_provider import StaticGasProvider
@@ -21,12 +16,7 @@
2116

2217

2318
# mint tx hash of Test coins:
24-
# https://goerli.explorer.zksync.io/tx/0xaaafc0e4228336783ad4e996292123c6eebb36b5c1b257e5b628e7e0281cccab
25-
26-
# https://goerli.explorer.zksync.io/tx/0xea09a90d09aa3644791ef300b5b5bbf397723812542a5040ff50237472e549fa
27-
# https://goerli.explorer.zksync.io/tx/0x86b1b6361cfe54dd54b2968fbfe97429d8876994a0240b27962ad9869acd512f
28-
# https://goerli.explorer.zksync.io/tx/0x78cf6db673a941495c6887badb84b4150bc09c3962a76c0d5c239190151e1ec5
29-
# SERC20
19+
# https://goerli.explorer.zksync.io/address/0xFC174650BDEbE4D94736442307D4D7fdBe799EeC#contract
3020

3121
class PaymasterTests(TestCase):
3222
ETH_TEST_URL = "https://rpc.ankr.com/eth_goerli"
@@ -139,7 +129,7 @@ def test_send_funds_for_fee(self):
139129
self.assertEqual(1, tx_receipt["status"])
140130

141131
def build_paymaster(self, trans: TxFunctionCall, fee: int) -> TxFunctionCall:
142-
paymaster_encoder = PaymasterFlowEncoder(self.web3)
132+
paymaster_encoder = PaymasterFlowEncoder(self.web3.zksync)
143133
encoded_approval_base = paymaster_encoder.encode_approval_based(self.SERC20_TOKEN.l2_address,
144134
fee,
145135
b'')
@@ -148,7 +138,7 @@ def build_paymaster(self, trans: TxFunctionCall, fee: int) -> TxFunctionCall:
148138
paymaster_input=encoded_approval_bin)
149139
return trans
150140

151-
@skip("Integration test, paymaster params test not implemented yet")
141+
# @skip("Integration test, paymaster params test not implemented yet")
152142
def test_send_funds_with_paymaster(self):
153143
gas_price = self.web3.zksync.gas_price
154144
paymaster_address = self.paymaster_address

zksync2/manage_contracts/erc20_contract.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def __init__(self, web3: Module,
4343
def _nonce(self) -> int:
4444
return self.module.get_transaction_count(self.account.address)
4545

46-
def approve_deposit(self, zksync_address: HexStr, max_erc20_approve_amount=MAX_ERC20_APPROVE_AMOUNT) -> TxReceipt:
46+
def approve_deposit(self, zksync_address: HexStr, max_erc20_approve_amount=MAX_ERC20_APPROVE_AMOUNT) -> bool:
4747
return self.contract.functions.approve(zksync_address, max_erc20_approve_amount).call(
4848
{
4949
"chainId": self.module.chain_id,

zksync2/manage_contracts/paymaster_utils.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
from web3 import Web3
33
from eth_typing import HexStr
44
import json
5+
6+
from web3.module import Module
7+
58
from zksync2.manage_contracts import contract_abi
69

710
paymaster_flow_abi_cache = None
@@ -20,10 +23,9 @@ def _paymaster_flow_abi_default():
2023

2124
class PaymasterFlowEncoder:
2225

23-
def __init__(self, zksync: Web3):
24-
self.web3 = zksync
25-
self.contract = self.web3.zksync.contract(address=None,
26-
abi=_paymaster_flow_abi_default())
26+
def __init__(self, module: Module):
27+
self.contract = module.contract(address=None,
28+
abi=_paymaster_flow_abi_default())
2729

2830
def encode_approval_based(self, address: HexStr, min_allowance: int, inner_input: bytes) -> HexStr:
2931
return self.contract.encodeABI(fn_name="approvalBased", args=[address, min_allowance, inner_input])

0 commit comments

Comments
 (0)