Skip to content

Commit aaf5e68

Browse files
committed
Added sendCalls & getCallsStatus methods
1 parent e495fa1 commit aaf5e68

5 files changed

Lines changed: 951 additions & 3 deletions

File tree

packages/permissionless/accounts/simple/toSimpleSmartAccount.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,13 +184,16 @@ export async function toSimpleSmartAccount<
184184
owner,
185185
factoryAddress: _factoryAddress,
186186
index = BigInt(0),
187-
address,
188-
nonceKey,
189187
eip7702 = false,
188+
address = eip7702 ? owner.address : undefined,
189+
nonceKey,
190190
accountLogicAddress = "0xe6Cae83BdE06E4c305530e199D7217f42808555B"
191191
} = parameters
192192

193-
const localOwner = await toOwner({ owner })
193+
const localOwner = await toOwner({
194+
owner,
195+
address
196+
})
194197

195198
const entryPoint = parameters.entryPoint
196199
? {
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
import { zeroAddress } from "viem"
2+
import { privateKeyToAccount } from "viem/accounts"
3+
import { describe, expect } from "vitest"
4+
import { testWithRpc } from "../../../permissionless-test/src/testWithRpc"
5+
import {
6+
getCoreSmartAccounts,
7+
getPublicClient
8+
} from "../../../permissionless-test/src/utils"
9+
import { getCallsStatus } from "./getCallsStatus"
10+
import { sendCalls } from "./sendCalls"
11+
12+
const privateKey =
13+
"0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356"
14+
15+
describe.each(getCoreSmartAccounts())(
16+
"getCallsStatus $name",
17+
({
18+
getSmartAccountClient,
19+
supportsEntryPointV06,
20+
supportsEntryPointV07,
21+
supportsEntryPointV08,
22+
isEip7702Compliant
23+
}) => {
24+
testWithRpc.skipIf(!supportsEntryPointV06)(
25+
"getCallsStatus_v06",
26+
async ({ rpc }) => {
27+
const { anvilRpc } = rpc
28+
29+
const smartClient = await getSmartAccountClient({
30+
entryPoint: {
31+
version: "0.6"
32+
},
33+
...rpc
34+
})
35+
36+
const userOpHash = await sendCalls(smartClient, {
37+
calls: [
38+
{
39+
to: zeroAddress,
40+
data: "0x",
41+
value: 0n
42+
}
43+
]
44+
})
45+
46+
expect(userOpHash).toBeTruthy()
47+
48+
const publicClient = getPublicClient(anvilRpc)
49+
50+
// Wait for the transaction to be mined
51+
await new Promise((resolve) => setTimeout(resolve, 2000))
52+
53+
const status = await getCallsStatus(smartClient, {
54+
id: userOpHash
55+
})
56+
57+
expect(status).toBeTruthy()
58+
expect(status.id).toBe(userOpHash)
59+
expect(status.version).toBe("1.0")
60+
expect(status.chainId).toBe(smartClient.chain.id)
61+
expect(status.atomic).toBe(true)
62+
expect(status.status).toBe("success")
63+
expect(status.statusCode).toBe(200)
64+
expect(status.receipts).toBeDefined()
65+
expect(status.receipts?.length).toBeGreaterThan(0)
66+
expect(status.receipts?.[0].status).toBe("success")
67+
}
68+
)
69+
70+
testWithRpc.skipIf(!supportsEntryPointV06)(
71+
"getCallsStatus_v06 pending status",
72+
async ({ rpc }) => {
73+
const smartClient = await getSmartAccountClient({
74+
entryPoint: {
75+
version: "0.6"
76+
},
77+
...rpc
78+
})
79+
80+
const userOpHash = await sendCalls(smartClient, {
81+
calls: [
82+
{
83+
to: zeroAddress,
84+
data: "0x",
85+
value: 0n
86+
}
87+
]
88+
})
89+
90+
expect(userOpHash).toBeTruthy()
91+
92+
// Check status immediately (should be pending)
93+
const status = await getCallsStatus(smartClient, {
94+
id: userOpHash
95+
})
96+
97+
expect(status).toBeTruthy()
98+
expect(status.id).toBe(userOpHash)
99+
expect(status.version).toBe("1.0")
100+
expect(status.chainId).toBe(smartClient.chain.id)
101+
expect(status.atomic).toBe(true)
102+
expect(["pending", "success"]).toContain(status.status)
103+
expect([100, 200]).toContain(status.statusCode)
104+
}
105+
)
106+
107+
testWithRpc.skipIf(!supportsEntryPointV07)(
108+
"getCallsStatus_v07",
109+
async ({ rpc }) => {
110+
const { anvilRpc } = rpc
111+
112+
const privateKeyAccount = privateKeyToAccount(privateKey)
113+
114+
const smartClient = await getSmartAccountClient({
115+
entryPoint: {
116+
version: "0.7"
117+
},
118+
privateKey,
119+
...rpc
120+
})
121+
122+
const publicClient = getPublicClient(anvilRpc)
123+
124+
const userOpHash = await sendCalls(smartClient, {
125+
calls: [
126+
{
127+
to: zeroAddress,
128+
data: "0x",
129+
value: 0n
130+
}
131+
],
132+
authorization: isEip7702Compliant
133+
? await privateKeyAccount.signAuthorization({
134+
address: (smartClient.account as any)
135+
.implementation,
136+
chainId: smartClient.chain.id,
137+
nonce: await publicClient.getTransactionCount({
138+
address: smartClient.account.address
139+
})
140+
})
141+
: undefined
142+
})
143+
144+
expect(userOpHash).toBeTruthy()
145+
146+
// Wait for the transaction to be mined
147+
await new Promise((resolve) => setTimeout(resolve, 2000))
148+
149+
const status = await getCallsStatus(smartClient, {
150+
id: userOpHash
151+
})
152+
153+
expect(status).toBeTruthy()
154+
expect(status.id).toBe(userOpHash)
155+
expect(status.version).toBe("1.0")
156+
expect(status.chainId).toBe(smartClient.chain.id)
157+
expect(status.atomic).toBe(true)
158+
expect(status.status).toBe("success")
159+
expect(status.statusCode).toBe(200)
160+
expect(status.receipts).toBeDefined()
161+
expect(status.receipts?.length).toBeGreaterThan(0)
162+
expect(status.receipts?.[0].status).toBe("success")
163+
}
164+
)
165+
166+
testWithRpc.skipIf(!supportsEntryPointV08)(
167+
"getCallsStatus_v08",
168+
async ({ rpc }) => {
169+
const { anvilRpc } = rpc
170+
171+
const privateKeyAccount = privateKeyToAccount(privateKey)
172+
173+
const smartClient = await getSmartAccountClient({
174+
entryPoint: {
175+
version: "0.8"
176+
},
177+
privateKey,
178+
...rpc
179+
})
180+
181+
const publicClient = getPublicClient(anvilRpc)
182+
183+
const authorization = isEip7702Compliant
184+
? await privateKeyAccount.signAuthorization({
185+
address: (smartClient.account as any).implementation,
186+
chainId: smartClient.chain.id,
187+
nonce: await publicClient.getTransactionCount({
188+
address: smartClient.account.address
189+
})
190+
})
191+
: undefined
192+
193+
const userOpHash = await sendCalls(smartClient, {
194+
calls: [
195+
{
196+
to: zeroAddress,
197+
data: "0x",
198+
value: 0n
199+
}
200+
],
201+
authorization
202+
})
203+
204+
expect(userOpHash).toBeTruthy()
205+
206+
// Wait for the transaction to be mined
207+
await new Promise((resolve) => setTimeout(resolve, 2000))
208+
209+
const status = await getCallsStatus(smartClient, {
210+
id: userOpHash
211+
})
212+
213+
expect(status).toBeTruthy()
214+
expect(status.id).toBe(userOpHash)
215+
expect(status.version).toBe("1.0")
216+
expect(status.chainId).toBe(smartClient.chain.id)
217+
expect(status.atomic).toBe(true)
218+
expect(status.status).toBe("success")
219+
expect(status.statusCode).toBe(200)
220+
expect(status.receipts).toBeDefined()
221+
expect(status.receipts?.length).toBeGreaterThan(0)
222+
expect(status.receipts?.[0].status).toBe("success")
223+
}
224+
)
225+
226+
testWithRpc.skipIf(!supportsEntryPointV06)(
227+
"getCallsStatus_v06 with invalid hash",
228+
async ({ rpc }) => {
229+
const smartClient = await getSmartAccountClient({
230+
entryPoint: {
231+
version: "0.6"
232+
},
233+
...rpc
234+
})
235+
236+
const invalidHash =
237+
"0x1234567890123456789012345678901234567890123456789012345678901234"
238+
239+
const status = await getCallsStatus(smartClient, {
240+
id: invalidHash
241+
})
242+
243+
expect(status).toBeTruthy()
244+
expect(status.id).toBe(invalidHash)
245+
expect(status.version).toBe("1.0")
246+
expect(status.chainId).toBe(smartClient.chain.id)
247+
expect(status.atomic).toBe(true)
248+
expect(status.status).toBe("pending")
249+
expect(status.statusCode).toBe(100)
250+
expect(status.receipts).toBeUndefined()
251+
}
252+
)
253+
}
254+
)
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import type {
2+
Chain,
3+
Client,
4+
GetCallsStatusParameters,
5+
GetCallsStatusReturnType,
6+
Transport
7+
} from "viem"
8+
import {
9+
type SmartAccount,
10+
getUserOperationReceipt
11+
} from "viem/account-abstraction"
12+
import { getChainId } from "viem/actions"
13+
import { getAction } from "viem/utils"
14+
import { AccountNotFoundError } from "../../errors"
15+
16+
const getStatus = (_status: number) => {
17+
const [status, statusCode] = (() => {
18+
const statusCode = _status
19+
if (statusCode >= 100 && statusCode < 200)
20+
return ["pending", statusCode] as const
21+
if (statusCode >= 200 && statusCode < 300)
22+
return ["success", statusCode] as const
23+
if (statusCode >= 300 && statusCode < 700)
24+
return ["failure", statusCode] as const
25+
// @ts-expect-error: for backwards compatibility
26+
if (statusCode === "CONFIRMED") return ["success", 200] as const
27+
// @ts-expect-error: for backwards compatibility
28+
if (statusCode === "PENDING") return ["pending", 100] as const
29+
return [undefined, statusCode]
30+
})()
31+
32+
return [status, statusCode] as const
33+
}
34+
35+
export async function getCallsStatus<
36+
account extends SmartAccount | undefined,
37+
chain extends Chain | undefined
38+
>(
39+
client: Client<Transport, chain, account>,
40+
args: GetCallsStatusParameters
41+
): Promise<GetCallsStatusReturnType> {
42+
const userOperationHash = args.id as `0x${string}`
43+
44+
const chainId = getAction(client, getChainId, "getChainId")(client)
45+
46+
try {
47+
const receipt = await getAction(
48+
client,
49+
getUserOperationReceipt,
50+
"getUserOperationReceipt"
51+
)({
52+
hash: args.id as `0x${string}`
53+
})
54+
55+
const userOpStatus = receipt.success
56+
const [status, statusCode] = getStatus(userOpStatus ? 200 : 500)
57+
58+
return {
59+
id: userOperationHash,
60+
version: "1.0",
61+
chainId: await chainId,
62+
status,
63+
statusCode,
64+
atomic: true,
65+
receipts: [
66+
{
67+
status: receipt.receipt.status,
68+
logs: receipt.receipt.logs,
69+
blockHash: receipt.receipt.blockHash,
70+
blockNumber: receipt.receipt.blockNumber,
71+
gasUsed: receipt.receipt.gasUsed,
72+
transactionHash: receipt.receipt.transactionHash
73+
}
74+
]
75+
}
76+
} catch {
77+
const [status, statusCode] = getStatus(100)
78+
79+
return {
80+
id: userOperationHash,
81+
version: "1.0",
82+
chainId: await chainId,
83+
atomic: true,
84+
status,
85+
statusCode
86+
}
87+
}
88+
}

0 commit comments

Comments
 (0)