diff --git a/.snippets/code/tutorials/integrations/0xgasless/dispatch.js b/.snippets/code/tutorials/integrations/0xgasless/dispatch.js new file mode 100644 index 000000000..8121c5a40 --- /dev/null +++ b/.snippets/code/tutorials/integrations/0xgasless/dispatch.js @@ -0,0 +1,96 @@ +require('dotenv').config(); +const ethers = require('ethers'); +const { + PaymasterMode, + createSmartAccountClient, +} = require('@0xgasless/smart-account'); + +const CHAIN_ID = 1284; // Moonbeam mainnet +const BUNDLER_URL = `https://bundler.0xgasless.com/${CHAIN_ID}`; +const PAYMASTER_URL = + 'https://paymaster.0xgasless.com/v1/1284/rpc/INSERT_API_KEY'; +const CONTRACT_ADDRESS = '0x3aE26f2c909EB4F1EdF97bf60B36529744b09213'; +const FUNCTION_SELECTOR = '0xd09de08a'; + +async function main() { + console.log('Starting the script...'); + try { + // Set up provider and wallet + console.log('Setting up provider and wallet...'); + const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL); + const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider); + + // Check connection and balance + console.log('Checking network connection...'); + const network = await provider.getNetwork(); + console.log( + `Connected to network: ${network.name} (Chain ID: ${network.chainId})` + ); + const balance = await provider.getBalance(wallet.address); + console.log(`Wallet balance: ${ethers.utils.formatEther(balance)} GLMR`); + + // Initialize smart account + console.log('Initializing smart account...'); + const smartWallet = await createSmartAccountClient({ + signer: wallet, + paymasterUrl: PAYMASTER_URL, + bundlerUrl: BUNDLER_URL, + chainId: CHAIN_ID, + }); + const smartWalletAddress = await smartWallet.getAddress(); + console.log('Smart Account Address:', smartWalletAddress); + + // Create a transaction for contract interaction + console.log('Creating contract transaction...'); + const transaction = { + to: CONTRACT_ADDRESS, + value: '0', // No native token transfer + data: FUNCTION_SELECTOR, // The function selector for the method we want to call + }; + + // Send the transaction + console.log('Sending transaction...'); + const userOpResponse = await smartWallet.sendTransaction(transaction, { + paymasterServiceData: { mode: PaymasterMode.SPONSORED }, + }); + console.log('UserOp Hash:', userOpResponse.hash); + + console.log('Waiting for transaction receipt...'); + const userOpReceipt = await waitForUserOpReceipt(userOpResponse, 60000); // Wait for up to 60 seconds + + if (userOpReceipt.success) { + console.log('Transaction successful!'); + console.log('Transaction hash:', userOpReceipt.receipt.transactionHash); + } else { + console.log('Transaction failed'); + console.log('Receipt:', userOpReceipt); + } + } catch (error) { + console.error('An error occurred:'); + console.error(error); + } +} + +async function waitForUserOpReceipt(userOpResponse, timeoutMs = 60000) { + return new Promise((resolve, reject) => { + const startTime = Date.now(); + const checkReceipt = async () => { + try { + const receipt = await userOpResponse.wait(); + resolve(receipt); + } catch (error) { + if (Date.now() - startTime > timeoutMs) { + reject(new Error(`Transaction wait timeout after ${timeoutMs}ms`)); + } else { + setTimeout(checkReceipt, 5000); // Retry every 5 seconds + } + } + }; + checkReceipt(); + }); +} + +main().catch((error) => { + console.error('Unhandled error in main function:'); + console.error(error); +}); \ No newline at end of file diff --git a/.snippets/code/tutorials/integrations/0xgasless/output.md b/.snippets/code/tutorials/integrations/0xgasless/output.md new file mode 100644 index 000000000..e2fcacf5b --- /dev/null +++ b/.snippets/code/tutorials/integrations/0xgasless/output.md @@ -0,0 +1,16 @@ +
+ 0xgasless % node dispatch.js + Starting the script... + Setting up provider and wallet... + Checking network connection... + Connected to network: unknown (Chain ID: 1284) + Wallet balance: 8.781249287153010128 GLMR + Initializing smart account... + Smart Account Address: 0xbBf77D3B43d81D426c4c3D200a76F4D3a914ccE3 + Creating contract transaction... + Sending transaction... + UserOp Hash: undefined + Waiting for transaction receipt... + Transaction successful! + Transaction hash: 0x9cb49cc0acc21abc364c13dd52b3f65c206ec61c57a13c23b635f59e1919cf7c +
\ No newline at end of file diff --git a/images/tutorials/integrations/0xgasless/0xgasless-1.webp b/images/tutorials/integrations/0xgasless/0xgasless-1.webp new file mode 100644 index 000000000..99ee2f171 Binary files /dev/null and b/images/tutorials/integrations/0xgasless/0xgasless-1.webp differ diff --git a/images/tutorials/integrations/0xgasless/0xgasless-2.webp b/images/tutorials/integrations/0xgasless/0xgasless-2.webp new file mode 100644 index 000000000..a776c30a4 Binary files /dev/null and b/images/tutorials/integrations/0xgasless/0xgasless-2.webp differ diff --git a/tutorials/integrations/.pages b/tutorials/integrations/.pages index 63b636127..19d568aad 100644 --- a/tutorials/integrations/.pages +++ b/tutorials/integrations/.pages @@ -9,3 +9,4 @@ nav: - 'Use Oracles to Fetch Off-Chain Data': - 'oracles.index.md' - '[Supra] Fetch Price Data': 'supra.md' + - 'Enable Gasless Transactions': '0xgasless.md' diff --git a/tutorials/integrations/0xgasless.md b/tutorials/integrations/0xgasless.md new file mode 100644 index 000000000..e07ad0794 --- /dev/null +++ b/tutorials/integrations/0xgasless.md @@ -0,0 +1,141 @@ +--- +title: Gasless Transactions with 0xGasless +description: Learn how to implement gasless transactions on Moonbeam using 0xGasless, enabling users to interact with smart contracts without holding native tokens. +--- + +# Enabling Gasless Transactions with 0xGasless + +## Why Gasless Transactions? + +One of the primary challenges in blockchain development has been the requirement for users to hold native tokens (like ETH or GLMR) to pay transaction fees. This traditional EOA-based model creates unnecessary friction, particularly when onboarding users who expect Web2-like experiences. + +Gasless transactions can help solve this through Account Abstraction ([ERC-4337](https://eips.ethereum.org/EIPS/eip-4337){target=\_blank}), implementing meta-transactions that separate user actions from fee payment. This architecture allows dApps or third-party paymasters to cover gas costs on behalf of users while smart contract wallets handle the transaction execution. [0xGasless](https://0xgasless.com/index.html){target=\_blank} leverages these principles in its SDK, enabling Moonbeam developers to implement sophisticated features like social logins, transaction batching, and custom wallet controls – all while abstracting away the complexity of gas management from end users. + +In the following tutorial, we'll go through the end-to-end steps of setting up a paymaster on 0xGasless and dispatching a gasless transaction to modify the state of a smart contract on Moonbeam. + +## Create and Fund a Paymaster + +First, you'll need to [register for an account on 0xGasless](https://dashboard.0xgasless.com/auth/sign-in){target=\_blank}. Then, [create a Paymaster](https://dashboard.0xgasless.com/paymaster){target=\_blank} for the Moonbeam Network by pressing **Create Paymaster** and then taking the following steps: + +1. Enter a name for your paymaster +2. Select **Moonbeam** as the chain +3. Press **Create** + +![Create Paymaster](/images/tutorials/integrations/0xgasless/0xgasless-1.webp) + +Your paymaster needs funds to cover gas fees for sponsored transactions. To deposit GLMR into your paymaster, take the following steps: + +1. Enter the amount you would like to deposit +2. Press **Deposit** and confirm the transaction in your wallet + +![Fund Paymaster](/images/tutorials/integrations/0xgasless/0xgasless-2.webp) + +Your deposited funds remain flexible - use them to sponsor gasless transactions or withdraw them whenever needed. + +## Dispatching a Gasless Transaction + +In the following section, we'll create a script demonstrating how to dispatch a gasless transaction. + +### Prerequisites + +Create a `.env` file in your project's root directory with the following: + +```bash +PRIVATE_KEY=INSERT_PRIVATE_KEY +RPC_URL=https://rpc.api.moonbeam.network +``` + +Why are we specifying a private key in the `.env`? While this transaction will be gasless, you still need a private key to sign the transaction. The account associated with this private key: + +- Does not need any GLMR tokens +- Will not pay for gas fees +- Is only used for transaction signing + +!!! note + + Never commit your .env file or share your private key. Add .env to your .gitignore file. + +Also, make sure you have installed the 0xGasless SDK and supporting `ethers` and `dotenv` packages: + +```bash +npm install ethers dotenv @0xgasless/smart-account +``` + +First, we'll import the required packages as follows: + +```js +require('dotenv').config(); +const ethers = require('ethers'); +const { + PaymasterMode, + createSmartAccountClient, +} = require('@0xgasless/smart-account'); +``` + +Next, we'll set the critical constants. We must define the `CHAIN_ID`, `BUNDLER_URL`, and `PAYMASTER_URL`. You can get your unique paymaster URL from the paymaster on your [0xGasless Dashboard](https://dashboard.0xgasless.com/paymaster){target=\_blank}. + +The contract address we've defined here is the address of an [Incrementer contract](https://moonscan.io/address/0x3ae26f2c909eb4f1edf97bf60b36529744b09213) on Moonbeam, on which we'll call the increment function specified by the function selector. This simple contract will allow us to easily see if the gasless transaction has been dispatched successfully. + +```js +const CHAIN_ID = 1284; // Moonbeam mainnet +const BUNDLER_URL = `https://bundler.0xgasless.com/${CHAIN_ID}`; +const PAYMASTER_URL = + 'https://paymaster.0xgasless.com/v1/1284/rpc/INSERT_API_KEY'; +const CONTRACT_ADDRESS = '0x3aE26f2c909EB4F1EdF97bf60B36529744b09213'; +const FUNCTION_SELECTOR = '0xd09de08a'; +``` + +!!! warning + + The Paymaster URL format has recently changed. Use: + + ``` + https://paymaster.0xgasless.com/v1/1284/rpc/INSERT_API_KEY + ``` + + Do not use the deprecated format: + + ``` + https://paymaster.0xgasless.com/api/v1/1284/rpc/INSERT_API_KEY + ``` + + The difference is that `/api` has been removed from the path. Make sure your code uses the current format. + +### Sending the Transaction + +To send a gasless transaction using the 0xGasless smart account, you can call `smartWallet.sendTransaction()` with two parameters: + + - The `transaction` object containing the contract interaction details + - A configuration object specifying `paymasterServiceData` with `SPONSORED` mode. This indicates that the 0xGasless paymaster will use the gas tank to pay for the gas. + +The function returns a UserOperation response containing a hash. Wait for the transaction receipt using the `waitForUserOpReceipt()` helper function, which polls for completion with a configurable timeout (default 60 seconds). + +```javascript +const userOpResponse = await smartWallet.sendTransaction(transaction, { + paymasterServiceData: { mode: PaymasterMode.SPONSORED }, +}); + +const receipt = await waitForUserOpReceipt(userOpResponse, 60000); +``` + +Putting it all together and adding plenty of logging and error handling for easy debugging, the full script is as follows: + +??? code "Dispatch a gasless transaction" + ```javascript + --8<-- 'code/tutorials/integrations/0xgasless/dispatch.js' + ``` + +### Verifying Completion + +Upon running the script, you'll see output that looks like the following: + +--8<-- 'code/tutorials/integrations/0xgasless/output.md' + +Since the gasless transaction we initiated interacts with an [Incrementer](https://moonscan.io/address/0x3ae26f2c909eb4f1edf97bf60b36529744b09213#readContract){target=\_blank} smart contract on Moonbeam, it's easy to check to see if the transaction was initiated successfully. You can return to [Read Contract section of the Incrementer contract on Moonscan](https://moonscan.io/address/0x3ae26f2c909eb4f1edf97bf60b36529744b09213#readContract) and check the number stored in the contract. Alternatively, you can head to the **Internal Transactions** tab and toggle advanced mode **ON** to see the contract call incrementing the contract. + +For more information about integrating support for gasless transactions into your dApp, be sure to check out the [0xGasless docs](https://gitbook.0xgasless.com/){target=\_blank}. + + +--8<-- 'text/_disclaimers/educational-tutorial.md' + +--8<-- 'text/_disclaimers/third-party-content.md'