Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Gasless Txn Tutorial for 0xGasless #1020

Merged
merged 6 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions .snippets/code/tutorials/integrations/0xgasless/dispatch.js
Original file line number Diff line number Diff line change
@@ -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);
});
16 changes: 16 additions & 0 deletions .snippets/code/tutorials/integrations/0xgasless/output.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<div id="termynal" data-termynal>
<span data-ty="input">0xgasless % node dispatch.js</span>
<span data-ty>Starting the script...</span>
<span data-ty>Setting up provider and wallet...</span>
<span data-ty>Checking network connection...</span>
<span data-ty>Connected to network: unknown (Chain ID: 1284)</span>
<span data-ty>Wallet balance: 8.781249287153010128 GLMR</span>
<span data-ty>Initializing smart account...</span>
<span data-ty>Smart Account Address: 0xbBf77D3B43d81D426c4c3D200a76F4D3a914ccE3</span>
<span data-ty>Creating contract transaction...</span>
<span data-ty>Sending transaction...</span>
<span data-ty>UserOp Hash: undefined</span>
<span data-ty>Waiting for transaction receipt...</span>
<span data-ty>Transaction successful!</span>
<span data-ty>Transaction hash: 0x9cb49cc0acc21abc364c13dd52b3f65c206ec61c57a13c23b635f59e1919cf7c</span>
</div>
Binary file not shown.
Binary file not shown.
1 change: 1 addition & 0 deletions tutorials/integrations/.pages
Original file line number Diff line number Diff line change
Expand Up @@ -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'
141 changes: 141 additions & 0 deletions tutorials/integrations/0xgasless.md
Original file line number Diff line number Diff line change
@@ -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'