Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 190 additions & 0 deletions sdks/smart-wallet-sdk/src/SmartAccount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import { ChainId } from '@uniswap/sdk-core'
import {
Address,
Abi,

Check failure on line 4 in sdks/smart-wallet-sdk/src/SmartAccount.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, and Test

'Abi' is defined but never used. Allowed unused vars must match /^_/u
decodeAbiParameters,

Check failure on line 5 in sdks/smart-wallet-sdk/src/SmartAccount.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, and Test

'decodeAbiParameters' is defined but never used. Allowed unused vars must match /^_/u
EntryPoint,
Hex,
PublicClient,
toHex,
encodeFunctionData,
decodeFunctionData,
getContract,
PrivateKeyAccount
} from 'viem'
import { getUserOperati, UserOperationonHash, toSmartAccount, getUserOperationHash, UserOperation } from 'viem/account-abstraction'

Check failure on line 15 in sdks/smart-wallet-sdk/src/SmartAccount.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, and Test

'getUserOperati' is defined but never used. Allowed unused vars must match /^_/u

Check failure on line 15 in sdks/smart-wallet-sdk/src/SmartAccount.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, and Test

'UserOperationonHash' is defined but never used. Allowed unused vars must match /^_/u

import { abi as minimalDelegationAbi } from '../abis/MinimalDelegation.json'

Check failure on line 17 in sdks/smart-wallet-sdk/src/SmartAccount.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, and Test

There should be at least one empty line between import groups
import { ModeType, SMART_WALLET_ADDRESSES, DELEGATION_MAGIC_PREFIX } from './constants'

Check failure on line 18 in sdks/smart-wallet-sdk/src/SmartAccount.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, and Test

'DELEGATION_MAGIC_PREFIX' is defined but never used. Allowed unused vars must match /^_/u
import { SmartWallet } from './smartWallet'
import { Call } from './types'
import { CALL_ABI_PARAMS } from './utils/callPlanner'

Check failure on line 21 in sdks/smart-wallet-sdk/src/SmartAccount.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, and Test

There should be at least one empty line between import groups

Check failure on line 21 in sdks/smart-wallet-sdk/src/SmartAccount.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, and Test

'CALL_ABI_PARAMS' is defined but never used. Allowed unused vars must match /^_/u
import { sign } from 'viem/accounts'

Check failure on line 22 in sdks/smart-wallet-sdk/src/SmartAccount.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, and Test

`viem/accounts` import should occur before import of `../abis/MinimalDelegation.json`

// 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

Check warning on line 151 in sdks/smart-wallet-sdk/src/SmartAccount.ts

View workflow job for this annotation

GitHub Actions / Lint, Build, and Test

Forbidden non-null assertion
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,
})
}
3 changes: 3 additions & 0 deletions sdks/smart-wallet-sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
46 changes: 44 additions & 2 deletions sdks/smart-wallet-sdk/src/smartWallet.ts
Original file line number Diff line number Diff line change
@@ -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<ToCoinbaseSmartAccountReturnType>

} 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
*/
Expand All @@ -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<RpcUserOperation> {
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')
Expand Down
1 change: 1 addition & 0 deletions sdks/smart-wallet-sdk/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './callPlanner'
export * from './delegation'
Loading