Skip to content

Commit b9d266f

Browse files
authored
Feat: Enable sendUserOp for userOpBuilder service (#680)
* chores:run prettier * add EncodeLib for SmartSession formatSignature * formatSignature implementation * add sendUserOp implementation * remove signature field from sendUserOp body * chores: refactor code use viem methods for encoding/decoding
1 parent 5825941 commit b9d266f

File tree

8 files changed

+388
-16
lines changed

8 files changed

+388
-16
lines changed

advanced/wallets/react-wallet-v2/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"react-dom": "17.0.2",
5555
"react-hot-toast": "^2.4.1",
5656
"react-qr-reader-es6": "2.2.1-2",
57+
"solady": "^0.0.234",
5758
"tronweb": "^4.4.0",
5859
"valtio": "1.13.2",
5960
"viem": "2.17.8",

advanced/wallets/react-wallet-v2/src/lib/smart-accounts/SmartAccountLib.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,12 @@ import {
2626
createSmartAccountClient
2727
} from 'permissionless'
2828
import { PimlicoBundlerActions, pimlicoBundlerActions } from 'permissionless/actions/pimlico'
29-
import { PIMLICO_NETWORK_NAMES, publicClientUrl, publicRPCUrl, UrlConfig } from '@/utils/SmartAccountUtil'
29+
import {
30+
PIMLICO_NETWORK_NAMES,
31+
publicClientUrl,
32+
publicRPCUrl,
33+
UrlConfig
34+
} from '@/utils/SmartAccountUtil'
3035
import { Chain } from '@/consts/smartAccounts'
3136
import { EntryPoint } from 'permissionless/types/entrypoint'
3237
import { Erc7579Actions, erc7579Actions } from 'permissionless/actions/erc7579'
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { SmartSessionMode } from '@biconomy/permission-context-builder'
2+
import { LibZip } from 'solady'
3+
import { Address, encodeAbiParameters, encodePacked, Hex } from 'viem'
4+
import { enableSessionsStructAbi } from './SmartSessionUserOpBuilder'
5+
6+
type EnableSessions = {
7+
isigner: Address
8+
isignerInitData: Hex
9+
userOpPolicies: readonly { policy: Address; initData: Hex }[]
10+
erc1271Policies: readonly { policy: Address; initData: Hex }[]
11+
actions: readonly {
12+
actionId: Hex
13+
actionPolicies: readonly { policy: Address; initData: Hex }[]
14+
}[]
15+
permissionEnableSig: Hex
16+
}
17+
18+
export function packMode(data: Hex, mode: SmartSessionMode, signerId: Hex): Hex {
19+
return encodePacked(['uint8', 'bytes32', 'bytes'], [mode, signerId, data])
20+
}
21+
22+
export function encodeUse(signerId: Hex, sig: Hex) {
23+
const data = encodeAbiParameters([{ type: 'bytes' }], [sig])
24+
const compressedData = LibZip.flzCompress(data) as Hex
25+
return packMode(compressedData, SmartSessionMode.USE, signerId)
26+
}
27+
28+
export function encodeEnable(signerId: Hex, sig: Hex, enableData: EnableSessions) {
29+
const data = encodeAbiParameters(
30+
[enableSessionsStructAbi[0], { type: 'bytes' }],
31+
[
32+
{
33+
...enableData
34+
},
35+
sig
36+
]
37+
)
38+
const compressedData = LibZip.flzCompress(data) as Hex
39+
return packMode(compressedData, SmartSessionMode.UNSAFE_ENABLE, signerId)
40+
}

advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/SafeUserOpBuilder.ts

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,28 @@ import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
22
import {
33
FillUserOpParams,
44
FillUserOpResponse,
5-
SendUserOpWithSigantureParams,
6-
SendUserOpWithSigantureResponse,
5+
SendUserOpWithSignatureParams,
6+
SendUserOpWithSignatureResponse,
77
UserOpBuilder
88
} from './UserOpBuilder'
99
import {
1010
Address,
1111
Chain,
1212
createPublicClient,
13-
GetStorageAtReturnType,
1413
Hex,
1514
http,
15+
pad,
1616
parseAbi,
1717
PublicClient,
18-
trim
18+
trim,
19+
zeroAddress
1920
} from 'viem'
2021
import { signerToSafeSmartAccount } from 'permissionless/accounts'
2122
import {
2223
createSmartAccountClient,
2324
ENTRYPOINT_ADDRESS_V07,
25+
getAccountNonce,
26+
getPackedUserOperation,
2427
getUserOperationHash
2528
} from 'permissionless'
2629
import {
@@ -31,6 +34,7 @@ import { bundlerUrl, paymasterUrl, publicClientUrl } from '@/utils/SmartAccountU
3134

3235
import { getChainById } from '@/utils/ChainUtil'
3336
import { SAFE_FALLBACK_HANDLER_STORAGE_SLOT } from '@/consts/smartAccounts'
37+
import { formatSignature } from './SmartSessionUserOpBuilder'
3438

3539
const ERC_7579_LAUNCHPAD_ADDRESS: Address = '0xEBe001b3D534B9B6E2500FB78E67a1A137f561CE'
3640

@@ -93,15 +97,33 @@ export class SafeUserOpBuilder implements UserOpBuilder {
9397
chain: this.chain,
9498
bundlerTransport,
9599
middleware: {
96-
sponsorUserOperation: paymasterClient.sponsorUserOperation, // optional
100+
sponsorUserOperation:
101+
params.capabilities.paymasterService && paymasterClient.sponsorUserOperation, // optional
97102
gasPrice: async () => (await pimlicoBundlerClient.getUserOperationGasPrice()).fast // if using pimlico bundler
98103
}
99104
})
100105
const account = smartAccountClient.account
101106

107+
const validatorAddress = (params.capabilities.permissions?.context.slice(0, 42) ||
108+
zeroAddress) as Address
109+
let nonce: bigint = await getAccountNonce(this.publicClient, {
110+
sender: this.accountAddress,
111+
entryPoint: ENTRYPOINT_ADDRESS_V07,
112+
key: BigInt(
113+
pad(validatorAddress, {
114+
dir: 'right',
115+
size: 24
116+
}) || 0
117+
)
118+
})
119+
102120
const userOp = await smartAccountClient.prepareUserOperationRequest({
103121
userOperation: {
104-
callData: await account.encodeCallData(params.calls)
122+
nonce: nonce,
123+
callData: await account.encodeCallData(params.calls),
124+
callGasLimit: BigInt('0x1E8480'),
125+
verificationGasLimit: BigInt('0x1E8480'),
126+
preVerificationGas: BigInt('0x1E8480')
105127
},
106128
account: account
107129
})
@@ -115,10 +137,48 @@ export class SafeUserOpBuilder implements UserOpBuilder {
115137
hash
116138
}
117139
}
118-
sendUserOpWithSignature(
119-
params: SendUserOpWithSigantureParams
120-
): Promise<SendUserOpWithSigantureResponse> {
121-
throw new Error('Method not implemented.')
140+
async sendUserOpWithSignature(
141+
params: SendUserOpWithSignatureParams
142+
): Promise<SendUserOpWithSignatureResponse> {
143+
const { userOp, permissionsContext } = params
144+
if (permissionsContext) {
145+
const formattedSignature = await formatSignature(this.publicClient, {
146+
signature: userOp.signature,
147+
permissionsContext,
148+
accountAddress: userOp.sender
149+
})
150+
userOp.signature = formattedSignature
151+
}
152+
const bundlerTransport = http(bundlerUrl({ chain: this.chain }), {
153+
timeout: 30000
154+
})
155+
const pimlicoBundlerClient = createPimlicoBundlerClient({
156+
chain: this.chain,
157+
transport: bundlerTransport,
158+
entryPoint: ENTRYPOINT_ADDRESS_V07
159+
})
160+
161+
const userOpHash = await pimlicoBundlerClient.sendUserOperation({
162+
userOperation: {
163+
...userOp,
164+
callData: userOp.callData,
165+
callGasLimit: BigInt(userOp.callGasLimit),
166+
nonce: BigInt(userOp.nonce),
167+
preVerificationGas: BigInt(userOp.preVerificationGas),
168+
verificationGasLimit: BigInt(userOp.verificationGasLimit),
169+
sender: userOp.sender,
170+
signature: userOp.signature,
171+
maxFeePerGas: BigInt(userOp.maxFeePerGas),
172+
maxPriorityFeePerGas: BigInt(userOp.maxPriorityFeePerGas)
173+
}
174+
})
175+
const receipt = await pimlicoBundlerClient.waitForUserOperationReceipt({
176+
hash: userOpHash
177+
})
178+
179+
return {
180+
receipt: receipt.receipt.transactionHash
181+
}
122182
}
123183

124184
private async getVersion(): Promise<string> {

0 commit comments

Comments
 (0)