diff --git a/sdks/smart-wallet-sdk/src/SmartAccount.ts b/sdks/smart-wallet-sdk/src/SmartAccount.ts new file mode 100644 index 000000000..fa656d454 --- /dev/null +++ b/sdks/smart-wallet-sdk/src/SmartAccount.ts @@ -0,0 +1,190 @@ +import { ChainId } from '@uniswap/sdk-core' +import { + Address, + Abi, + decodeAbiParameters, + EntryPoint, + Hex, + PublicClient, + toHex, + encodeFunctionData, + decodeFunctionData, + getContract, + PrivateKeyAccount +} from 'viem' +import { getUserOperati, UserOperationonHash, toSmartAccount, getUserOperationHash, UserOperation } from 'viem/account-abstraction' + +import { abi as minimalDelegationAbi } from '../abis/MinimalDelegation.json' +import { ModeType, SMART_WALLET_ADDRESSES, DELEGATION_MAGIC_PREFIX } from './constants' +import { SmartWallet } from './smartWallet' +import { Call } from './types' +import { CALL_ABI_PARAMS } from './utils/callPlanner' +import { sign } from 'viem/accounts' + +// Default entry point from ERC-4337 +const DEFAULT_ENTRY_POINT: Address = '0x0000000071727De22E5E9d8BAf0edAc6f37da032' + +const toUniswapSmartAccountParameters = { + client: PublicClient, + chainId: ChainId, + owner: PrivateKeyAccount, + entryPoint?: EntryPoint +} + +export const toUniswapSmartAccount = ( + client: PublicClient, + chainId: ChainId, + owner: PrivateKeyAccount, + entryPoint?: EntryPoint +) => { + const contractAddress = SMART_WALLET_ADDRESSES[chainId] as Address + + if (!contractAddress) { + throw new Error(`Smart wallet not found for chainId: ${chainId}`) + } + + // Create a contract instance for decoding + const minimalDelegation = getContract({ + address: contractAddress, + abi: minimalDelegationAbi, + client + }) + + // Create a smart account using viem's toSmartAccount function + return toSmartAccount({ + client, + entryPoint: entryPoint || { + address: DEFAULT_ENTRY_POINT, + version: '0.7', + abi: [], + }, + + // Decode calls from calldata + async decodeCalls(data) { + try { + // First, decode the execute function call + const decodedFunction = decodeFunctionData({ + abi: minimalDelegationAbi, + data + }) + + // TODO: decode + + } catch (error) { + console.error('Error decoding calls:', error) + } + + // If we can't decode or it's not an execute call, return empty array + return [] + }, + + // Encode calls as defined by the Smart Account contract + async encodeCalls(calls) { + // Convert from viem format to our internal Call format + const convertedCalls: Call[] = calls.map(call => ({ + to: call.to, + data: call.data || '0x', + value: call.value || 0n + })) + + // Use SmartWallet to encode calls + const methodParams = SmartWallet.encodeCalls(convertedCalls) + + // Create execute calldata with mode + const mode = ModeType.BATCHED_CALL + return encodeFunctionData({ + abi: minimalDelegationAbi, + functionName: 'execute', + args: [ + mode as Hex, + methodParams.calldata + ] + }) + }, + + // Get the address of the Smart Account + async getAddress() { + // In a full implementation, this would be the address of the + // delegated contract that wraps the owner + return contractAddress + }, + + // Build the Factory properties for the Smart Account + async getFactoryArgs() { + // In a full implementation, this would return the bytecode and + // initialization args for the minimal delegation contract + return { + args: [], + bytecode: '0x' as Hex + } + }, + + // Get the nonce of the Smart Account + async getNonce() { + try { + return minimalDelegation.read.getNonce(); + } catch (error) { + console.error('Error fetching nonce:', error) + return 0n + } + }, + + // Get the stub signature for User Operations + async getStubSignature() { + return '0x' as Hex + }, + + // Sign message to be verified by the Smart Account contract + async signMessage(message) { + throw new Error('Not implemented') + }, + + // Sign typed data to be verified by the Smart Account contract + async signTypedData(typedData) { + // In a full implementation, this would properly call the + // owner account to sign the typed data + return '0x' as Hex + }, + + // Sign a User Operation to be broadcasted via the Bundler + async signUserOperation(parameters) { + const { chainId = client.chain!.id, ...userOperation } = parameters + const address = await this.getAddress() + + const hash = getUserOperationHash({ + chainId, + entryPointAddress: entryPoint.address, + entryPointVersion: entryPoint.version, + userOperation: { + ...(userOperation as unknown as UserOperation), + sender: address, + }, + }) + + if (owner.type === 'address') throw new Error('owner cannot sign') + const signature = await sign({ hash, owner }) + + return wrapSignature({ + ownerIndex, + signature, + }) + }, + + // Optional: User operation configuration + userOperation: { + async estimateGas(userOperation) { + // Placeholder for gas estimation + const LARGE_LIMIT = 1_000_000n + + return { + callGasLimit: toHex(LARGE_LIMIT), + preVerificationGas: toHex(LARGE_LIMIT), + verificationGasLimit: toHex(LARGE_LIMIT) + } + }, + }, + + // Owner account for signing + source: owner, + }) +} diff --git a/sdks/smart-wallet-sdk/src/index.ts b/sdks/smart-wallet-sdk/src/index.ts index cd6635216..7d90ad540 100644 --- a/sdks/smart-wallet-sdk/src/index.ts +++ b/sdks/smart-wallet-sdk/src/index.ts @@ -13,5 +13,8 @@ export * from './utils' // Export main class export * from './smartWallet' +// Export smart account functionality +export * from './SmartAccount' + // Export generated contracts (will be available after running typechain) // export * from './contracts' \ No newline at end of file diff --git a/sdks/smart-wallet-sdk/src/smartWallet.ts b/sdks/smart-wallet-sdk/src/smartWallet.ts index 996d22a7e..ce041ba08 100644 --- a/sdks/smart-wallet-sdk/src/smartWallet.ts +++ b/sdks/smart-wallet-sdk/src/smartWallet.ts @@ -1,12 +1,19 @@ import { ChainId } from '@uniswap/sdk-core' -import { encodeAbiParameters, encodeFunctionData } from 'viem' - +import { encodeAbiParameters, encodeFunctionData, PublicClient, RpcUserOperation, RpcUserOperationRequest, toHex } from 'viem' +import { + createBundlerClient, + toCoinbaseSmartAccount +function toCoinbaseSmartAccount(parameters: ToCoinbaseSmartAccountParameters): Promise + +} from 'viem/account-abstraction' import { abi } from '../abis/MinimalDelegation.json' import { MODE_TYPE_ABI_PARAMETERS, ModeType, SMART_WALLET_ADDRESSES } from './constants' import { Call, MethodParameters, ExecuteOptions, AdvancedCall } from './types' import { CallPlanner } from './utils' +const bundlerClient = createBundler + /** * Main SDK class for interacting with ERC7821-compatible smart wallets */ @@ -32,6 +39,41 @@ export class SmartWallet { } } + // Nonce is fetched using getNonce() on the user's account + // Signature must be + public static async toUserOperation(client: , nonce: bigint, methodParameters: MethodParameters, paymasterAndData?: `0x${string}`): Promise { + const chainId = await client.getChainId() + const address = SMART_WALLET_ADDRESSES[chainId] + if(!address) { + throw new Error(`Smart wallet not found for chainId: ${chainId}`) + } + + + // estimate gas + const gasLimit = await client.estimateGas({ + to: address, + data: methodParameters.calldata, + value: methodParameters.value + }) + + const feeData = await client.estimateFeesPerGas() + + const LARGE_LIMIT = 1_000_000n; + + return { + sender: address, + callData: methodParameters.calldata, + callGasLimit: toHex(gasLimit), + maxFeePerGas: toHex(feeData.maxFeePerGas), + maxPriorityFeePerGas: toHex(feeData.maxPriorityFeePerGas), + nonce: toHex(nonce), + paymasterAndData, + preVerificationGas: toHex(LARGE_LIMIT), + verificationGasLimit: toHex(LARGE_LIMIT), + signature: '0x' + } + } + /// To be implemented public static encodeAdvancedCalls(calls: AdvancedCall[], opData: string, _options: ExecuteOptions = {}): MethodParameters { throw new Error('Not implemented') diff --git a/sdks/smart-wallet-sdk/src/utils/index.ts b/sdks/smart-wallet-sdk/src/utils/index.ts index 0ce8b3523..544b2cc1a 100644 --- a/sdks/smart-wallet-sdk/src/utils/index.ts +++ b/sdks/smart-wallet-sdk/src/utils/index.ts @@ -1 +1,2 @@ export * from './callPlanner' +export * from './delegation'