diff --git a/advanced/wallets/react-wallet-v2/package.json b/advanced/wallets/react-wallet-v2/package.json index 8dc2f3223..4193c608d 100644 --- a/advanced/wallets/react-wallet-v2/package.json +++ b/advanced/wallets/react-wallet-v2/package.json @@ -10,7 +10,6 @@ "prettier:write": "prettier --write '**/*.{js,ts,jsx,tsx}'" }, "dependencies": { - "@biconomy/permission-context-builder": "^1.1.2", "@cosmjs/amino": "0.32.3", "@cosmjs/encoding": "0.32.3", "@cosmjs/proto-signing": "0.32.3", @@ -29,7 +28,7 @@ "@nextui-org/react": "1.0.8-beta.5", "@polkadot/keyring": "^10.1.2", "@polkadot/types": "^9.3.3", - "@rhinestone/module-sdk": "0.1.13", + "@rhinestone/module-sdk": "0.1.15", "@solana/web3.js": "1.89.2", "@taquito/signer": "^15.1.0", "@taquito/taquito": "^15.1.0", diff --git a/advanced/wallets/react-wallet-v2/src/data/EIP7715Data.ts b/advanced/wallets/react-wallet-v2/src/data/EIP7715Data.ts index bda174d30..61652e732 100644 --- a/advanced/wallets/react-wallet-v2/src/data/EIP7715Data.ts +++ b/advanced/wallets/react-wallet-v2/src/data/EIP7715Data.ts @@ -1,6 +1,82 @@ +import { Address } from 'viem' + /** * EIP7715Method */ export const EIP7715_METHOD = { WALLET_GRANT_PERMISSIONS: 'wallet_grantPermissions' } + +// `data` is not necessary for this signer type as the wallet is both the signer and grantor of these permissions +export type WalletSigner = { + type: 'wallet' + data: {} +} + +// A signer representing a single key. +// `id` is a did:key identifier and can therefore represent both Secp256k1 or Secp256r1 keys, among other key types. +export type KeySigner = { + type: 'key' + data: { + id: string + } +} + +// A signer representing a multisig signer. +// Each element of `ids` is a did:key identifier just like the `key` signer. +export type MultiKeySigner = { + type: 'keys' + data: { + ids: string[] + address?: Address + } +} + +// An account that can be granted with permissions as in ERC-7710. +export type AccountSigner = { + type: 'account' + data: { + id: `0x${string}` + } +} + +export enum SignerType { + EOA, + PASSKEY +} + +export type Signer = { + type: SignerType + data: string +} + +export type Permission = { + type: PermissionType + policies: Policy[] + required: boolean + data: any +} +export type Policy = { + type: PolicyType + data: any +} +export type PermissionType = + | 'native-token-transfer' + | 'erc20-token-transfer' + | 'erc721-token-transfer' + | 'erc1155-token-transfer' + | { + custom: any + } +export type PolicyType = + | 'gas-limit' + | 'call-limit' + | 'rate-limit' + | 'spent-limit' + | 'value-limit' + | 'time-frame' + | 'uni-action' + | 'simpler-signer' + | { + custom: any + } diff --git a/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/SafeSmartAccountLib.ts b/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/SafeSmartAccountLib.ts index cae9866d1..48b0d3bd9 100644 --- a/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/SafeSmartAccountLib.ts +++ b/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/SafeSmartAccountLib.ts @@ -11,18 +11,16 @@ import { Hex, WalletGrantPermissionsParameters, createWalletClient, + encodeFunctionData, http, type WalletGrantPermissionsReturnType } from 'viem' import { MultiKeySigner } from 'viem/_types/experimental/erc7715/types/signer' -import { - mockValidator, - Permission, - userOperationBuilderAddress -} from '@biconomy/permission-context-builder' import { ModuleType } from 'permissionless/actions/erc7579' -import { getContext } from './builders/ContextBuilderUtil' -const { SMART_SESSIONS_ADDRESS } = +import { MOCK_VALIDATOR_ADDRESSES } from './builders/SmartSessionUtil' +import { Permission } from '@/data/EIP7715Data' +import { getSmartSessionContext } from './builders/ContextBuilderUtil' +const { SMART_SESSIONS_ADDRESS, getAccount } = require('@rhinestone/module-sdk') as typeof import('@rhinestone/module-sdk') export class SafeSmartAccountLib extends SmartAccountLib { protected ERC_7579_LAUNCHPAD_ADDRESS: Address = '0xEBe001b3D534B9B6E2500FB78E67a1A137f561CE' @@ -75,11 +73,15 @@ export class SafeSmartAccountLib extends SmartAccountLib { console.log('walletClient chainId:', walletClient.chain.id) let permissionContext = '0x' try { - permissionContext = await getContext(walletClient, { + permissionContext = await getSmartSessionContext({ + walletClient, + account: getAccount({ + address: this.client.account.address, + type: 'safe' + }), permissions: [...grantPermissionsRequestParameters.permissions] as unknown as Permission[], expiry: grantPermissionsRequestParameters.expiry, - signer: grantPermissionsRequestParameters.signer as MultiKeySigner, - smartAccountAddress: this.client.account.address + signer: grantPermissionsRequestParameters.signer as MultiKeySigner }) } catch (error) { console.error(`Error getting permission context: ${error}`) @@ -92,7 +94,6 @@ export class SafeSmartAccountLib extends SmartAccountLib { grantedPermissions: grantPermissionsRequestParameters.permissions, expiry: grantPermissionsRequestParameters.expiry, signerData: { - userOpBuilder: userOperationBuilderAddress[this.chain.id] as Address, submitToAddress: this.client.account.address } } as WalletGrantPermissionsReturnType @@ -114,15 +115,17 @@ export class SafeSmartAccountLib extends SmartAccountLib { this.publicClient, this.client.account.address ) + // TODO: check if account trust the attesters of the module + await this.trustAttesters() let smartSessionValidatorInstalled = false let mockValidatorInstalled = false console.log(`SmartSession Address: ${SMART_SESSIONS_ADDRESS}`) - console.log(`mockValidator Address: ${mockValidator[this.chain.id]}`) + console.log(`mockValidator Address: ${MOCK_VALIDATOR_ADDRESSES[this.chain.id]}`) if (isAccountDeployed) { ;[smartSessionValidatorInstalled, mockValidatorInstalled] = await Promise.all([ this.isValidatorModuleInstalled(SMART_SESSIONS_ADDRESS as Address), - this.isValidatorModuleInstalled(mockValidator[this.chain.id] as Address) + this.isValidatorModuleInstalled(MOCK_VALIDATOR_ADDRESSES[this.chain.id] as Address) ]) } console.log({ smartSessionValidatorInstalled, mockValidatorInstalled }) @@ -150,7 +153,7 @@ export class SafeSmartAccountLib extends SmartAccountLib { if (!isAccountDeployed || !mockValidatorInstalled) { installModules.push({ - address: mockValidator[this.chain.id], + address: MOCK_VALIDATOR_ADDRESSES[this.chain.id], type: 'validator', context: '0x' }) @@ -193,4 +196,35 @@ export class SafeSmartAccountLib extends SmartAccountLib { const receipt = await this.bundlerClient.waitForUserOperationReceipt({ hash: userOpHash }) console.log(`Module installation receipt:`, receipt) } + + private async trustAttesters(): Promise { + if (!this.client?.account) { + throw new Error('Client not initialized') + } + const trustAttestersAction = { + to: '0x000000000069E2a187AEFFb852bF3cCdC95151B2' as Address, // mock-registry + value: BigInt(0), + data: encodeFunctionData({ + abi: [ + { + inputs: [ + { type: 'uint8', name: 'threshold' }, + { type: 'address[]', name: 'attesters' } + ], + name: 'trustAttesters', + type: 'function', + stateMutability: 'nonpayable', + outputs: [] + } + ], + functionName: 'trustAttesters', + args: [1, ['0xA4C777199658a41688E9488c4EcbD7a2925Cc23A']] + }) + } + + const userOpHash = await this.sendTransaction(trustAttestersAction) + console.log(`Trust Attesters userOpHash:`, userOpHash) + // const receipt = await this.bundlerClient.waitForUserOperationReceipt({ hash: userOpHash }) + // console.log(`Trust Attesters receipt:`, receipt) + } } diff --git a/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/ContextBuilderUtil.ts b/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/ContextBuilderUtil.ts index b2d442a94..09c6ae6f2 100644 --- a/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/ContextBuilderUtil.ts +++ b/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/ContextBuilderUtil.ts @@ -1,114 +1,79 @@ import { decodeDIDToPublicKey, KEY_TYPES } from '@/utils/HelperUtil' -import { mockValidator, multiKeySignerAddress, timeFramePolicyAddress } from '@biconomy/permission-context-builder' import { - type AccountType, - type Session, - type ChainSession, - type EnableSessionData, - -} from '@rhinestone/module-sdk' + MOCK_VALIDATOR_ADDRESSES, + MULTIKEY_SIGNER_ADDRESSES, + TIME_FRAME_POLICY_ADDRESSES +} from './SmartSessionUtil' +import type { Session, ChainSession, Account } from '@rhinestone/module-sdk' const { - getAccount, getPermissionId, getSessionDigest, getSessionNonce, SMART_SESSIONS_ADDRESS, - // OWNABLE_VALIDATOR_ADDRESS, encodeSmartSessionSignature, SmartSessionMode, hashChainSessions, encodeUseOrEnableSmartSessionSignature, - encodeValidationData + decodeSmartSessionSignature } = require('@rhinestone/module-sdk') as typeof import('@rhinestone/module-sdk') - -import { LibZip } from 'solady' import { - Address, createPublicClient, - decodeAbiParameters, encodeAbiParameters, encodePacked, Hex, http, + pad, parseAbiParameters, PublicClient, + slice, toBytes, toFunctionSelector, toHex, WalletClient } from 'viem' import { publicKeyToAddress } from 'viem/accounts' -import { sepolia } from 'viem/chains' +import { ENTRYPOINT_ADDRESS_V07, getAccountNonce } from 'permissionless' import { parsePublicKey } from 'webauthn-p256' +import { MultiKeySigner, Permission, Signer, SignerType } from '@/data/EIP7715Data' -const MULTIKEY_SIGNER_ADDRESS = multiKeySignerAddress[sepolia.id] as Address -export enum SignerType { - EOA, - PASSKEY -} - -export type Signer = { - type: SignerType - data: string +type GetNonceWithContextParams = { + publicClient: PublicClient + account: Account + permissionsContext: Hex } -export type MultiKeySigner = { - type: 'keys' - data: { - ids: string[] - address?: Address - } -} -export type Permission = { - type: PermissionType - policies: Policy[] - required: boolean - data: any +type GetDummySignatureParams = { + publicClient: PublicClient + permissionsContext: Hex + account: Account } -export type Policy = { - type: PolicyType - data: any +type FormatSignatureParams = { + publicClient: PublicClient + modifiedSignature: Hex + permissionsContext: Hex + account: Account } -export type PermissionType = - | 'native-token-transfer' - | 'erc20-token-transfer' - | 'erc721-token-transfer' - | 'erc1155-token-transfer' - | { - custom: any - } -export type PolicyType = - | 'gas-limit' - | 'call-limit' - | 'rate-limit' - | 'spent-limit' - | 'value-limit' - | 'time-frame' - | 'uni-action' - | 'simpler-signer' - | { - custom: any - } -export type GrantPermissionsRequestParams = { - smartAccountAddress: `0x${string}` - signer: MultiKeySigner +type GetSmartSessionContextParams = { + walletClient: WalletClient + account: Account permissions: Permission[] expiry: number + signer: MultiKeySigner } -export async function getContext( - walletClient: WalletClient, - { smartAccountAddress, permissions, expiry, signer }: GrantPermissionsRequestParams -): Promise<`0x${string}`> { +export async function getSmartSessionContext({ + walletClient, + account, + permissions, + expiry, + signer +}: GetSmartSessionContextParams): Promise<`0x${string}`> { if (walletClient.chain === undefined) { - throw new Error('Chain is undefined') + throw new Error('GetSmartSessionContextParams:Chain is undefined') } if (walletClient.account === undefined) { throw new Error('wallet account is undefined') } - const account = getAccount({ - address: smartAccountAddress, - type: 'safe' - }) + const chainId = walletClient.chain.id let signers: Signer[] = [] @@ -190,7 +155,7 @@ export async function getContext( message: { raw: permissionEnableHash } }) - return encodeSmartSessionSignature({ + const encodedSmartSessionSignature = encodeSmartSessionSignature({ mode: SmartSessionMode.ENABLE, permissionId, signature: '0xe8b94748580ca0b4993c9a1b86b5be851bfc076ff5ce3a1ff65bf16392acfcb800f9b4f1aef1555c7fce5599fffb17e7c635502154a0333ba21f3ae491839af51c', @@ -201,32 +166,38 @@ export async function getContext( sessionToEnable: session, permissionEnableSig }, - validator: mockValidator[chainId], + validator: MOCK_VALIDATOR_ADDRESSES[chainId], //smartAccountAddress, accountType: 'safe' } }) -} -export async function getDummySignature( - publicClient: PublicClient, - params: { - permissionsContext: Hex - accountAddress: Address + const smartSessionContext = encodePacked( + ['address', 'bytes'], + [SMART_SESSIONS_ADDRESS, encodedSmartSessionSignature] + ) + return smartSessionContext +} +export async function getDummySignature({ + publicClient, + permissionsContext, + account +}: GetDummySignatureParams) { + const validatorAddress = slice(permissionsContext, 0, 20) + if (validatorAddress.toLowerCase() !== SMART_SESSIONS_ADDRESS.toLowerCase()) { + throw new Error('getDummySignature:Invalid permission context') } -) { - const { permissionsContext, accountAddress } = params - const { permissionId,signature, enableSessionData } = decodeSmartSessionSignature(permissionsContext) - const account = getAccount({ - address: accountAddress, - type: 'safe' + + const smartSessionSignature = slice(permissionsContext, 20) + const { permissionId, enableSessionData } = decodeSmartSessionSignature({ + signature: smartSessionSignature, + account: account }) + if (!enableSessionData) { - throw new Error('EnableSessionData is undefined, invalid permissionsContext') + throw new Error('EnableSessionData is undefined, invalid smartSessionSignature') } - console.log("encodedSignature",signature) const signerValidatorInitData = enableSessionData?.enableSession.sessionToEnable.sessionValidatorInitData - console.log('multiKeySignerValidatorInitData', signerValidatorInitData) const signers = decodeSigners(signerValidatorInitData) console.log('signers', signers) const dummySignatures: `0x${string}`[] = [] @@ -242,7 +213,7 @@ export async function getDummySignature( } } const concatenatedDummySignature = encodeAbiParameters([{ type: 'bytes[]' }], [dummySignatures]) - console.log('concatenatedDummySignature', concatenatedDummySignature) + return encodeUseOrEnableSmartSessionSignature({ account: account, client: publicClient, @@ -251,40 +222,65 @@ export async function getDummySignature( signature: concatenatedDummySignature }) } - -export async function formatSignature( - publicClient: PublicClient, - params: { - signature: Hex - permissionsContext: Hex - accountAddress: Address +export async function formatSignature({ + publicClient, + account, + modifiedSignature, + permissionsContext +}: FormatSignatureParams) { + const validatorAddress = slice(permissionsContext, 0, 20) + if (validatorAddress.toLowerCase() !== SMART_SESSIONS_ADDRESS.toLowerCase()) { + throw new Error('formatSignature:Invalid permission context') } -) { - const { permissionsContext, signature, accountAddress } = params - const { permissionId, enableSessionData } = decodeSmartSessionSignature(permissionsContext) - const account = getAccount({ - address: accountAddress, - type: 'safe' + + const smartSessionSignature = slice(permissionsContext, 20) + const { permissionId, enableSessionData } = decodeSmartSessionSignature({ + signature: smartSessionSignature, + account: account }) + if (!enableSessionData) { - throw new Error('EnableSessionData is undefined, invalid permissionsContext') + throw new Error('EnableSessionData is undefined, invalid smartSessionSignature') } + return encodeUseOrEnableSmartSessionSignature({ account: account, client: publicClient, enableSessionData: enableSessionData, permissionId: permissionId, - signature: signature + signature: modifiedSignature + }) +} +export async function getNonce({ + publicClient, + account, + permissionsContext +}: GetNonceWithContextParams): Promise { + const chainId = await publicClient.getChainId() + const validatorAddress = slice(permissionsContext, 0, 20) + if (validatorAddress.toLowerCase() !== SMART_SESSIONS_ADDRESS.toLowerCase()) { + throw new Error('getNonce:Invalid permission context') + } + + return await getAccountNonce(publicClient, { + sender: account.address, + entryPoint: ENTRYPOINT_ADDRESS_V07, + key: BigInt( + pad(validatorAddress, { + dir: 'right', + size: 24 + }) || 0 + ) }) } function getSamplePermissions( signers: Signer[], chainId: number, - { permissions, expiry }: Omit + { permissions, expiry }: { permissions: Permission[]; expiry: number } ): Session { return { - sessionValidator: MULTIKEY_SIGNER_ADDRESS, + sessionValidator: MULTIKEY_SIGNER_ADDRESSES[chainId], sessionValidatorInitData: encodeMultiKeySignerInitData(signers), salt: toHex(toBytes('1', { size: 32 })), userOpPolicies: [], @@ -293,7 +289,7 @@ function getSamplePermissions( actionTargetSelector: toFunctionSelector(permission.data.functionName), actionPolicies: [ { - policy: timeFramePolicyAddress[chainId], + policy: TIME_FRAME_POLICY_ADDRESSES[chainId], initData: encodePacked(['uint128', 'uint128'], [BigInt(expiry), BigInt(0)]) // hardcoded for demo } ] @@ -307,7 +303,6 @@ function getSamplePermissions( function encodeMultiKeySignerInitData(signers: Signer[]): Hex { let encoded: Hex = encodePacked(['uint8'], [signers.length]) - // signer.data = decoded public key from DID for (const signer of signers) { encoded = encodePacked(['bytes', 'uint8', 'bytes'], [encoded, signer.type, signer.data as Hex]) } @@ -315,9 +310,7 @@ function encodeMultiKeySignerInitData(signers: Signer[]): Hex { return encoded } -export function decodeSigners( - encodedData: `0x${string}` -): Array<{ type: number; data: `0x${string}` }> { +function decodeSigners(encodedData: `0x${string}`): Array<{ type: number; data: `0x${string}` }> { let offset = 2 // Start after '0x' const signers: Array<{ type: number; data: `0x${string}` }> = [] @@ -349,241 +342,3 @@ export function decodeSigners( return signers } - -export const decodeSmartSessionSignature = ( - encodedData: Hex -): { - mode: Hex - permissionId: Hex - signature: Hex - enableSessionData?: EnableSessionData -} => { - const mode = `0x${encodedData.slice(2, 4)}` as Hex - const permissionId = `0x${encodedData.slice(4, 68)}` as Hex - const compressedData = `0x${encodedData.slice(68)}` as Hex - // const [mode, permissionId, compressedData] = decodeAbiParameters( - // [{ type: 'bytes1' }, { type: 'bytes32' }, { type: 'bytes' }], - // encodedData - // ) as [Hex, Hex, Hex] - - const decompressedData = LibZip.flzDecompress(compressedData) as Hex - - switch (mode) { - case SmartSessionMode.USE: - const [signature] = decodeAbiParameters([{ type: 'bytes' }], decompressedData) as [Hex] - return { mode, permissionId, signature } - - case SmartSessionMode.ENABLE: - case SmartSessionMode.UNSAFE_ENABLE: - const { enableSessionData, signature: enableSignature } = - decodeEnableSessionSignature(decompressedData) - return { mode, permissionId, signature: enableSignature, enableSessionData } - - default: - throw new Error(`Unknown mode ${mode}`) - } -} - -const decodeEnableSessionSignature = ( - encodedData: Hex -): { enableSessionData: EnableSessionData; signature: Hex } => { - const [enableSessionDataEncoded, signature] = decodeAbiParameters( - encodeEnableSessionSignatureAbi, - encodedData - ) as [any, Hex] - - const { chainDigestIndex, hashesAndChainIds, sessionToEnable, permissionEnableSig } = - enableSessionDataEncoded - - const { validator, accountType, decodedSignature } = - decodePermissionEnableSig(permissionEnableSig) - - const enableSessionData: EnableSessionData = { - enableSession: { - chainDigestIndex, - hashesAndChainIds: hashesAndChainIds.map((digest: any) => ({ - chainId: BigInt(digest.chainId), - sessionDigest: digest.sessionDigest - })), - sessionToEnable: { - sessionValidator: sessionToEnable.sessionValidator, - sessionValidatorInitData: sessionToEnable.sessionValidatorInitData, - salt: sessionToEnable.salt, - userOpPolicies: sessionToEnable.userOpPolicies.map((policy: any) => ({ - policy: policy.policy, - initData: policy.initData - })), - erc7739Policies: { - allowedERC7739Content: sessionToEnable.erc7739Policies.allowedERC7739Content, - erc1271Policies: sessionToEnable.erc7739Policies.erc1271Policies.map((policy: any) => ({ - policy: policy.policy, - initData: policy.initData - })) - }, - actions: sessionToEnable.actions.map((action: any) => ({ - actionTargetSelector: action.actionTargetSelector, - actionTarget: action.actionTarget, - actionPolicies: action.actionPolicies.map((policy: any) => ({ - policy: policy.policy, - initData: policy.initData - })) - })) - }, - permissionEnableSig: decodedSignature - }, - validator, - accountType - } - - return { enableSessionData, signature } -} - -const decodePermissionEnableSig = ( - encodedSig: Hex -): { validator: Address; accountType: AccountType; decodedSignature: Hex } => { - if (encodedSig.startsWith('0x01')) { - // Kernel type - const validator = `0x${encodedSig.slice(4, 44)}` as Address - const signature = `0x${encodedSig.slice(44)}` as Hex - // const [, validator, signature] = decodeAbiParameters( - // [{ type: 'bytes1' }, { type: 'address' }, { type: 'bytes' }], - // encodedSig - // ) as [Hex, Address, Hex] - return { validator, accountType: 'kernel', decodedSignature: signature } - } else { - // Other types (erc7579-implementation, nexus, safe) - const validator = `0x${encodedSig.slice(2, 42)}` as Address - const signature = `0x${encodedSig.slice(42)}` as Hex - // const [validator, signature] = decodeAbiParameters( - // [{ type: 'address' }, { type: 'bytes' }], - // encodedSig - // ) as [Address, Hex] - // Note: We can't determine the exact account type here, so we'll default to 'erc7579-implementation' - return { validator, accountType: 'erc7579-implementation', decodedSignature: signature } - } -} - -export const encodeEnableSessionSignatureAbi = [ - { - components: [ - { - type: 'uint8', - name: 'chainDigestIndex' - }, - { - type: 'tuple[]', - components: [ - { - internalType: 'uint64', - name: 'chainId', - type: 'uint64' - }, - { - internalType: 'bytes32', - name: 'sessionDigest', - type: 'bytes32' - } - ], - name: 'hashesAndChainIds' - }, - { - components: [ - { - internalType: 'contract ISessionValidator', - name: 'sessionValidator', - type: 'address' - }, - { - internalType: 'bytes', - name: 'sessionValidatorInitData', - type: 'bytes' - }, - { internalType: 'bytes32', name: 'salt', type: 'bytes32' }, - { - components: [ - { internalType: 'address', name: 'policy', type: 'address' }, - { internalType: 'bytes', name: 'initData', type: 'bytes' } - ], - internalType: 'struct PolicyData[]', - name: 'userOpPolicies', - type: 'tuple[]' - }, - { - components: [ - { - internalType: 'string[]', - name: 'allowedERC7739Content', - type: 'string[]' - }, - { - components: [ - { - internalType: 'address', - name: 'policy', - type: 'address' - }, - { - internalType: 'bytes', - name: 'initData', - type: 'bytes' - } - ], - internalType: 'struct PolicyData[]', - name: 'erc1271Policies', - type: 'tuple[]' - } - ], - internalType: 'struct ERC7739Data', - name: 'erc7739Policies', - type: 'tuple' - }, - { - components: [ - { - internalType: 'bytes4', - name: 'actionTargetSelector', - type: 'bytes4' - }, - { - internalType: 'address', - name: 'actionTarget', - type: 'address' - }, - { - components: [ - { - internalType: 'address', - name: 'policy', - type: 'address' - }, - { - internalType: 'bytes', - name: 'initData', - type: 'bytes' - } - ], - internalType: 'struct PolicyData[]', - name: 'actionPolicies', - type: 'tuple[]' - } - ], - internalType: 'struct ActionData[]', - name: 'actions', - type: 'tuple[]' - } - ], - internalType: 'struct Session', - name: 'sessionToEnable', - type: 'tuple' - }, - { - type: 'bytes', - name: 'permissionEnableSig' - } - ], - internalType: 'struct EnableSession', - name: 'enableSession', - type: 'tuple' - }, - { type: 'bytes' } -] as const diff --git a/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/EncodeLib.ts b/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/EncodeLib.ts deleted file mode 100644 index 52aab3751..000000000 --- a/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/EncodeLib.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { SmartSessionMode } from '@biconomy/permission-context-builder' -import { LibZip } from 'solady' -import { Address, encodeAbiParameters, encodePacked, Hex } from 'viem' -import { enableSessionsStructAbi } from './SmartSessionUserOpBuilder' - -type EnableSessions = { - isigner: Address - isignerInitData: Hex - userOpPolicies: readonly { policy: Address; initData: Hex }[] - erc1271Policies: readonly { policy: Address; initData: Hex }[] - actions: readonly { - actionId: Hex - actionPolicies: readonly { policy: Address; initData: Hex }[] - }[] - permissionEnableSig: Hex -} - -export function packMode(data: Hex, mode: SmartSessionMode, signerId: Hex): Hex { - return encodePacked(['uint8', 'bytes32', 'bytes'], [mode, signerId, data]) -} - -export function encodeUse(signerId: Hex, sig: Hex) { - const data = encodeAbiParameters([{ type: 'bytes' }], [sig]) - const compressedData = LibZip.flzCompress(data) as Hex - return packMode(compressedData, SmartSessionMode.USE, signerId) -} - -export function encodeEnable(signerId: Hex, sig: Hex, enableData: EnableSessions) { - const data = encodeAbiParameters( - [enableSessionsStructAbi[0], { type: 'bytes' }], - [ - { - ...enableData - }, - sig - ] - ) - const compressedData = LibZip.flzCompress(data) as Hex - return packMode(compressedData, SmartSessionMode.UNSAFE_ENABLE, signerId) -} diff --git a/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/SafeUserOpBuilder.ts b/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/SafeUserOpBuilder.ts index 01a29d82c..daf568705 100644 --- a/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/SafeUserOpBuilder.ts +++ b/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/SafeUserOpBuilder.ts @@ -1,28 +1,27 @@ -import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts' +import { generatePrivateKey, privateKeyToAccount, signMessage } from 'viem/accounts' import { - FillUserOpParams, - FillUserOpResponse, - SendUserOpWithSignatureParams, - SendUserOpWithSignatureResponse, + BuildUserOpRequestParams, + BuildUserOpResponseReturn, + SendUserOpRequestParams, + SendUserOpResponseReturn, UserOpBuilder } from './UserOpBuilder' import { Address, Chain, createPublicClient, + encodeAbiParameters, Hex, http, - pad, parseAbi, PublicClient, - trim, - zeroAddress + toHex, + trim } from 'viem' import { signerToSafeSmartAccount } from 'permissionless/accounts' import { createSmartAccountClient, ENTRYPOINT_ADDRESS_V07, - getAccountNonce, getUserOperationHash } from 'permissionless' import { @@ -30,13 +29,11 @@ import { createPimlicoPaymasterClient } from 'permissionless/clients/pimlico' import { bundlerUrl, paymasterUrl, publicClientUrl } from '@/utils/SmartAccountUtil' - import { getChainById } from '@/utils/ChainUtil' import { SAFE_FALLBACK_HANDLER_STORAGE_SLOT } from '@/consts/smartAccounts' -import { formatSignature, getDummySignature } from './ContextBuilderUtil' -const { - SMART_SESSIONS_ADDRESS, -} = require('@rhinestone/module-sdk') as typeof import('@rhinestone/module-sdk') +import { formatSignature, getDummySignature, getNonce } from './ContextBuilderUtil' +import { WalletConnectCosigner } from './WalletConnectCosignerUtils' +const { getAccount } = require('@rhinestone/module-sdk') as typeof import('@rhinestone/module-sdk') const ERC_7579_LAUNCHPAD_ADDRESS: Address = '0xEBe001b3D534B9B6E2500FB78E67a1A137f561CE' @@ -53,7 +50,7 @@ export class SafeUserOpBuilder implements UserOpBuilder { this.accountAddress = accountAddress } - async fillUserOp(params: FillUserOpParams): Promise { + async fillUserOp(params: BuildUserOpRequestParams): Promise { const privateKey = generatePrivateKey() const signer = privateKeyToAccount(privateKey) @@ -99,62 +96,96 @@ export class SafeUserOpBuilder implements UserOpBuilder { chain: this.chain, bundlerTransport, middleware: { - sponsorUserOperation:paymasterClient.sponsorUserOperation, - // params.capabilities.paymasterService && paymasterClient.sponsorUserOperation, // optional + sponsorUserOperation: paymasterClient.sponsorUserOperation, + // params.capabilities.paymasterService && paymasterClient.sponsorUserOperation, // optional gasPrice: async () => (await pimlicoBundlerClient.getUserOperationGasPrice()).fast // if using pimlico bundler } }) - const account = smartAccountClient.account - const nonceKey = SMART_SESSIONS_ADDRESS - // const validatorAddress = (params.capabilities.permissions?.context.slice(0, 42) || - // zeroAddress) as Address - const validatorAddress = SMART_SESSIONS_ADDRESS as Address - let nonce: bigint = await getAccountNonce(this.publicClient, { - sender: this.accountAddress, - entryPoint: ENTRYPOINT_ADDRESS_V07, - key: BigInt( - pad(validatorAddress, { - dir: 'right', - size: 24 - }) || 0 - ) + + const account = getAccount({ + address: smartAccountClient.account.address, + type: 'safe' + }) + + let nonce: bigint = await getNonce({ + publicClient: this.publicClient, + account, + permissionsContext: params.capabilities.permissions?.context! }) - const signature = await getDummySignature(this.publicClient, { - permissionsContext: params.capabilities.permissions?.context!, - accountAddress: this.accountAddress + const callData = await smartAccountClient.account.encodeCallData(params.calls) + + const dummySignature = await getDummySignature({ + publicClient: this.publicClient, + account, + permissionsContext: params.capabilities.permissions?.context! }) - console.log('dummySignature', signature) + const userOp = await smartAccountClient.prepareUserOperationRequest({ userOperation: { nonce: nonce, - callData: await account.encodeCallData(params.calls), - // callGasLimit: BigInt('0x1E8480'), - // verificationGasLimit: BigInt('0x1E8480'), - // preVerificationGas: BigInt('0x1E8480'), - signature: signature + callData: callData, + signature: dummySignature }, - account: account + account: smartAccountClient.account }) + const hash = getUserOperationHash({ userOperation: userOp, chainId: this.chain.id, entryPoint: ENTRYPOINT_ADDRESS_V07 }) + return { - userOp, + userOp: { + ...userOp, + nonce: toHex(userOp.nonce), + callGasLimit: toHex(userOp.callGasLimit), + verificationGasLimit: toHex(userOp.verificationGasLimit), + preVerificationGas: toHex(userOp.preVerificationGas), + maxFeePerGas: toHex(userOp.maxFeePerGas), + maxPriorityFeePerGas: toHex(userOp.maxPriorityFeePerGas), + paymasterPostOpGasLimit: userOp.paymasterPostOpGasLimit + ? toHex(userOp.paymasterPostOpGasLimit) + : undefined, + paymasterVerificationGasLimit: userOp.paymasterVerificationGasLimit + ? toHex(userOp.paymasterVerificationGasLimit) + : undefined, + factory: userOp.factory, + factoryData: userOp.factoryData, + paymaster: userOp.paymaster, + paymasterData: userOp.paymasterData + }, hash } } + async sendUserOpWithSignature( - params: SendUserOpWithSignatureParams - ): Promise { - const { userOp, permissionsContext } = params + projectId: string, + params: SendUserOpRequestParams + ): Promise { + const { chainId, userOp, permissionsContext, pci } = params + if (pci && projectId) { + const walletConnectCosigner = new WalletConnectCosigner(projectId) + const caip10AccountAddress = `eip155:${chainId}:${userOp.sender}` + const cosignResponse = await walletConnectCosigner.coSignUserOperation(caip10AccountAddress, { + pci, + userOp + }) + console.log('cosignResponse:', cosignResponse) + userOp.signature = cosignResponse.signature + } + const account = getAccount({ + address: userOp.sender, + type: 'safe' + }) + if (permissionsContext) { - const formattedSignature = await formatSignature(this.publicClient, { - signature: userOp.signature, - permissionsContext, - accountAddress: userOp.sender + const formattedSignature = await formatSignature({ + publicClient: this.publicClient, + account, + modifiedSignature: userOp.signature, + permissionsContext }) userOp.signature = formattedSignature } @@ -167,23 +198,25 @@ export class SafeUserOpBuilder implements UserOpBuilder { entryPoint: ENTRYPOINT_ADDRESS_V07 }) - const userOpHash = await pimlicoBundlerClient.sendUserOperation({ + const userOpId = await pimlicoBundlerClient.sendUserOperation({ userOperation: { ...userOp, - callData: userOp.callData, + signature: userOp.signature, 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) + maxPriorityFeePerGas: BigInt(userOp.maxPriorityFeePerGas), + paymasterVerificationGasLimit: + userOp.paymasterVerificationGasLimit && BigInt(userOp.paymasterVerificationGasLimit), + paymasterPostOpGasLimit: + userOp.paymasterPostOpGasLimit && BigInt(userOp.paymasterPostOpGasLimit) } }) return { - receipt: userOpHash + userOpId } } diff --git a/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/SmartSessionUserOpBuilder.ts b/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/SmartSessionUserOpBuilder.ts deleted file mode 100644 index 1eaeb0e22..000000000 --- a/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/SmartSessionUserOpBuilder.ts +++ /dev/null @@ -1,322 +0,0 @@ -import { - Address, - decodeAbiParameters, - encodeAbiParameters, - getAddress, - Hex, - PublicClient -} from 'viem' -import { encodeEnable, encodeUse } from './EncodeLib' -const { SMART_SESSIONS_ADDRESS } = - require('@rhinestone/module-sdk') as typeof import('@rhinestone/module-sdk') -import { readContract } from 'viem/actions' - -export const enableSessionsStructAbi = [ - { - components: [ - { - name: 'isigner', - type: 'address' - }, - { - name: 'isignerInitData', - type: 'bytes' - }, - { - name: 'userOpPolicies', - type: 'tuple[]', - components: [ - { - name: 'policy', - type: 'address' - }, - { - name: 'initData', - type: 'bytes' - } - ] - }, - { - name: 'erc1271Policies', - type: 'tuple[]', - components: [ - { - name: 'policy', - type: 'address' - }, - { - name: 'initData', - type: 'bytes' - } - ] - }, - { - name: 'actions', - type: 'tuple[]', - components: [ - { - name: 'actionId', - type: 'bytes32' - }, - { - name: 'actionPolicies', - type: 'tuple[]', - components: [ - { - name: 'policy', - type: 'address' - }, - { - name: 'initData', - type: 'bytes' - } - ] - } - ] - }, - { - name: 'permissionEnableSig', - type: 'bytes' - } - ], - name: 'EnableSessions', - type: 'tuple' - } -] as const - -export const isPermissionsEnabledAbi = [ - { - inputs: [ - { - internalType: 'SignerId', - name: 'signerId', - type: 'bytes32' - }, - { - internalType: 'address', - name: 'account', - type: 'address' - }, - { - components: [ - { - internalType: 'contract ISigner', - name: 'isigner', - type: 'address' - }, - { - internalType: 'bytes', - name: 'isignerInitData', - type: 'bytes' - }, - { - components: [ - { - internalType: 'address', - name: 'policy', - type: 'address' - }, - { - internalType: 'bytes', - name: 'initData', - type: 'bytes' - } - ], - internalType: 'struct PolicyData[]', - name: 'userOpPolicies', - type: 'tuple[]' - }, - { - components: [ - { - internalType: 'address', - name: 'policy', - type: 'address' - }, - { - internalType: 'bytes', - name: 'initData', - type: 'bytes' - } - ], - internalType: 'struct PolicyData[]', - name: 'erc1271Policies', - type: 'tuple[]' - }, - { - components: [ - { - internalType: 'ActionId', - name: 'actionId', - type: 'bytes32' - }, - { - components: [ - { - internalType: 'address', - name: 'policy', - type: 'address' - }, - { - internalType: 'bytes', - name: 'initData', - type: 'bytes' - } - ], - internalType: 'struct PolicyData[]', - name: 'actionPolicies', - type: 'tuple[]' - } - ], - internalType: 'struct ActionData[]', - name: 'actions', - type: 'tuple[]' - }, - { - internalType: 'bytes', - name: 'permissionEnableSig', - type: 'bytes' - } - ], - internalType: 'struct EnableSessions', - name: 'enableData', - type: 'tuple' - } - ], - name: 'isPermissionEnabled', - outputs: [ - { - internalType: 'bool', - name: 'isEnabled', - type: 'bool' - } - ], - stateMutability: 'view', - type: 'function' - } -] as const - -export async function formatSignature( - publicClient: PublicClient, - params: { - signature: Hex - permissionsContext: Hex - accountAddress: Address - } -) { - const { signature, permissionsContext, accountAddress } = params - if (!permissionsContext || permissionsContext.length < 178) { - throw new Error('Permissions context too short') - } - const signerId = `0x${permissionsContext.slice(114, 178)}` as Hex - - if (permissionsContext.length == 178) { - return encodeUse(signerId, signature) - } - - const validatorAddress = permissionsContext.slice(0, 42) as Address - - if (getAddress(validatorAddress) !== getAddress(SMART_SESSIONS_ADDRESS)) { - throw new Error( - `Validator ${validatorAddress} is not the smart session validator ${SMART_SESSIONS_ADDRESS}` - ) - } - - const enableData = `0x${permissionsContext.slice(178)}` as Hex - const enableSession = Array.from(decodeAbiParameters(enableSessionsStructAbi, enableData)) - - const isPermissionsEnabled = await readContract(publicClient, { - address: SMART_SESSIONS_ADDRESS, - abi: isPermissionsEnabledAbi, - functionName: 'isPermissionEnabled', - args: [signerId, accountAddress, enableSession[0]] - }) - - if (isPermissionsEnabled) { - return encodeUse(signerId, signature) - } else { - return encodeEnable(signerId, signature, enableSession[0]) - } -} - -export async function getDummySignature( - publicClient: PublicClient, - params: { - permissionsContext: Hex - accountAddress: Address - } -) { - const { permissionsContext, accountAddress } = params - if (!permissionsContext || permissionsContext.length < 178) { - throw new Error('Permissions context too short') - } - - const validatorAddress = permissionsContext.slice(0, 42) as Address - - if (getAddress(validatorAddress) !== getAddress(SMART_SESSIONS_ADDRESS)) { - throw new Error( - `Validator ${validatorAddress} is not the smart session validator ${SMART_SESSIONS_ADDRESS}` - ) - } - - const enableData = `0x${permissionsContext.slice(178)}` as Hex - const enableSession = decodeAbiParameters(enableSessionsStructAbi, enableData) - const signerInitData = enableSession[0].isignerInitData - console.log('signerInitData', signerInitData) - const signers = decodeSigners(signerInitData) - console.log('signers', signers) - const dummySignatures: `0x${string}`[] = [] - const dummyECDSASignature: `0x${string}` = - '0xe8b94748580ca0b4993c9a1b86b5be851bfc076ff5ce3a1ff65bf16392acfcb800f9b4f1aef1555c7fce5599fffb17e7c635502154a0333ba21f3ae491839af51c' - const dummyPasskeySignature: `0x${string}` = '0x' - for (let i = 0; i < signers.length; i++) { - const signer = signers[i] - if (signer.type === 0) { - dummySignatures.push(dummyECDSASignature) - } else if (signer.type === 1) { - dummySignatures.push(dummyPasskeySignature) - } - } - const concatenatedDummySignature = encodeAbiParameters([{ type: 'bytes[]' }], [dummySignatures]) - - return formatSignature(publicClient, { - signature: concatenatedDummySignature, - permissionsContext, - accountAddress - }) -} - -export function decodeSigners( - encodedData: `0x${string}` -): Array<{ type: number; data: `0x${string}` }> { - let offset = 2 // Start after '0x' - const signers: Array<{ type: number; data: `0x${string}` }> = [] - - // Decode the number of signers - const signersCount = parseInt(encodedData.slice(offset, offset + 2), 16) - offset += 2 - - for (let i = 0; i < signersCount; i++) { - // Decode signer type - const signerType = parseInt(encodedData.slice(offset, offset + 2), 16) - offset += 2 - - // Determine data length based on signer type - let dataLength: number - if (signerType === 0) { - dataLength = 40 // 20 bytes - } else if (signerType === 1) { - dataLength = 128 // 64 bytes - } else { - throw new Error(`Unknown signer type: ${signerType}`) - } - - // Decode signer data - const signerData = `0x${encodedData.slice(offset, offset + dataLength)}` as `0x${string}` - offset += dataLength - - signers.push({ type: signerType, data: signerData }) - } - - return signers -} diff --git a/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/SmartSessionUtil.ts b/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/SmartSessionUtil.ts new file mode 100644 index 000000000..b925850c8 --- /dev/null +++ b/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/SmartSessionUtil.ts @@ -0,0 +1,22 @@ +import { Address } from 'viem' + +export const TIME_FRAME_POLICY_ADDRESSES: Record = { + 84532: '0x9A6c4974dcE237E01Ff35c602CA9555a3c0Fa5EF', + 11155111: '0x6E1FCe0ec6feaD8dBD2D36a5b9eCf8e33A538479' +} + +export const MULTIKEY_SIGNER_ADDRESSES: Record = { + 84532: '0x207b90941d9cff79A750C1E5c05dDaA17eA01B9F', + 11155111: '0x3cA2D7D588FA66248a49c1C885997e5017aF9Dc7' +} + +export const MOCK_VALIDATOR_ADDRESSES: Record = { + 84532: '0x8F8842B9b7346529484F282902Af173217411076', + 11155111: '0xaE15a31afb2770cE4c5C6131925564B03b597Fe3' +} + +// All on sepolia +export const SIMPLE_SIGNER = '0x6ff7E9992160bB25f5c67b0Ce389c28d8faD3Bfb' as Address +export const UNI_ACTION_POLICY = '0x237C7567Ac09D4DB7Dd48852a438F77a6bd65fc4' as Address +export const USAGE_LIMIT_POLICY = '0x1f265E3beDc6ce93e1A36Dc80E1B1c65844F9861' as Address +export const VALUE_LIMIT_POLICY = '0x6F0eC0c77cCAF4c25ff8FF7113D329caAA769688' as Address diff --git a/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/UserOpBuilder.ts b/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/UserOpBuilder.ts index 8fccfae7d..b15f4b789 100644 --- a/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/UserOpBuilder.ts +++ b/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/UserOpBuilder.ts @@ -1,11 +1,27 @@ -import { UserOperation } from 'permissionless' import { Address, Hex } from 'viem' type Call = { to: Address; value: bigint; data: Hex } -type UserOp = UserOperation<'v0.7'> - -export type FillUserOpParams = { +export type UserOperationWithBigIntAsHex = { + sender: Address + nonce: Hex + factory: Address | undefined + factoryData: Hex | undefined + callData: 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 BuildUserOpRequestParams = { chainId: number account: Address calls: Call[] @@ -14,28 +30,28 @@ export type FillUserOpParams = { permissions?: { context: Hex } } } -export type FillUserOpResponse = { - userOp: UserOp +export type BuildUserOpResponseReturn = { + userOp: UserOperationWithBigIntAsHex hash: Hex } - export type ErrorResponse = { message: string error: string } - -export type SendUserOpWithSignatureParams = { - chainId: Hex - userOp: UserOp +export type SendUserOpRequestParams = { + chainId: number + userOp: UserOperationWithBigIntAsHex + pci?: string permissionsContext?: Hex } -export type SendUserOpWithSignatureResponse = { - receipt: Hex +export type SendUserOpResponseReturn = { + userOpId: Hex } export interface UserOpBuilder { - fillUserOp(params: FillUserOpParams): Promise + fillUserOp(params: BuildUserOpRequestParams): Promise sendUserOpWithSignature( - params: SendUserOpWithSignatureParams - ): Promise + projectId: string, + params: SendUserOpRequestParams + ): Promise } diff --git a/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/WalletConnectCosignerUtils.ts b/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/WalletConnectCosignerUtils.ts new file mode 100644 index 000000000..6b658973c --- /dev/null +++ b/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/WalletConnectCosignerUtils.ts @@ -0,0 +1,169 @@ +import axios, { AxiosError } from 'axios' +import { UserOperationWithBigIntAsHex } from './UserOpBuilder' +import { bigIntReplacer } from '@/utils/HelperUtil' +import { WC_COSIGNER_BASE_URL } from '@/utils/ConstantsUtil' + +// Define types for the request and response +type AddPermission = { + permissionType: string + data: string + required: boolean + onChainValidated: boolean +} + +type AddPermissionRequest = { + permission: AddPermission +} + +export type AddPermissionResponse = { + pci: string + key: string +} + +type Signer = { + type: string + data: { + ids: string[] + } +} + +type SignerData = { + userOpBuilder: string +} + +type PermissionsContext = { + signer: Signer + expiry: number + signerData: SignerData + factory?: string + factoryData?: string + permissionsContext: string +} + +type UpdatePermissionsContextRequest = { + pci: string + signature?: string + context: PermissionsContext +} + +type RevokePermissionRequest = { + pci: string + signature: string +} + +type CoSignRequest = { + pci: string + userOp: UserOperationWithBigIntAsHex +} + +type CoSignResponse = { + signature: `0x${string}` +} + +// Define a custom error type +export class CoSignerApiError extends Error { + constructor(public status: number, message: string) { + super(message) + this.name = 'CoSignerApiError' + } +} + +// Function to send requests to the CoSigner API +async function sendCoSignerRequest< + TRequest, + TResponse, + TQueryParams extends Record = Record +>(args: { + url: string + data: TRequest + queryParams?: TQueryParams + headers: Record + transformRequest?: (data: TRequest) => unknown +}): Promise { + const { url, data, queryParams = {}, headers, transformRequest } = args + const transformedData = transformRequest ? transformRequest(data) : data + + try { + const response = await axios.post(url, transformedData, { + params: queryParams, + headers + }) + + return response.data + } catch (error) { + if (axios.isAxiosError(error)) { + const axiosError = error as AxiosError + if (axiosError.response) { + throw new CoSignerApiError( + axiosError.response.status, + JSON.stringify(axiosError.response.data) + ) + } else { + throw new CoSignerApiError(500, 'Network error') + } + } + // Re-throw if it's not an Axios error + throw error + } +} + +// Class to interact with the WalletConnect CoSigner API +export class WalletConnectCosigner { + private baseUrl: string + private projectId: string + + constructor(projectId: string) { + this.baseUrl = WC_COSIGNER_BASE_URL + this.projectId = projectId + } + + async addPermission(address: string, permission: AddPermission): Promise { + const url = `${this.baseUrl}/${encodeURIComponent(address)}` + + return await sendCoSignerRequest< + AddPermissionRequest, + AddPermissionResponse, + { projectId: string } + >({ + url, + data: { permission }, + queryParams: { projectId: this.projectId }, + headers: { 'Content-Type': 'application/json' } + }) + } + + async updatePermissionsContext( + address: string, + updateData: UpdatePermissionsContextRequest + ): Promise { + const url = `${this.baseUrl}/${encodeURIComponent(address)}/context` + await sendCoSignerRequest({ + url, + data: updateData, + queryParams: { projectId: this.projectId }, + headers: { 'Content-Type': 'application/json' } + }) + } + + async revokePermission(address: string, revokeData: RevokePermissionRequest): Promise { + const url = `${this.baseUrl}/${encodeURIComponent(address)}/revoke` + await sendCoSignerRequest({ + url, + data: revokeData, + queryParams: { projectId: this.projectId }, + headers: { 'Content-Type': 'application/json' } + }) + } + + async coSignUserOperation(address: string, coSignData: CoSignRequest): Promise { + const url = `${this.baseUrl}/${encodeURIComponent(address)}/sign` + + return await sendCoSignerRequest({ + url, + data: coSignData, + queryParams: { projectId: this.projectId }, + headers: { 'Content-Type': 'application/json' }, + transformRequest: (value: CoSignRequest) => JSON.stringify(value, bigIntReplacer) + }) + } +} diff --git a/advanced/wallets/react-wallet-v2/src/pages/api/build.ts b/advanced/wallets/react-wallet-v2/src/pages/api/build.ts index 9456b5fc3..878e910fe 100644 --- a/advanced/wallets/react-wallet-v2/src/pages/api/build.ts +++ b/advanced/wallets/react-wallet-v2/src/pages/api/build.ts @@ -1,11 +1,14 @@ -import { ErrorResponse, FillUserOpResponse } from '@/lib/smart-accounts/builders/UserOpBuilder' +import { + ErrorResponse, + BuildUserOpResponseReturn +} from '@/lib/smart-accounts/builders/UserOpBuilder' import { getChainById } from '@/utils/ChainUtil' import { getUserOpBuilder } from '@/utils/UserOpBuilderUtil' import { NextApiRequest, NextApiResponse } from 'next' export default async function handler( req: NextApiRequest, - res: NextApiResponse + res: NextApiResponse ) { const chainId = req.body.chainId const account = req.body.account diff --git a/advanced/wallets/react-wallet-v2/src/pages/api/sendUserOp.ts b/advanced/wallets/react-wallet-v2/src/pages/api/sendUserOp.ts index 3a61cee0f..8aaf90c1c 100644 --- a/advanced/wallets/react-wallet-v2/src/pages/api/sendUserOp.ts +++ b/advanced/wallets/react-wallet-v2/src/pages/api/sendUserOp.ts @@ -1,6 +1,6 @@ import { ErrorResponse, - SendUserOpWithSignatureResponse + SendUserOpResponseReturn } from '@/lib/smart-accounts/builders/UserOpBuilder' import { getChainById } from '@/utils/ChainUtil' import { getUserOpBuilder } from '@/utils/UserOpBuilderUtil' @@ -8,8 +8,37 @@ import { NextApiRequest, NextApiResponse } from 'next' export default async function handler( req: NextApiRequest, - res: NextApiResponse + res: NextApiResponse ) { + if (req.method !== 'POST') { + return res.status(405).json({ + message: 'Method not allowed', + error: 'Method not allowed' + }) + } + if (!req.body.chainId) { + return res.status(400).json({ + message: 'chainId is required', + error: 'chainId is required' + }) + } + if (!req.body.userOp) { + return res.status(400).json({ + message: 'userOp is required', + error: 'userOp is required' + }) + } + if ( + req.query.projectId === undefined || + req.query.projectId === '' || + typeof req.query.projectId !== 'string' + ) { + return res.status(400).json({ + message: 'invalid projectId', + error: 'invalid projectId' + }) + } + const projectId = req.query.projectId const chainId = req.body.chainId const account = req.body.userOp.sender const chain = getChainById(chainId) @@ -18,7 +47,7 @@ export default async function handler( account, chain }) - const response = await builder.sendUserOpWithSignature(req.body) + const response = await builder.sendUserOpWithSignature(projectId, req.body) res.status(200).json(response) } catch (error: any) { diff --git a/advanced/wallets/react-wallet-v2/src/utils/ConstantsUtil.ts b/advanced/wallets/react-wallet-v2/src/utils/ConstantsUtil.ts new file mode 100644 index 000000000..e24109177 --- /dev/null +++ b/advanced/wallets/react-wallet-v2/src/utils/ConstantsUtil.ts @@ -0,0 +1,2 @@ +export const WC_COSIGNER_BASE_URL = 'https://maksy.ngrok.dev/v1/sessions' +// export const WC_COSIGNER_BASE_URL = 'https://rpc.walletconnect.org/v1/sessions' diff --git a/advanced/wallets/react-wallet-v2/yarn.lock b/advanced/wallets/react-wallet-v2/yarn.lock index ad1f38d51..7e8c1a43a 100644 --- a/advanced/wallets/react-wallet-v2/yarn.lock +++ b/advanced/wallets/react-wallet-v2/yarn.lock @@ -98,15 +98,6 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" -"@biconomy/permission-context-builder@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@biconomy/permission-context-builder/-/permission-context-builder-1.1.2.tgz#caa7a96583784cf632e5450a3d9cfd1b2bfb4a90" - integrity sha512-VmKsX4NmLS+Y9vViQFJoSlpl9XXJ7MpTIOzLNfRAeBNqGnR0UcU+K/wzer9jRmp4MC4XoK0qfkHUjj+D8NVquw== - dependencies: - tslib "^2.7.0" - typescript "^5.5.4" - webauthn-p256 "^0.0.8" - "@cosmjs/amino@0.32.3", "@cosmjs/amino@^0.32.3": version "0.32.3" resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.32.3.tgz#b81d4a2b8d61568431a1afcd871e1344a19d97ff" @@ -2086,10 +2077,10 @@ "@react-types/grid" "^3.2.6" "@react-types/shared" "^3.23.1" -"@rhinestone/module-sdk@0.1.13": - version "0.1.13" - resolved "https://registry.yarnpkg.com/@rhinestone/module-sdk/-/module-sdk-0.1.13.tgz#5df0a07d65a4fd3c0d5838714fc4468feaad0a55" - integrity sha512-Tb78W6BYOmBnjtqIHFmq43SBfd9vucZ4pxgN8h43zto1fKSwrBYLf4Pzrm/NwaAfFCw0BHuYo5YNQtPJj3oqUA== +"@rhinestone/module-sdk@0.1.15": + version "0.1.15" + resolved "https://registry.yarnpkg.com/@rhinestone/module-sdk/-/module-sdk-0.1.15.tgz#8d49af671ed39881caa28e0a97351a8bc9261580" + integrity sha512-9EHglokR1+NCj7CE37lFbzrsCgI7Anr6KLIwNdJ9Rs7cNAsyDNvbzDaY6FBlPbK9dk3bEZHfyphSVMJVvH+Siw== dependencies: solady "^0.0.235" @@ -6848,11 +6839,6 @@ tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.6.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== -tslib@^2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" - integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== - tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" @@ -6926,11 +6912,6 @@ typescript@5.2.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== -typescript@^5.5.4: - version "5.5.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" - integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== - u3@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/u3/-/u3-0.1.1.tgz#5f52044f42ee76cd8de33148829e14528494b73b" @@ -7116,14 +7097,6 @@ webauthn-p256@0.0.2: "@noble/curves" "^1.4.0" "@noble/hashes" "^1.4.0" -webauthn-p256@^0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/webauthn-p256/-/webauthn-p256-0.0.8.tgz#eefcaa1eab9929c69f7b2d03572c03472efc9805" - integrity sha512-nswXgJdl1N2CDSRA4Py6iIhPdY0mPIhy0ityyxzcZ4MRFFnyW05/79dyvWlDn03XGh+eCinOrqjTYzmu9rRD3w== - dependencies: - "@noble/curves" "^1.4.0" - "@noble/hashes" "^1.4.0" - webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"