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
4 changes: 2 additions & 2 deletions packages/demo/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@
"attribution": "tsx scripts/attribution.ts"
},
"dependencies": {
"@eth-optimism/utils-app": "^0.0.6",
"@eth-optimism/actions-sdk": "workspace:*",
"@eth-optimism/utils-app": "^0.0.6",
"@eth-optimism/viem": "^0.4.13",
"@hono/node-server": "^1.14.0",
"@privy-io/server-auth": "^1.31.1",
"@privy-io/node": "^0.3.0",
"commander": "^13.1.0",
"dotenv": "^16.4.5",
"envalid": "^8.1.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/demo/backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class ActionsApp extends App {
return null
},
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowHeaders: ['Content-Type', 'Authorization'],
allowHeaders: ['Content-Type', 'Authorization', 'privy-id-token'],
}),
)

Expand Down
16 changes: 11 additions & 5 deletions packages/demo/backend/src/config/actions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createActions } from '@eth-optimism/actions-sdk'
import type { NodeActionsConfig } from '@eth-optimism/actions-sdk/node'
import { PrivyClient } from '@privy-io/server-auth'
import { type AuthorizationContext, PrivyClient } from '@privy-io/node'

import { BASE_SEPOLIA, UNICHAIN } from './chains.js'
import { env } from './env.js'
Expand All @@ -16,6 +16,7 @@ export function createActionsConfig(): NodeActionsConfig<'privy'> {
type: 'privy',
config: {
privyClient: getPrivyClient(),
authorizationContext: getAuthorizationContext(),
},
},
},
Expand Down Expand Up @@ -51,9 +52,14 @@ export function getActions() {
}

export function getPrivyClient() {
const privy = new PrivyClient(env.PRIVY_APP_ID, env.PRIVY_APP_SECRET)
if (env.SESSION_SIGNER_PK) {
privy.walletApi.updateAuthorizationKey(env.SESSION_SIGNER_PK)
return new PrivyClient({
appId: env.PRIVY_APP_ID,
appSecret: env.PRIVY_APP_SECRET,
})
}

export function getAuthorizationContext(): AuthorizationContext {
return {
authorization_private_keys: [env.SESSION_SIGNER_PK],
}
return privy
}
10 changes: 4 additions & 6 deletions packages/demo/backend/src/controllers/lend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,19 +69,18 @@ export async function openPosition(c: Context) {
body: { amount, tokenAddress, marketId },
} = validation.data
const auth = c.get('auth') as AuthContext | undefined
if (!auth || !auth.userId) {
if (!auth || !auth.idToken) {
return c.json({ error: 'Unauthorized' }, 401)
}

const result = await lendService.openPosition({
userId: auth.userId,
idToken: auth.idToken,
amount,
tokenAddress: tokenAddress as Address,
marketId: {
address: marketId.address as Address,
chainId: marketId.chainId as SupportedChainId,
},
isUserWallet: Boolean(auth?.userId),
})

return c.json({ result: serializeBigInt(result) })
Expand Down Expand Up @@ -113,19 +112,18 @@ export async function closePosition(c: Context) {
body: { amount, tokenAddress, marketId },
} = validation.data
const auth = c.get('auth') as AuthContext | undefined
if (!auth || !auth.userId) {
if (!auth || !auth.idToken) {
return c.json({ error: 'Unauthorized' }, 401)
}

const result = await lendService.closePosition({
userId: auth.userId,
idToken: auth.idToken,
amount,
tokenAddress: tokenAddress as Address,
marketId: {
address: marketId.address as Address,
chainId: marketId.chainId as SupportedChainId,
},
isUserWallet: Boolean(auth?.userId),
})

return c.json({ result: serializeBigInt(result) })
Expand Down
55 changes: 10 additions & 45 deletions packages/demo/backend/src/controllers/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ import { type Address } from 'viem'
import { z } from 'zod'

import type { AuthContext } from '@/middleware/auth.js'
import type {
CreateWalletResponse,
GetWalletResponse,
} from '@/types/service.js'
import type { GetWalletResponse } from '@/types/service.js'

import { validateRequest } from '../helpers/validation.js'
import * as walletService from '../services/wallet.js'
Expand All @@ -23,63 +20,31 @@ const LendPositionRequestSchema = z.object({
})

export class WalletController {
/**
* POST - Create a new wallet for a user
*/
async createWallet(c: Context) {
try {
const auth = c.get('auth') as AuthContext | undefined

if (!auth || !auth.userId) {
return c.json({ error: 'Unauthorized' }, 401)
}

const { privyAddress, smartWalletAddress } =
await walletService.createWallet()

return c.json({
privyAddress,
smartWalletAddress,
userId: auth.userId,
} satisfies CreateWalletResponse)
} catch (error) {
console.error(error)
return c.json(
{
error: 'Failed to create wallet',
message: error instanceof Error ? error.message : 'Unknown error',
},
500,
)
}
}

/**
* GET - Retrieve wallet information by user ID
*/
async getWallet(c: Context) {
try {
const auth = c.get('auth') as AuthContext | undefined

if (!auth || !auth.userId) {
if (!auth || !auth.idToken) {
return c.json({ error: 'Unauthorized' }, 401)
}

const wallet = await walletService.getWallet(auth.userId)
const wallet = await walletService.getWallet(auth.idToken)

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

return c.json({
address: wallet.address,
userId: auth.userId,
} satisfies GetWalletResponse)
} catch (error) {
console.error(error)
Expand All @@ -100,11 +65,11 @@ export class WalletController {
try {
const auth = c.get('auth') as AuthContext | undefined

if (!auth || !auth.userId) {
if (!auth || !auth.idToken) {
return c.json({ error: 'Unauthorized' }, 401)
}

const wallet = await walletService.getWallet(auth.userId)
const wallet = await walletService.getWallet(auth.idToken)
if (!wallet) {
throw new Error('Wallet not found')
}
Expand Down Expand Up @@ -138,11 +103,11 @@ export class WalletController {

const auth = c.get('auth') as AuthContext | undefined

if (!auth || !auth.userId) {
if (!auth || !auth.idToken) {
return c.json({ error: 'Unauthorized' }, 401)
}

const wallet = await walletService.getWallet(auth.userId)
const wallet = await walletService.getWallet(auth.idToken)
if (!wallet) {
throw new Error('Wallet not found')
}
Expand All @@ -156,11 +121,11 @@ export class WalletController {
async fundWallet(c: Context) {
try {
const auth = c.get('auth') as AuthContext | undefined
if (!auth || !auth.userId) {
if (!auth || !auth.idToken) {
return c.json({ error: 'Unauthorized' }, 401)
}

const wallet = await walletService.getWallet(auth.userId)
const wallet = await walletService.getWallet(auth.idToken)
if (!wallet) {
throw new Error('Wallet not found')
}
Expand Down
12 changes: 8 additions & 4 deletions packages/demo/backend/src/middleware/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,28 @@ import type { Context, Next } from 'hono'
import { getPrivyClient } from '@/config/actions.js'

export interface AuthContext {
userId: string
idToken: string
}

export async function authMiddleware(c: Context, next: Next) {
const authHeader = c.req.header('Authorization')
const idToken = c.req.header('privy-id-token')

if (!authHeader?.startsWith('Bearer ')) {
return c.json({ error: 'Unauthorized' }, 401)
}

if (!idToken) {
return c.json({ error: 'Unauthorized' }, 401)
}

const accessToken = parseAuthorizationHeader(authHeader)

try {
const privy = getPrivyClient()
const verifiedPrivy = await privy.verifyAuthToken(accessToken)
const userId = verifiedPrivy.userId
await privy.utils().auth().verifyAuthToken(accessToken)
const authContext: AuthContext = {
userId,
idToken,
}
c.set('auth', authContext)
} catch (err) {
Expand Down
2 changes: 1 addition & 1 deletion packages/demo/backend/src/mocks/MockPrivyClient.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { PrivyClient } from '@privy-io/server-auth'
import type { PrivyClient } from '@privy-io/node'
import type { Address } from 'viem'

import { getRandomAddress } from '@/utils/testUtils.js'
Expand Down
1 change: 0 additions & 1 deletion packages/demo/backend/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ router.get(
walletController.getLendPosition,
)
// Parameterized routes
router.post('/wallet', authMiddleware, walletController.createWallet)
router.get('/wallet', authMiddleware, walletController.getWallet)
router.post('/wallet/fund', authMiddleware, walletController.fundWallet)

Expand Down
3 changes: 1 addition & 2 deletions packages/demo/backend/src/services/lend.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,13 @@ describe('Lend Service', () => {

await expect(
lendService.closePosition({
userId: 'invalid-wallet',
idToken: 'invalid-wallet',
amount: 500,
tokenAddress: '0x078d782b760474a361dda0af3839290b0ef57ad6',
marketId: {
address: '0x38f4f3B6533de0023b9DCd04b02F93d36ad1F9f9',
chainId: 130,
},
isUserWallet: false,
}),
).rejects.toThrow('Wallet not found')
})
Expand Down
21 changes: 3 additions & 18 deletions packages/demo/backend/src/services/lend.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type {
LendMarket,
LendMarketId,
LendMarketPosition,
LendTransactionReceipt,
SupportedChainId,
} from '@eth-optimism/actions-sdk'
Expand Down Expand Up @@ -50,30 +49,16 @@ export async function getMarket(marketId: LendMarketId): Promise<LendMarket> {
return await actions.lend.getMarket(marketId)
}

export async function getPosition(
marketId: LendMarketId,
walletId: string,
): Promise<LendMarketPosition> {
// Try to get wallet as authenticated user first (for Privy user IDs like did:privy:...)
const wallet = await getWallet(walletId)

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

return wallet.lend!.getPosition({ marketId })
}

async function executePosition(
params: PositionParams,
operation: 'open' | 'close',
): Promise<LendTransactionReceipt> {
const { userId, amount, tokenAddress, marketId } = params
const { idToken, amount, tokenAddress, marketId } = params

try {
const wallet = await getWallet(userId)
const wallet = await getWallet(idToken)
if (!wallet) {
const error = `Wallet not found for user ID: ${userId}`
const error = `Wallet not found`
console.error('[executePositionV1] ERROR:', error)
throw new Error(error)
}
Expand Down
Loading