diff --git a/content/sdk/10.js/00.ethers/04.guides/02.accounts-l1-l2.md b/content/sdk/10.js/00.ethers/04.guides/02.accounts-l1-l2.md index bd03ecb2..0dbf04bf 100644 --- a/content/sdk/10.js/00.ethers/04.guides/02.accounts-l1-l2.md +++ b/content/sdk/10.js/00.ethers/04.guides/02.accounts-l1-l2.md @@ -13,7 +13,42 @@ If you need background information on how L1<->L2 interactions work on ZKsync, c `Wallet` and `L1Signer` objects allow you to deposit funds from L1 to L2. - **More Information**: See the method specification [`Deposit`](/sdk/js/ethers/api/v5/accounts/wallet#deposit). -- **Example**: [Deposit ETH and ERC20 token](https://github.com/zksync-sdk/zksync2-examples/blob/main/js/src/01_deposit.ts). +- **Example**: Deposit ETH and ERC20 token + +::collapsible + + ```bash +import { Provider, types, utils, Wallet } from "zksync-ethers"; +import { ethers } from "ethers"; + +const provider = Provider.getDefaultProvider(types.Network.Sepolia); +const ethProvider = ethers.getDefaultProvider("sepolia"); +const PRIVATE_KEY = process.env.PRIVATE_KEY; +const wallet = new Wallet(PRIVATE_KEY, provider, ethProvider); + +async function main() { + console.log(`L2 balance before deposit: ${await wallet.getBalance()}`); + console.log(`L1 balance before deposit: ${await wallet.getBalanceL1()}`); + + const tx = await wallet.deposit({ + token: utils.ETH_ADDRESS, + to: await wallet.getAddress(), + amount: ethers.parseEther("0.00020"), + refundRecipient: await wallet.getAddress(), + }); + const receipt = await tx.wait(); + console.log(`Tx: ${receipt.hash}`); + + console.log(`L2 balance after deposit: ${await wallet.getBalance()}`); + console.log(`L1 balance after deposit: ${await wallet.getBalanceL1()}`); +} + +main() + .then() + .catch((error) => { + console.log(`Error: ${error}`); + }); +:: ## Request execute @@ -44,4 +79,40 @@ If you need background information on how L1<->L2 interactions work on ZKsync, c `Wallet` and `Signer` objects enable you to withdraw funds from L2 to L1. - **More Information**: See the method specification [`Withdraw`](/sdk/js/ethers/api/v5/accounts/wallet#withdraw). -- **Example**: [Withdraw ETH and ERC20 token](https://github.com/zksync-sdk/zksync2-examples/blob/main/js/src/04_withdraw.ts). +- **Example**: Withdraw ETH and ERC20 token + +::collapsible + ```bash + + import { Provider, types, utils, Wallet } from "zksync-ethers"; +import { ethers } from "ethers"; + +const provider = Provider.getDefaultProvider(types.Network.Sepolia); +const ethProvider = ethers.getDefaultProvider("sepolia"); +const PRIVATE_KEY = process.env.PRIVATE_KEY; +const wallet = new Wallet(PRIVATE_KEY, provider, ethProvider); + +async function main() { + console.log(`L2 balance before withdraw: ${await wallet.getBalance()}`); + console.log(`L1 balance before withdraw: ${await wallet.getBalanceL1()}`); + + const tx = await wallet.withdraw({ + token: utils.ETH_ADDRESS, + to: await wallet.getAddress(), + amount: ethers.parseEther("0.00020"), + }); + const receipt = await tx.wait(); + console.log(`Tx: ${receipt.hash}`); + + // The duration for submitting a withdrawal transaction to L1 can last up to 24 hours. For additional information, + // please refer to the documentation: https://era.zksync.io/docs/reference/troubleshooting/withdrawal-delay.html. + // Once the withdrawal transaction is submitted on L1, it needs to be finalized. + // To learn more about how to achieve this, please take a look at the 04_finalize_withdraw.ts script. +} + +main() + .then() + .catch((error) => { + console.log(`Error: ${error}`); + }); +:: diff --git a/content/sdk/20.go/00.introduction/01.why-zksync2-go.md b/content/sdk/20.go/00.introduction/01.why-zksync2-go.md index cbe12581..a7f116bb 100644 --- a/content/sdk/20.go/00.introduction/01.why-zksync2-go.md +++ b/content/sdk/20.go/00.introduction/01.why-zksync2-go.md @@ -1,6 +1,6 @@ --- title: Why zksync2-go -description: Benefits and Advantages of using `zksync2-go` +description: Benefits and Advantages of using zksync2-go tags: ["zksync", "zksync2-go", "ethereum", "layer-2", "zero-knowledge rollups", "go library"] --- diff --git a/content/sdk/20.go/01.guides/00.getting-started.md b/content/sdk/20.go/01.guides/00.getting-started.md index 611121b6..1787ec2e 100644 --- a/content/sdk/20.go/01.guides/00.getting-started.md +++ b/content/sdk/20.go/01.guides/00.getting-started.md @@ -91,14 +91,856 @@ fmt.Printf("%+v\n", transactionByHash) Also, the following examples demonstrate how to: -1. [Deposit ETH and tokens from Ethereum into ZKsync Era](https://github.com/zksync-sdk/zksync2-examples/blob/main/go/01_deposit.go). -2. [Transfer ETH and tokens on ZKsync Era](https://github.com/zksync-sdk/zksync2-examples/blob/main/go/02_transfer.go). -3. [Withdraw ETH and tokens from ZKsync Era to Ethereum](https://github.com/zksync-sdk/zksync2-examples/blob/main/go/03_withdraw.go). -4. [Deploy a smart contract using CREATE method](https://github.com/zksync-sdk/zksync2-examples/blob/main/go/08_deploy_create.go). -5. [Deploy a smart contract using CREATE2 method](https://github.com/zksync-sdk/zksync2-examples/blob/main/go/11_deploy_create2.go). -6. [Deploy custom token on ZKsync Era](https://github.com/zksync-sdk/zksync2-examples/blob/main/go/14_deploy_token_create.go). -7. [Deploy smart account](https://github.com/zksync-sdk/zksync2-examples/blob/main/go/16_deploy_create_account.go). -8. [Use paymaster to pay fee with token](https://github.com/zksync-sdk/zksync2-examples/blob/main/go/18_use_paymaster.go). - -Full code for all examples is available in the [documentation](https://github.com/zksync-sdk/zksync2-examples/tree/main/go). +1. Deposit ETH and tokens from Ethereum into ZKsync Era + +::collapsible + + ```bash +package main + +import ( + "context" + "fmt" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/zksync-sdk/zksync2-go/accounts" + "github.com/zksync-sdk/zksync2-go/clients" + "github.com/zksync-sdk/zksync2-go/utils" + "log" + "math/big" + "os" +) + +func main() { + var ( + PrivateKey = os.Getenv("PRIVATE_KEY") + ZkSyncEraProvider = "https://sepolia.era.zksync.dev" + EthereumProvider = "https://rpc.ankr.com/eth_sepolia" + ) + + // Connect to ZKsync network + client, err := clients.Dial(ZkSyncEraProvider) + if err != nil { + log.Panic(err) + } + defer client.Close() + + // Connect to Ethereum network + ethClient, err := ethclient.Dial(EthereumProvider) + if err != nil { + log.Panic(err) + } + defer ethClient.Close() + + // Create wallet + wallet, err := accounts.NewWallet(common.Hex2Bytes(PrivateKey), &client, ethClient) + if err != nil { + log.Panic(err) + } + + // Show balance before deposit + balance, err := wallet.Balance(context.Background(), utils.EthAddress, nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Balance before deposit: ", balance) + + // Perform deposit + tx, err := wallet.Deposit(nil, accounts.DepositTransaction{ + Token: utils.EthAddress, + Amount: big.NewInt(1_000_000_000), + To: wallet.Address(), + }) + if err != nil { + log.Panic(err) + } + fmt.Println("L1 transaction: ", tx.Hash()) + + // Wait for deposit transaction to be finalized on L1 network + fmt.Println("Waiting for deposit transaction to be finalized on L1 network") + _, err = bind.WaitMined(context.Background(), ethClient, tx) + if err != nil { + log.Panic(err) + } + + // Get transaction receipt for deposit transaction on L1 network + l1Receipt, err := ethClient.TransactionReceipt(context.Background(), tx.Hash()) + if err != nil { + log.Panic(err) + } + // Get deposit transaction on L2 network + l2Tx, err := client.L2TransactionFromPriorityOp(context.Background(), l1Receipt) + if err != nil { + log.Panic(err) + } + + fmt.Println("L2 transaction", l2Tx.Hash) + + // Wait for deposit transaction to be finalized on L2 network (5-7 minutes) + fmt.Println("Waiting for deposit transaction to be finalized on L2 network (5-7 minutes)") + _, err = client.WaitMined(context.Background(), l2Tx.Hash) + if err != nil { + log.Panic(err) + } + + balance, err = wallet.Balance(context.Background(), utils.EthAddress, nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Balance after deposit: ", balance) + + /* + // ClaimFailedDeposit is used when transaction on L2 has failed. + cfdTx, err := wallet.ClaimFailedDeposit(nil, l2Tx.Hash) + if err != nil { + fmt.Println(err) // this should be triggered if deposit was successful + } + fmt.Println("ClaimFailedDeposit hash: ", cfdTx.Hash()) + */ +} +:: + +2. Transfer ETH and tokens on ZKsync Era + +::collapsible + + ```bash + +package main +import ( + "context" + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/zksync-sdk/zksync2-go/accounts" + "github.com/zksync-sdk/zksync2-go/clients" + "github.com/zksync-sdk/zksync2-go/utils" + "log" + "math/big" + "os" +) + +func main() { + var ( + PrivateKey1 = os.Getenv("PRIVATE_KEY") + PublicKey2 = "0x81E9D85b65E9CC8618D85A1110e4b1DF63fA30d9" + ZkSyncEraProvider = "https://sepolia.era.zksync.dev" + ) + // Connect to ZKsync network + client, err := clients.Dial(ZkSyncEraProvider) + if err != nil { + log.Panic(err) + } + defer client.Close() + // Create wallet + wallet, err := accounts.NewWallet(common.Hex2Bytes(PrivateKey1), &client, nil) + if err != nil { + log.Panic(err) + } + // Show balances before transfer for both accounts + account1Balance, err := wallet.Balance(context.Background(), utils.EthAddress, nil) + if err != nil { + log.Panic(err) + } + account2Balance, err := client.BalanceAt(context.Background(), common.HexToAddress(PublicKey2), nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Account1 balance before transfer: ", account1Balance) + fmt.Println("Account2 balance before transfer: ", account2Balance) + // Perform transfer + tx, err := wallet.Transfer(nil, accounts.TransferTransaction{ + To: common.HexToAddress(PublicKey2), + Amount: big.NewInt(1_000_000_000), + Token: utils.EthAddress, + }) + if err != nil { + log.Panic(err) + } + fmt.Println("Transaction: ", tx.Hash()) + // Wait for transaction to be finalized on L2 network + _, err = client.WaitMined(context.Background(), tx.Hash()) + if err != nil { + log.Panic(err) + } + // Show balances after transfer for both accounts + account1Balance, err = wallet.Balance(context.Background(), utils.EthAddress, nil) + if err != nil { + log.Panic(err) + } + account2Balance, err = client.BalanceAt(context.Background(), common.HexToAddress(PublicKey2), nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Account1 balance after transfer: ", account1Balance) + fmt.Println("Account2 balance after transfer: ", account2Balance) +} +:: +3. Withdraw ETH and tokens from ZKsync Era to Ethereum + +::collapsible + + ```bash + +package main + +import ( + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/zksync-sdk/zksync2-go/accounts" + "github.com/zksync-sdk/zksync2-go/clients" + "github.com/zksync-sdk/zksync2-go/utils" + "log" + "math/big" + "os" +) + +func main() { + var ( + PrivateKey = os.Getenv("PRIVATE_KEY") + ZkSyncEraProvider = "https://sepolia.era.zksync.dev" + EthereumProvider = "https://rpc.ankr.com/eth_sepolia" + ) + + // Connect to ZKsync network + client, err := clients.Dial(ZkSyncEraProvider) + if err != nil { + log.Panic(err) + } + defer client.Close() + + // Connect to Ethereum network + ethClient, err := ethclient.Dial(EthereumProvider) + if err != nil { + log.Panic(err) + } + defer ethClient.Close() + + // Create wallet + wallet, err := accounts.NewWallet(common.Hex2Bytes(PrivateKey), &client, ethClient) + if err != nil { + log.Panic(err) + } + + // Perform withdrawal + tx, err := wallet.Withdraw(nil, accounts.WithdrawalTransaction{ + To: wallet.Address(), + Amount: big.NewInt(1_000_000_000), + Token: utils.EthAddress, + }) + if err != nil { + log.Panic(err) + } + fmt.Println("Withdraw transaction: ", tx.Hash()) + + // The duration for submitting a withdrawal transaction to L1 can last up to 24 hours. For additional information, + // please refer to the documentation: https://era.zksync.io/docs/reference/troubleshooting/withdrawal-delay.html. + // Once the withdrawal transaction is submitted on L1, it needs to be finalized. + // To learn more about how to achieve this, please take a look at the 04_finalize_withdraw.go script. +} +:: +4. Deploy a smart contract using CREATE method + +::collapsible + + ```bash + + package main + +import ( + "context" + "fmt" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/zksync-sdk/zksync2-go/accounts" + "github.com/zksync-sdk/zksync2-go/clients" + "github.com/zksync-sdk/zksync2-go/types" + "log" + "math/big" + "os" + "zksync2-examples/contracts/storage" +) + +func main() { + var ( + PrivateKey = os.Getenv("PRIVATE_KEY") + ZkSyncEraProvider = "https://sepolia.era.zksync.dev" + ) + + // Connect to ZKsync network + client, err := clients.Dial(ZkSyncEraProvider) + if err != nil { + log.Panic(err) + } + defer client.Close() + + // Create wallet + wallet, err := accounts.NewWallet(common.Hex2Bytes(PrivateKey), &client, nil) + if err != nil { + log.Panic(err) + } + + // Read smart contract bytecode + bytecode, err := os.ReadFile("../solidity/storage/build/Storage.zbin") + if err != nil { + log.Panic(err) + } + + //Deploy smart contract + hash, err := wallet.DeployWithCreate(nil, accounts.CreateTransaction{Bytecode: bytecode}) + if err != nil { + panic(err) + } + fmt.Println("Transaction: ", hash) + + // Wait unit transaction is finalized + receipt, err := client.WaitMined(context.Background(), hash) + if err != nil { + log.Panic(err) + } + + contractAddress := receipt.ContractAddress + fmt.Println("Smart contract address", contractAddress.String()) + + // INTERACT WITH SMART CONTRACT + + // Create instance of Storage smart contract + storageContract, err := storage.NewStorage(contractAddress, client) + if err != nil { + log.Panic(err) + } + + abi, err := storage.StorageMetaData.GetAbi() + if err != nil { + log.Panic(err) + } + // Encode set function arguments + setArguments, err := abi.Pack("set", big.NewInt(700)) + if err != nil { + log.Panic(err) + } + gas, err := client.EstimateGasL2(context.Background(), types.CallMsg{ + CallMsg: ethereum.CallMsg{ + To: &contractAddress, + From: wallet.Address(), + Data: setArguments, + }, + }) + if err != nil { + log.Panic(err) + } + fmt.Println("Gas: ", gas) + + result, err := wallet.CallContract(context.Background(), accounts.CallMsg{ + To: &contractAddress, + Data: setArguments, + }, nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Result: ", result) + + // Execute Get method from storage smart contract + value, err := storageContract.Get(nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Value:", value) + + // Start configuring transaction parameters + opts, err := bind.NewKeyedTransactorWithChainID(wallet.Signer().PrivateKey(), wallet.Signer().Domain().ChainId) + if err != nil { + log.Panic(err) + } + + // Execute Set method from storage smart contract with configured transaction parameters + tx, err := storageContract.Set(opts, big.NewInt(200)) + if err != nil { + log.Panic(err) + } + // Wait for transaction to be finalized + _, err = client.WaitMined(context.Background(), tx.Hash()) + if err != nil { + log.Panic(err) + } + + // Execute Get method again to check if state is changed + value, err = storageContract.Get(nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Value after first Set method execution: ", value) + + // INTERACT WITH SMART CONTRACT USING EIP-712 TRANSACTIONS + abi, err = storage.StorageMetaData.GetAbi() + if err != nil { + log.Panic(err) + } + // Encode set function arguments + setArguments, err = abi.Pack("set", big.NewInt(500)) + if err != nil { + log.Panic(err) + } + // Execute set function + execute, err := wallet.SendTransaction(context.Background(), &accounts.Transaction{ + To: &contractAddress, + Data: setArguments, + }) + if err != nil { + log.Panic(err) + } + + _, err = client.WaitMined(context.Background(), execute) + if err != nil { + log.Panic(err) + } + + // Execute Get method again to check if state is changed + value, err = storageContract.Get(nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Value after second Set method execution: ", value) +} +:: + +5. Deploy a smart contract using CREATE2 method + +::collapsible + + ```bash + +package main + +import ( + "context" + "fmt" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/zksync-sdk/zksync2-go/accounts" + "github.com/zksync-sdk/zksync2-go/clients" + "log" + "math/big" + "os" + "zksync2-examples/contracts/storage" +) + +func main() { + var ( + PrivateKey = os.Getenv("PRIVATE_KEY") + ZkSyncEraProvider = "https://sepolia.era.zksync.dev" + ) + + // Connect to ZKsync network + client, err := clients.Dial(ZkSyncEraProvider) + if err != nil { + log.Panic(err) + } + defer client.Close() + + // Create wallet + wallet, err := accounts.NewWallet(common.Hex2Bytes(PrivateKey), &client, nil) + if err != nil { + log.Panic(err) + } + + // Read smart contract bytecode + bytecode, err := os.ReadFile("../solidity/storage/build/Storage.zbin") + if err != nil { + log.Panic(err) + } + + //Deploy smart contract + hash, err := wallet.Deploy(nil, accounts.Create2Transaction{Bytecode: bytecode}) + if err != nil { + log.Panic(err) + } + fmt.Println("Transaction: ", hash) + + // Wait unit transaction is finalized + receipt, err := client.WaitMined(context.Background(), hash) + if err != nil { + log.Panic(err) + } + + contractAddress := receipt.ContractAddress + fmt.Println("Smart contract address", contractAddress.String()) + + // INTERACT WITH SMART CONTRACT + + // Create instance of Storage smart contract + storageContract, err := storage.NewStorage(contractAddress, client) + if err != nil { + log.Panic(err) + } + + // Execute Get method from storage smart contract + value, err := storageContract.Get(nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Value:", value) + + // Start configuring transaction parameters + opts, err := bind.NewKeyedTransactorWithChainID(wallet.Signer().PrivateKey(), wallet.Signer().Domain().ChainId) + if err != nil { + log.Panic(err) + } + + // Execute Set method from storage smart contract with configured transaction parameters + tx, err := storageContract.Set(opts, big.NewInt(200)) + if err != nil { + log.Panic(err) + } + // Wait for transaction to be finalized + _, err = client.WaitMined(context.Background(), tx.Hash()) + if err != nil { + log.Panic(err) + } + + // Execute Get method again to check if state is changed + value, err = storageContract.Get(nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Value after Set method execution: ", value) +} +:: +6. Deploy custom token on ZKsync Era + +::collapsible + + ```bash + +package main + +import ( + "context" + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/zksync-sdk/zksync2-go/accounts" + "github.com/zksync-sdk/zksync2-go/clients" + "log" + "os" + "zksync2-examples/contracts/token" +) + +func main() { + var ( + PrivateKey = os.Getenv("PRIVATE_KEY") + ZkSyncEraProvider = "https://sepolia.era.zksync.dev" + ) + + // Connect to ZKsync network + client, err := clients.Dial(ZkSyncEraProvider) + if err != nil { + log.Panic(err) + } + defer client.Close() + + // Create wallet + wallet, err := accounts.NewWallet(common.Hex2Bytes(PrivateKey), &client, nil) + if err != nil { + log.Panic(err) + } + + tokenAbi, err := token.TokenMetaData.GetAbi() + if err != nil { + log.Panic(err) + } + + constructor, err := tokenAbi.Pack("", "Crown", "Crown", uint8(18)) + if err != nil { + log.Panic(err) + } + + //Deploy smart contract + hash, err := wallet.DeployWithCreate(nil, accounts.CreateTransaction{ + Bytecode: common.FromHex(token.TokenMetaData.Bin), + Calldata: constructor, + }) + if err != nil { + log.Panic(err) + } + fmt.Println("Transaction: ", hash) + + // Wait unit transaction is finalized + receipt, err := client.WaitMined(context.Background(), hash) + if err != nil { + log.Panic(err) + } + + // Get address of deployed smart contract + tokenAddress := receipt.ContractAddress + fmt.Println("Token address", tokenAddress.String()) + + // Create instance of token contract + tokenContract, err := token.NewToken(tokenAddress, client) + if err != nil { + log.Panic(err) + } + + symbol, err := tokenContract.Symbol(nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Symbol: ", symbol) + + decimals, err := tokenContract.Decimals(nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Decimals: ", decimals) +} +:: +7. Deploy smart account + +::collapsible + + ```bash + +package main + +import ( + "context" + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/zksync-sdk/zksync2-go/accounts" + "github.com/zksync-sdk/zksync2-go/clients" + "github.com/zksync-sdk/zksync2-go/utils" + "log" + "os" +) + +func main() { + var ( + PrivateKey = os.Getenv("PRIVATE_KEY") + ZkSyncEraProvider = "https://sepolia.era.zksync.dev" + TokenAddress = "0x927488F48ffbc32112F1fF721759649A89721F8F" // Crown token which can be minted for free + ) + + // Connect to ZKsync network + client, err := clients.Dial(ZkSyncEraProvider) + if err != nil { + log.Panic(err) + } + defer client.Close() + + // Create wallet + wallet, err := accounts.NewWallet(common.Hex2Bytes(PrivateKey), &client, nil) + if err != nil { + log.Panic(err) + } + + _, paymasterAbi, bytecode, err := utils.ReadStandardJson("../solidity/custom_paymaster/paymaster/build/Paymaster.json") + if err != nil { + log.Panic(err) + } + + constructor, err := paymasterAbi.Pack("", common.HexToAddress(TokenAddress)) + if err != nil { + log.Panic(err) + } + + // Deploy paymaster contract + hash, err := wallet.DeployAccountWithCreate(nil, accounts.CreateTransaction{ + Bytecode: bytecode, + Calldata: constructor, + }) + if err != nil { + log.Panic(err) + } + fmt.Println("Transaction: ", hash) + + // Wait unit transaction is finalized + receipt, err := client.WaitMined(context.Background(), hash) + if err != nil { + log.Panic(err) + } + + contractAddress := receipt.ContractAddress + fmt.Println("Paymaster address", contractAddress.String()) +} +:: +8. Use paymaster to pay fee with token + +::collapsible + + ```bash + +package main + +import ( + "context" + "fmt" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/zksync-sdk/zksync2-go/accounts" + "github.com/zksync-sdk/zksync2-go/clients" + "github.com/zksync-sdk/zksync2-go/types" + "github.com/zksync-sdk/zksync2-go/utils" + "log" + "math/big" + "os" + "zksync2-examples/contracts/token" +) + +/* +This example demonstrates how to use a paymaster to facilitate fee payment with an ERC20 token. +The user initiates a mint transaction that is configured to be paid with an ERC20 token through the paymaster. +During transaction execution, the paymaster receives the ERC20 token from the user and covers the transaction fee using ETH. +*/ +func main() { + var ( + PrivateKey = os.Getenv("PRIVATE_KEY") + ZkSyncEraProvider = "https://sepolia.era.zksync.dev" + TokenAddress = common.HexToAddress("0x927488F48ffbc32112F1fF721759649A89721F8F") // Crown tokenContract which can be minted for free + PaymasterAddress = common.HexToAddress("0x13D0D8550769f59aa241a41897D4859c87f7Dd46") // Paymaster for Crown tokenContract + ) + + // Connect to ZKsync network + client, err := clients.Dial(ZkSyncEraProvider) + if err != nil { + log.Panic(err) + } + defer client.Close() + + // Create wallet + wallet, err := accounts.NewWallet(common.Hex2Bytes(PrivateKey), &client, nil) + if err != nil { + log.Panic(err) + } + + // Get tokenContract contract + tokenContract, err := token.NewToken(TokenAddress, client) + if err != nil { + log.Panic(tokenContract) + } + + // Transfer some ETH to paymaster, so it can pay fee with ETH + transferTx, err := wallet.Transfer(nil, accounts.TransferTransaction{ + To: PaymasterAddress, + Amount: big.NewInt(2_000_000_000_000_000_000), + Token: utils.EthAddress, + }) + if err != nil { + log.Panic(err) + } + + _, err = client.WaitMined(context.Background(), transferTx.Hash()) + if err != nil { + log.Panic(err) + } + + // Also mint some tokens to user account, so it can offer to pay fee with it + opts, err := bind.NewKeyedTransactorWithChainID(wallet.Signer().PrivateKey(), wallet.Signer().Domain().ChainId) + if err != nil { + log.Panic(err) + } + tx, err := tokenContract.Mint(opts, wallet.Address(), big.NewInt(10)) + if err != nil { + log.Panic(err) + } + _, err = client.WaitMined(context.Background(), tx.Hash()) + if err != nil { + log.Panic(err) + } + + // Read tokenContract and ETH balances from user and paymaster accounts + balance, err := wallet.Balance(context.Background(), utils.EthAddress, nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Account balance before mint: ", balance) + + tokenBalance, err := tokenContract.BalanceOf(nil, wallet.Address()) + if err != nil { + log.Panic(err) + } + fmt.Println("Account tokenContract balance before mint: ", tokenBalance) + + balance, err = client.BalanceAt(context.Background(), PaymasterAddress, nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Paymaster balance before mint: ", balance) + + tokenBalance, err = tokenContract.BalanceOf(nil, TokenAddress) + if err != nil { + log.Panic(err) + } + fmt.Println("Paymaster tokenContract balance before mint: ", tokenBalance) + + abi, err := token.TokenMetaData.GetAbi() + if err != nil { + log.Panic(err) + } + + // Encode mint function from tokenContract contract + calldata, err := abi.Pack("mint", wallet.Address(), big.NewInt(7)) + if err != nil { + log.Panic(err) + } + + // Create paymaster parameters with encoded paymaster input + paymasterParams, err := utils.GetPaymasterParams( + PaymasterAddress, + &types.ApprovalBasedPaymasterInput{ + Token: TokenAddress, + MinimalAllowance: big.NewInt(1), + InnerInput: []byte{}, + }) + if err != nil { + log.Panic(err) + } + + // In order to use paymaster, EIP712 transaction + // need to be created with configured paymaster parameters + hash, err := wallet.SendTransaction(context.Background(), &accounts.Transaction{ + To: &TokenAddress, + Data: calldata, + Meta: &types.Eip712Meta{ + PaymasterParams: paymasterParams, + }, + }) + if err != nil { + log.Panic(err) + } + + _, err = client.WaitMined(context.Background(), hash) + if err != nil { + log.Panic(err) + } + + fmt.Println("Tx: ", hash) + + balance, err = wallet.Balance(context.Background(), utils.EthAddress, nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Account balance after mint: ", balance) + + tokenBalance, err = tokenContract.BalanceOf(nil, wallet.Address()) + if err != nil { + log.Panic(err) + } + fmt.Println("Account tokenContract balance after mint: ", tokenBalance) + + balance, err = client.BalanceAt(context.Background(), PaymasterAddress, nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Paymaster balance after mint: ", balance) + + tokenBalance, err = tokenContract.BalanceOf(nil, TokenAddress) + if err != nil { + log.Panic(err) + } + fmt.Println("Paymaster tokenContract balance after mint: ", tokenBalance) + +} +:: + Examples are configured to interact with `ZKsync Era`, and `Sepolia` test networks. diff --git a/content/sdk/20.go/01.guides/02.accounts-l1-l2.md b/content/sdk/20.go/01.guides/02.accounts-l1-l2.md index 2bd753ae..41f13fd4 100644 --- a/content/sdk/20.go/01.guides/02.accounts-l1-l2.md +++ b/content/sdk/20.go/01.guides/02.accounts-l1-l2.md @@ -20,8 +20,251 @@ specification [`Deposit`](/sdk/go/api/accounts/walletl1#deposit). For a comprehensive example demonstrating the deposit workflow, refer to the following: -- [Deposit ETH](https://github.com/zksync-sdk/zksync2-examples/blob/main/go/01_deposit.go). -- [Deposit ERC20 tokens](https://github.com/zksync-sdk/zksync2-examples/blob/main/go/05_deposit_token.go). +- Deposit ETH + +::collapsible + + ```sh +package main + +import ( + "context" + "fmt" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/zksync-sdk/zksync2-go/accounts" + "github.com/zksync-sdk/zksync2-go/clients" + "github.com/zksync-sdk/zksync2-go/utils" + "log" + "math/big" + "os" +) + +func main() { + var ( + PrivateKey = os.Getenv("PRIVATE_KEY") + ZkSyncEraProvider = "https://sepolia.era.zksync.dev" + EthereumProvider = "https://rpc.ankr.com/eth_sepolia" + ) + + // Connect to ZKsync network + client, err := clients.Dial(ZkSyncEraProvider) + if err != nil { + log.Panic(err) + } + defer client.Close() + + // Connect to Ethereum network + ethClient, err := ethclient.Dial(EthereumProvider) + if err != nil { + log.Panic(err) + } + defer ethClient.Close() + + // Create wallet + wallet, err := accounts.NewWallet(common.Hex2Bytes(PrivateKey), &client, ethClient) + if err != nil { + log.Panic(err) + } + + // Show balance before deposit + balance, err := wallet.Balance(context.Background(), utils.EthAddress, nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Balance before deposit: ", balance) + + // Perform deposit + tx, err := wallet.Deposit(nil, accounts.DepositTransaction{ + Token: utils.EthAddress, + Amount: big.NewInt(1_000_000_000), + To: wallet.Address(), + }) + if err != nil { + log.Panic(err) + } + fmt.Println("L1 transaction: ", tx.Hash()) + + // Wait for deposit transaction to be finalized on L1 network + fmt.Println("Waiting for deposit transaction to be finalized on L1 network") + _, err = bind.WaitMined(context.Background(), ethClient, tx) + if err != nil { + log.Panic(err) + } + + // Get transaction receipt for deposit transaction on L1 network + l1Receipt, err := ethClient.TransactionReceipt(context.Background(), tx.Hash()) + if err != nil { + log.Panic(err) + } + // Get deposit transaction on L2 network + l2Tx, err := client.L2TransactionFromPriorityOp(context.Background(), l1Receipt) + if err != nil { + log.Panic(err) + } + + fmt.Println("L2 transaction", l2Tx.Hash) + + // Wait for deposit transaction to be finalized on L2 network (5-7 minutes) + fmt.Println("Waiting for deposit transaction to be finalized on L2 network (5-7 minutes)") + _, err = client.WaitMined(context.Background(), l2Tx.Hash) + if err != nil { + log.Panic(err) + } + + balance, err = wallet.Balance(context.Background(), utils.EthAddress, nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Balance after deposit: ", balance) + + /* + // ClaimFailedDeposit is used when transaction on L2 has failed. + cfdTx, err := wallet.ClaimFailedDeposit(nil, l2Tx.Hash) + if err != nil { + fmt.Println(err) // this should be triggered if deposit was successful + } + fmt.Println("ClaimFailedDeposit hash: ", cfdTx.Hash()) + */ +} +:: +- Deposit ERC20 tokens + +::collapsible + ```sh + +package main + +import ( + "context" + "fmt" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/zksync-sdk/zksync2-go/accounts" + "github.com/zksync-sdk/zksync2-go/clients" + "github.com/zksync-sdk/zksync2-go/contracts/erc20" + "github.com/zksync-sdk/zksync2-go/utils" + "log" + "math/big" + "os" +) + +func main() { + var ( + PrivateKey = os.Getenv("PRIVATE_KEY") + ZkSyncEraProvider = "https://sepolia.era.zksync.dev" + EthereumProvider = "https://rpc.ankr.com/eth_sepolia" + TokenL1Address = common.HexToAddress("0x56E69Fa1BB0d1402c89E3A4E3417882DeA6B14Be") + ) + + // Connect to ZKsync network + client, err := clients.Dial(ZkSyncEraProvider) + if err != nil { + log.Panic(err) + } + defer client.Close() + + // Connect to Ethereum network + ethClient, err := ethclient.Dial(EthereumProvider) + if err != nil { + log.Panic(err) + } + defer ethClient.Close() + + // Create wallet + wallet, err := accounts.NewWallet(common.Hex2Bytes(PrivateKey), &client, ethClient) + if err != nil { + log.Panic(err) + } + + // Get token contract on Ethereum network + tokenL1, err := erc20.NewIERC20(TokenL1Address, ethClient) + if err != nil { + log.Panic(err) + } + + // Show balances before deposit + balance, err := wallet.Balance(context.Background(), utils.EthAddress, nil) + if err != nil { + log.Panic(err) + } + tokenBalance, err := tokenL1.BalanceOf(nil, wallet.Address()) + if err != nil { + log.Panic(err) + } + + fmt.Println("Balance before deposit on L1 network: ", balance) + fmt.Println("Token balance before deposit on L1 network: ", tokenBalance) + + tx, err := wallet.Deposit(nil, accounts.DepositTransaction{ + Token: TokenL1Address, + Amount: big.NewInt(5), + To: wallet.Address(), + ApproveERC20: true, + RefundRecipient: wallet.Address(), + }) + if err != nil { + log.Panic(err) + } + + fmt.Println("L1 deposit transaction: ", tx.Hash()) + + // Wait for deposit transaction to be finalized on L1 network + fmt.Println("Waiting for deposit transaction to be finalized on L1 network") + _, err = bind.WaitMined(context.Background(), ethClient, tx) + if err != nil { + log.Panic(err) + } + + // Get transaction receipt for deposit transaction on L1 network + l1Receipt, err := ethClient.TransactionReceipt(context.Background(), tx.Hash()) + if err != nil { + log.Panic(err) + } + + // Get deposit transaction hash on L2 network + l2Tx, err := client.L2TransactionFromPriorityOp(context.Background(), l1Receipt) + if err != nil { + log.Panic(err) + } + fmt.Println("L2 transaction", l2Tx.Hash) + + // Wait for deposit transaction to be finalized on L2 network (5-7 minutes) + fmt.Println("Waiting for deposit transaction to be finalized on L2 network (5-7 minutes)") + _, err = client.WaitMined(context.Background(), l2Tx.Hash) + if err != nil { + log.Panic(err) + } + + balance, err = wallet.Balance(context.Background(), utils.EthAddress, nil) + if err != nil { + log.Panic(err) + } + tokenBalance, err = tokenL1.BalanceOf(nil, wallet.Address()) + if err != nil { + log.Panic(err) + } + + fmt.Println("Balance after deposit on L1 network: ", balance) + fmt.Println("Token balance after deposit on L1 network: ", tokenBalance) + + tokenL2Address, err := client.L2TokenAddress(context.Background(), TokenL1Address) + if err != nil { + log.Panic(err) + } + + fmt.Println("Token L2 address: ", tokenL2Address) + + tokenL2Balance, err := wallet.Balance(context.Background(), tokenL2Address, nil) + if err != nil { + log.Panic(err) + } + + fmt.Println("Token balance on L2 network: ", tokenL2Balance) +} +:: ## Request execute @@ -50,5 +293,129 @@ please refer to the method specification [`Deposit`](/sdk/go/api/accounts/wallet For a complete example of how to execute the deposit workflow, take a look at the following: -- [Withdraw ETH](https://github.com/zksync-sdk/zksync2-examples/blob/main/go/03_withdraw.go). -- [Withdraw ERC20 token](https://github.com/zksync-sdk/zksync2-examples/blob/main/go/07_withdraw_token.go). +- Withdraw ETH + +::collapsible + + ```sh +package main + +import ( + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/zksync-sdk/zksync2-go/accounts" + "github.com/zksync-sdk/zksync2-go/clients" + "github.com/zksync-sdk/zksync2-go/utils" + "log" + "math/big" + "os" +) + +func main() { + var ( + PrivateKey = os.Getenv("PRIVATE_KEY") + ZkSyncEraProvider = "https://sepolia.era.zksync.dev" + EthereumProvider = "https://rpc.ankr.com/eth_sepolia" + ) + + // Connect to ZKsync network + client, err := clients.Dial(ZkSyncEraProvider) + if err != nil { + log.Panic(err) + } + defer client.Close() + + // Connect to Ethereum network + ethClient, err := ethclient.Dial(EthereumProvider) + if err != nil { + log.Panic(err) + } + defer ethClient.Close() + + // Create wallet + wallet, err := accounts.NewWallet(common.Hex2Bytes(PrivateKey), &client, ethClient) + if err != nil { + log.Panic(err) + } + + // Perform withdrawal + tx, err := wallet.Withdraw(nil, accounts.WithdrawalTransaction{ + To: wallet.Address(), + Amount: big.NewInt(1_000_000_000), + Token: utils.EthAddress, + }) + if err != nil { + log.Panic(err) + } + fmt.Println("Withdraw transaction: ", tx.Hash()) + + // The duration for submitting a withdrawal transaction to L1 can last up to 24 hours. For additional information, + // please refer to the documentation: https://era.zksync.io/docs/reference/troubleshooting/withdrawal-delay.html. + // Once the withdrawal transaction is submitted on L1, it needs to be finalized. + // To learn more about how to achieve this, please take a look at the 04_finalize_withdraw.go script. +} +:: +- Withdraw ERC20 token + +::collapsible + ```sh + +package main + +import ( + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/zksync-sdk/zksync2-go/accounts" + "github.com/zksync-sdk/zksync2-go/clients" + "log" + "math/big" + "os" +) + +func main() { + var ( + PrivateKey = os.Getenv("PRIVATE_KEY") + ZkSyncEraProvider = "https://sepolia.era.zksync.dev" + EthereumProvider = "https://rpc.ankr.com/eth_sepolia" + TokenL2Address = common.HexToAddress("0x6a4Fb925583F7D4dF82de62d98107468aE846FD1") + ) + + // Connect to ZKsync network + client, err := clients.Dial(ZkSyncEraProvider) + if err != nil { + log.Panic(err) + } + defer client.Close() + + // Connect to Ethereum network + ethClient, err := ethclient.Dial(EthereumProvider) + if err != nil { + log.Panic(err) + } + defer ethClient.Close() + + // Create wallet + wallet, err := accounts.NewWallet(common.Hex2Bytes(PrivateKey), &client, ethClient) + if err != nil { + log.Panic(err) + } + + // Perform withdraw + tx, err := wallet.Withdraw(nil, accounts.WithdrawalTransaction{ + To: wallet.Address(), + Amount: big.NewInt(1), + Token: TokenL2Address, + }) + if err != nil { + log.Panic(err) + } + fmt.Println("Withdraw transaction: ", tx.Hash()) + + // The duration for submitting a withdrawal transaction to L1 can last up to 24 hours. For additional information, + // please refer to the documentation: https://era.zksync.io/docs/reference/troubleshooting/withdrawal-delay.html. + // Once the withdrawal transaction is submitted on L1, it needs to be finalized. + // To learn more about how to achieve this, please take a look at the 04_finalize_withdraw.go script. +} +:: diff --git a/content/sdk/20.go/02.api/03.contracts/00.contracts.md b/content/sdk/20.go/02.api/03.contracts/00.contracts.md index 0c86db80..7af131d6 100644 --- a/content/sdk/20.go/02.api/03.contracts/00.contracts.md +++ b/content/sdk/20.go/02.api/03.contracts/00.contracts.md @@ -17,10 +17,407 @@ Contract instantiation is the same as in the [`geth`](https://geth.ethereum.org/docs/developers/dapp-developer/native-bindings) library. For examples of how to deploy and instantiate contracts and accounts, refer to the following: -- [Deploy smart contracts using CREATE method](https://github.com/zksync-sdk/zksync2-examples/blob/main/go/08_deploy_create.go). -- [Deploy smart contracts using CREATE2 method](https://github.com/zksync-sdk/zksync2-examples/blob/main/go/11_deploy_create2.go). -- [Deploy smart accounts using CREATE method](https://github.com/zksync-sdk/zksync2-examples/blob/main/go/16_deploy_create_account.go). -- [Deploy smart accounts using CREATE2 method](https://github.com/zksync-sdk/zksync2-examples/blob/main/go/17_deploy_create2_account.go). +- Deploy smart contracts using `CREATE` method + +::collapsible + + ```sh +package main + +import ( + "context" + "fmt" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/zksync-sdk/zksync2-go/accounts" + "github.com/zksync-sdk/zksync2-go/clients" + "github.com/zksync-sdk/zksync2-go/types" + "log" + "math/big" + "os" + "zksync2-examples/contracts/storage" +) + +func main() { + var ( + PrivateKey = os.Getenv("PRIVATE_KEY") + ZkSyncEraProvider = "https://sepolia.era.zksync.dev" + ) + + // Connect to ZKsync network + client, err := clients.Dial(ZkSyncEraProvider) + if err != nil { + log.Panic(err) + } + defer client.Close() + + // Create wallet + wallet, err := accounts.NewWallet(common.Hex2Bytes(PrivateKey), &client, nil) + if err != nil { + log.Panic(err) + } + + // Read smart contract bytecode + bytecode, err := os.ReadFile("../solidity/storage/build/Storage.zbin") + if err != nil { + log.Panic(err) + } + + //Deploy smart contract + hash, err := wallet.DeployWithCreate(nil, accounts.CreateTransaction{Bytecode: bytecode}) + if err != nil { + panic(err) + } + fmt.Println("Transaction: ", hash) + + // Wait unit transaction is finalized + receipt, err := client.WaitMined(context.Background(), hash) + if err != nil { + log.Panic(err) + } + + contractAddress := receipt.ContractAddress + fmt.Println("Smart contract address", contractAddress.String()) + + // INTERACT WITH SMART CONTRACT + + // Create instance of Storage smart contract + storageContract, err := storage.NewStorage(contractAddress, client) + if err != nil { + log.Panic(err) + } + + abi, err := storage.StorageMetaData.GetAbi() + if err != nil { + log.Panic(err) + } + // Encode set function arguments + setArguments, err := abi.Pack("set", big.NewInt(700)) + if err != nil { + log.Panic(err) + } + gas, err := client.EstimateGasL2(context.Background(), types.CallMsg{ + CallMsg: ethereum.CallMsg{ + To: &contractAddress, + From: wallet.Address(), + Data: setArguments, + }, + }) + if err != nil { + log.Panic(err) + } + fmt.Println("Gas: ", gas) + + result, err := wallet.CallContract(context.Background(), accounts.CallMsg{ + To: &contractAddress, + Data: setArguments, + }, nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Result: ", result) + + // Execute Get method from storage smart contract + value, err := storageContract.Get(nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Value:", value) + + // Start configuring transaction parameters + opts, err := bind.NewKeyedTransactorWithChainID(wallet.Signer().PrivateKey(), wallet.Signer().Domain().ChainId) + if err != nil { + log.Panic(err) + } + + // Execute Set method from storage smart contract with configured transaction parameters + tx, err := storageContract.Set(opts, big.NewInt(200)) + if err != nil { + log.Panic(err) + } + // Wait for transaction to be finalized + _, err = client.WaitMined(context.Background(), tx.Hash()) + if err != nil { + log.Panic(err) + } + + // Execute Get method again to check if state is changed + value, err = storageContract.Get(nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Value after first Set method execution: ", value) + + // INTERACT WITH SMART CONTRACT USING EIP-712 TRANSACTIONS + abi, err = storage.StorageMetaData.GetAbi() + if err != nil { + log.Panic(err) + } + // Encode set function arguments + setArguments, err = abi.Pack("set", big.NewInt(500)) + if err != nil { + log.Panic(err) + } + // Execute set function + execute, err := wallet.SendTransaction(context.Background(), &accounts.Transaction{ + To: &contractAddress, + Data: setArguments, + }) + if err != nil { + log.Panic(err) + } + + _, err = client.WaitMined(context.Background(), execute) + if err != nil { + log.Panic(err) + } + + // Execute Get method again to check if state is changed + value, err = storageContract.Get(nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Value after second Set method execution: ", value) +} +:: +- Deploy smart contracts using `CREATE2` method + +::collapsible + ```sh + +package main + +import ( + "context" + "fmt" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/zksync-sdk/zksync2-go/accounts" + "github.com/zksync-sdk/zksync2-go/clients" + "log" + "math/big" + "os" + "zksync2-examples/contracts/storage" +) + +func main() { + var ( + PrivateKey = os.Getenv("PRIVATE_KEY") + ZkSyncEraProvider = "https://sepolia.era.zksync.dev" + ) + + // Connect to ZKsync network + client, err := clients.Dial(ZkSyncEraProvider) + if err != nil { + log.Panic(err) + } + defer client.Close() + + // Create wallet + wallet, err := accounts.NewWallet(common.Hex2Bytes(PrivateKey), &client, nil) + if err != nil { + log.Panic(err) + } + + // Read smart contract bytecode + bytecode, err := os.ReadFile("../solidity/storage/build/Storage.zbin") + if err != nil { + log.Panic(err) + } + + //Deploy smart contract + hash, err := wallet.Deploy(nil, accounts.Create2Transaction{Bytecode: bytecode}) + if err != nil { + log.Panic(err) + } + fmt.Println("Transaction: ", hash) + + // Wait unit transaction is finalized + receipt, err := client.WaitMined(context.Background(), hash) + if err != nil { + log.Panic(err) + } + + contractAddress := receipt.ContractAddress + fmt.Println("Smart contract address", contractAddress.String()) + + // INTERACT WITH SMART CONTRACT + + // Create instance of Storage smart contract + storageContract, err := storage.NewStorage(contractAddress, client) + if err != nil { + log.Panic(err) + } + + // Execute Get method from storage smart contract + value, err := storageContract.Get(nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Value:", value) + + // Start configuring transaction parameters + opts, err := bind.NewKeyedTransactorWithChainID(wallet.Signer().PrivateKey(), wallet.Signer().Domain().ChainId) + if err != nil { + log.Panic(err) + } + + // Execute Set method from storage smart contract with configured transaction parameters + tx, err := storageContract.Set(opts, big.NewInt(200)) + if err != nil { + log.Panic(err) + } + // Wait for transaction to be finalized + _, err = client.WaitMined(context.Background(), tx.Hash()) + if err != nil { + log.Panic(err) + } + + // Execute Get method again to check if state is changed + value, err = storageContract.Get(nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Value after Set method execution: ", value) +} +:: + +- Deploy smart accounts using `CREATE` method + +::collapsible + + ```sh +package main + +import ( + "context" + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/zksync-sdk/zksync2-go/accounts" + "github.com/zksync-sdk/zksync2-go/clients" + "github.com/zksync-sdk/zksync2-go/utils" + "log" + "os" +) + +func main() { + var ( + PrivateKey = os.Getenv("PRIVATE_KEY") + ZkSyncEraProvider = "https://sepolia.era.zksync.dev" + TokenAddress = "0x927488F48ffbc32112F1fF721759649A89721F8F" // Crown token which can be minted for free + ) + + // Connect to ZKsync network + client, err := clients.Dial(ZkSyncEraProvider) + if err != nil { + log.Panic(err) + } + defer client.Close() + + // Create wallet + wallet, err := accounts.NewWallet(common.Hex2Bytes(PrivateKey), &client, nil) + if err != nil { + log.Panic(err) + } + + _, paymasterAbi, bytecode, err := utils.ReadStandardJson("../solidity/custom_paymaster/paymaster/build/Paymaster.json") + if err != nil { + log.Panic(err) + } + + constructor, err := paymasterAbi.Pack("", common.HexToAddress(TokenAddress)) + if err != nil { + log.Panic(err) + } + + // Deploy paymaster contract + hash, err := wallet.DeployAccountWithCreate(nil, accounts.CreateTransaction{ + Bytecode: bytecode, + Calldata: constructor, + }) + if err != nil { + log.Panic(err) + } + fmt.Println("Transaction: ", hash) + + // Wait unit transaction is finalized + receipt, err := client.WaitMined(context.Background(), hash) + if err != nil { + log.Panic(err) + } + + contractAddress := receipt.ContractAddress + fmt.Println("Paymaster address", contractAddress.String()) +} +:: +- Deploy smart accounts using `CREATE2` method + +::collapsible + ```sh + +package main + +import ( + "context" + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/zksync-sdk/zksync2-go/accounts" + "github.com/zksync-sdk/zksync2-go/clients" + "github.com/zksync-sdk/zksync2-go/utils" + "log" + "os" +) + +func main() { + var ( + PrivateKey = os.Getenv("PRIVATE_KEY") + ZkSyncEraProvider = "https://sepolia.era.zksync.dev" + TokenAddress = "0x927488F48ffbc32112F1fF721759649A89721F8F" // Crown token which can be minted for free + ) + + // Connect to ZKsync network + client, err := clients.Dial(ZkSyncEraProvider) + if err != nil { + log.Panic(err) + } + defer client.Close() + + // Create wallet + wallet, err := accounts.NewWallet(common.Hex2Bytes(PrivateKey), &client, nil) + if err != nil { + log.Panic(err) + } + + // Read paymaster contract from standard json + _, paymasterAbi, bytecode, err := utils.ReadStandardJson("../solidity/custom_paymaster/paymaster/build/Paymaster.json") + if err != nil { + log.Panic(err) + } + + // Encode paymaster constructor + constructor, err := paymasterAbi.Pack("", common.HexToAddress(TokenAddress)) + if err != nil { + log.Panic(err) + } + + // Deploy paymaster contract + hash, err := wallet.DeployAccount(nil, accounts.Create2Transaction{Bytecode: bytecode, Calldata: constructor}) + if err != nil { + log.Panic(err) + } + fmt.Println("Transaction: ", hash) + + // Wait unit transaction is finalized + receipt, err := client.WaitMined(context.Background(), hash) + if err != nil { + log.Panic(err) + } + + contractAddress := receipt.ContractAddress + fmt.Println("Paymaster address: ", contractAddress.String()) +} +:: ## Contracts interfaces diff --git a/content/sdk/20.go/02.api/04.utilities/01.paymaster-utils.md b/content/sdk/20.go/02.api/04.utilities/01.paymaster-utils.md index 28b678a2..fd1b00c8 100644 --- a/content/sdk/20.go/02.api/04.utilities/01.paymaster-utils.md +++ b/content/sdk/20.go/02.api/04.utilities/01.paymaster-utils.md @@ -72,4 +72,181 @@ func GetPaymasterParams(paymasterAddress common.Address, paymasterInput types.Pa ``` Find out more about the [`PaymasterInput` type](/sdk/go/api/types). -Check out the [example](https://github.com/zksync-sdk/zksync2-examples/blob/main/go/18_use_paymaster.go) how to use paymaster. + +Check out the example on how to use paymaster. + +::collapsible + + ```sh +package main + +import ( + "context" + "fmt" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/zksync-sdk/zksync2-go/accounts" + "github.com/zksync-sdk/zksync2-go/clients" + "github.com/zksync-sdk/zksync2-go/types" + "github.com/zksync-sdk/zksync2-go/utils" + "log" + "math/big" + "os" + "zksync2-examples/contracts/token" +) + +/* +This example demonstrates how to use a paymaster to facilitate fee payment with an ERC20 token. +The user initiates a mint transaction that is configured to be paid with an ERC20 token through the paymaster. +During transaction execution, the paymaster receives the ERC20 token from the user and covers the transaction fee using ETH. +*/ +func main() { + var ( + PrivateKey = os.Getenv("PRIVATE_KEY") + ZkSyncEraProvider = "https://sepolia.era.zksync.dev" + TokenAddress = common.HexToAddress("0x927488F48ffbc32112F1fF721759649A89721F8F") // Crown tokenContract which can be minted for free + PaymasterAddress = common.HexToAddress("0x13D0D8550769f59aa241a41897D4859c87f7Dd46") // Paymaster for Crown tokenContract + ) + + // Connect to ZKsync network + client, err := clients.Dial(ZkSyncEraProvider) + if err != nil { + log.Panic(err) + } + defer client.Close() + + // Create wallet + wallet, err := accounts.NewWallet(common.Hex2Bytes(PrivateKey), &client, nil) + if err != nil { + log.Panic(err) + } + + // Get tokenContract contract + tokenContract, err := token.NewToken(TokenAddress, client) + if err != nil { + log.Panic(tokenContract) + } + + // Transfer some ETH to paymaster, so it can pay fee with ETH + transferTx, err := wallet.Transfer(nil, accounts.TransferTransaction{ + To: PaymasterAddress, + Amount: big.NewInt(2_000_000_000_000_000_000), + Token: utils.EthAddress, + }) + if err != nil { + log.Panic(err) + } + + _, err = client.WaitMined(context.Background(), transferTx.Hash()) + if err != nil { + log.Panic(err) + } + + // Also mint some tokens to user account, so it can offer to pay fee with it + opts, err := bind.NewKeyedTransactorWithChainID(wallet.Signer().PrivateKey(), wallet.Signer().Domain().ChainId) + if err != nil { + log.Panic(err) + } + tx, err := tokenContract.Mint(opts, wallet.Address(), big.NewInt(10)) + if err != nil { + log.Panic(err) + } + _, err = client.WaitMined(context.Background(), tx.Hash()) + if err != nil { + log.Panic(err) + } + + // Read tokenContract and ETH balances from user and paymaster accounts + balance, err := wallet.Balance(context.Background(), utils.EthAddress, nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Account balance before mint: ", balance) + + tokenBalance, err := tokenContract.BalanceOf(nil, wallet.Address()) + if err != nil { + log.Panic(err) + } + fmt.Println("Account tokenContract balance before mint: ", tokenBalance) + + balance, err = client.BalanceAt(context.Background(), PaymasterAddress, nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Paymaster balance before mint: ", balance) + + tokenBalance, err = tokenContract.BalanceOf(nil, TokenAddress) + if err != nil { + log.Panic(err) + } + fmt.Println("Paymaster tokenContract balance before mint: ", tokenBalance) + + abi, err := token.TokenMetaData.GetAbi() + if err != nil { + log.Panic(err) + } + + // Encode mint function from tokenContract contract + calldata, err := abi.Pack("mint", wallet.Address(), big.NewInt(7)) + if err != nil { + log.Panic(err) + } + + // Create paymaster parameters with encoded paymaster input + paymasterParams, err := utils.GetPaymasterParams( + PaymasterAddress, + &types.ApprovalBasedPaymasterInput{ + Token: TokenAddress, + MinimalAllowance: big.NewInt(1), + InnerInput: []byte{}, + }) + if err != nil { + log.Panic(err) + } + + // In order to use paymaster, EIP712 transaction + // need to be created with configured paymaster parameters + hash, err := wallet.SendTransaction(context.Background(), &accounts.Transaction{ + To: &TokenAddress, + Data: calldata, + Meta: &types.Eip712Meta{ + PaymasterParams: paymasterParams, + }, + }) + if err != nil { + log.Panic(err) + } + + _, err = client.WaitMined(context.Background(), hash) + if err != nil { + log.Panic(err) + } + + fmt.Println("Tx: ", hash) + + balance, err = wallet.Balance(context.Background(), utils.EthAddress, nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Account balance after mint: ", balance) + + tokenBalance, err = tokenContract.BalanceOf(nil, wallet.Address()) + if err != nil { + log.Panic(err) + } + fmt.Println("Account tokenContract balance after mint: ", tokenBalance) + + balance, err = client.BalanceAt(context.Background(), PaymasterAddress, nil) + if err != nil { + log.Panic(err) + } + fmt.Println("Paymaster balance after mint: ", balance) + + tokenBalance, err = tokenContract.BalanceOf(nil, TokenAddress) + if err != nil { + log.Panic(err) + } + fmt.Println("Paymaster tokenContract balance after mint: ", tokenBalance) + +} +:: diff --git a/content/sdk/30.python/00.introduction/00.overview.md b/content/sdk/30.python/00.introduction/00.overview.md index c6c91b6a..d5514229 100644 --- a/content/sdk/30.python/00.introduction/00.overview.md +++ b/content/sdk/30.python/00.introduction/00.overview.md @@ -18,11 +18,6 @@ To make it easy to use all ZKsync Era features, we created the `zksync2` Python to [web3.py](https://web3py.readthedocs.io/en/latest/index.html). In fact, `web3.py` is a peer dependency of our library. Most objects exported by `zksync2` inherit from `web3.py` objects and only change the fields that need adjustments. -::callout{icon="i-heroicons-light-bulb"} -Explore the [Python SDK examples](https://github.com/zksync-sdk/zksync2-examples/tree/main/python) to quickly and -efficiently develop applications with the ZKsync protocol. -:: - ## Key features - **Transaction management**: Easily create, sign, and send transactions on the ZKsync network. diff --git a/content/sdk/30.python/01.guides/00.getting-started.md b/content/sdk/30.python/01.guides/00.getting-started.md index 8bac4824..05535d12 100644 --- a/content/sdk/30.python/01.guides/00.getting-started.md +++ b/content/sdk/30.python/01.guides/00.getting-started.md @@ -42,10 +42,346 @@ transaction = zksync_web3.zksync.eth_get_transaction_by_hash(hash); Also, the following examples demonstrate how to: -1. [Deposit ETH and tokens from Ethereum into ZKsync Era](https://github.com/zksync-sdk/zksync2-examples/blob/main/python/01_deposit.py) -2. [Transfer ETH and tokens on ZKsync Era](https://github.com/zksync-sdk/zksync2-examples/blob/main/python/02_transfer.py) -3. [Withdraw ETH and tokens from ZKsync Era to Ethereum](https://github.com/zksync-sdk/zksync2-examples/blob/main/python/09_withdrawal.py) -4. [Use paymaster to pay fees with tokens](https://github.com/zksync-sdk/zksync2-examples/blob/main/python/15_use_paymaster.py) +1. Deposit ETH and tokens from Ethereum into ZKsync Era -Full code for all examples is available in the [Python ZKsync SDK](https://github.com/zksync-sdk/zksync2-examples/tree/main/python). +::collapsible + + ```bash +import os + +from eth_account import Account +from eth_account.signers.local import LocalAccount +from eth_typing import HexStr +from web3 import Web3 + +from zksync2.account.wallet import Wallet +from zksync2.core.types import Token, DepositTransaction +from zksync2.manage_contracts.utils import zksync_abi_default +from zksync2.module.module_builder import ZkSyncBuilder + +if __name__ == "__main__": + # Get the private key from OS environment variables + PRIVATE_KEY = bytes.fromhex(os.environ.get("PRIVATE_KEY")) + + # Set a provider + ZKSYNC_PROVIDER = "https://sepolia.era.zksync.dev" + ETH_PROVIDER = "https://rpc.ankr.com/eth_sepolia" + + # Connect to ZKsync network + zk_web3 = ZkSyncBuilder.build(ZKSYNC_PROVIDER) + + # Connect to Ethereum network + eth_web3 = Web3(Web3.HTTPProvider(ETH_PROVIDER)) + + # Get account object by providing from private key + account: LocalAccount = Account.from_key(PRIVATE_KEY) + + # Create Ethereum provider + wallet = Wallet(zk_web3, eth_web3, account) + + #ZkSync contract + zksync_contract = eth_web3.eth.contract(Web3.to_checksum_address(zk_web3.zksync.main_contract_address), + abi=zksync_abi_default()) + + amount = 0.1 + l2_balance_before = wallet.get_balance() + + tx_hash = wallet.deposit(DepositTransaction(token=Token.create_eth().l1_address, + amount=Web3.to_wei(amount, "ether"), + to=wallet.address)) + + l1_tx_receipt = eth_web3.eth.wait_for_transaction_receipt(tx_hash) + l2_hash = zk_web3.zksync.get_l2_hash_from_priority_op(l1_tx_receipt, zksync_contract) + l2_tx_receipt = zk_web3.zksync.wait_for_transaction_receipt(transaction_hash=l2_hash, + timeout=360, + poll_latency=10) + + print(f"L1 transaction: {l1_tx_receipt}") + print(f"L2 transaction: {l2_tx_receipt}") +:: +2. Transfer ETH and tokens on ZKsync Era + +::collapsible + ```bash +import os + +from eth_account import Account +from eth_account.signers.local import LocalAccount +from eth_typing import HexStr +from web3 import Web3 + +from zksync2.account.wallet import Wallet +from zksync2.core.types import TransferTransaction +from zksync2.module.module_builder import ZkSyncBuilder + +if __name__ == "__main__": + + # Get the private key from OS environment variables + PRIVATE_KEY = bytes.fromhex(os.environ.get("PRIVATE_KEY")) + + # Set a provider + ZKSYNC_PROVIDER = "https://sepolia.era.zksync.dev" + ETH_PROVIDER = "https://rpc.ankr.com/eth_sepolia" + + # Connect to ZKsync network + zk_web3 = ZkSyncBuilder.build(ZKSYNC_PROVIDER) + + # Connect to Ethereum network + eth_web3 = Web3(Web3.HTTPProvider(ETH_PROVIDER)) + + # Get account object by providing from private key + account: LocalAccount = Account.from_key(PRIVATE_KEY) + + # Create Ethereum provider + wallet = Wallet(zk_web3, eth_web3, account) + + # Show balance before ETH transfer + print(f"Balance before transfer : {wallet.get_l1_balance()} ETH") + + # Perform the ETH transfer + tx_hash = wallet.transfer(TransferTransaction( + to=HexStr("0x81E9D85b65E9CC8618D85A1110e4b1DF63fA30d9"), + amount=Web3.to_wei(0.001, "ether"), + + )) + zk_web3.zksync.wait_for_transaction_receipt( + tx_hash, timeout=240, poll_latency=0.5 + ) + + # Show balance after ETH transfer + print(f"Balance after transfer : {wallet.get_balance()} ETH") +:: +3. Withdraw ETH and tokens from ZKsync Era to Ethereum + +::collapsible + ```bash +import os + +from eth_account import Account +from eth_account.signers.local import LocalAccount +from eth_typing import HexStr +from web3 import Web3 + +from zksync2.account.wallet import Wallet +from zksync2.core.types import Token, WithdrawTransaction +from zksync2.module.module_builder import ZkSyncBuilder + +if __name__ == "__main__": + # Get the private key from OS environment variables + PRIVATE_KEY = bytes.fromhex(os.environ.get("PRIVATE_KEY")) + + # Set a provider + ZKSYNC_PROVIDER = "https://sepolia.era.zksync.dev" + ETH_PROVIDER = "https://rpc.ankr.com/eth_sepolia" + + # Connect to ZKsync network + zk_web3 = ZkSyncBuilder.build(ZKSYNC_PROVIDER) + + # Connect to Ethereum network + eth_web3 = Web3(Web3.HTTPProvider(ETH_PROVIDER)) + + # Get account object by providing from private key + account: LocalAccount = Account.from_key(PRIVATE_KEY) + + # Create Ethereum provider + wallet = Wallet(zk_web3, eth_web3, account) + + l2_balance_before = wallet.get_balance() + amount = 0.005 + + withdraw_tx_hash = wallet.withdraw( + WithdrawTransaction(token=Token.create_eth().l1_address, amount=Web3.to_wei(amount, "ether"))) + + tx_receipt = zk_web3.zksync.wait_for_transaction_receipt( + withdraw_tx_hash, timeout=240, poll_latency=0.5 + ) + + l2_balance_after = wallet.get_balance() + + print(f"Withdraw transaction receipt: {tx_receipt}") + print("Wait for withdraw transaction to be finalized on L2 network (11-24 hours)") + print("Read more about withdrawal delay: https://era.zksync.io/docs/dev/troubleshooting/withdrawal-delay.html") + print("When withdraw transaction is finalized, execute 10_finalize_withdrawal.py script " + "with WITHDRAW_TX_HASH environment variable set") +:: +4. Use paymaster to pay fees with tokens + +::collapsible + ```bash +import json +import os +from pathlib import Path + +from eth_account import Account +from eth_account.signers.local import LocalAccount +from eth_typing import HexStr + +from zksync2.core.types import EthBlockParams, PaymasterParams +from zksync2.manage_contracts.contract_encoder_base import ContractEncoder, JsonConfiguration +from zksync2.manage_contracts.paymaster_utils import PaymasterFlowEncoder +from zksync2.module.module_builder import ZkSyncBuilder +from zksync2.signer.eth_signer import PrivateKeyEthSigner +from zksync2.transaction.transaction_builders import TxFunctionCall + + +def get_abi_from_standard_json(standard_json: Path): + with standard_json.open(mode="r") as json_f: + return json.load(json_f)["abi"] + + +""" +This example demonstrates how to use a paymaster to facilitate fee payment with an ERC20 token. +The user initiates a mint transaction that is configured to be paid with an ERC20 token through the paymaster. +During transaction execution, the paymaster receives the ERC20 token from the user and covers the transaction fee using ETH. +""" +if __name__ == "__main__": + # Set a provider + PROVIDER = "https://sepolia.era.zksync.dev" + + # Get the private key from OS environment variables + PRIVATE_KEY = HexStr("c273a8616a4c58de9e58750fd2672d07b10497d64cd91b5942cce0909aaa391a") + + # Connect to ZKsync network + zk_web3 = ZkSyncBuilder.build(PROVIDER) + + # Get account object by providing from private key + account: LocalAccount = Account.from_key(PRIVATE_KEY) + + # Crown token than can be minted for free + token_address = zk_web3.to_checksum_address("0x927488F48ffbc32112F1fF721759649A89721F8F") + # Paymaster for Crown token + paymaster_address = zk_web3.to_checksum_address("0x13D0D8550769f59aa241a41897D4859c87f7Dd46") + + # Provide a compiled JSON source contract + contract_path = Path("../solidity/custom_paymaster/token/build/Token.json") + token_json = ContractEncoder.from_json(zk_web3, contract_path, JsonConfiguration.STANDARD) + + token_contract = zk_web3.zksync.contract(token_address, abi=token_json.abi) + + # MINT TOKEN TO USER ACCOUNT (so user can pay fee with token) + balance = token_contract.functions.balanceOf(account.address).call() + print(f"Crown token balance before mint: {balance}") + + mint_tx = token_contract.functions.mint(account.address, 50).build_transaction({ + "nonce": zk_web3.zksync.get_transaction_count(account.address, EthBlockParams.LATEST.value), + "from": account.address, + "maxPriorityFeePerGas": 1_000_000, + "maxFeePerGas": zk_web3.zksync.gas_price, + }) + + signed = account.sign_transaction(mint_tx) + + # Send mint transaction to ZKsync network + tx_hash = zk_web3.zksync.send_raw_transaction(signed.rawTransaction) + + tx_receipt = zk_web3.zksync.wait_for_transaction_receipt( + tx_hash, timeout=240, poll_latency=0.5 + ) + print(f"Tx status: {tx_receipt['status']}") + + balance = token_contract.functions.balanceOf(account.address).call() + print(f"Crown token balance after mint: {balance}") + + # SEND SOME ETH TO PAYMASTER (so it can pay fee with ETH) + + chain_id = zk_web3.zksync.chain_id + gas_price = zk_web3.zksync.gas_price + signer = PrivateKeyEthSigner(account, chain_id) + + tx_func_call = TxFunctionCall( + chain_id=zk_web3.zksync.chain_id, + nonce=zk_web3.zksync.get_transaction_count(account.address, EthBlockParams.LATEST.value), + from_=account.address, + to=paymaster_address, + value=zk_web3.to_wei(1, "ether"), + data=HexStr("0x"), + gas_limit=0, # Unknown at this state, estimation is done in next step + gas_price=gas_price, + max_priority_fee_per_gas=100_000_000, + ) + + # ZkSync transaction gas estimation + estimate_gas = zk_web3.zksync.eth_estimate_gas(tx_func_call.tx) + print(f"Fee for transaction is: {estimate_gas * gas_price}") + + # Convert transaction to EIP-712 format + tx_712 = tx_func_call.tx712(estimate_gas) + + # Sign message & encode it + signed_message = signer.sign_typed_data(tx_712.to_eip712_struct()) + + # Encode signed message + msg = tx_712.encode(signed_message) + + # Transfer ETH + tx_hash = zk_web3.zksync.send_raw_transaction(msg) + print(f"Transaction hash is : {tx_hash.hex()}") + + # Wait for transaction to be included in a block + tx_receipt = zk_web3.zksync.wait_for_transaction_receipt( + tx_hash, timeout=240, poll_latency=0.5 + ) + print(f"Tx status: {tx_receipt['status']}") + + # USE PAYMASTER TO PAY MINT TRANSACTION WITH CROWN TOKEN + + print(f"Paymaster balance before mint: " + f"{zk_web3.zksync.get_balance(account.address, EthBlockParams.LATEST.value)}") + print(f"User's Crown token balance before mint: {token_contract.functions.balanceOf(account.address).call()}") + print(f"Paymaster balance before mint: " + f"{zk_web3.zksync.get_balance(paymaster_address, EthBlockParams.LATEST.value)}") + print(f"Paymaster Crown token balance before mint: {token_contract.functions.balanceOf(paymaster_address).call()}") + + # Use the paymaster to pay mint transaction with token + calladata = token_contract.encodeABI(fn_name="mint", args=[account.address, 7]) + + # Create paymaster parameters + paymaster_params = PaymasterParams(**{ + "paymaster": paymaster_address, + "paymaster_input": zk_web3.to_bytes( + hexstr=PaymasterFlowEncoder(zk_web3).encode_approval_based(token_address, 1, b'')) + }) + + tx_func_call = TxFunctionCall( + chain_id=zk_web3.zksync.chain_id, + nonce=zk_web3.zksync.get_transaction_count(account.address, EthBlockParams.LATEST.value), + from_=account.address, + to=token_address, + data=calladata, + gas_limit=0, # Unknown at this state, estimation is done in next step + gas_price=gas_price, + max_priority_fee_per_gas=100_000_000, + paymaster_params=paymaster_params + ) + a = tx_func_call.tx; + # ZkSync transaction gas estimation + estimate_gas = zk_web3.zksync.eth_estimate_gas(tx_func_call.tx) + print(f"Fee for transaction is: {estimate_gas * gas_price}") + + # Convert transaction to EIP-712 format + tx_712 = tx_func_call.tx712(estimate_gas) + + # Sign message & encode it + signed_message = signer.sign_typed_data(tx_712.to_eip712_struct()) + + # Encode signed message + msg = tx_712.encode(signed_message) + + # Transfer ETH + tx_hash = zk_web3.zksync.send_raw_transaction(msg) + print(f"Transaction hash is : {tx_hash.hex()}") + + # Wait for transaction to be included in a block + tx_receipt = zk_web3.zksync.wait_for_transaction_receipt( + tx_hash, timeout=240, poll_latency=0.5 + ) + print(f"Tx status: {tx_receipt['status']}") + + print(f"Paymaster balance after mint: " + f"{zk_web3.zksync.get_balance(account.address, EthBlockParams.LATEST.value)}") + print(f"User's Crown token balance after mint: {token_contract.functions.balanceOf(account.address).call()}") + print(f"Paymaster balance after mint: " + f"{zk_web3.zksync.get_balance(paymaster_address, EthBlockParams.LATEST.value)}") + print(f"Paymaster Crown token balance after mint: {token_contract.functions.balanceOf(paymaster_address).call()}") +:: Examples are configured to interact with `ZKsync Era` and `Sepolia` test networks. diff --git a/content/sdk/30.python/01.guides/02.accounts-l1-l2.md b/content/sdk/30.python/01.guides/02.accounts-l1-l2.md index 8d9a0672..ac7944e9 100644 --- a/content/sdk/30.python/01.guides/02.accounts-l1-l2.md +++ b/content/sdk/30.python/01.guides/02.accounts-l1-l2.md @@ -18,7 +18,63 @@ Full examples of actions below are available on the getting started page. method specification [`Deposit`](/sdk/python/api/accounts/wallet#deposit). For a complete example of how to execute the deposit workflow, take a look at the following: -[Deposit ETH and ERC20 token](https://github.com/zksync-sdk/zksync2-examples/blob/main/python/01_deposit.py). +Deposit ETH and ERC20 token + +::collapsible + + ```bash +import os + +from eth_account import Account +from eth_account.signers.local import LocalAccount +from eth_typing import HexStr +from web3 import Web3 + +from zksync2.account.wallet import Wallet +from zksync2.core.types import Token, DepositTransaction +from zksync2.manage_contracts.utils import zksync_abi_default +from zksync2.module.module_builder import ZkSyncBuilder + +if __name__ == "__main__": + # Get the private key from OS environment variables + PRIVATE_KEY = bytes.fromhex(os.environ.get("PRIVATE_KEY")) + + # Set a provider + ZKSYNC_PROVIDER = "https://sepolia.era.zksync.dev" + ETH_PROVIDER = "https://rpc.ankr.com/eth_sepolia" + + # Connect to ZKsync network + zk_web3 = ZkSyncBuilder.build(ZKSYNC_PROVIDER) + + # Connect to Ethereum network + eth_web3 = Web3(Web3.HTTPProvider(ETH_PROVIDER)) + + # Get account object by providing from private key + account: LocalAccount = Account.from_key(PRIVATE_KEY) + + # Create Ethereum provider + wallet = Wallet(zk_web3, eth_web3, account) + + #ZkSync contract + zksync_contract = eth_web3.eth.contract(Web3.to_checksum_address(zk_web3.zksync.main_contract_address), + abi=zksync_abi_default()) + + amount = 0.1 + l2_balance_before = wallet.get_balance() + + tx_hash = wallet.deposit(DepositTransaction(token=Token.create_eth().l1_address, + amount=Web3.to_wei(amount, "ether"), + to=wallet.address)) + + l1_tx_receipt = eth_web3.eth.wait_for_transaction_receipt(tx_hash) + l2_hash = zk_web3.zksync.get_l2_hash_from_priority_op(l1_tx_receipt, zksync_contract) + l2_tx_receipt = zk_web3.zksync.wait_for_transaction_receipt(transaction_hash=l2_hash, + timeout=360, + poll_latency=10) + + print(f"L1 transaction: {l1_tx_receipt}") + print(f"L2 transaction: {l2_tx_receipt}") +:: ## Request execute @@ -45,4 +101,57 @@ method specification [`getBaseCost`](/sdk/python/api/accounts/wallet#getbasecost `Wallet` object provides a withdrawal workflow. For more information, please refer to the method specification [`Deposit`](/sdk/python/api/accounts/wallet#deposit). For a complete example of how to execute the deposit workflow, take a look at the following: -[Withdraw ETH and ERC20 token](https://github.com/zksync-sdk/zksync2-examples/blob/main/python/09_withdrawal.py). +Withdraw ETH and ERC20 token + +::collapsible + ```bash + +import os + +from eth_account import Account +from eth_account.signers.local import LocalAccount +from eth_typing import HexStr +from web3 import Web3 + +from zksync2.account.wallet import Wallet +from zksync2.core.types import Token, WithdrawTransaction +from zksync2.module.module_builder import ZkSyncBuilder + +if __name__ == "__main__": + # Get the private key from OS environment variables + PRIVATE_KEY = bytes.fromhex(os.environ.get("PRIVATE_KEY")) + + # Set a provider + ZKSYNC_PROVIDER = "https://sepolia.era.zksync.dev" + ETH_PROVIDER = "https://rpc.ankr.com/eth_sepolia" + + # Connect to ZKsync network + zk_web3 = ZkSyncBuilder.build(ZKSYNC_PROVIDER) + + # Connect to Ethereum network + eth_web3 = Web3(Web3.HTTPProvider(ETH_PROVIDER)) + + # Get account object by providing from private key + account: LocalAccount = Account.from_key(PRIVATE_KEY) + + # Create Ethereum provider + wallet = Wallet(zk_web3, eth_web3, account) + + l2_balance_before = wallet.get_balance() + amount = 0.005 + + withdraw_tx_hash = wallet.withdraw( + WithdrawTransaction(token=Token.create_eth().l1_address, amount=Web3.to_wei(amount, "ether"))) + + tx_receipt = zk_web3.zksync.wait_for_transaction_receipt( + withdraw_tx_hash, timeout=240, poll_latency=0.5 + ) + + l2_balance_after = wallet.get_balance() + + print(f"Withdraw transaction receipt: {tx_receipt}") + print("Wait for withdraw transaction to be finalized on L2 network (11-24 hours)") + print("Read more about withdrawal delay: https://era.zksync.io/docs/dev/troubleshooting/withdrawal-delay.html") + print("When withdraw transaction is finalized, execute 10_finalize_withdrawal.py script " + "with WITHDRAW_TX_HASH environment variable set") +:: diff --git a/content/sdk/30.python/02.api/03.utilities/01.paymaster-utils.md b/content/sdk/30.python/02.api/03.utilities/01.paymaster-utils.md index 06e7107f..73a53ba2 100644 --- a/content/sdk/30.python/02.api/03.utilities/01.paymaster-utils.md +++ b/content/sdk/30.python/02.api/03.utilities/01.paymaster-utils.md @@ -59,5 +59,180 @@ paymaster_params = PaymasterParams(**{ ``` Find out more about the [`PaymasterParams` type](/sdk/python/api/types). -Check out the [example](https://github.com/zksync-sdk/zksync2-examples/blob/main/python/15_use_paymaster.py) h -ow to use paymaster. +Check out the example on how to use paymaster. + +::collapsible + +```bash +import json +import os +from pathlib import Path + +from eth_account import Account +from eth_account.signers.local import LocalAccount +from eth_typing import HexStr + +from zksync2.core.types import EthBlockParams, PaymasterParams +from zksync2.manage_contracts.contract_encoder_base import ContractEncoder, JsonConfiguration +from zksync2.manage_contracts.paymaster_utils import PaymasterFlowEncoder +from zksync2.module.module_builder import ZkSyncBuilder +from zksync2.signer.eth_signer import PrivateKeyEthSigner +from zksync2.transaction.transaction_builders import TxFunctionCall + +def get_abi_from_standard_json(standard_json: Path): + with standard_json.open(mode="r") as json_f: + return json.load[json_f]("abi") + +""" +This example demonstrates how to use a paymaster to facilitate fee payment with an ERC20 token. +The user initiates a mint transaction that is configured to be paid with an ERC20 token through the paymaster. +During transaction execution, the paymaster receives the ERC20 token from the user and covers the transaction fee using ETH. +""" +if __name__ == "__main__": + # Set a provider + PROVIDER = "https://sepolia.era.zksync.dev" + # Get the private key from OS environment variables + PRIVATE_KEY = HexStr("c273a8616a4c58de9e58750fd2672d07b10497d64cd91b5942cce0909aaa391a") + # Connect to ZKsync network + zk_web3 = ZkSyncBuilder.build(PROVIDER) + + # Get account object by providing from private key + account: LocalAccount = Account.from_key(PRIVATE_KEY) + + # Crown token than can be minted for free + token_address = zk_web3.to_checksum_address("0x927488F48ffbc32112F1fF721759649A89721F8F") + # Paymaster for Crown token + paymaster_address = zk_web3.to_checksum_address("0x13D0D8550769f59aa241a41897D4859c87f7Dd46") + + # Provide a compiled JSON source contract + contract_path = Path("../solidity/custom_paymaster/token/build/Token.json") + token_json = ContractEncoder.from_json(zk_web3, contract_path, JsonConfiguration.STANDARD) + + token_contract = zk_web3.zksync.contract(token_address, abi=token_json.abi) + + # MINT TOKEN TO USER ACCOUNT (so user can pay fee with token) + balance = token_contract.functions.balanceOf(account.address).call() + print(f"Crown token balance before mint: {balance}") + + mint_tx = token_contract.functions.mint(account.address, 50).build_transaction({ + "nonce": zk_web3.zksync.get_transaction_count(account.address, EthBlockParams.LATEST.value), + "from": account.address, + "maxPriorityFeePerGas": 1_000_000, + "maxFeePerGas": zk_web3.zksync.gas_price, + }) + + signed = account.sign_transaction(mint_tx) + + # Send mint transaction to ZKsync network + tx_hash = zk_web3.zksync.send_raw_transaction(signed.rawTransaction) + + tx_receipt = zk_web3.zksync.wait_for_transaction_receipt( + tx_hash, timeout=240, poll_latency=0.5 + ) + print(f"Tx status: {tx_receipt['status']}") + + balance = token_contract.functions.balanceOf(account.address).call() + print(f"Crown token balance after mint: {balance}") + + # SEND SOME ETH TO PAYMASTER (so it can pay fee with ETH) + + chain_id = zk_web3.zksync.chain_id + gas_price = zk_web3.zksync.gas_price + signer = PrivateKeyEthSigner(account, chain_id) + + tx_func_call = TxFunctionCall( + chain_id=zk_web3.zksync.chain_id, + nonce=zk_web3.zksync.get_transaction_count(account.address, EthBlockParams.LATEST.value), + from_=account.address, + to=paymaster_address, + value=zk_web3.to_wei(1, "ether"), + data=HexStr("0x"), + gas_limit=0, # Unknown at this state, estimation is done in next step + gas_price=gas_price, + max_priority_fee_per_gas=100_000_000, + ) + + # ZkSync transaction gas estimation + estimate_gas = zk_web3.zksync.eth_estimate_gas(tx_func_call.tx) + print(f"Fee for transaction is: {estimate_gas * gas_price}") + + # Convert transaction to EIP-712 format + tx_712 = tx_func_call.tx712(estimate_gas) + + # Sign message & encode it + signed_message = signer.sign_typed_data(tx_712.to_eip712_struct()) + + # Encode signed message + msg = tx_712.encode(signed_message) + + # Transfer ETH + tx_hash = zk_web3.zksync.send_raw_transaction(msg) + print(f"Transaction hash is : {tx_hash.hex()}") + + # Wait for transaction to be included in a block + tx_receipt = zk_web3.zksync.wait_for_transaction_receipt( + tx_hash, timeout=240, poll_latency=0.5 + ) + print(f"Tx status: {tx_receipt['status']}") + + # USE PAYMASTER TO PAY MINT TRANSACTION WITH CROWN TOKEN + + print(f"Paymaster balance before mint: " + f"{zk_web3.zksync.get_balance(account.address, EthBlockParams.LATEST.value)}") + print(f"User's Crown token balance before mint: {token_contract.functions.balanceOf(account.address).call()}") + print(f"Paymaster balance before mint: " + f"{zk_web3.zksync.get_balance(paymaster_address, EthBlockParams.LATEST.value)}") + print(f"Paymaster Crown token balance before mint: {token_contract.functions.balanceOf(paymaster_address).call()}") + + # Use the paymaster to pay mint transaction with token + calladata = token_contract.encodeABI(fn_name="mint", args=[account.address, 7]) + + # Create paymaster parameters + paymaster_params = PaymasterParams(**{ + "paymaster": paymaster_address, + "paymaster_input": zk_web3.to_bytes( + hexstr=PaymasterFlowEncoder(zk_web3).encode_approval_based(token_address, 1, b'')) + }) + + tx_func_call = TxFunctionCall( + chain_id=zk_web3.zksync.chain_id, + nonce=zk_web3.zksync.get_transaction_count(account.address, EthBlockParams.LATEST.value), + from_=account.address, + to=token_address, + data=calladata, + gas_limit=0, # Unknown at this state, estimation is done in next step + gas_price=gas_price, + max_priority_fee_per_gas=100_000_000, + paymaster_params=paymaster_params + ) + a = tx_func_call.tx; + # ZkSync transaction gas estimation + estimate_gas = zk_web3.zksync.eth_estimate_gas(tx_func_call.tx) + print(f"Fee for transaction is: {estimate_gas * gas_price}") + + # Convert transaction to EIP-712 format + tx_712 = tx_func_call.tx712(estimate_gas) + + # Sign message & encode it + signed_message = signer.sign_typed_data(tx_712.to_eip712_struct()) + + # Encode signed message + msg = tx_712.encode(signed_message) + + # Transfer ETH + tx_hash = zk_web3.zksync.send_raw_transaction(msg) + print(f"Transaction hash is : {tx_hash.hex()}") + + # Wait for transaction to be included in a block + tx_receipt = zk_web3.zksync.wait_for_transaction_receipt( + tx_hash, timeout=240, poll_latency=0.5 + ) + print(f"Tx status: {tx_receipt['status']}") + + print(f"Paymaster balance after mint: " + f"{zk_web3.zksync.get_balance(account.address, EthBlockParams.LATEST.value)}") + print(f"User's Crown token balance after mint: {token_contract.functions.balanceOf(account.address).call()}") + print(f"Paymaster balance after mint: " + f"{zk_web3.zksync.get_balance(paymaster_address, EthBlockParams.LATEST.value)}") + print(f"Paymaster Crown token balance after mint: {token_contract.functions.balanceOf(paymaster_address).call()}") +:: diff --git a/content/sdk/40.java/01.guides/02.accounts-l1-l2.md b/content/sdk/40.java/01.guides/02.accounts-l1-l2.md index 1bdc7f6c..9c61e9cd 100644 --- a/content/sdk/40.java/01.guides/02.accounts-l1-l2.md +++ b/content/sdk/40.java/01.guides/02.accounts-l1-l2.md @@ -16,7 +16,42 @@ If you want some background on how L1<->L2 interaction works on ZKsync, go throu For more information, please refer to the method specification [`Deposit`](/sdk/java/api/accounts/wallet#deposit). For a complete example of how to execute the deposit workflow, take a look at the following: -[Deposit ETH and ERC20 token](https://github.com/zksync-sdk/zksync2-examples/blob/main/js/src/01_deposit.ts). +Deposit ETH and ERC20 token + +::collapsible + + ```bash +import { Provider, types, utils, Wallet } from "zksync-ethers"; +import { ethers } from "ethers"; + +const provider = Provider.getDefaultProvider(types.Network.Sepolia); +const ethProvider = ethers.getDefaultProvider("sepolia"); +const PRIVATE_KEY = process.env.PRIVATE_KEY; +const wallet = new Wallet(PRIVATE_KEY, provider, ethProvider); + +async function main() { + console.log(`L2 balance before deposit: ${await wallet.getBalance()}`); + console.log(`L1 balance before deposit: ${await wallet.getBalanceL1()}`); + + const tx = await wallet.deposit({ + token: utils.ETH_ADDRESS, + to: await wallet.getAddress(), + amount: ethers.parseEther("0.00020"), + refundRecipient: await wallet.getAddress(), + }); + const receipt = await tx.wait(); + console.log(`Tx: ${receipt.hash}`); + + console.log(`L2 balance after deposit: ${await wallet.getBalance()}`); + console.log(`L1 balance after deposit: ${await wallet.getBalanceL1()}`); +} + +main() + .then() + .catch((error) => { + console.log(`Error: ${error}`); + }); +:: ## Request execute @@ -34,4 +69,39 @@ For more information, please refer to the method specification [`getBaseCost`](/ For more information, please refer to the method specification [`Deposit`](/sdk/java/api/accounts/wallet#deposit). For a complete example of how to execute the deposit workflow, take a look at the following: -[Withdraw ETH and ERC20 token](https://github.com/zksync-sdk/zksync2-examples/blob/main/js/src/04_withdraw.ts). +Withdraw ETH and ERC20 token + +::collapsible + ```bash +import { Provider, types, utils, Wallet } from "zksync-ethers"; +import { ethers } from "ethers"; + +const provider = Provider.getDefaultProvider(types.Network.Sepolia); +const ethProvider = ethers.getDefaultProvider("sepolia"); +const PRIVATE_KEY = process.env.PRIVATE_KEY; +const wallet = new Wallet(PRIVATE_KEY, provider, ethProvider); + +async function main() { + console.log(`L2 balance before withdraw: ${await wallet.getBalance()}`); + console.log(`L1 balance before withdraw: ${await wallet.getBalanceL1()}`); + + const tx = await wallet.withdraw({ + token: utils.ETH_ADDRESS, + to: await wallet.getAddress(), + amount: ethers.parseEther("0.00020"), + }); + const receipt = await tx.wait(); + console.log(`Tx: ${receipt.hash}`); + + // The duration for submitting a withdrawal transaction to L1 can last up to 24 hours. For additional information, + // please refer to the documentation: https://era.zksync.io/docs/reference/troubleshooting/withdrawal-delay.html. + // Once the withdrawal transaction is submitted on L1, it needs to be finalized. + // To learn more about how to achieve this, please take a look at the 04_finalize_withdraw.ts script. +} + +main() + .then() + .catch((error) => { + console.log(`Error: ${error}`); + }); +:: diff --git a/content/sdk/60.rust/contract-deployment-and-interaction.md b/content/sdk/60.rust/contract-deployment-and-interaction.md index ad762917..3e70ec64 100644 --- a/content/sdk/60.rust/contract-deployment-and-interaction.md +++ b/content/sdk/60.rust/contract-deployment-and-interaction.md @@ -122,7 +122,9 @@ let contract_address = { }; ``` -**Congratulations! You have deployed and verified a smart contract to ZKsync Era Testnet** 🎉 +::callout +Congratulations! You have deployed and verified a smart contract to ZKsync Era Testnet 🎉 +:: ## Calling the `greet()` view method