Skip to content

Commit 26baa14

Browse files
Merge pull request #4 from etherspot/ft-simple-7702-eg
minor fixes for types, added example for simple 7702 account
2 parents 6d596ab + 51bc601 commit 26baa14

File tree

12 files changed

+280
-24
lines changed

12 files changed

+280
-24
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
node_modules
2+
dist

bun.lockb

107 KB
Binary file not shown.

examples/USAGE.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Free Bundler Usage Examples
2+
3+
This guide provides example commands for running user operations (userops) using the Free Bundler client.
4+
5+
## Prerequisites
6+
7+
- Node.js >= 18.0.0 or Bun >= 1.0.0
8+
- A private key for the smart account
9+
- RPC URL for the target chain (optional, defaults to free bundler endpoints)
10+
11+
## Command Line Arguments
12+
13+
The example script accepts the following command line arguments:
14+
15+
- `--chain-id` (required): Chain ID for the userop
16+
- `--account-type` (required): Type of smart account implementation
17+
- `--private-key` (required): Private key that controls the smart account
18+
- `--rpc-url` (optional): RPC URL for the chain
19+
- `--paymaster-url` (optional): Paymaster URL to sponsor user ops
20+
- `--paymaster-context` (optional): JSON string for paymaster context
21+
22+
## Example Commands
23+
24+
### 1. Basic UserOp on Ethereum Mainnet
25+
26+
```bash
27+
bun run ./examples/index.ts \
28+
--chain-id 1 \
29+
--account-type simple7702 \
30+
--private-key "0x..."
31+
```
32+
33+
### 2. UserOp on Optimism with Custom RPC
34+
35+
```bash
36+
bun run ./examples/index.ts \
37+
--chain-id 10 \
38+
--account-type simple7702 \
39+
--private-key "0x..." \
40+
--rpc-url "https://optimism-mainnet.infura.io/v3/YOUR_INFURA_KEY"
41+
```
42+
43+
### 3. UserOp on Optimism with Paymaster
44+
45+
```bash
46+
bun run ./examples/index.ts \
47+
--chain-id 10 \
48+
--account-type simple7702 \
49+
--private-key "0x..." \
50+
--paymaster-url "https://api.stackup.sh/v1/paymaster/YOUR_STACKUP_KEY" \
51+
--paymaster-context '{"sponsor": true}'
52+
```
53+
54+
## Supported Networks
55+
56+
The following networks are supported by the free bundler:
57+
58+
- **Ethereum Mainnet**: Chain ID `1`
59+
- **Optimism**: Chain ID `10`
60+
- **Arbitrum One**: Chain ID `42161`
61+
62+
## Account Types
63+
64+
Currently supported smart account implementations:
65+
66+
- **simple7702**: EIP-7702 compatible smart account
67+
68+
## What the Example Does
69+
70+
The example script demonstrates:
71+
72+
1. **Smart Account Creation**: Creates a Simple7702 smart account from a private key
73+
2. **Authorization Check**: Automatically handles EIP-7702 authorization if needed
74+
3. **UserOp Execution**: Sends a user operation that transfers 0.0000001 ETH to a test address
75+
4. **Paymaster Integration**: Optional paymaster support for sponsored transactions
76+
77+
### Getting Help
78+
79+
```bash
80+
bun run ./examples/index.ts --help
81+
```
82+
83+
This will display all available options and their descriptions.
84+
85+
## Development
86+
87+
To run in development mode with auto-reload:
88+
89+
```bash
90+
bun run dev
91+
```
92+
93+
To build the project:
94+
95+
```bash
96+
bun run build
97+
```

examples/accounts/simple7702.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import {
2+
createPaymasterClient,
3+
toSimple7702SmartAccount,
4+
ToSimple7702SmartAccountReturnType
5+
} from "viem/account-abstraction";
6+
import { createFreeBundler } from "../../src/client.js"
7+
import { privateKeyToAccount } from "viem/accounts";
8+
import {
9+
http,
10+
parseUnits,
11+
createClient,
12+
publicActions,
13+
walletActions,
14+
SignAuthorizationReturnType,
15+
Hex,
16+
Chain
17+
} from "viem";
18+
import { FREE_BUNDLER_URLS } from "../../src/config.js";
19+
20+
21+
export default async (
22+
{
23+
chain,
24+
paymasterContext,
25+
paymasterUrl,
26+
privateKey,
27+
rpcUrl
28+
}:
29+
{
30+
chain: Chain,
31+
privateKey: Hex,
32+
rpcUrl?: string,
33+
paymasterUrl?: string,
34+
paymasterContext?: string
35+
}
36+
) => {
37+
const owner = privateKeyToAccount(privateKey);
38+
39+
const client = createClient({
40+
account: owner,
41+
transport: http(rpcUrl || FREE_BUNDLER_URLS[chain.id]),
42+
chain
43+
}).extend(publicActions).extend(walletActions);
44+
45+
const smartAccount: ToSimple7702SmartAccountReturnType = await toSimple7702SmartAccount({
46+
client,
47+
owner,
48+
});
49+
50+
console.log("wallet:: ", smartAccount.address);
51+
52+
const bundlerClient = createFreeBundler(
53+
chain.id,
54+
{account: smartAccount, chain}
55+
);
56+
57+
// check sender's code to decide if eip7702Auth tuple is necessary for userOp.
58+
const senderCode = await client.getCode({
59+
address: smartAccount.address
60+
});
61+
62+
let authorization: SignAuthorizationReturnType | undefined;
63+
const { address: delegateAddress } = smartAccount.authorization;
64+
65+
if(senderCode !== `0xef0100${delegateAddress.toLowerCase().substring(2)}`) {
66+
authorization = await client.signAuthorization(smartAccount.authorization)
67+
}
68+
69+
const paymasterClient = paymasterUrl ? createPaymasterClient({
70+
transport: http(paymasterUrl)
71+
}) : undefined;
72+
73+
const userOpHash = await bundlerClient.sendUserOperation({
74+
authorization,
75+
calls: [
76+
{to: "0x09FD4F6088f2025427AB1e89257A44747081Ed59", value: parseUnits('0.0000001', 18)}
77+
],
78+
paymaster: paymasterClient,
79+
paymasterContext: paymasterContext ? JSON.parse(paymasterContext) : undefined,
80+
});
81+
82+
console.log("userOpHash:: ", userOpHash);
83+
return userOpHash;
84+
}

examples/args.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import yargs from "yargs";
2+
import { hideBin } from "yargs/helpers";
3+
4+
export const argv = await yargs(hideBin(process.argv))
5+
.option('chain-id', {
6+
description: 'chain id for the userop, supported networks - (ethereum(1), optimism(10), arbitrum(42161))',
7+
type: 'number',
8+
demandOption: true
9+
})
10+
.option('account-type', {
11+
description: 'type of smart account implementation, available:\n1. simple7702',
12+
type: 'string',
13+
demandOption: true
14+
})
15+
.option('private-key', {
16+
description: 'private key which controls the smart account',
17+
type: 'string',
18+
demandOption: true
19+
})
20+
.option('rpc-url', {
21+
description: 'rpc url for the chain on which userop needs to execute',
22+
type: 'string'
23+
})
24+
.option('paymaster-url', {
25+
description: 'paymaster url to sponsor user ops',
26+
type: 'string'
27+
})
28+
.option('paymaster-context', {
29+
description: 'stringified json to be used as context for paymaster',
30+
type: 'string'
31+
})
32+
.help()
33+
.alias('help', 'h')
34+
.argv;

examples/index.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {argv} from "./args.js";
2+
import { getChainById } from "./utils.js";
3+
import sendSimple7702UserOp from "./accounts/simple7702.js";
4+
import { Hex } from "viem";
5+
6+
export const main = async () => {
7+
const {
8+
chainId,
9+
rpcUrl,
10+
accountType,
11+
paymasterContext,
12+
paymasterUrl,
13+
privateKey
14+
} = argv;
15+
16+
const chain = getChainById(chainId);
17+
18+
switch(accountType) {
19+
case "simple7702":
20+
return sendSimple7702UserOp({
21+
chain,
22+
privateKey: privateKey as Hex,
23+
paymasterContext,
24+
paymasterUrl,
25+
rpcUrl
26+
})
27+
default:
28+
throw new Error(`Unsupported account type: ${accountType}`);
29+
}
30+
}
31+
main();

examples/utils.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import * as chains from "viem/chains"
2+
3+
export const getChainById = (id: number): chains.Chain => {
4+
for (const chain of Object.values(chains)) {
5+
if ('id' in chain && chain.id === id) {
6+
return chain;
7+
}
8+
}
9+
throw new Error(`Chain with id ${id} not found`);
10+
}

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"name": "@etherspot/free-bundler",
3-
"version": "1.0.0",
3+
"version": "1.0.1-alpha.0",
4+
"type": "module",
45
"description": "Free bundler client for ERC-4337 account abstraction with preconfigured endpoints",
56
"main": "dist/index.js",
67
"module": "dist/index.mjs",
@@ -67,11 +68,13 @@
6768
},
6869
"devDependencies": {
6970
"@types/node": "^20.0.0",
71+
"@types/yargs": "^17.0.33",
7072
"@typescript-eslint/eslint-plugin": "^6.0.0",
7173
"@typescript-eslint/parser": "^6.0.0",
7274
"eslint": "^8.0.0",
7375
"tsup": "^8.5.0",
74-
"typescript": "^5.0.0"
76+
"typescript": "^5.0.0",
77+
"yargs": "^18.0.0"
7578
},
7679
"engines": {
7780
"node": ">=18.0.0",

src/client.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Account, Chain, Transport } from 'viem'
22
import { createClient, http } from 'viem'
3-
import { bundlerActions } from 'viem/account-abstraction'
3+
import { bundlerActions, SmartAccount } from 'viem/account-abstraction'
44

55
import type { FreeBundlerClient, CreateFreeBundlerOptions } from './types'
66
import { DEFAULT_TRANSPORT_OPTIONS, DEFAULT_CLIENT_OPTIONS } from './config.js'
@@ -17,32 +17,32 @@ import { getFreeBundlerUrl, validateChainId } from './utils.js'
1717
* ```typescript
1818
* import { createFreeBundler } from '@etherspot/free-bundler'
1919
*
20-
* // Create bundler client for Ethereum Sepolia
20+
* Create bundler client for Ethereum Sepolia
2121
* const bundlerClient = createFreeBundler(11155111)
2222
*
23-
* // Create bundler client for Polygon with custom transport options
23+
* Create bundler client for Polygon with custom transport options
2424
* const bundlerClient = createFreeBundler(137, {
2525
* transport: {
2626
* timeout: 10000,
2727
* retryCount: 3
2828
* }
2929
* })
3030
*
31-
* // Use the bundler client
31+
* Use the bundler client
3232
* const userOperation = await bundlerClient.sendUserOperation({
3333
* userOperation: {
3434
* sender: '0x...',
3535
* nonce: 0n,
3636
* callData: '0x...',
37-
* // ... other fields
37+
* ... other fields
3838
* },
3939
* entryPoint: '0x...'
4040
* })
4141
* ```
4242
*/
4343
export function createFreeBundler<
4444
chain extends Chain | undefined = undefined,
45-
account extends Account | undefined = undefined,
45+
account extends SmartAccount | undefined = undefined,
4646
>(
4747
chainId: number,
4848
options: CreateFreeBundlerOptions<chain, account> = {}
@@ -71,6 +71,5 @@ export function createFreeBundler<
7171
})
7272

7373
// Extend with bundler actions
74-
// @ts-ignore - bundlerActions type compatibility issue
7574
return client.extend(bundlerActions) as FreeBundlerClient<Transport, chain, account>
7675
}

src/config.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,23 @@ import type { BundlerConfig } from './types'
55
*/
66
export const FREE_BUNDLER_URLS: Record<number, string> = {
77
// Ethereum Mainnet
8-
1: 'https://bundler.etherspot.io/1/',
8+
1: 'https://bundler.etherspot.io/1',
99

1010
// Ethereum Sepolia
11-
11155111: 'https://bundler.etherspot.io/11155111/',
11+
11155111: 'https://bundler.etherspot.io/11155111',
1212

1313

1414
// Arbitrum One
15-
42161: 'https://bundler.etherspot.io/42161/',
15+
42161: 'https://bundler.etherspot.io/42161',
1616

1717
// Arbitrum Sepolia
18-
421614: 'https://bundler.etherspot.io/421614/',
18+
421614: 'https://bundler.etherspot.io/421614',
1919

2020
// Optimism Mainnet
21-
10: 'https://bundler.etherspot.io/10/',
21+
10: 'https://bundler.etherspot.io/10',
2222

2323
// Optimism Sepolia
24-
11155420: 'https://bundler.etherspot.io/11155420/',
24+
11155420: 'https://bundler.etherspot.io/11155420',
2525

2626
} as const
2727

0 commit comments

Comments
 (0)