-
Notifications
You must be signed in to change notification settings - Fork 9
/
tutorial-3.ts
120 lines (107 loc) · 3.6 KB
/
tutorial-3.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import "dotenv/config"
import { writeFileSync } from "fs"
import { createSmartAccountClient } from "permissionless"
import { toSafeSmartAccount } from "permissionless/accounts"
import { createPimlicoClient } from "permissionless/clients/pimlico"
import { Hex, createPublicClient, encodeFunctionData, http, parseAbiItem } from "viem"
import { entryPoint07Address } from "viem/account-abstraction"
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"
import { sepolia } from "viem/chains"
const erc20PaymasterAddress = "0x000000000041F3aFe8892B48D88b6862efe0ec8d" as const
const usdcAddress = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"
const privateKey =
(process.env.PRIVATE_KEY as Hex) ??
(() => {
const pk = generatePrivateKey()
writeFileSync(".env", `PRIVATE_KEY=${pk}`)
return pk
})()
const publicClient = createPublicClient({
chain: sepolia,
transport: http("https://rpc.ankr.com/eth_sepolia"),
})
const apiKey = process.env.PIMLICO_API_KEY // REPLACE THIS
const pimlicoUrl = `https://api.pimlico.io/v2/sepolia/rpc?apikey=${apiKey}`
const pimlicoClient = createPimlicoClient({
transport: http(pimlicoUrl),
entryPoint: {
address: entryPoint07Address,
version: "0.7",
},
})
const account = await toSafeSmartAccount( {
client: publicClient,
owner: privateKeyToAccount(privateKey),
version: "1.4.1",
setupTransactions: [
{
to: usdcAddress,
value: 0n,
data: encodeFunctionData({
abi: [parseAbiItem("function approve(address spender, uint256 amount)")],
args: [
erc20PaymasterAddress,
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn,
],
}),
},
],
})
console.log(`Smart account address: https://sepolia.etherscan.io/address/${account.address}`)
const senderUsdcBalance = await publicClient.readContract({
abi: [parseAbiItem("function balanceOf(address account) returns (uint256)")],
address: usdcAddress,
functionName: "balanceOf",
args: [account.address],
})
if (senderUsdcBalance < 1_000_000n) {
throw new Error(
`insufficient USDC balance for counterfactual wallet address ${account.address}: ${
Number(senderUsdcBalance) / 1000000
} USDC, required at least 1 USDC. Load up balance at https://faucet.circle.com/`,
)
}
console.log(`Smart account USDC balance: ${Number(senderUsdcBalance) / 1000000} USDC`)
const smartAccountClient = createSmartAccountClient({
client: publicClient,
account,
chain: sepolia,
bundlerTransport: http(pimlicoUrl),
paymaster: {
async getPaymasterData(parameters) {
const gasEstimates = await pimlicoClient.estimateUserOperationGas({
...parameters,
paymaster: erc20PaymasterAddress,
})
return {
paymaster: erc20PaymasterAddress,
paymasterData: "0x" as Hex,
paymasterPostOpGasLimit: gasEstimates.paymasterPostOpGasLimit ?? 0n,
paymasterVerificationGasLimit: gasEstimates.paymasterVerificationGasLimit ?? 0n,
}
},
async getPaymasterStubData(parameters) {
const gasEstimates = await pimlicoClient.estimateUserOperationGas({
...parameters,
paymaster: erc20PaymasterAddress
})
return {
paymaster: erc20PaymasterAddress,
paymasterData: "0x" as Hex,
paymasterPostOpGasLimit: gasEstimates.paymasterPostOpGasLimit ?? 0n,
paymasterVerificationGasLimit: gasEstimates.paymasterVerificationGasLimit ?? 0n
}
}
},
userOperation: {
estimateFeesPerGas: async () => {
return (await pimlicoClient.getUserOperationGasPrice()).fast
},
}
})
const txHash = await smartAccountClient.sendTransaction({
to: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
value: 0n,
data: "0x1234",
})
console.log(`User operation included: https://sepolia.etherscan.io/tx/${txHash}`)