Skip to content
Open
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
140 changes: 139 additions & 1 deletion sdks/smart-wallet-sdk/src/smartWallet.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,153 @@
import { ChainId } from '@uniswap/sdk-core'
import { decodeFunctionData } from 'viem'
import { decodeAbiParameters, decodeFunctionData } from 'viem'

import abi from '../abis/MinimalDelegationEntry.json'

import { ModeType, SMART_WALLET_ADDRESSES } from './constants'
import { SmartWallet } from './smartWallet'
import { BatchedCall, Call } from './types'
import { BATCHED_CALL_ABI_PARAMS } from './utils/batchedCallPlanner'

const EXECUTE_SELECTOR = '0xe9ae5c53' as `0x${string}`
const EXECUTE_USER_OP_SELECTOR = '0x8dd7712f' as `0x${string}`

describe('SmartWallet', () => {
describe('encodeUserOp', () => {
it('encodes a single call correctly', () => {
const calls: Call[] = [
{
to: '0x1111111111111111111111111111111111111111',
data: '0x1234',
value: 0n,
},
]

const result = SmartWallet.encodeUserOp(calls)

expect(result).toBeDefined()
expect(result.calldata).toBeDefined()
expect(result.calldata.startsWith(EXECUTE_USER_OP_SELECTOR)).toBe(true)
expect(result.value).toBe(0n)
})

it('encodes multiple calls correctly', () => {
const calls: Call[] = [
{
to: '0x1111111111111111111111111111111111111111',
data: '0x1234',
value: 0n,
},
{
to: '0x2222222222222222222222222222222222222222',
data: '0x5678',
value: 0n,
},
]

const result = SmartWallet.encodeUserOp(calls)

expect(result).toBeDefined()
expect(result.calldata).toBeDefined()
expect(result.calldata.startsWith(EXECUTE_USER_OP_SELECTOR)).toBe(true)
expect(result.value).toBe(0n)
})

it('sums the value of all calls', () => {
const calls: Call[] = [
{
to: '0x1111111111111111111111111111111111111111',
data: '0x1234',
value: 100n,
},
{
to: '0x2222222222222222222222222222222222222222',
data: '0x5678',
value: 200n,
},
]

const result = SmartWallet.encodeUserOp(calls)

expect(result.value).toBe(300n)
})

it('encodes with revertOnFailure: true', () => {
const calls: Call[] = [
{
to: '0x1111111111111111111111111111111111111111',
data: '0x1234',
value: 0n,
},
]

const result = SmartWallet.encodeUserOp(calls, { revertOnFailure: true })

expect(result).toBeDefined()
expect(result.calldata).toBeDefined()
expect(result.calldata.startsWith(EXECUTE_USER_OP_SELECTOR)).toBe(true)

// Decode using BATCHED_CALL_ABI_PARAMS which matches the encoding format
const argsData = `0x${result.calldata.slice(10)}` as `0x${string}`
const decoded = decodeAbiParameters(BATCHED_CALL_ABI_PARAMS, argsData)
expect((decoded[0] as BatchedCall).revertOnFailure).toBe(true)
})

it('encodes with revertOnFailure: false', () => {
const calls: Call[] = [
{
to: '0x1111111111111111111111111111111111111111',
data: '0x1234',
value: 0n,
},
]

const result = SmartWallet.encodeUserOp(calls, { revertOnFailure: false })

expect(result).toBeDefined()
expect(result.calldata).toBeDefined()

// Decode using BATCHED_CALL_ABI_PARAMS which matches the encoding format
const argsData = `0x${result.calldata.slice(10)}` as `0x${string}`
const decoded = decodeAbiParameters(BATCHED_CALL_ABI_PARAMS, argsData)
expect((decoded[0] as BatchedCall).revertOnFailure).toBe(false)
})

it('defaults revertOnFailure to true when no options provided', () => {
const calls: Call[] = [
{
to: '0x1111111111111111111111111111111111111111',
data: '0x1234',
value: 0n,
},
]

const result = SmartWallet.encodeUserOp(calls)

// Decode using BATCHED_CALL_ABI_PARAMS which matches the encoding format
const argsData = `0x${result.calldata.slice(10)}` as `0x${string}`
const decoded = decodeAbiParameters(BATCHED_CALL_ABI_PARAMS, argsData)
// When no option is provided, BatchedCallPlanner defaults revertOnFailure to true
expect((decoded[0] as BatchedCall).revertOnFailure).toBe(true)
})

it('handles calls with chainId property', () => {
const calls: Call[] = [
{
to: '0x1111111111111111111111111111111111111111',
data: '0x1234',
value: 50n,
chainId: ChainId.SEPOLIA,
},
]

const result = SmartWallet.encodeUserOp(calls)

expect(result).toBeDefined()
expect(result.calldata.startsWith(EXECUTE_USER_OP_SELECTOR)).toBe(true)
expect(result.value).toBe(50n)
})
})

describe('encodeBatchedCall', () => {
it('encodes batched call correctly', () => {
const calls: Call[] = [
Expand Down
30 changes: 29 additions & 1 deletion sdks/smart-wallet-sdk/src/smartWallet.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ChainId } from '@uniswap/sdk-core'
import { encodeFunctionData } from 'viem'
import { concatHex, encodeFunctionData } from 'viem'

import abi from '../abis/MinimalDelegationEntry.json'

Expand All @@ -12,6 +12,34 @@ import { BatchedCallPlanner } from './utils/batchedCallPlanner'
* Main SDK class for interacting with Uniswap smart wallet contracts
*/
export class SmartWallet {
/**
* Creates method parameters for a UserOperation to be executed through a smart wallet
* @dev Compatible with EntryPoint versions v0.7.0 and v0.8.0 (not v0.6.0)
*
* @param calls Array of calls to encode
* @param options Basic options for the execution
* @returns Method parameters with userOp calldata and value
*/
public static encodeUserOp(calls: Call[], options: ExecuteOptions = {}): MethodParameters {
const planner = new CallPlanner(calls)
const batchedCallPlanner = new BatchedCallPlanner(planner, options.revertOnFailure)

// UserOp callData format: executeUserOp selector (0x8dd7712f) + abi.encode(abi.encode(Call[]), revertOnFailure)

// The EntryPoint recognizes this selector and calls executeUserOp(userOp, userOpHash) on the account.
// The account then extracts the execution data from userOp.callData (slicing off the selector).

// We manually concat the selector + encoded data rather than using encodeFunctionData because
// the callData is not a standard ABI-encoded function call to executeUserOp.
const EXECUTE_USER_OP_SELECTOR = '0x8dd7712f'
const calldata = concatHex([EXECUTE_USER_OP_SELECTOR, batchedCallPlanner.encode()])

return {
calldata,
value: planner.value,
}
}

/**
* Creates method parameters for executing a simple batch of calls through a smart wallet
* @param calls Array of calls to encode
Expand Down
Loading