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

adding base sepolia support for wagmi permission tests #2851

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { Button, Flex, Stack, Text } from '@chakra-ui/react'
import { useAccount } from 'wagmi'
import { useReadContract } from 'wagmi'
import { useState } from 'react'
import { useChakraToast } from '../Toast'
import { encodeFunctionData, parseEther } from 'viem'
import { abi as donutContractAbi, address as donutContractaddress } from '../../utils/DonutContract'
import { sepolia } from 'viem/chains'
import { useLocalEcdsaKey } from '../../context/LocalEcdsaKeyContext'
import { useERC7715Permissions } from '../../hooks/useERC7715Permissions'
import { executeActionsWithECDSAAndCosignerPermissions } from '../../utils/ERC7715Utils'

export function WagmiPurchaseDonutAsyncPermissionsTest() {
const { privateKey } = useLocalEcdsaKey()

const { chain } = useAccount()
const { grantedPermissions, pci } = useERC7715Permissions()

const {
Expand All @@ -32,6 +32,9 @@ export function WagmiPurchaseDonutAsyncPermissionsTest() {
async function onPurchaseDonutWithPermissions() {
setTransactionPending(true)
try {
if (!chain) {
throw new Error(`Account connected chain not available`)
}
if (!privateKey) {
throw new Error(`Unable to get dApp private key`)
}
Expand All @@ -55,7 +58,7 @@ export function WagmiPurchaseDonutAsyncPermissionsTest() {
]
const txHash = await executeActionsWithECDSAAndCosignerPermissions({
actions: purchaseDonutCallDataExecution,
chain: sepolia,
chain,
ecdsaPrivateKey: privateKey as `0x${string}`,
permissions: grantedPermissions,
pci
Expand Down
2 changes: 1 addition & 1 deletion apps/laboratory/src/utils/DonutContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,4 @@ export const abi = [
}
]

export const address = '0xfcfCFD8D9f4A23D8DD11b03b212B69262A3ba1b8'
export const address = '0x2E65BAfA07238666c3b239E94F32DaD3cDD6498D'
150 changes: 51 additions & 99 deletions apps/laboratory/src/utils/ERC7715Utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import type { GrantPermissionsParameters, GrantPermissionsReturnType } from 'viem/experimental'
import { abi as donutContractAbi, address as donutContractAddress } from './DonutContract'
import { encodeAbiParameters, hashMessage, parseEther, type Chain } from 'viem'
import { WalletConnectCosigner } from './WalletConnectCosignerUtils'
import { buildUserOp, type Call, type FillUserOpResponse } from './UserOpBuilderServiceUtils'
import { buildUserOp, sendUserOp, type Call } from './UserOpBuilderServiceUtils'
import { signMessage } from 'viem/accounts'
import { bigIntReplacer } from './CommonUtils'
import { sign as signWithPasskey } from 'webauthn-p256'
Expand All @@ -26,41 +25,14 @@ export function getPurchaseDonutPermissions(): GrantPermissionsParameters {
target: donutContractAddress,
abi: donutContractAbi,
valueLimit: parseEther('10').toString(),
functionName: 'function purchase()'
functionName: 'purchase(uint256)'
},
policies: []
}
]
}
}

async function prepareUserOperationWithPermissions(args: {
actions: Call[]
chain: Chain
permissions: GrantPermissionsReturnType
}): Promise<FillUserOpResponse> {
const { actions, chain, permissions } = args
if (!permissions) {
throw new Error('No permissions available')
}
const { signerData, permissionsContext } = permissions

if (!signerData?.userOpBuilder || !signerData.submitToAddress || !permissionsContext) {
throw new Error(`Invalid permissions ${JSON.stringify(permissions, bigIntReplacer)}`)
}

const filledUserOp = await buildUserOp({
account: signerData.submitToAddress,
chainId: chain.id,
calls: actions,
capabilities: {
permissions: { context: permissionsContext as `0x${string}` }
}
})

return filledUserOp
}

async function signUserOperationWithPasskey(args: {
passkeyId: string
userOpHash: `0x${string}`
Expand Down Expand Up @@ -95,20 +67,6 @@ async function signUserOperationWithPasskey(args: {
return passkeySignature
}

async function signUserOperationWithECDSAKey(args: {
ecdsaPrivateKey: `0x${string}`
userOpHash: `0x${string}`
}): Promise<`0x${string}`> {
const { ecdsaPrivateKey, userOpHash } = args

const dappSignatureOnUserOp = await signMessage({
privateKey: ecdsaPrivateKey,
message: { raw: userOpHash }
})

return dappSignatureOnUserOp
}

export async function executeActionsWithPasskeyAndCosignerPermissions(args: {
actions: Call[]
passkeyId: string
Expand All @@ -117,19 +75,26 @@ export async function executeActionsWithPasskeyAndCosignerPermissions(args: {
pci: string
}): Promise<`0x${string}`> {
const { actions, passkeyId, chain, permissions, pci } = args
const accountAddress = permissions?.signerData?.submitToAddress
if (!accountAddress) {
throw new Error(`Unable to get account details from granted permission`)
}

if (!pci) {
throw new Error('No WC_COSIGNER PCI data available')
}
const caip10Address = `eip155:${chain?.id}:${accountAddress}`
const filledUserOp = await prepareUserOperationWithPermissions({
actions,
chain,
permissions
if (!permissions) {
throw new Error('No permissions available')
}
const { signerData, permissionsContext } = permissions

if (!signerData?.userOpBuilder || !signerData.submitToAddress || !permissionsContext) {
throw new Error(`Invalid permissions ${JSON.stringify(permissions, bigIntReplacer)}`)
}
const accountAddress = signerData.submitToAddress

const filledUserOp = await buildUserOp({
account: accountAddress,
chainId: chain.id,
calls: actions,
capabilities: {
permissions: { context: permissionsContext as `0x${string}` }
}
})
const userOp = filledUserOp.userOp
const signature = await signUserOperationWithPasskey({
Expand All @@ -138,25 +103,14 @@ export async function executeActionsWithPasskeyAndCosignerPermissions(args: {
})

userOp.signature = signature

const walletConnectCosigner = new WalletConnectCosigner()
const cosignResponse = await walletConnectCosigner.coSignUserOperation(caip10Address, {
const sendUserOpResponse = await sendUserOp({
userOp,
pci,
userOp: {
...userOp,
callData: userOp.callData,
callGasLimit: BigInt(userOp.callGasLimit),
nonce: BigInt(userOp.nonce),
preVerificationGas: BigInt(userOp.preVerificationGas),
verificationGasLimit: BigInt(userOp.verificationGasLimit),
sender: userOp.sender,
signature: userOp.signature,
maxFeePerGas: BigInt(userOp.maxFeePerGas),
maxPriorityFeePerGas: BigInt(userOp.maxPriorityFeePerGas)
}
chainId: chain.id,
permissionsContext: permissionsContext as `0x${string}`
})

return cosignResponse.receipt as `0x${string}`
return sendUserOpResponse.userOpId
}

export async function executeActionsWithECDSAAndCosignerPermissions(args: {
Expand All @@ -167,44 +121,42 @@ export async function executeActionsWithECDSAAndCosignerPermissions(args: {
pci: string
}): Promise<`0x${string}`> {
const { ecdsaPrivateKey, actions, chain, permissions, pci } = args
const accountAddress = permissions?.signerData?.submitToAddress
if (!accountAddress) {
throw new Error(`Unable to get account details from granted permission`)
}

if (!pci) {
throw new Error('No WC_COSIGNER PCI data available')
}
const caip10Address = `eip155:${chain?.id}:${accountAddress}`
const filledUserOp = await prepareUserOperationWithPermissions({
actions,
chain,
permissions
if (!permissions) {
throw new Error('No permissions available')
}
const { signerData, permissionsContext } = permissions

if (!signerData?.submitToAddress || !permissionsContext) {
throw new Error(`Invalid permissions ${JSON.stringify(permissions, bigIntReplacer)}`)
}
const accountAddress = signerData.submitToAddress

const filledUserOp = await buildUserOp({
account: accountAddress,
chainId: chain.id,
calls: actions,
capabilities: {
permissions: { context: permissionsContext as `0x${string}` }
}
})

const userOp = filledUserOp.userOp

const dappSignature = await signUserOperationWithECDSAKey({
ecdsaPrivateKey,
userOpHash: filledUserOp.hash
const dappSignature = await signMessage({
privateKey: ecdsaPrivateKey,
message: { raw: filledUserOp.hash }
})

userOp.signature = dappSignature
const walletConnectCosigner = new WalletConnectCosigner()
const cosignResponse = await walletConnectCosigner.coSignUserOperation(caip10Address, {

const sendUserOpResponse = await sendUserOp({
userOp,
pci,
userOp: {
...userOp,
callData: userOp.callData,
callGasLimit: BigInt(userOp.callGasLimit),
nonce: BigInt(userOp.nonce),
preVerificationGas: BigInt(userOp.preVerificationGas),
verificationGasLimit: BigInt(userOp.verificationGasLimit),
sender: userOp.sender,
signature: userOp.signature,
maxFeePerGas: BigInt(userOp.maxFeePerGas),
maxPriorityFeePerGas: BigInt(userOp.maxPriorityFeePerGas)
}
chainId: chain.id,
permissionsContext: permissionsContext as `0x${string}`
})

return cosignResponse.receipt as `0x${string}`
return sendUserOpResponse.userOpId
}
82 changes: 44 additions & 38 deletions apps/laboratory/src/utils/UserOpBuilderServiceUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,37 @@ import { USEROP_BUILDER_SERVICE_BASE_URL } from './ConstantsUtil'

export type Call = { to: Address; value: bigint; data: Hex }

export type BuildUserOpRequestArguments = {
account: Address
chainId: number
calls: Call[]
capabilities: {
paymasterService?: { url: string }
permissions?: { context: Hex }
}
}
/**
* UserOperation v0.7
*/
export type UserOperation = {
export type UserOperationWithBigIntAsHex = {
sender: Address
nonce: bigint
factory?: Address
factoryData?: Hex
nonce: Hex
factory: Address | undefined
factoryData: Hex | undefined
callData: Hex
callGasLimit: bigint
verificationGasLimit: bigint
preVerificationGas: bigint
maxFeePerGas: bigint
maxPriorityFeePerGas: bigint
paymaster?: Address
paymasterVerificationGasLimit?: bigint
paymasterPostOpGasLimit?: bigint
paymasterData?: Hex
callGasLimit: Hex
verificationGasLimit: Hex
preVerificationGas: Hex
maxFeePerGas: Hex
maxPriorityFeePerGas: Hex
paymaster: Address | undefined
paymasterVerificationGasLimit: Hex | undefined
paymasterPostOpGasLimit: Hex | undefined
paymasterData: Hex | undefined
signature: Hex
initCode?: never
paymasterAndData?: never
}

export type FillUserOpResponse = {
userOp: UserOperation
export type BuildUserOpRequestParams = {
chainId: number
account: Address
calls: Call[]
capabilities: {
paymasterService?: { url: string }
permissions?: { context: Hex }
}
}
export type BuildUserOpResponseReturn = {
userOp: UserOperationWithBigIntAsHex
hash: Hex
}

Expand All @@ -47,14 +44,14 @@ export type ErrorResponse = {
error: string
}

export type SendUserOpWithSignatureParams = {
export type SendUserOpRequestParams = {
chainId: number
userOp: UserOperation
signature: Hex
userOp: UserOperationWithBigIntAsHex
pci?: string
permissionsContext?: Hex
}
export type SendUserOpWithSignatureResponse = {
receipt: Hex
export type SendUserOpResponseReturn = {
userOpId: Hex
}

// Define a custom error type
Expand Down Expand Up @@ -111,8 +108,13 @@ async function sendUserOpBuilderRequest<
}
}

export async function buildUserOp(args: BuildUserOpRequestArguments): Promise<FillUserOpResponse> {
const response = await sendUserOpBuilderRequest<BuildUserOpRequestArguments, FillUserOpResponse>({
export async function buildUserOp(
args: BuildUserOpRequestParams
): Promise<BuildUserOpResponseReturn> {
const response = await sendUserOpBuilderRequest<
BuildUserOpRequestParams,
BuildUserOpResponseReturn
>({
url: `${USEROP_BUILDER_SERVICE_BASE_URL}/build`,
data: args,
headers: {
Expand All @@ -124,12 +126,16 @@ export async function buildUserOp(args: BuildUserOpRequestArguments): Promise<Fi
return response
}

export async function sendUserOp(args: SendUserOpWithSignatureParams) {
export async function sendUserOp(args: SendUserOpRequestParams) {
const projectId = process.env['NEXT_PUBLIC_PROJECT_ID']
if (!projectId) {
throw new Error('NEXT_PUBLIC_PROJECT_ID is not set')
}
const response = await sendUserOpBuilderRequest<
SendUserOpWithSignatureParams,
SendUserOpWithSignatureResponse
SendUserOpRequestParams,
SendUserOpResponseReturn
>({
url: `${USEROP_BUILDER_SERVICE_BASE_URL}/sendUserOp`,
url: `${USEROP_BUILDER_SERVICE_BASE_URL}/sendUserOp?projectId=${projectId}`,
data: args,
headers: {
'Content-Type': 'application/json'
Expand Down
4 changes: 2 additions & 2 deletions apps/laboratory/src/utils/WalletConnectCosignerUtils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable max-classes-per-file */
import axios, { AxiosError } from 'axios'
import { bigIntReplacer } from './CommonUtils'
import type { UserOperation } from './UserOpBuilderServiceUtils'
import type { UserOperationWithBigIntAsHex } from './UserOpBuilderServiceUtils'
import { WC_COSIGNER_BASE_URL } from './ConstantsUtil'

// Define types for the request and response
Expand Down Expand Up @@ -54,7 +54,7 @@ type RevokePermissionRequest = {

type CoSignRequest = {
pci: string
userOp: UserOperation
userOp: UserOperationWithBigIntAsHex
}

type CoSignResponse = {
Expand Down
Loading