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
2 changes: 2 additions & 0 deletions packages/demo/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
},
"dependencies": {
"@clerk/backend": "^2.12.0",
"@dynamic-labs-wallet/node": "^0.0.158",
"@dynamic-labs-wallet/node-evm": "^0.0.158",
"@eth-optimism/utils-app": "^0.0.6",
"@eth-optimism/verbs-sdk": "workspace:*",
"@eth-optimism/viem": "^0.4.13",
Expand Down
2 changes: 2 additions & 0 deletions packages/demo/backend/src/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,6 @@ export const env = cleanEnv(process.env, {
BASE_SEPOLIA_BUNDER_URL: str({ devDefault: 'dummy' }),
UNICHAIN_BUNDLER_URL: str({ devDefault: 'dummy' }),
UNICHAIN_BUNDLER_SPONSORSHIP_POLICY: str({ devDefault: 'dummy' }),
DYNAMIC_AUTH_TOKEN: str({ devDefault: 'dummy' }),
DYNAMIC_ENVIRONMENT_ID: str({ devDefault: 'dummy' }),
})
21 changes: 13 additions & 8 deletions packages/demo/backend/src/config/verbs.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
import { DynamicEvmWalletClient } from '@dynamic-labs-wallet/node-evm'
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: Verbs<'privy'>
let verbsInstance: Verbs<'dynamic'>

export function createVerbsConfig(): VerbsConfig<'privy'> {
export function createVerbsConfig(): VerbsConfig<'dynamic'> {
return {
wallet: {
hostedWalletConfig: {
provider: {
type: 'privy',
type: 'dynamic',
config: {
privyClient: new PrivyClient(
env.PRIVY_APP_ID,
env.PRIVY_APP_SECRET,
),
dynamicClient: getDynamicClient(),
},
},
},
Expand Down Expand Up @@ -55,7 +53,7 @@ export function createVerbsConfig(): VerbsConfig<'privy'> {
}
}

export function initializeVerbs(config?: VerbsConfig<'privy'>): void {
export function initializeVerbs(config?: VerbsConfig<'dynamic'>): void {
const verbsConfig = config || createVerbsConfig()
verbsInstance = new Verbs(verbsConfig)
}
Expand All @@ -70,3 +68,10 @@ export function getVerbs() {
export function getPrivyClient() {
return new PrivyClient(env.PRIVY_APP_ID, env.PRIVY_APP_SECRET)
}

export function getDynamicClient() {
return new DynamicEvmWalletClient({
authToken: env.DYNAMIC_AUTH_TOKEN,
environmentId: env.DYNAMIC_ENVIRONMENT_ID,
})
}
20 changes: 13 additions & 7 deletions packages/demo/backend/src/controllers/lend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import { serializeBigInt } from '../utils/serializers.js'

const DepositRequestSchema = z.object({
body: z.object({
walletId: z.string().min(1, 'walletId is required'),
walletAddress: z
.string()
.regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid wallet address format')
.trim(),
amount: z.number().positive('amount must be positive'),
tokenAddress: z
.string()
Expand All @@ -23,7 +26,10 @@ const MarketBalanceParamsSchema = z.object({
vaultAddress: z
.string()
.regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid vault address format'),
walletId: z.string().min(1, 'walletId is required'),
walletAddress: z
.string()
.regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid wallet address format')
.trim(),
}),
})

Expand Down Expand Up @@ -93,11 +99,11 @@ export class LendController {
if (!validation.success) return validation.response

const {
params: { vaultAddress, walletId },
params: { vaultAddress, walletAddress },
} = validation.data
const balance = await lendService.getMarketBalance(
vaultAddress as Address,
walletId,
walletAddress as Address,
)
const formattedBalance =
await lendService.formatMarketBalanceResponse(balance)
Expand All @@ -122,16 +128,16 @@ export class LendController {
if (!validation.success) return validation.response

const {
body: { walletId, amount, tokenAddress, chainId },
body: { walletAddress, amount, tokenAddress, chainId },
} = validation.data
const lendTransaction = await lendService.deposit(
walletId,
walletAddress as Address,
amount,
tokenAddress as Address,
chainId as SupportedChainId,
)
const result = await lendService.executeLendTransaction(
walletId,
walletAddress as Address,
lendTransaction,
chainId as SupportedChainId,
)
Expand Down
53 changes: 28 additions & 25 deletions packages/demo/backend/src/controllers/wallet.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Context } from 'hono'
import type { Address } from 'viem'
import { getAddress } from 'viem'
import { z } from 'zod'

import type {
Expand All @@ -12,21 +13,30 @@ import { validateRequest } from '../helpers/validation.js'
import * as walletService from '../services/wallet.js'
import { serializeBigInt } from '../utils/serializers.js'

const UserIdParamSchema = z.object({
const WalletAddressParamSchema = z.object({
params: z.object({
userId: z.string().min(1, 'User ID is required').trim(),
walletAddress: z
.string()
.regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid wallet address format')
.trim(),
}),
})

const FundWalletRequestSchema = z.object({
params: z.object({
userId: z.string().min(1, 'User ID is required').trim(),
walletAddress: z
.string()
.regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid wallet address format')
.trim(),
}),
})

const SendTokensRequestSchema = z.object({
body: z.object({
walletId: z.string().min(1, 'walletId is required'),
walletAddress: z
.string()
.regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid wallet address format')
.trim(),
amount: z.number().positive('amount must be positive'),
recipientAddress: z
.string()
Expand All @@ -50,19 +60,12 @@ export class WalletController {
*/
async createWallet(c: Context) {
try {
const validation = await validateRequest(c, UserIdParamSchema)
if (!validation.success) return validation.response

const {
params: { userId },
} = validation.data
const { privyAddress, smartWalletAddress } =
const { signerAddress, smartWalletAddress } =
await walletService.createWallet()

return c.json({
privyAddress,
signerAddress,
smartWalletAddress,
userId,
} satisfies CreateWalletResponse)
} catch (error) {
console.error(error)
Expand All @@ -81,27 +84,27 @@ export class WalletController {
*/
async getWallet(c: Context) {
try {
const validation = await validateRequest(c, UserIdParamSchema)
const validation = await validateRequest(c, WalletAddressParamSchema)
if (!validation.success) return validation.response

const {
params: { userId },
params: { walletAddress },
} = validation.data
const wallet = await walletService.getWallet(userId)
const wallet = await walletService.getWallet(getAddress(walletAddress))

if (!wallet) {
return c.json(
{
error: 'Wallet not found',
message: `No wallet found for user ${userId}`,
message: `No wallet found for user ${walletAddress}`,
},
404,
)
}

return c.json({
address: wallet.address,
userId,
userId: walletAddress,
} satisfies GetWalletResponse)
} catch (error) {
console.error(error)
Expand Down Expand Up @@ -153,13 +156,13 @@ export class WalletController {
*/
async getBalance(c: Context) {
try {
const validation = await validateRequest(c, UserIdParamSchema)
const validation = await validateRequest(c, WalletAddressParamSchema)
if (!validation.success) return validation.response

const {
params: { userId },
params: { walletAddress },
} = validation.data
const balance = await walletService.getBalance(userId)
const balance = await walletService.getBalance(getAddress(walletAddress))

return c.json({ balance: serializeBigInt(balance) })
} catch (error) {
Expand All @@ -183,10 +186,10 @@ export class WalletController {
if (!validation.success) return validation.response

const {
params: { userId },
params: { walletAddress },
} = validation.data

const result = await walletService.fundWallet(userId)
const result = await walletService.fundWallet(getAddress(walletAddress))

return c.json(result)
} catch (error) {
Expand All @@ -209,11 +212,11 @@ export class WalletController {
if (!validation.success) return validation.response

const {
body: { walletId, amount, recipientAddress },
body: { walletAddress, amount, recipientAddress },
} = validation.data

const transactionData = await walletService.sendTokens(
walletId,
getAddress(walletAddress),
amount,
recipientAddress as Address,
)
Expand Down
8 changes: 4 additions & 4 deletions packages/demo/backend/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,17 @@ router.get('/version', (c) => {
// router.post('/wallet/:userId', authMiddleware, walletController.createWallet)

router.get('/wallets', walletController.getAllWallets)
router.post('/wallet/:userId', walletController.createWallet)
router.post('/wallet', walletController.createWallet)
router.get('/wallet/:userId', walletController.getWallet)
router.get('/wallet/:userId/balance', walletController.getBalance)
router.post('/wallet/:userId/fund', walletController.fundWallet)
router.get('/wallet/:walletAddress/balance', walletController.getBalance)
router.post('/wallet/:walletAddress/fund', walletController.fundWallet)
router.post('/wallet/send', walletController.sendTokens)

// Lend endpoints
router.get('/lend/markets', lendController.getMarkets)
router.get('/lend/market/:chainId/:marketId', lendController.getMarket)
router.get(
'/lend/market/:vaultAddress/balance/:walletId',
'/lend/market/:vaultAddress/balance/:walletAddress',
lendController.getMarketBalance,
)
router.post('/lend/deposit', lendController.deposit)
18 changes: 9 additions & 9 deletions packages/demo/backend/src/services/lend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,13 @@ export async function getMarket(

export async function getMarketBalance(
vaultAddress: Address,
walletId: string,
walletAddress: Address,
): Promise<MarketBalanceResult> {
const verbs = getVerbs()
const wallet = await getWallet(walletId)
const wallet = await getWallet(walletAddress)

if (!wallet) {
throw new Error(`Wallet not found for user ID: ${walletId}`)
throw new Error(`Wallet not found for user ID: ${walletAddress}`)
}

return verbs.lend.getMarketBalance(vaultAddress, wallet.address)
Expand Down Expand Up @@ -109,15 +109,15 @@ export async function formatMarketBalanceResponse(
}

export async function deposit(
walletId: string,
walletAddress: Address,
amount: number,
tokenAddress: Address,
chainId: SupportedChainId,
): Promise<LendTransaction> {
const wallet = await getWallet(walletId)
const wallet = await getWallet(walletAddress)

if (!wallet) {
throw new Error(`Wallet not found for user ID: ${walletId}`)
throw new Error(`Wallet not found for user ID: ${walletAddress}`)
}

if ('lendExecute' in wallet && typeof wallet.lendExecute === 'function') {
Expand All @@ -130,14 +130,14 @@ export async function deposit(
}

export async function executeLendTransaction(
walletId: string,
walletAddress: Address,
lendTransaction: LendTransaction,
chainId: SupportedChainId,
): Promise<LendTransaction & { blockExplorerUrl: string }> {
const wallet = await getWallet(walletId)
const wallet = await getWallet(walletAddress)

if (!wallet) {
throw new Error(`Wallet not found for user ID: ${walletId}`)
throw new Error(`Wallet not found for user ID: ${walletAddress}`)
}

if (!lendTransaction.transactionData) {
Expand Down
Loading