Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
78008bc
spike: use smart account factory
tremarkley Aug 18, 2025
54a59b0
new approach to smart wallets
tremarkley Aug 20, 2025
3f6a465
refactored privy and lend is working
tremarkley Aug 21, 2025
275bd38
spike: paymaster with smart accounts on base sepolia
tremarkley Aug 23, 2025
4477070
clean up
tremarkley Aug 25, 2025
a36b8ee
cleaned up smart wallet class and provider
tremarkley Aug 25, 2025
6b8c1c8
remove unused functions on SmartWallet
tremarkley Aug 25, 2025
d05354b
refactored smart wallet to not contain privy
tremarkley Aug 25, 2025
2095e9b
clean up rpc urls
tremarkley Aug 25, 2025
f8de7d1
nit
tremarkley Aug 25, 2025
7cc09f1
remove unused env var
tremarkley Aug 25, 2025
66c40f1
remove nonce from getWallet and make synchronous
tremarkley Aug 25, 2025
edabe86
remove baseSepolia from smart wallet
tremarkley Aug 25, 2025
11da967
move bundler url into rpc config
tremarkley Aug 25, 2025
7e0eb69
update env var name
tremarkley Aug 25, 2025
74c5204
refactor to more extensible classes and providers
tremarkley Aug 27, 2025
6f8158c
remove find-vault script
tremarkley Aug 27, 2025
d8ddbe2
improve the WalletProvider to make it moer expressive
tremarkley Aug 27, 2025
c725b93
improvements
tremarkley Aug 27, 2025
7ccfdec
clean up
tremarkley Aug 27, 2025
4fc6d97
fix serialization bug
tremarkley Aug 27, 2025
3e39786
nit
tremarkley Aug 27, 2025
ca4dcd1
all files are now well documented
tremarkley Aug 27, 2025
0072d9e
nit
tremarkley Aug 27, 2025
cf2fc4f
morpho tests working
tremarkley Aug 27, 2025
aba3137
supersim tests passing
tremarkley Aug 28, 2025
d312f0f
add warning comment
tremarkley Aug 28, 2025
57af857
remove unused tests
tremarkley Aug 28, 2025
83708f4
remove unused tests
tremarkley Aug 28, 2025
897718d
lint
tremarkley Aug 28, 2025
9a8dcf4
fix frontend tests
tremarkley Aug 28, 2025
7cd3dfe
fix backend tests
tremarkley Aug 28, 2025
3a78885
fix env var
tremarkley Aug 28, 2025
b2adc3b
fix integration tests
tremarkley Aug 28, 2025
78afda8
lint
tremarkley Aug 28, 2025
5391c06
rename privy provider file
tremarkley Aug 28, 2025
3b9f1c5
nit
tremarkley Aug 28, 2025
b8d2536
add docs to chainmanager
tremarkley Aug 28, 2025
a7213c3
rename
tremarkley Aug 28, 2025
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
1 change: 1 addition & 0 deletions packages/demo/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@eth-optimism/verbs-sdk": "workspace:*",
"@eth-optimism/viem": "^0.4.13",
"@hono/node-server": "^1.14.0",
"@privy-io/server-auth": "^1.31.1",
"commander": "^13.1.0",
"dotenv": "^16.4.5",
"envalid": "^8.1.0",
Expand Down
130 changes: 85 additions & 45 deletions packages/demo/backend/src/app.integration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,54 +12,93 @@ import { router } from './router.js'
vi.mock('./config/verbs.js', () => ({
initializeVerbs: vi.fn(),
getVerbs: vi.fn(() => ({
createWallet: vi.fn((userId: string) =>
Promise.resolve({
id: `wallet-${userId}`,
address: `0x${userId.padEnd(40, '0')}`,
getBalance: () =>
Promise.resolve([
{ symbol: 'USDC', balance: 1000000n },
{ symbol: 'MORPHO', balance: 500000n },
]),
}),
),
getWallet: vi.fn((userId: string) => {
// Simulate some users existing and others not
if (userId.includes('non-existent')) {
return Promise.resolve(null)
}
return Promise.resolve({
id: `wallet-${userId}`,
address: `0x${userId.padEnd(40, '0')}`,
getBalance: () =>
Promise.resolve([
{ symbol: 'USDC', balance: 1000000n },
{ symbol: 'MORPHO', balance: 500000n },
]),
})
}),
getAllWallets: vi.fn(() =>
Promise.resolve([
{
id: 'wallet-1',
address: '0x1111111111111111111111111111111111111111',
wallet: {
createWalletWithEmbeddedSigner: vi.fn(() =>
Promise.resolve({
id: `wallet-1`,
signer: {
address: `0x1111111111111111111111111111111111111111`,
},
getAddress: () =>
Promise.resolve(`0x1111111111111111111111111111111111111111`),
getBalance: () =>
Promise.resolve([
{ symbol: 'USDC', balance: 1000000n },
{ symbol: 'MORPHO', balance: 500000n },
]),
}),
),
getSmartWalletWithEmbeddedSigner: vi.fn(
({ walletId: userId }: { walletId: string }) => {
// Simulate some users existing and others not
if (userId.includes('non-existent')) {
return Promise.resolve(null)
}
return Promise.resolve({
id: `wallet-${userId}`,
getAddress: () => Promise.resolve(`0x${userId.padEnd(40, '0')}`),
getBalance: () =>
Promise.resolve([
{ symbol: 'USDC', balance: 1000000n },
{ symbol: 'MORPHO', balance: 500000n },
]),
})
},
{
id: 'wallet-2',
address: '0x2222222222222222222222222222222222222222',
getBalance: () =>
Promise.resolve([
{ symbol: 'USDC', balance: 2000000n },
{ symbol: 'MORPHO', balance: 750000n },
]),
},
]),
),
),
smartWalletProvider: {
getWalletAddress: vi.fn(({ owners }: { owners: string[] }) => {
return Promise.resolve(owners[0])
}),
getWallet: vi.fn(
({
walletAddress,
signer,
ownerIndex,
}: {
walletAddress: string
signer: string
ownerIndex: number
}) => {
return {
address: walletAddress,
getAddress: () => Promise.resolve(walletAddress),
signer,
ownerIndex,
}
},
),
},
embeddedWalletProvider: {
getAllWallets: vi.fn(() =>
Promise.resolve([
{
id: 'wallet-1',
address: '0x1111111111111111111111111111111111111111',
getBalance: () =>
Promise.resolve([
{ symbol: 'USDC', balance: 1000000n },
{ symbol: 'MORPHO', balance: 500000n },
]),
signer: vi.fn().mockResolvedValue({
address: '0x1111111111111111111111111111111111111111',
}),
},
{
id: 'wallet-2',
address: '0x2222222222222222222222222222222222222222',
getBalance: () =>
Promise.resolve([
{ symbol: 'USDC', balance: 2000000n },
{ symbol: 'MORPHO', balance: 750000n },
]),
signer: vi.fn().mockResolvedValue({
address: '0x2222222222222222222222222222222222222222',
}),
},
]),
),
},
},
lend: {
getVaults: vi.fn(() =>
Promise.resolve([
Expand Down Expand Up @@ -186,11 +225,12 @@ describe('HTTP API Integration', () => {

expect(response.statusCode).toBe(200)
const data = (await response.body.json()) as any

expect(data).toHaveProperty('address')
expect(data).toHaveProperty('privyAddress')
expect(data).toHaveProperty('smartWalletAddress')
expect(data).toHaveProperty('userId')
expect(data.userId).toBe(testUserId)
expect(data.address).toMatch(/^0x[a-zA-Z0-9\-]{1,}$/) // Basic address format validation
expect(data.smartWalletAddress).toMatch(/^0x[a-zA-Z0-9\-]{1,}$/)
expect(data.privyAddress).toMatch(/^0x[a-zA-Z0-9\-]{1,}$/)
})

it('should get an existing wallet', async () => {
Expand Down
1 change: 1 addition & 0 deletions packages/demo/backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,5 @@ class VerbsApp extends App {
}
}

export * from '@/types/index.js'
export { VerbsApp }
4 changes: 3 additions & 1 deletion packages/demo/backend/src/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,14 @@ export const env = cleanEnv(process.env, {
PRIVY_APP_ID: str({ devDefault: 'dummy' }),
PRIVY_APP_SECRET: str({ devDefault: 'dummy' }),
LOCAL_DEV: bool({ default: false }),
RPC_URL: str({ default: 'http://127.0.0.1:9545' }),
BASE_SEPOLIA_RPC_URL: str({ default: undefined }),
UNICHAIN_RPC_URL: str({ default: undefined }),
FAUCET_ADMIN_PRIVATE_KEY: str({
default:
'0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
}),
FAUCET_ADDRESS: str({
default: getFaucetAddressDefault(),
}),
BASE_SEPOLIA_BUNDER_URL: str({ default: undefined }),
})
36 changes: 23 additions & 13 deletions packages/demo/backend/src/config/verbs.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,49 @@
import {
initVerbs,
type VerbsConfig,
type VerbsInterface,
} from '@eth-optimism/verbs-sdk'
import { unichain } from 'viem/chains'
import { Verbs, type VerbsConfig } from '@eth-optimism/verbs-sdk'
import { PrivyClient } from '@privy-io/server-auth'
import { baseSepolia, unichain } from 'viem/chains'

import { env } from './env.js'

let verbsInstance: VerbsInterface
let verbsInstance: Verbs

export function createVerbsConfig(): VerbsConfig {
return {
wallet: {
type: 'privy',
appId: env.PRIVY_APP_ID,
appSecret: env.PRIVY_APP_SECRET,
embeddedWalletConfig: {
provider: {
type: 'privy',
privyClient: new PrivyClient(env.PRIVY_APP_ID, env.PRIVY_APP_SECRET),
},
},
smartWalletConfig: {
provider: {
type: 'default',
},
},
},
lend: {
type: 'morpho',
},
chains: [
{
chainId: unichain.id,
rpcUrl: env.RPC_URL,
rpcUrl: env.UNICHAIN_RPC_URL || unichain.rpcUrls.default.http[0],
},
{
chainId: baseSepolia.id,
rpcUrl: env.BASE_SEPOLIA_RPC_URL || baseSepolia.rpcUrls.default.http[0],
bundlerUrl: env.BASE_SEPOLIA_BUNDER_URL,
},
],
}
}

export function initializeVerbs(config?: VerbsConfig): void {
const verbsConfig = config || createVerbsConfig()
verbsInstance = initVerbs(verbsConfig)
verbsInstance = new Verbs(verbsConfig)
}

export function getVerbs(): VerbsInterface {
export function getVerbs() {
if (!verbsInstance) {
throw new Error('Verbs SDK not initialized. Call initializeVerbs() first.')
}
Expand Down
13 changes: 11 additions & 2 deletions packages/demo/backend/src/controllers/lend.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { Context } from 'hono'
import type { Address } from 'viem'
import { baseSepolia } from 'viem/chains'
import { z } from 'zod'

import { validateRequest } from '../helpers/validation.js'
import * as lendService from '../services/lend.js'
import { serializeBigInt } from '../utils/serializers.js'

const DepositRequestSchema = z.object({
body: z.object({
Expand Down Expand Up @@ -117,10 +119,16 @@ export class LendController {
const {
body: { walletId, amount, token },
} = validation.data
const lendTransaction = await lendService.deposit(walletId, amount, token)
const lendTransaction = await lendService.deposit(
walletId,
amount,
token,
baseSepolia.id,
)
const result = await lendService.executeLendTransaction(
walletId,
lendTransaction,
baseSepolia.id,
)

return c.json({
Expand All @@ -132,10 +140,11 @@ export class LendController {
apy: result.apy,
timestamp: result.timestamp,
slippage: result.slippage,
transactionData: result.transactionData,
transactionData: serializeBigInt(result.transactionData),
},
})
} catch (error) {
console.error('Failed to deposit', error)
return c.json(
{
error: 'Failed to deposit',
Expand Down
34 changes: 22 additions & 12 deletions packages/demo/backend/src/controllers/wallet.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { Context } from 'hono'
import type { Address } from 'viem'
import { z } from 'zod'

import type {
CreateWalletResponse,
GetAllWalletsResponse,
GetWalletResponse,
} from '@eth-optimism/verbs-sdk'
import type { Context } from 'hono'
import type { Address } from 'viem'
import { z } from 'zod'
} from '@/types/service.js'

import { validateRequest } from '../helpers/validation.js'
import * as walletService from '../services/wallet.js'
Expand Down Expand Up @@ -58,13 +59,16 @@ export class WalletController {
const {
params: { userId },
} = validation.data
const wallet = await walletService.createWallet(userId)
const { privyAddress, smartWalletAddress } =
await walletService.createWallet()

return c.json({
address: wallet.address,
privyAddress,
smartWalletAddress,
userId,
} satisfies CreateWalletResponse)
} catch (error) {
console.error(error)
return c.json(
{
error: 'Failed to create wallet',
Expand All @@ -86,7 +90,7 @@ export class WalletController {
const {
params: { userId },
} = validation.data
const wallet = await walletService.getWallet(userId)
const { wallet } = await walletService.getWallet(userId)

if (!wallet) {
return c.json(
Expand All @@ -97,12 +101,14 @@ export class WalletController {
404,
)
}
const walletAddress = await wallet.getAddress()

return c.json({
address: wallet.address,
address: walletAddress,
userId,
} satisfies GetWalletResponse)
} catch (error) {
console.error(error)
return c.json(
{
error: 'Failed to get wallet',
Expand All @@ -125,15 +131,19 @@ export class WalletController {
query: { limit, cursor },
} = validation.data
const wallets = await walletService.getAllWallets({ limit, cursor })
const walletsData = await Promise.all(
wallets.map(async ({ wallet, id }) => ({
address: await wallet.getAddress(),
id,
})),
)

return c.json({
wallets: wallets.map((wallet) => ({
address: wallet.address,
id: wallet.id,
})),
wallets: walletsData,
count: wallets.length,
} satisfies GetAllWalletsResponse)
} catch (error) {
console.error(error)
return c.json(
{
error: 'Failed to get wallets',
Expand Down
Loading