From 07274a5fcb32d769fbd9a510c4493a9b4460bb11 Mon Sep 17 00:00:00 2001 From: tre Date: Thu, 30 Oct 2025 15:12:09 -0700 Subject: [PATCH] chore: update privy dependency --- packages/demo/backend/package.json | 4 +- packages/demo/backend/src/app.ts | 2 +- packages/demo/backend/src/config/actions.ts | 16 +- packages/demo/backend/src/controllers/lend.ts | 10 +- .../demo/backend/src/controllers/wallet.ts | 55 ++----- packages/demo/backend/src/middleware/auth.ts | 12 +- .../demo/backend/src/mocks/MockPrivyClient.ts | 2 +- packages/demo/backend/src/router.ts | 1 - .../demo/backend/src/services/lend.spec.ts | 3 +- packages/demo/backend/src/services/lend.ts | 21 +-- .../demo/backend/src/services/wallet.spec.ts | 94 ------------ packages/demo/backend/src/services/wallet.ts | 38 +---- packages/demo/backend/src/types/lend.ts | 3 +- packages/demo/backend/src/types/service.ts | 26 +--- .../src/components/EarnWithFrontendWallet.tsx | 6 +- .../components/EarnWithPrivyServerWallet.tsx | 30 +++- .../src/components/EarnWithServerWallet.tsx | 13 +- .../docs/ConfigureActionsSection.tsx | 8 +- .../docs/ConfigureSignersSection.tsx | 7 +- .../docs/ConfigureWalletsSection.tsx | 7 +- .../components/home/HostedWalletsSection.tsx | 7 +- .../components/home/SmartWalletsSection.tsx | 7 +- .../src/hooks/useBalanceOperations.ts | 38 ++--- packages/sdk/package.json | 2 +- packages/sdk/src/actions.test.ts | 28 +++- packages/sdk/src/test/MockPrivyClient.ts | 115 +++----------- .../__tests__/WalletNamespace.spec.ts | 117 +++++++-------- .../__tests__/WalletProvider.spec.ts | 86 ++++++----- .../HostedWalletProviderRegistry.spec.ts | 27 +++- .../hosted/privy/PrivyHostedWalletProvider.ts | 36 +++-- .../PrivyHostedWalletProvider.spec.ts | 61 +++++--- .../NodeHostedWalletProviderRegistry.ts | 7 +- .../NodeHostedWalletProviderRegistry.spec.ts | 19 ++- .../node/providers/hosted/types/index.ts | 11 +- .../node/wallets/hosted/privy/PrivyWallet.ts | 20 ++- .../privy/__tests__/PrivyWallet.spec.ts | 75 +++++---- .../utils/__tests__/createSigner.spec.ts | 32 ++-- .../hosted/privy/utils/createSigner.ts | 20 +-- pnpm-lock.yaml | 142 +++++------------- 39 files changed, 510 insertions(+), 698 deletions(-) delete mode 100644 packages/demo/backend/src/services/wallet.spec.ts diff --git a/packages/demo/backend/package.json b/packages/demo/backend/package.json index 3bc255d9..12d7c4b7 100644 --- a/packages/demo/backend/package.json +++ b/packages/demo/backend/package.json @@ -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", diff --git a/packages/demo/backend/src/app.ts b/packages/demo/backend/src/app.ts index 6c152ecc..e09ef468 100644 --- a/packages/demo/backend/src/app.ts +++ b/packages/demo/backend/src/app.ts @@ -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'], }), ) diff --git a/packages/demo/backend/src/config/actions.ts b/packages/demo/backend/src/config/actions.ts index 8a5d21e2..45f29591 100644 --- a/packages/demo/backend/src/config/actions.ts +++ b/packages/demo/backend/src/config/actions.ts @@ -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' @@ -16,6 +16,7 @@ export function createActionsConfig(): NodeActionsConfig<'privy'> { type: 'privy', config: { privyClient: getPrivyClient(), + authorizationContext: getAuthorizationContext(), }, }, }, @@ -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 } diff --git a/packages/demo/backend/src/controllers/lend.ts b/packages/demo/backend/src/controllers/lend.ts index 6aa8623e..595dba8b 100644 --- a/packages/demo/backend/src/controllers/lend.ts +++ b/packages/demo/backend/src/controllers/lend.ts @@ -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) }) @@ -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) }) diff --git a/packages/demo/backend/src/controllers/wallet.ts b/packages/demo/backend/src/controllers/wallet.ts index 6568210d..595a1726 100644 --- a/packages/demo/backend/src/controllers/wallet.ts +++ b/packages/demo/backend/src/controllers/wallet.ts @@ -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' @@ -23,37 +20,6 @@ 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 */ @@ -61,17 +27,17 @@ 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) { return c.json( { error: 'Wallet not found', - message: `No wallet found for user ${auth.userId}`, + message: `No wallet found for user`, }, 404, ) @@ -79,7 +45,6 @@ export class WalletController { return c.json({ address: wallet.address, - userId: auth.userId, } satisfies GetWalletResponse) } catch (error) { console.error(error) @@ -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') } @@ -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') } @@ -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') } diff --git a/packages/demo/backend/src/middleware/auth.ts b/packages/demo/backend/src/middleware/auth.ts index 368e6735..6ba6b814 100644 --- a/packages/demo/backend/src/middleware/auth.ts +++ b/packages/demo/backend/src/middleware/auth.ts @@ -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) { diff --git a/packages/demo/backend/src/mocks/MockPrivyClient.ts b/packages/demo/backend/src/mocks/MockPrivyClient.ts index 38f77c40..46f4382b 100644 --- a/packages/demo/backend/src/mocks/MockPrivyClient.ts +++ b/packages/demo/backend/src/mocks/MockPrivyClient.ts @@ -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' diff --git a/packages/demo/backend/src/router.ts b/packages/demo/backend/src/router.ts index 00e7de98..d221ddc1 100644 --- a/packages/demo/backend/src/router.ts +++ b/packages/demo/backend/src/router.ts @@ -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) diff --git a/packages/demo/backend/src/services/lend.spec.ts b/packages/demo/backend/src/services/lend.spec.ts index 2b5b6107..4fa2ee55 100644 --- a/packages/demo/backend/src/services/lend.spec.ts +++ b/packages/demo/backend/src/services/lend.spec.ts @@ -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') }) diff --git a/packages/demo/backend/src/services/lend.ts b/packages/demo/backend/src/services/lend.ts index 387e50d6..1af763cf 100644 --- a/packages/demo/backend/src/services/lend.ts +++ b/packages/demo/backend/src/services/lend.ts @@ -1,7 +1,6 @@ import type { LendMarket, LendMarketId, - LendMarketPosition, LendTransactionReceipt, SupportedChainId, } from '@eth-optimism/actions-sdk' @@ -50,30 +49,16 @@ export async function getMarket(marketId: LendMarketId): Promise { return await actions.lend.getMarket(marketId) } -export async function getPosition( - marketId: LendMarketId, - walletId: string, -): Promise { - // 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 { - 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) } diff --git a/packages/demo/backend/src/services/wallet.spec.ts b/packages/demo/backend/src/services/wallet.spec.ts deleted file mode 100644 index 9cc0048e..00000000 --- a/packages/demo/backend/src/services/wallet.spec.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' - -import * as walletService from './wallet.js' - -// Mock the Actions SDK -const mockActions = { - wallet: { - hostedWalletProvider: { - toActionsWallet: vi.fn(), - }, - smartWalletProvider: { - getWalletAddress: vi.fn(), - getWallet: vi.fn(), - }, - getSmartWallet: vi.fn(), - createSmartWallet: vi.fn(), - toActionsWallet: vi.fn(({ address }: { address: string }) => ({ - address, - signer: { - address, - }, - })), - createSigner: vi.fn(({ address }: { address: string }) => ({ - address, - type: 'local', - })), - }, -} -const mockPrivyClient = { - walletApi: { - createWallet: vi.fn(), - getWallet: vi.fn(), - getWallets: vi.fn(), - }, -} - -// Mock the getActions function -vi.mock('../config/actions.js', () => ({ - getActions: () => mockActions, - getPrivyClient: () => mockPrivyClient, -})) - -describe('Wallet Service', () => { - beforeEach(() => { - vi.clearAllMocks() - }) - - describe('createWallet', () => { - it('should create a wallet using the Actions SDK', async () => { - const mockPrivyWallet = { - id: 'wallet-123', - address: '0x1234567890123456789012345678901234567890', - } - mockPrivyClient.walletApi.createWallet.mockResolvedValue(mockPrivyWallet) - - const mockWallet = { - wallet: { - id: 'wallet-123', - address: '0x1234567890123456789012345678901234567890', - signer: { - address: '0x1234567890123456789012345678901234567890', - }, - lend: {}, - }, - deployments: [{ chainId: 1, receipt: undefined, success: true }], - } - - mockActions.wallet.createSmartWallet.mockResolvedValue(mockWallet) - - const result = await walletService.createWallet() - - expect(mockActions.wallet.createSmartWallet).toHaveBeenCalledWith({ - signer: { - type: 'local', - address: '0x1234567890123456789012345678901234567890', - }, - }) - expect(result).toEqual({ - privyAddress: '0x1234567890123456789012345678901234567890', - smartWalletAddress: '0x1234567890123456789012345678901234567890', - }) - }) - - it('should handle wallet creation errors', async () => { - const error = new Error('Wallet creation failed') - - mockActions.wallet.createSmartWallet.mockRejectedValue(error) - - await expect(walletService.createWallet()).rejects.toThrow( - 'Wallet creation failed', - ) - }) - }) -}) diff --git a/packages/demo/backend/src/services/wallet.ts b/packages/demo/backend/src/services/wallet.ts index 71f599fe..5ecc6ba8 100644 --- a/packages/demo/backend/src/services/wallet.ts +++ b/packages/demo/backend/src/services/wallet.ts @@ -7,7 +7,7 @@ import type { Wallet, } from '@eth-optimism/actions-sdk' import { getTokenBySymbol } from '@eth-optimism/actions-sdk' -import type { WalletWithMetadata } from '@privy-io/server-auth' +import type { User } from '@privy-io/node' import type { Address } from 'viem' import { encodeFunctionData, formatUnits, getAddress } from 'viem' import { baseSepolia } from 'viem/chains' @@ -28,45 +28,21 @@ export interface GetAllWalletsOptions { cursor?: string } -export async function createWallet(): Promise<{ - privyAddress: string - smartWalletAddress: string -}> { - const actions = getActions() - const privyClient = getPrivyClient() - const privyWallet = await privyClient.walletApi.createWallet({ - chainType: 'ethereum', - }) - const privySigner = await actions.wallet.createSigner({ - walletId: privyWallet.id, - address: getAddress(privyWallet.address), - }) - const { wallet } = await actions.wallet.createSmartWallet({ - signer: privySigner, - }) - const smartWalletAddress = wallet.address - return { - privyAddress: wallet.signer.address, - smartWalletAddress, - } -} - -export async function getWallet(userId: string): Promise { +export async function getWallet(idToken: string): Promise { const actions = getActions() const privyClient = getPrivyClient() - // Get wallet via user ID (for authenticated users) - const privyUser = await privyClient.getUserById(userId) + const privyUser = await privyClient.users().get({ id_token: idToken }) if (!privyUser) { return null } // Get the first embedded ethereum wallet from linked accounts - const walletAccount = privyUser.linkedAccounts?.find( - (account): account is WalletWithMetadata => + const walletAccount = privyUser.linked_accounts.find( + (account): account is User.LinkedAccountEthereumEmbeddedWallet => account.type === 'wallet' && - account.walletClientType === 'privy' && - account.chainType === 'ethereum', + account.wallet_client === 'privy' && + account.chain_type === 'ethereum', ) if (!walletAccount) { diff --git a/packages/demo/backend/src/types/lend.ts b/packages/demo/backend/src/types/lend.ts index 873dda92..a0b0f21b 100644 --- a/packages/demo/backend/src/types/lend.ts +++ b/packages/demo/backend/src/types/lend.ts @@ -26,11 +26,10 @@ export interface FormattedMarketResponse { * Position parameters for opening/closing */ export interface PositionParams { - userId: string + idToken: string amount: number tokenAddress: Address marketId: LendMarketId - isUserWallet?: boolean } /** diff --git a/packages/demo/backend/src/types/service.ts b/packages/demo/backend/src/types/service.ts index 2b99bb89..cfd0b03a 100644 --- a/packages/demo/backend/src/types/service.ts +++ b/packages/demo/backend/src/types/service.ts @@ -16,35 +16,11 @@ export interface WalletData { } /** - * Response from GET /wallets endpoint - */ -export interface GetAllWalletsResponse { - /** Array of wallet data */ - wallets: WalletData[] - /** Total count of wallets returned */ - count: number -} - -/** - * Response from POST /wallet/:userId endpoint - */ -export interface CreateWalletResponse { - /** Wallet address */ - privyAddress: string - /** Smart wallet address */ - smartWalletAddress: string - /** User ID */ - userId: string -} - -/** - * Response from GET /wallet/:userId endpoint + * Response from GET /wallet endpoint */ export interface GetWalletResponse { /** Wallet address */ address: Address - /** User ID */ - userId: string } /** diff --git a/packages/demo/frontend/src/components/EarnWithFrontendWallet.tsx b/packages/demo/frontend/src/components/EarnWithFrontendWallet.tsx index 69d21fe7..b720e153 100644 --- a/packages/demo/frontend/src/components/EarnWithFrontendWallet.tsx +++ b/packages/demo/frontend/src/components/EarnWithFrontendWallet.tsx @@ -68,7 +68,7 @@ export function EarnWithFrontendWallet({ wallet!.lend!.closePosition(positionParams), [wallet], ) - const isReady = useCallback(() => !!wallet, [wallet]) + const ready = !!wallet const { usdcBalance, @@ -87,12 +87,12 @@ export function EarnWithFrontendWallet({ mintUSDC, openPosition, closePosition, - isReady, + ready, }) return ( ()) + const ethereumEmbeddedWallets = useMemo( () => (user?.linkedAccounts?.filter( @@ -29,13 +33,26 @@ export function EarnWithPrivyServerWallet() { [user], ) + const { identityToken } = useIdentityToken() + + const isReady = + !!user?.id && ready && ethereumEmbeddedWallets.length > 0 && !!identityToken + const addSessionSigner = useCallback( async (walletAddress: string) => { + // Skip if already processed or in progress + if (processedWallets.current.has(walletAddress)) { + return + } + if (!env.VITE_SESSION_SIGNER_ID) { console.error('SESSION_SIGNER_ID must be defined to addSessionSigner') return } + // Mark as in progress + processedWallets.current.add(walletAddress) + try { await addSessionSigners({ address: walletAddress, @@ -47,6 +64,8 @@ export function EarnWithPrivyServerWallet() { }) } catch (error) { console.error('Error adding session signer:', error) + // Remove from processed set on error so it can be retried + processedWallets.current.delete(walletAddress) } }, [addSessionSigners], @@ -54,8 +73,11 @@ export function EarnWithPrivyServerWallet() { const getAuthHeaders = useCallback(async () => { const token = await getAccessToken() - return token ? { Authorization: `Bearer ${token}` } : undefined - }, [getAccessToken]) + + return token + ? { Authorization: `Bearer ${token}`, 'privy-id-token': identityToken } + : undefined + }, [getAccessToken, identityToken]) // Add session signers for undelegated wallets useEffect(() => { @@ -77,7 +99,7 @@ export function EarnWithPrivyServerWallet() { userId={user?.id} embeddedWalletExists={ethereumEmbeddedWallets.length > 0} userEmailAddress={user?.email?.address} - ready={ready} + ready={isReady} logout={logout} getAuthHeaders={getAuthHeaders} /> diff --git a/packages/demo/frontend/src/components/EarnWithServerWallet.tsx b/packages/demo/frontend/src/components/EarnWithServerWallet.tsx index 808a41b9..81fba78f 100644 --- a/packages/demo/frontend/src/components/EarnWithServerWallet.tsx +++ b/packages/demo/frontend/src/components/EarnWithServerWallet.tsx @@ -27,8 +27,6 @@ interface EarnWithServerWalletProps { * and passes data/callbacks to the presentational EarnContent component */ export function EarnWithServerWallet({ - userId, - embeddedWalletExists, ready, getAuthHeaders, logout, @@ -77,11 +75,6 @@ export function EarnWithServerWallet({ [getAuthHeaders], ) - const isReady = useCallback( - () => !!userId && embeddedWalletExists, - [userId, embeddedWalletExists], - ) - const { usdcBalance, isLoadingBalance, @@ -99,7 +92,7 @@ export function EarnWithServerWallet({ mintUSDC, openPosition, closePosition, - isReady, + ready, }) const fetchWalletAddress = useCallback(async () => { @@ -109,10 +102,10 @@ export function EarnWithServerWallet({ }, [getAuthHeaders]) useEffect(() => { - if (isReady()) { + if (ready) { fetchWalletAddress() } - }, [isReady, fetchWalletAddress]) + }, [ready, fetchWalletAddress]) return ( Promise - /** Predicate to check if balance operations can be executed */ - isReady: () => boolean + /** is ready to perform balance operations */ + ready: boolean } export function useBalanceOperations(params: UseBalanceOperationsConfig) { @@ -41,7 +41,7 @@ export function useBalanceOperations(params: UseBalanceOperationsConfig) { getMarkets: getMarketsRaw, getPosition: getPositionRaw, mintUSDC: mintUSDCRaw, - isReady, + ready, openPosition, closePosition, } = params @@ -193,7 +193,7 @@ export function useBalanceOperations(params: UseBalanceOperationsConfig) { // Function to mint demo USDC const handleMintUSDC = useCallback(async () => { // Early exit if precondition not met - if (!isReady()) { + if (!ready) { return } @@ -228,11 +228,11 @@ export function useBalanceOperations(params: UseBalanceOperationsConfig) { console.error('Error minting USDC:', error) setIsLoadingBalance(false) } - }, [mintUSDC, isReady, fetchBalance, usdcBalance, getTokenBalances]) + }, [mintUSDC, ready, fetchBalance, usdcBalance, getTokenBalances]) // Auto-initialize balance on first ready state useEffect(() => { - if (!isReady() || hasInitialized.current) { + if (!ready || hasInitialized.current) { return } @@ -248,11 +248,11 @@ export function useBalanceOperations(params: UseBalanceOperationsConfig) { } initialize() - }, [isReady, fetchBalance]) + }, [ready, fetchBalance]) const executePositon = useCallback( async (operation: 'open' | 'close', amount: number) => { - if (!isReady() || !marketData) { + if (!ready || !marketData) { throw new Error('User or market data not available') } const marketId = marketData.marketId @@ -318,13 +318,13 @@ export function useBalanceOperations(params: UseBalanceOperationsConfig) { return { transaction } }, - [isReady, marketData], + [ready, marketData], ) // Handle transaction (lend or withdraw) const handleTransaction = useCallback( async (mode: 'lend' | 'withdraw', amount: number) => { - if (!isReady() || !marketData) { + if (!ready || !marketData) { throw new Error('User or market data not available') } @@ -350,7 +350,7 @@ export function useBalanceOperations(params: UseBalanceOperationsConfig) { // Refresh position after successful transaction with a small delay to ensure state is updated setTimeout(async () => { - if (isReady() && marketData) { + if (ready && marketData) { try { await fetchPosition() } catch { @@ -360,7 +360,7 @@ export function useBalanceOperations(params: UseBalanceOperationsConfig) { }, 1000) // Also refresh wallet balance - if (isReady()) { + if (ready) { setTimeout(async () => { await fetchBalance() }, 2000) @@ -371,7 +371,7 @@ export function useBalanceOperations(params: UseBalanceOperationsConfig) { blockExplorerUrl: explorerUrl, } }, - [isReady, marketData, fetchBalance], + [ready, marketData, fetchBalance], ) // Fetch market APY and data on mount @@ -424,7 +424,7 @@ export function useBalanceOperations(params: UseBalanceOperationsConfig) { const fetchPosition = useCallback( async (backgroundPolling: boolean = false) => { - if (!isReady() || !marketChainId || !marketAddress) return + if (!ready || !marketChainId || !marketAddress) return try { if (!backgroundPolling) { @@ -449,22 +449,22 @@ export function useBalanceOperations(params: UseBalanceOperationsConfig) { } } }, - [isReady, marketChainId, marketAddress, getPosition], + [ready, marketChainId, marketAddress, getPosition], ) // Fetch position when market data is available or user changes useEffect(() => { - if (isReady() && marketChainId && marketAddress) { + if (ready && marketChainId && marketAddress) { fetchPosition() } - }, [isReady, marketChainId, marketAddress, fetchPosition]) + }, [ready, marketChainId, marketAddress, fetchPosition]) useEffect(() => { - if (!isReady() || !marketChainId || !marketAddress) return + if (!ready || !marketChainId || !marketAddress) return const intervalId = setInterval(() => fetchPosition(true), 5000) return () => clearInterval(intervalId) - }, [isReady, marketChainId, marketAddress, fetchPosition]) + }, [ready, marketChainId, marketAddress, fetchPosition]) return { usdcBalance, diff --git a/packages/sdk/package.json b/packages/sdk/package.json index da904555..bbf403cf 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -67,7 +67,7 @@ "@dynamic-labs/waas-evm": ">=4.31.4", "@dynamic-labs/wallet-connector-core": ">=4.31.4", "@privy-io/react-auth": ">=2.24.0", - "@privy-io/server-auth": ">=1.31.1", + "@privy-io/node": ">=0.3.0", "@turnkey/core": ">=1.1.1", "@turnkey/http": ">=3.12.1", "@turnkey/sdk-server": ">=4.9.1", diff --git a/packages/sdk/src/actions.test.ts b/packages/sdk/src/actions.test.ts index 8594fcb6..bf1691d1 100644 --- a/packages/sdk/src/actions.test.ts +++ b/packages/sdk/src/actions.test.ts @@ -3,7 +3,10 @@ import { unichain } from 'viem/chains' import { describe, expect, it } from 'vitest' import { Actions } from '@/actions.js' -import { createMockPrivyClient } from '@/test/MockPrivyClient.js' +import { + createMockPrivyClient, + getMockAuthorizationContext, +} from '@/test/MockPrivyClient.js' import type { LendMarketConfig, MorphoLendConfig } from '@/types/lend/index.js' import { externalTest } from '@/utils/test.js' import { HostedWalletProviderRegistry } from '@/wallet/core/providers/hosted/registry/HostedWalletProviderRegistry.js' @@ -36,10 +39,11 @@ describe('Actions SDK', () => { return Boolean((options as NodeOptionsMap['privy'])?.privyClient) }, create({ chainManager }, options) { - return new PrivyHostedWalletProvider( - options.privyClient, + return new PrivyHostedWalletProvider({ + privyClient: options.privyClient, chainManager, - ) + authorizationContext: options.authorizationContext, + }) }, }) } @@ -67,6 +71,7 @@ describe('Actions SDK', () => { 'test-id', 'test-secret', ), + authorizationContext: getMockAuthorizationContext(), }, }, }, @@ -107,6 +112,7 @@ describe('Actions SDK', () => { 'test-id', 'test-secret', ), + authorizationContext: getMockAuthorizationContext(), }, }, }, @@ -166,6 +172,7 @@ describe('Actions SDK', () => { 'test-id', 'test-secret', ), + authorizationContext: getMockAuthorizationContext(), }, }, }, @@ -248,6 +255,7 @@ describe('Actions SDK', () => { 'test-id', 'test-secret', ), + authorizationContext: getMockAuthorizationContext(), }, }, }, @@ -291,6 +299,7 @@ describe('Actions SDK', () => { 'test-id', 'test-secret', ), + authorizationContext: getMockAuthorizationContext(), }, }, }, @@ -324,6 +333,7 @@ describe('Actions SDK', () => { 'test-id', 'test-secret', ), + authorizationContext: getMockAuthorizationContext(), }, }, }, @@ -369,6 +379,7 @@ describe('Actions SDK', () => { 'test-id', 'test-secret', ), + authorizationContext: getMockAuthorizationContext(), }, }, }, @@ -408,6 +419,7 @@ describe('Actions SDK', () => { 'test-id', 'test-secret', ), + authorizationContext: getMockAuthorizationContext(), }, }, }, @@ -449,6 +461,7 @@ describe('Actions SDK', () => { 'test-id', 'test-secret', ), + authorizationContext: getMockAuthorizationContext(), }, }, }, @@ -492,6 +505,7 @@ describe('Actions SDK', () => { 'test-app-id', 'test-app-secret', ), + authorizationContext: getMockAuthorizationContext(), }, }, }, @@ -545,6 +559,7 @@ describe('Actions SDK', () => { 'test-app-id', 'test-app-secret', ), + authorizationContext: getMockAuthorizationContext(), }, }, }, @@ -599,6 +614,7 @@ describe('Actions SDK', () => { 'test-app-id', 'test-app-secret', ), + authorizationContext: getMockAuthorizationContext(), }, }, }, @@ -686,6 +702,7 @@ describe('Actions SDK', () => { 'test-app-id', 'test-app-secret', ), + authorizationContext: getMockAuthorizationContext(), }, }, }, @@ -748,6 +765,7 @@ describe('Actions SDK', () => { 'test-app-id', 'test-app-secret', ), + authorizationContext: getMockAuthorizationContext(), }, }, }, @@ -801,6 +819,7 @@ describe('Actions SDK', () => { 'test-app-id', 'test-app-secret', ), + authorizationContext: getMockAuthorizationContext(), }, }, }, @@ -852,6 +871,7 @@ describe('Actions SDK', () => { 'test-app-id', 'test-app-secret', ), + authorizationContext: getMockAuthorizationContext(), }, }, }, diff --git a/packages/sdk/src/test/MockPrivyClient.ts b/packages/sdk/src/test/MockPrivyClient.ts index e68c17e5..d5dc3af1 100644 --- a/packages/sdk/src/test/MockPrivyClient.ts +++ b/packages/sdk/src/test/MockPrivyClient.ts @@ -1,5 +1,6 @@ -import type { PrivyClient } from '@privy-io/server-auth' +import type { AuthorizationContext, PrivyClient } from '@privy-io/node' import type { Address } from 'viem' +import { generatePrivateKey } from 'viem/accounts' import { getRandomAddress } from '@/test/utils.js' @@ -8,102 +9,12 @@ import { getRandomAddress } from '@/test/utils.js' * @description Provides a mock implementation of PrivyClient for testing purposes */ export class MockPrivyClient { - public walletApi = { - createWallet: async (params: { chainType: string }) => { - const walletId = `mock-wallet-${++this.walletCounter}` - const address = getRandomAddress() - - const wallet = new MockWallet(walletId, address) - this.mockWallets.set(walletId, wallet) - - return { - id: walletId, - address: address, - chainType: params.chainType, - } - }, - - getWallet: async (params: { id: string }) => { - const wallet = this.mockWallets.get(params.id) - if (!wallet) { - throw new Error(`Wallet ${params.id} not found`) - } - - return { - id: wallet.id, - address: wallet.address, - chainType: 'ethereum', - } - }, - - getWallets: async (params?: { limit?: number; cursor?: string }) => { - const wallets = Array.from(this.mockWallets.values()) - const limit = params?.limit || wallets.length - - return { - data: wallets.slice(0, limit).map((wallet) => ({ - id: wallet.id, - address: wallet.address, - chainType: 'ethereum', - })), - } - }, - - ethereum: { - signMessage: async (params: { walletId: string; message: string }) => { - const wallet = this.mockWallets.get(params.walletId) - if (!wallet) { - throw new Error(`Wallet ${params.walletId} not found`) - } - - // Mock signature - deterministic based on message - const mockSig = `0x${'a'.repeat(128)}${params.message.length.toString(16).padStart(2, '0')}` - return { signature: mockSig } - }, - - secp256k1Sign: async (params: { walletId: string; hash: string }) => { - const wallet = this.mockWallets.get(params.walletId) - if (!wallet) { - throw new Error(`Wallet ${params.walletId} not found`) - } - - // Mock signature - deterministic based on hash - const mockSig = `0x${'b'.repeat(128)}${params.hash.slice(-2)}` - return { signature: mockSig } - }, - - signTransaction: async (params: { - walletId: string - transaction: unknown - }) => { - const wallet = this.mockWallets.get(params.walletId) - if (!wallet) { - throw new Error(`Wallet ${params.walletId} not found`) - } - - // Mock signed transaction - const mockSignedTx = `0x${'c'.repeat(200)}` - return { signedTransaction: mockSignedTx } - }, - }, - } - - private mockWallets = new Map() - private walletCounter = 0 - constructor( public appId: string, public appSecret: string, ) {} } -class MockWallet { - constructor( - public id: string, - public address: Address, - ) {} -} - /** * Create a mock Privy client cast as PrivyClient type * @param appId - Mock app ID @@ -116,3 +27,25 @@ export function createMockPrivyClient( ): PrivyClient { return new MockPrivyClient(appId, appSecret) as unknown as PrivyClient } + +export function createMockPrivyWallet(params?: { + id?: string + address?: Address +}): { + id: string + address: Address +} { + const { id, address } = params ?? {} + return { + id: id ?? 'mock-wallet-1', + address: address ?? getRandomAddress(), + } +} + +export function getMockAuthorizationContext( + privateKey?: string, +): AuthorizationContext { + return { + authorization_private_keys: [privateKey ?? generatePrivateKey()], + } +} diff --git a/packages/sdk/src/wallet/core/namespace/__tests__/WalletNamespace.spec.ts b/packages/sdk/src/wallet/core/namespace/__tests__/WalletNamespace.spec.ts index 5867dff0..3693a0d0 100644 --- a/packages/sdk/src/wallet/core/namespace/__tests__/WalletNamespace.spec.ts +++ b/packages/sdk/src/wallet/core/namespace/__tests__/WalletNamespace.spec.ts @@ -1,4 +1,4 @@ -import type { PrivyClient } from '@privy-io/server-auth' +import type { PrivyClient } from '@privy-io/node' import { getAddress } from 'viem' import { unichain } from 'viem/chains' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' @@ -7,7 +7,11 @@ import type { SupportedChainId } from '@/constants/supportedChains.js' import type { ChainManager } from '@/services/ChainManager.js' import { MockChainManager } from '@/test/MockChainManager.js' import { createMockLendProvider } from '@/test/MockLendProvider.js' -import { createMockPrivyClient } from '@/test/MockPrivyClient.js' +import { + createMockPrivyClient, + createMockPrivyWallet, + getMockAuthorizationContext, +} from '@/test/MockPrivyClient.js' import { getRandomAddress } from '@/test/utils.js' import { WalletNamespace } from '@/wallet/core/namespace/WalletNamespace.js' import { DefaultSmartWalletProvider } from '@/wallet/core/providers/smart/default/DefaultSmartWalletProvider.js' @@ -35,10 +39,11 @@ describe('WalletNamespace', () => { describe('hostedWalletProvider', () => { it('should provide access to hosted wallet provider', () => { - const hostedWalletProvider = new PrivyHostedWalletProvider( - mockPrivyClient, - mockChainManager, - ) + const hostedWalletProvider = new PrivyHostedWalletProvider({ + privyClient: mockPrivyClient, + chainManager: mockChainManager, + authorizationContext: getMockAuthorizationContext(), + }) const smartWalletProvider = new DefaultSmartWalletProvider( mockChainManager, mockLendProvider, @@ -59,10 +64,11 @@ describe('WalletNamespace', () => { 'test-app-id', 'test-app-secret', ) - const hostedWalletProvider = new PrivyHostedWalletProvider( - mockPrivyClient, - mockChainManager, - ) + const hostedWalletProvider = new PrivyHostedWalletProvider({ + privyClient: mockPrivyClient, + authorizationContext: getMockAuthorizationContext(), + chainManager: mockChainManager, + }) const smartWalletProvider = new DefaultSmartWalletProvider( mockChainManager, mockLendProvider, @@ -79,10 +85,11 @@ describe('WalletNamespace', () => { describe('createSmartWallet', () => { it('should create a smart wallet and return deployment result', async () => { - const hostedWalletProvider = new PrivyHostedWalletProvider( - mockPrivyClient, - mockChainManager, - ) + const hostedWalletProvider = new PrivyHostedWalletProvider({ + privyClient: mockPrivyClient, + authorizationContext: getMockAuthorizationContext(), + chainManager: mockChainManager, + }) const smartWalletProvider = new DefaultSmartWalletProvider( mockChainManager, mockLendProvider, @@ -95,9 +102,7 @@ describe('WalletNamespace', () => { const walletNamespace = new WalletNamespace(walletProvider) // Create a hosted wallet to use as signer - const privyWallet = await mockPrivyClient.walletApi.createWallet({ - chainType: 'ethereum', - }) + const privyWallet = createMockPrivyWallet() const hostedWallet = await walletProvider.hostedWalletProvider.toActionsWallet({ walletId: privyWallet.id, @@ -121,10 +126,11 @@ describe('WalletNamespace', () => { }) it('should report deployment successes and failures', async () => { - const hostedWalletProvider = new PrivyHostedWalletProvider( - mockPrivyClient, - mockChainManager, - ) + const hostedWalletProvider = new PrivyHostedWalletProvider({ + privyClient: mockPrivyClient, + authorizationContext: getMockAuthorizationContext(), + chainManager: mockChainManager, + }) const smartWalletProvider = new DefaultSmartWalletProvider( mockChainManager, mockLendProvider, @@ -136,9 +142,7 @@ describe('WalletNamespace', () => { const walletNamespace = new WalletNamespace(walletProvider) // Create a hosted wallet to use as signer - const privyWallet = await mockPrivyClient.walletApi.createWallet({ - chainType: 'ethereum', - }) + const privyWallet = createMockPrivyWallet() const hostedWallet = await walletProvider.hostedWalletProvider.toActionsWallet({ walletId: privyWallet.id, @@ -206,10 +210,11 @@ describe('WalletNamespace', () => { 'test-app-id', 'test-app-secret', ) - const hostedWalletProvider = new PrivyHostedWalletProvider( - mockPrivyClient, - mockChainManager, - ) + const hostedWalletProvider = new PrivyHostedWalletProvider({ + privyClient: mockPrivyClient, + authorizationContext: getMockAuthorizationContext(), + chainManager: mockChainManager, + }) const smartWalletProvider = new DefaultSmartWalletProvider( mockChainManager, mockLendProvider, @@ -221,9 +226,7 @@ describe('WalletNamespace', () => { const getSmartWalletSpy = vi.spyOn(walletProvider, 'getSmartWallet') const walletNamespace = new WalletNamespace(walletProvider) - const privyWallet = await mockPrivyClient.walletApi.createWallet({ - chainType: 'ethereum', - }) + const privyWallet = createMockPrivyWallet() const hostedWallet = await walletProvider.hostedWalletProvider.toActionsWallet({ walletId: privyWallet.id, @@ -249,10 +252,11 @@ describe('WalletNamespace', () => { 'test-app-id', 'test-app-secret', ) - const hostedWalletProvider = new PrivyHostedWalletProvider( - mockPrivyClient, - mockChainManager, - ) + const hostedWalletProvider = new PrivyHostedWalletProvider({ + privyClient: mockPrivyClient, + authorizationContext: getMockAuthorizationContext(), + chainManager: mockChainManager, + }) const smartWalletProvider = new DefaultSmartWalletProvider( mockChainManager, mockLendProvider, @@ -263,9 +267,7 @@ describe('WalletNamespace', () => { ) const walletNamespace = new WalletNamespace(walletProvider) - const privyWallet = await mockPrivyClient.walletApi.createWallet({ - chainType: 'ethereum', - }) + const privyWallet = createMockPrivyWallet() const hostedWallet = await walletProvider.hostedWalletProvider.toActionsWallet({ walletId: privyWallet.id, @@ -286,10 +288,11 @@ describe('WalletNamespace', () => { describe('toActionsWallet', () => { it('should convert a hosted wallet to an Actions wallet', async () => { - const hostedWalletProvider = new PrivyHostedWalletProvider( - mockPrivyClient, - mockChainManager, - ) + const hostedWalletProvider = new PrivyHostedWalletProvider({ + privyClient: mockPrivyClient, + authorizationContext: getMockAuthorizationContext(), + chainManager: mockChainManager, + }) const smartWalletProvider = new DefaultSmartWalletProvider( mockChainManager, mockLendProvider, @@ -300,9 +303,7 @@ describe('WalletNamespace', () => { ) const walletNamespace = new WalletNamespace(walletProvider) - const privyWallet = await mockPrivyClient.walletApi.createWallet({ - chainType: 'ethereum', - }) + const privyWallet = createMockPrivyWallet() const hostedWallet = await walletProvider.hostedWalletProvider.toActionsWallet({ walletId: privyWallet.id, @@ -330,10 +331,11 @@ describe('WalletNamespace', () => { describe('createSigner', () => { it('should delegate to hosted wallet provider createSigner', async () => { - const hostedWalletProvider = new PrivyHostedWalletProvider( - mockPrivyClient, - mockChainManager, - ) + const hostedWalletProvider = new PrivyHostedWalletProvider({ + privyClient: mockPrivyClient, + authorizationContext: getMockAuthorizationContext(), + chainManager: mockChainManager, + }) const smartWalletProvider = new DefaultSmartWalletProvider( mockChainManager, mockLendProvider, @@ -345,9 +347,7 @@ describe('WalletNamespace', () => { const createSignerSpy = vi.spyOn(walletProvider, 'createSigner') const walletNamespace = new WalletNamespace(walletProvider) - const privyWallet = await mockPrivyClient.walletApi.createWallet({ - chainType: 'ethereum', - }) + const privyWallet = createMockPrivyWallet() const params = { walletId: privyWallet.id, address: getAddress(privyWallet.address), @@ -361,10 +361,11 @@ describe('WalletNamespace', () => { }) it('should return a LocalAccount that can be used as a smart wallet signer', async () => { - const hostedWalletProvider = new PrivyHostedWalletProvider( - mockPrivyClient, - mockChainManager, - ) + const hostedWalletProvider = new PrivyHostedWalletProvider({ + privyClient: mockPrivyClient, + authorizationContext: getMockAuthorizationContext(), + chainManager: mockChainManager, + }) const smartWalletProvider = new DefaultSmartWalletProvider( mockChainManager, mockLendProvider, @@ -375,9 +376,7 @@ describe('WalletNamespace', () => { ) const walletNamespace = new WalletNamespace(walletProvider) - const privyWallet = await mockPrivyClient.walletApi.createWallet({ - chainType: 'ethereum', - }) + const privyWallet = createMockPrivyWallet() const signer = await walletNamespace.createSigner({ walletId: privyWallet.id, address: getAddress(privyWallet.address), diff --git a/packages/sdk/src/wallet/core/providers/__tests__/WalletProvider.spec.ts b/packages/sdk/src/wallet/core/providers/__tests__/WalletProvider.spec.ts index de84c9e2..151ba1d8 100644 --- a/packages/sdk/src/wallet/core/providers/__tests__/WalletProvider.spec.ts +++ b/packages/sdk/src/wallet/core/providers/__tests__/WalletProvider.spec.ts @@ -6,7 +6,11 @@ import type { SupportedChainId } from '@/constants/supportedChains.js' import type { ChainManager } from '@/services/ChainManager.js' import { MockChainManager } from '@/test/MockChainManager.js' import { createMockLendProvider } from '@/test/MockLendProvider.js' -import { createMockPrivyClient } from '@/test/MockPrivyClient.js' +import { + createMockPrivyClient, + createMockPrivyWallet, + getMockAuthorizationContext, +} from '@/test/MockPrivyClient.js' import { getRandomAddress } from '@/test/utils.js' import { DefaultSmartWalletProvider } from '@/wallet/core/providers/smart/default/DefaultSmartWalletProvider.js' import { WalletProvider } from '@/wallet/core/providers/WalletProvider.js' @@ -31,10 +35,11 @@ describe('WalletProvider', () => { describe('createSmartWallet', () => { it('should create a smart wallet and return deployment result', async () => { - const hostedWalletProvider = new PrivyHostedWalletProvider( - mockPrivyClient, - mockChainManager, - ) + const hostedWalletProvider = new PrivyHostedWalletProvider({ + privyClient: mockPrivyClient, + authorizationContext: getMockAuthorizationContext(), + chainManager: mockChainManager, + }) const smartWalletProvider = new DefaultSmartWalletProvider( mockChainManager, mockLendProvider, @@ -84,10 +89,11 @@ describe('WalletProvider', () => { }) it('should pass through deployment successes and failures', async () => { - const hostedWalletProvider = new PrivyHostedWalletProvider( - mockPrivyClient, - mockChainManager, - ) + const hostedWalletProvider = new PrivyHostedWalletProvider({ + privyClient: mockPrivyClient, + authorizationContext: getMockAuthorizationContext(), + chainManager: mockChainManager, + }) const smartWalletProvider = new DefaultSmartWalletProvider( mockChainManager, mockLendProvider, @@ -138,10 +144,11 @@ describe('WalletProvider', () => { }) it('should forward deploymentChainIds parameter', async () => { - const hostedWalletProvider = new PrivyHostedWalletProvider( - mockPrivyClient, - mockChainManager, - ) + const hostedWalletProvider = new PrivyHostedWalletProvider({ + privyClient: mockPrivyClient, + authorizationContext: getMockAuthorizationContext(), + chainManager: mockChainManager, + }) const smartWalletProvider = new DefaultSmartWalletProvider( mockChainManager, mockLendProvider, @@ -190,10 +197,11 @@ describe('WalletProvider', () => { }) it('should throw error if signer is not in signers array', async () => { - const hostedWalletProvider = new PrivyHostedWalletProvider( - mockPrivyClient, - mockChainManager, - ) + const hostedWalletProvider = new PrivyHostedWalletProvider({ + privyClient: mockPrivyClient, + authorizationContext: getMockAuthorizationContext(), + chainManager: mockChainManager, + }) const smartWalletProvider = new DefaultSmartWalletProvider( mockChainManager, mockLendProvider, @@ -222,10 +230,11 @@ describe('WalletProvider', () => { describe('getSmartWallet', () => { it('should get a smart wallet with provided signer', async () => { - const hostedWalletProvider = new PrivyHostedWalletProvider( - mockPrivyClient, - mockChainManager, - ) + const hostedWalletProvider = new PrivyHostedWalletProvider({ + privyClient: mockPrivyClient, + authorizationContext: getMockAuthorizationContext(), + chainManager: mockChainManager, + }) const smartWalletProvider = new DefaultSmartWalletProvider( mockChainManager, mockLendProvider, @@ -268,10 +277,11 @@ describe('WalletProvider', () => { }) it('should throw error when getting smart wallet without required parameters', async () => { - const hostedWalletProvider = new PrivyHostedWalletProvider( - mockPrivyClient, - mockChainManager, - ) + const hostedWalletProvider = new PrivyHostedWalletProvider({ + privyClient: mockPrivyClient, + authorizationContext: getMockAuthorizationContext(), + chainManager: mockChainManager, + }) const smartWalletProvider = new DefaultSmartWalletProvider( mockChainManager, mockLendProvider, @@ -301,10 +311,11 @@ describe('WalletProvider', () => { describe('hostedWalletToActionsWallet', () => { it('should convert a hosted wallet to an Actions wallet', async () => { - const hostedWalletProvider = new PrivyHostedWalletProvider( - mockPrivyClient, - mockChainManager, - ) + const hostedWalletProvider = new PrivyHostedWalletProvider({ + privyClient: mockPrivyClient, + authorizationContext: getMockAuthorizationContext(), + chainManager: mockChainManager, + }) const smartWalletProvider = new DefaultSmartWalletProvider( mockChainManager, mockLendProvider, @@ -318,9 +329,7 @@ describe('WalletProvider', () => { 'toActionsWallet', ) - const privyWallet = await mockPrivyClient.walletApi.createWallet({ - chainType: 'ethereum', - }) + const privyWallet = createMockPrivyWallet() const hostedWallet = await walletProvider.hostedWalletToActionsWallet({ walletId: privyWallet.id, address: privyWallet.address, @@ -342,10 +351,11 @@ describe('WalletProvider', () => { 'test-app-id', 'test-app-secret', ) - const hostedWalletProvider = new PrivyHostedWalletProvider( - mockPrivyClient, - mockChainManager, - ) + const hostedWalletProvider = new PrivyHostedWalletProvider({ + privyClient: mockPrivyClient, + authorizationContext: getMockAuthorizationContext(), + chainManager: mockChainManager, + }) const smartWalletProvider = new DefaultSmartWalletProvider( mockChainManager, mockLendProvider, @@ -356,9 +366,7 @@ describe('WalletProvider', () => { ) const createSignerSpy = vi.spyOn(hostedWalletProvider, 'createSigner') - const privyWallet = await mockPrivyClient.walletApi.createWallet({ - chainType: 'ethereum', - }) + const privyWallet = createMockPrivyWallet() const params = { walletId: privyWallet.id, address: privyWallet.address, diff --git a/packages/sdk/src/wallet/core/providers/hosted/registry/__tests__/HostedWalletProviderRegistry.spec.ts b/packages/sdk/src/wallet/core/providers/hosted/registry/__tests__/HostedWalletProviderRegistry.spec.ts index 6092f506..44ed7034 100644 --- a/packages/sdk/src/wallet/core/providers/hosted/registry/__tests__/HostedWalletProviderRegistry.spec.ts +++ b/packages/sdk/src/wallet/core/providers/hosted/registry/__tests__/HostedWalletProviderRegistry.spec.ts @@ -1,10 +1,13 @@ -import type { PrivyClient } from '@privy-io/server-auth' +import type { PrivyClient } from '@privy-io/node' import { unichain } from 'viem/chains' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { ChainManager } from '@/services/ChainManager.js' import { MockChainManager } from '@/test/MockChainManager.js' -import { createMockPrivyClient } from '@/test/MockPrivyClient.js' +import { + createMockPrivyClient, + getMockAuthorizationContext, +} from '@/test/MockPrivyClient.js' import { HostedWalletProviderRegistry } from '@/wallet/core/providers/hosted/registry/HostedWalletProviderRegistry.js' import { PrivyHostedWalletProvider } from '@/wallet/node/providers/hosted/privy/PrivyHostedWalletProvider.js' import type { NodeOptionsMap } from '@/wallet/node/providers/hosted/types/index.js' @@ -23,7 +26,11 @@ class TestHostedWalletProviderRegistry extends HostedWalletProviderRegistry< return Boolean((options as NodeOptionsMap['privy'])?.privyClient) }, create({ chainManager }, options) { - return new PrivyHostedWalletProvider(options.privyClient, chainManager) + return new PrivyHostedWalletProvider({ + privyClient: options.privyClient, + chainManager, + authorizationContext: options.authorizationContext, + }) }, }) } @@ -48,9 +55,12 @@ describe('HostedWalletProviderRegistry', () => { const factory = registry.getFactory('privy') expect(factory.type).toBe('privy') - expect(factory.validateOptions?.({ privyClient: mockPrivyClient })).toBe( - true, - ) + expect( + factory.validateOptions?.({ + privyClient: mockPrivyClient, + authorizationContext: getMockAuthorizationContext(), + }), + ).toBe(true) // Invalid shape should not pass validation expect(factory.validateOptions?.({})).toBe(false) }) @@ -61,7 +71,10 @@ describe('HostedWalletProviderRegistry', () => { const provider = factory.create( { chainManager: mockChainManager }, - { privyClient: mockPrivyClient }, + { + privyClient: mockPrivyClient, + authorizationContext: getMockAuthorizationContext(), + }, ) expect(provider).toBeInstanceOf(PrivyHostedWalletProvider) diff --git a/packages/sdk/src/wallet/node/providers/hosted/privy/PrivyHostedWalletProvider.ts b/packages/sdk/src/wallet/node/providers/hosted/privy/PrivyHostedWalletProvider.ts index b1711a37..9f1e9ece 100644 --- a/packages/sdk/src/wallet/node/providers/hosted/privy/PrivyHostedWalletProvider.ts +++ b/packages/sdk/src/wallet/node/providers/hosted/privy/PrivyHostedWalletProvider.ts @@ -1,4 +1,4 @@ -import type { PrivyClient } from '@privy-io/server-auth' +import type { AuthorizationContext, PrivyClient } from '@privy-io/node' import type { LocalAccount } from 'viem' import { getAddress } from 'viem' @@ -21,16 +21,29 @@ export class PrivyHostedWalletProvider extends HostedWalletProvider< 'privy', NodeToActionsOptionsMap > { + private readonly privyClient: PrivyClient + private readonly authorizationContext?: AuthorizationContext + /** * Create a new Privy wallet provider - * @param privyClient - Privy client instance + * @param params - Configuration parameters + * @param params.privyClient - Privy client instance + * @param params.chainManager - Chain manager for multi-chain operations + * @param params.lendProvider - Optional lend provider for DeFi operations + * @param params.authorizationContext - Optional authorization context for the Privy client. + * Used when Privy needs to sign requests. + * See https://docs.privy.io/controls/authorization-keys/using-owners/sign/automatic#using-the-authorization-context + * for more information on building and using the authorization context. */ - constructor( - private readonly privyClient: PrivyClient, - chainManager: ChainManager, - lendProvider?: LendProvider, - ) { - super(chainManager, lendProvider) + constructor(params: { + privyClient: PrivyClient + chainManager: ChainManager + lendProvider?: LendProvider + authorizationContext?: AuthorizationContext + }) { + super(params.chainManager, params.lendProvider) + this.privyClient = params.privyClient + this.authorizationContext = params.authorizationContext } async toActionsWallet( @@ -38,6 +51,7 @@ export class PrivyHostedWalletProvider extends HostedWalletProvider< ): Promise { return PrivyWallet.create({ privyClient: this.privyClient, + authorizationContext: this.authorizationContext, walletId: params.walletId, address: getAddress(params.address), chainManager: this.chainManager, @@ -60,6 +74,10 @@ export class PrivyHostedWalletProvider extends HostedWalletProvider< async createSigner( params: NodeToActionsOptionsMap['privy'], ): Promise { - return createSigner({ ...params, privyClient: this.privyClient }) + return createSigner({ + ...params, + privyClient: this.privyClient, + authorizationContext: this.authorizationContext, + }) } } diff --git a/packages/sdk/src/wallet/node/providers/hosted/privy/__tests__/PrivyHostedWalletProvider.spec.ts b/packages/sdk/src/wallet/node/providers/hosted/privy/__tests__/PrivyHostedWalletProvider.spec.ts index 384b331a..f2ab573e 100644 --- a/packages/sdk/src/wallet/node/providers/hosted/privy/__tests__/PrivyHostedWalletProvider.spec.ts +++ b/packages/sdk/src/wallet/node/providers/hosted/privy/__tests__/PrivyHostedWalletProvider.spec.ts @@ -5,7 +5,11 @@ import { describe, expect, it, vi } from 'vitest' import type { ChainManager } from '@/services/ChainManager.js' import { MockChainManager } from '@/test/MockChainManager.js' -import { createMockPrivyClient } from '@/test/MockPrivyClient.js' +import { + createMockPrivyClient, + createMockPrivyWallet, + getMockAuthorizationContext, +} from '@/test/MockPrivyClient.js' import { getRandomAddress } from '@/test/utils.js' import type { LendConfig, LendProvider } from '@/types/lend/index.js' import { Wallet } from '@/wallet/core/wallets/abstract/Wallet.js' @@ -20,12 +24,14 @@ describe('PrivyHostedWalletProvider', () => { describe('toActionsWallet', () => { it('toActionsWallet creates an ActionsWallet with correct address and signer', async () => { const privy = createMockPrivyClient('app', 'secret') - const provider = new PrivyHostedWalletProvider(privy, mockChainManager) - - const hostedWallet = await privy.walletApi.createWallet({ - chainType: 'ethereum', + const provider = new PrivyHostedWalletProvider({ + privyClient: privy, + authorizationContext: getMockAuthorizationContext(), + chainManager: mockChainManager, }) + const hostedWallet = createMockPrivyWallet() + const actionsWallet = await provider.toActionsWallet({ walletId: hostedWallet.id, address: hostedWallet.address as Address, @@ -38,17 +44,23 @@ describe('PrivyHostedWalletProvider', () => { it('forwards params to PrivyWallet.create', async () => { const privy = createMockPrivyClient('app', 'secret') - const provider = new PrivyHostedWalletProvider(privy, mockChainManager) + const authorizationContext = getMockAuthorizationContext() + const provider = new PrivyHostedWalletProvider({ + privyClient: privy, + authorizationContext, + chainManager: mockChainManager, + }) const spy = vi.spyOn(PrivyWallet, 'create') const id = 'mock-wallet-123' - const addr = getRandomAddress().toLowerCase() + const addr = getRandomAddress() - await provider.toActionsWallet({ walletId: id, address: addr as Address }) + await provider.toActionsWallet({ walletId: id, address: addr }) expect(spy).toHaveBeenCalledWith( expect.objectContaining({ privyClient: privy, + authorizationContext, walletId: id, address: getAddress(addr), chainManager: mockChainManager, @@ -58,7 +70,11 @@ describe('PrivyHostedWalletProvider', () => { it('throws on invalid address', async () => { const privy = createMockPrivyClient('app', 'secret') - const provider = new PrivyHostedWalletProvider(privy, mockChainManager) + const provider = new PrivyHostedWalletProvider({ + privyClient: privy, + authorizationContext: getMockAuthorizationContext(), + chainManager: mockChainManager, + }) await expect( provider.toActionsWallet({ walletId: 'id', address: '0x123' }), @@ -68,19 +84,20 @@ describe('PrivyHostedWalletProvider', () => { it('forwards lendProvider when provided to constructor', async () => { const privy = createMockPrivyClient('app', 'secret') const mockLendProvider = {} as LendProvider - const provider = new PrivyHostedWalletProvider( - privy, - mockChainManager, - mockLendProvider, - ) + const provider = new PrivyHostedWalletProvider({ + privyClient: privy, + authorizationContext: getMockAuthorizationContext(), + chainManager: mockChainManager, + lendProvider: mockLendProvider, + }) const spy = vi.spyOn(PrivyWallet, 'create') const id = 'mock-wallet-123' - const addr = getRandomAddress().toLowerCase() + const addr = getRandomAddress() await provider.toActionsWallet({ walletId: id, - address: addr as Address, + address: addr, }) expect(spy).toHaveBeenCalledWith( @@ -94,15 +111,17 @@ describe('PrivyHostedWalletProvider', () => { describe('createSigner', () => { it('should create a LocalAccount with correct address', async () => { const privy = createMockPrivyClient('app', 'secret') - const provider = new PrivyHostedWalletProvider(privy, mockChainManager) - - const hostedWallet = await privy.walletApi.createWallet({ - chainType: 'ethereum', + const provider = new PrivyHostedWalletProvider({ + privyClient: privy, + authorizationContext: getMockAuthorizationContext(), + chainManager: mockChainManager, }) + const hostedWallet = createMockPrivyWallet() + const signer = await provider.createSigner({ walletId: hostedWallet.id, - address: hostedWallet.address as Address, + address: hostedWallet.address, }) expect(signer.address).toBe(hostedWallet.address) diff --git a/packages/sdk/src/wallet/node/providers/hosted/registry/NodeHostedWalletProviderRegistry.ts b/packages/sdk/src/wallet/node/providers/hosted/registry/NodeHostedWalletProviderRegistry.ts index 56897d5f..f35a5bd8 100644 --- a/packages/sdk/src/wallet/node/providers/hosted/registry/NodeHostedWalletProviderRegistry.ts +++ b/packages/sdk/src/wallet/node/providers/hosted/registry/NodeHostedWalletProviderRegistry.ts @@ -29,11 +29,12 @@ export class NodeHostedWalletProviderRegistry extends HostedWalletProviderRegist return Boolean((options as NodeOptionsMap['privy'])?.privyClient) }, create({ chainManager, lendProvider }, options) { - return new PrivyHostedWalletProvider( - options.privyClient, + return new PrivyHostedWalletProvider({ + privyClient: options.privyClient, chainManager, lendProvider, - ) + authorizationContext: options.authorizationContext, + }) }, }) diff --git a/packages/sdk/src/wallet/node/providers/hosted/registry/__tests__/NodeHostedWalletProviderRegistry.spec.ts b/packages/sdk/src/wallet/node/providers/hosted/registry/__tests__/NodeHostedWalletProviderRegistry.spec.ts index 77814504..5d6b94a7 100644 --- a/packages/sdk/src/wallet/node/providers/hosted/registry/__tests__/NodeHostedWalletProviderRegistry.spec.ts +++ b/packages/sdk/src/wallet/node/providers/hosted/registry/__tests__/NodeHostedWalletProviderRegistry.spec.ts @@ -1,11 +1,14 @@ -import type { PrivyClient } from '@privy-io/server-auth' +import type { PrivyClient } from '@privy-io/node' import type { TurnkeyClient } from '@turnkey/http' import { unichain } from 'viem/chains' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { ChainManager } from '@/services/ChainManager.js' import { MockChainManager } from '@/test/MockChainManager.js' -import { createMockPrivyClient } from '@/test/MockPrivyClient.js' +import { + createMockPrivyClient, + getMockAuthorizationContext, +} from '@/test/MockPrivyClient.js' import { PrivyHostedWalletProvider } from '@/wallet/node/providers/hosted/privy/PrivyHostedWalletProvider.js' import { NodeHostedWalletProviderRegistry } from '@/wallet/node/providers/hosted/registry/NodeHostedWalletProviderRegistry.js' import { TurnkeyHostedWalletProvider } from '@/wallet/node/providers/hosted/turnkey/TurnkeyHostedWalletProvider.js' @@ -31,6 +34,12 @@ describe('NodeHostedWalletProviderRegistry', () => { const factory = registry.getFactory('privy') expect(factory.type).toBe('privy') + expect( + factory.validateOptions?.({ + privyClient: mockPrivyClient, + authorizationContext: getMockAuthorizationContext(), + } as NodeOptionsMap['privy']), + ).toBe(true) expect( factory.validateOptions?.({ privyClient: mockPrivyClient, @@ -38,6 +47,11 @@ describe('NodeHostedWalletProviderRegistry', () => { ).toBe(true) // Invalid shape should not pass validation expect(factory.validateOptions?.({})).toBe(false) + expect( + factory.validateOptions?.({ + authorizationContext: getMockAuthorizationContext(), + } as NodeOptionsMap['privy']), + ).toBe(false) }) it('creates a PrivyHostedWalletProvider instance', () => { @@ -46,6 +60,7 @@ describe('NodeHostedWalletProviderRegistry', () => { const provider = factory.create({ chainManager: mockChainManager }, { privyClient: mockPrivyClient, + authorizationContext: getMockAuthorizationContext(), } as NodeOptionsMap['privy']) expect(provider).toBeInstanceOf(PrivyHostedWalletProvider) diff --git a/packages/sdk/src/wallet/node/providers/hosted/types/index.ts b/packages/sdk/src/wallet/node/providers/hosted/types/index.ts index a46652c9..e0500bef 100644 --- a/packages/sdk/src/wallet/node/providers/hosted/types/index.ts +++ b/packages/sdk/src/wallet/node/providers/hosted/types/index.ts @@ -1,4 +1,4 @@ -import type { PrivyClient } from '@privy-io/server-auth' +import type { AuthorizationContext, PrivyClient } from '@privy-io/node' import type { TurnkeySDKClientBase } from '@turnkey/core' import type { TurnkeyClient as TurnkeyHttpClient } from '@turnkey/http' import type { TurnkeyServerClient } from '@turnkey/sdk-server' @@ -29,8 +29,15 @@ export interface NodeOptionsMap { /** * Privy provider configuration * @property privyClient Server-side Privy client instance used to query/create wallets + * @property authorizationContext Optional authorization context for the Privy client. + * Used when Privy needs to sign requests. + * See https://docs.privy.io/controls/authorization-keys/using-owners/sign/automatic#using-the-authorization-context + * for more information on building and using the authorization context. */ - privy: { privyClient: PrivyClient } + privy: { + privyClient: PrivyClient + authorizationContext?: AuthorizationContext + } /** * Turnkey provider configuration * @property client Turnkey SDK/HTTP client used to sign and manage keys diff --git a/packages/sdk/src/wallet/node/wallets/hosted/privy/PrivyWallet.ts b/packages/sdk/src/wallet/node/wallets/hosted/privy/PrivyWallet.ts index 08a48b90..20769211 100644 --- a/packages/sdk/src/wallet/node/wallets/hosted/privy/PrivyWallet.ts +++ b/packages/sdk/src/wallet/node/wallets/hosted/privy/PrivyWallet.ts @@ -1,4 +1,4 @@ -import type { PrivyClient } from '@privy-io/server-auth' +import type { AuthorizationContext, PrivyClient } from '@privy-io/node' import { type Address, type LocalAccount } from 'viem' import type { ChainManager } from '@/services/ChainManager.js' @@ -15,12 +15,15 @@ export class PrivyWallet extends EOAWallet { public signer!: LocalAccount public readonly address: Address private privyClient: PrivyClient - + private authorizationContext?: AuthorizationContext /** - * Create a new Privy wallet provider - * @param appId - Privy application ID - * @param appSecret - Privy application secret - * @param actions - Actions instance for accessing configured providers + * Create a new Privy wallet instance + * @param privyClient - Privy client instance for wallet operations + * @param authorizationContext - Authorization context for signing requests + * @param walletId - Privy wallet identifier + * @param address - Ethereum address of the wallet + * @param chainManager - Chain manager for multi-chain operations + * @param lendProvider - Optional lend provider for DeFi operations */ private constructor( privyClient: PrivyClient, @@ -28,15 +31,18 @@ export class PrivyWallet extends EOAWallet { address: Address, chainManager: ChainManager, lendProvider?: LendProvider, + authorizationContext?: AuthorizationContext, ) { super(chainManager, lendProvider) this.privyClient = privyClient + this.authorizationContext = authorizationContext this.walletId = walletId this.address = address } static async create(params: { privyClient: PrivyClient + authorizationContext?: AuthorizationContext walletId: string address: Address chainManager: ChainManager @@ -48,6 +54,7 @@ export class PrivyWallet extends EOAWallet { params.address, params.chainManager, params.lendProvider, + params.authorizationContext, ) await wallet.initialize() return wallet @@ -73,6 +80,7 @@ export class PrivyWallet extends EOAWallet { walletId: this.walletId, address: this.address, privyClient: this.privyClient, + authorizationContext: this.authorizationContext, }) } } diff --git a/packages/sdk/src/wallet/node/wallets/hosted/privy/__tests__/PrivyWallet.spec.ts b/packages/sdk/src/wallet/node/wallets/hosted/privy/__tests__/PrivyWallet.spec.ts index 4cccc103..f7085b9f 100644 --- a/packages/sdk/src/wallet/node/wallets/hosted/privy/__tests__/PrivyWallet.spec.ts +++ b/packages/sdk/src/wallet/node/wallets/hosted/privy/__tests__/PrivyWallet.spec.ts @@ -1,5 +1,5 @@ -import type { PrivyClient } from '@privy-io/server-auth' -import { createViemAccount } from '@privy-io/server-auth/viem' +import type { AuthorizationContext } from '@privy-io/node' +import { createViemAccount } from '@privy-io/node/viem' import { type Address, createWalletClient, @@ -8,11 +8,15 @@ import { type WalletClient, } from 'viem' import { unichain } from 'viem/chains' -import { describe, expect, it, vi } from 'vitest' +import { beforeEach, describe, expect, it, vi } from 'vitest' import type { ChainManager } from '@/services/ChainManager.js' import { MockChainManager } from '@/test/MockChainManager.js' -import { createMockPrivyClient } from '@/test/MockPrivyClient.js' +import { + createMockPrivyClient, + createMockPrivyWallet, + getMockAuthorizationContext, +} from '@/test/MockPrivyClient.js' import { getRandomAddress } from '@/test/utils.js' import { PrivyWallet } from '@/wallet/node/wallets/hosted/privy/PrivyWallet.js' @@ -28,9 +32,9 @@ vi.mock('viem/accounts', async () => ({ toAccount: vi.fn(), })) -vi.mock('@privy-io/server-auth/viem', async () => ({ +vi.mock('@privy-io/node/viem', async () => ({ // @ts-ignore - importActual returns unknown - ...(await vi.importActual('@privy-io/server-auth/viem')), + ...(await vi.importActual('@privy-io/node/viem')), createViemAccount: vi.fn(), })) @@ -48,10 +52,12 @@ const mockLocalAccount = { } as unknown as LocalAccount describe('PrivyWallet', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + it('should return the correct wallet ID', async () => { - const createdWallet = await mockPrivyClient.walletApi.createWallet({ - chainType: 'ethereum', - }) + const createdWallet = createMockPrivyWallet() const wallet = await createAndInitPrivyWallet({ address: getAddress(createdWallet.address), @@ -62,9 +68,7 @@ describe('PrivyWallet', () => { }) it('should return the correct address', async () => { - const createdWallet = await mockPrivyClient.walletApi.createWallet({ - chainType: 'ethereum', - }) + const createdWallet = createMockPrivyWallet() const wallet = await createAndInitPrivyWallet({ address: getAddress(createdWallet.address), @@ -76,28 +80,29 @@ describe('PrivyWallet', () => { it('should create an account with correct configuration', async () => { // Create a wallet using the mock client first - const createdWallet = await mockPrivyClient.walletApi.createWallet({ - chainType: 'ethereum', - }) + const createdWallet = createMockPrivyWallet() vi.mocked(createViemAccount).mockResolvedValue(mockLocalAccount) + const authorizationContext = getMockAuthorizationContext() const wallet = await createAndInitPrivyWallet({ address: getAddress(createdWallet.address), walletId: createdWallet.id, + authorizationContext, }) - expect(createViemAccount).toHaveBeenCalledWith({ + expect(createViemAccount).toHaveBeenCalledWith(mockPrivyClient, { walletId: createdWallet.id, address: createdWallet.address, - privy: mockPrivyClient, + authorizationContext, }) expect(wallet.signer).toBe(mockLocalAccount) }) it('should create a wallet client with correct configuration', async () => { - const createdWallet = await mockPrivyClient.walletApi.createWallet({ - chainType: 'ethereum', + const createdWallet = createMockPrivyWallet() + const wallet = await createAndInitPrivyWallet({ + walletId: createdWallet.id, + address: createdWallet.address, }) - const wallet = await createAndInitPrivyWallet() const mockWalletClient = { account: mockLocalAccount, @@ -116,27 +121,17 @@ describe('PrivyWallet', () => { }) }) -async function createAndInitPrivyWallet( - params: { - privyClient?: PrivyClient - walletId?: string - address?: Address - chainManager?: ChainManager - } = {}, -) { - const { - privyClient = mockPrivyClient, +async function createAndInitPrivyWallet(params: { + walletId: string + address: Address + authorizationContext?: AuthorizationContext +}) { + const { walletId, address, authorizationContext } = params + return PrivyWallet.create({ + privyClient: mockPrivyClient, + authorizationContext: authorizationContext ?? getMockAuthorizationContext(), walletId, address, - chainManager = mockChainManager, - } = params - const createdWallet = await privyClient.walletApi.createWallet({ - chainType: 'ethereum', - }) - return PrivyWallet.create({ - privyClient, - walletId: walletId ?? createdWallet.id, - address: address ?? getAddress(createdWallet.address), - chainManager, + chainManager: mockChainManager, }) } diff --git a/packages/sdk/src/wallet/node/wallets/hosted/privy/utils/__tests__/createSigner.spec.ts b/packages/sdk/src/wallet/node/wallets/hosted/privy/utils/__tests__/createSigner.spec.ts index c63f5674..5ab78a69 100644 --- a/packages/sdk/src/wallet/node/wallets/hosted/privy/utils/__tests__/createSigner.spec.ts +++ b/packages/sdk/src/wallet/node/wallets/hosted/privy/utils/__tests__/createSigner.spec.ts @@ -1,14 +1,18 @@ -import { createViemAccount } from '@privy-io/server-auth/viem' -import type { Address, LocalAccount } from 'viem' +import { createViemAccount } from '@privy-io/node/viem' +import type { LocalAccount } from 'viem' import { describe, expect, it, vi } from 'vitest' -import { createMockPrivyClient } from '@/test/MockPrivyClient.js' +import { + createMockPrivyClient, + createMockPrivyWallet, + getMockAuthorizationContext, +} from '@/test/MockPrivyClient.js' import { getRandomAddress } from '@/test/utils.js' import { createSigner } from '@/wallet/node/wallets/hosted/privy/utils/createSigner.js' -vi.mock('@privy-io/server-auth/viem', async () => ({ +vi.mock('@privy-io/node/viem', async () => ({ // @ts-ignore - importActual returns unknown - ...(await vi.importActual('@privy-io/server-auth/viem')), + ...(await vi.importActual('@privy-io/node/viem')), createViemAccount: vi.fn(), })) @@ -26,22 +30,22 @@ describe('createSigner (Node Privy)', () => { signTypedData: vi.fn(), } as unknown as LocalAccount - it('should create a LocalAccount with correct configuration', async () => { - const createdWallet = await mockPrivyClient.walletApi.createWallet({ - chainType: 'ethereum', - }) - vi.mocked(createViemAccount).mockResolvedValue(mockLocalAccount) + it('should create a LocalAccount with correct configuration', () => { + const createdWallet = createMockPrivyWallet() + vi.mocked(createViemAccount).mockReturnValue(mockLocalAccount) + const authorizationContext = getMockAuthorizationContext() - const signer = await createSigner({ + const signer = createSigner({ privyClient: mockPrivyClient, + authorizationContext: authorizationContext, walletId: createdWallet.id, - address: createdWallet.address as Address, + address: createdWallet.address, }) - expect(createViemAccount).toHaveBeenCalledWith({ + expect(createViemAccount).toHaveBeenCalledWith(mockPrivyClient, { walletId: createdWallet.id, address: createdWallet.address, - privy: mockPrivyClient, + authorizationContext, }) expect(signer).toBe(mockLocalAccount) }) diff --git a/packages/sdk/src/wallet/node/wallets/hosted/privy/utils/createSigner.ts b/packages/sdk/src/wallet/node/wallets/hosted/privy/utils/createSigner.ts index 1ac406b1..637f16ff 100644 --- a/packages/sdk/src/wallet/node/wallets/hosted/privy/utils/createSigner.ts +++ b/packages/sdk/src/wallet/node/wallets/hosted/privy/utils/createSigner.ts @@ -1,5 +1,4 @@ -import type { GetViemAccountInputType } from '@privy-io/server-auth/viem' -import { createViemAccount } from '@privy-io/server-auth/viem' +import { createViemAccount } from '@privy-io/node/viem' import type { LocalAccount } from 'viem' import type { @@ -15,18 +14,21 @@ import type { * @param params.walletId - Privy wallet identifier * @param params.address - Ethereum address of the wallet * @param params.privyClient - Privy client instance - * @returns Promise resolving to a LocalAccount configured for signing operations + * @param params.authorizationContext - Optional authorization context for the Privy client. + * Used when Privy needs to sign requests. + * See https://docs.privy.io/controls/authorization-keys/using-owners/sign/automatic#using-the-authorization-context + * for more information on building and using the authorization context. + * @returns LocalAccount configured for signing operations * @throws Error if wallet retrieval fails or signing operations are not supported */ -export async function createSigner( +export function createSigner( params: PrivyHostedWalletToActionsWalletOptions & NodeOptionsMap['privy'], -): Promise { - const { walletId, address, privyClient } = params - const account = await createViemAccount({ +): LocalAccount { + const { walletId, address, privyClient, authorizationContext } = params + const account = createViemAccount(privyClient, { walletId, address, - // TODO: Fix this type error - privy: privyClient as unknown as GetViemAccountInputType['privy'], + authorizationContext, }) return account } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aba38dd5..ef1c76e9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -86,9 +86,9 @@ importers: '@hono/node-server': specifier: ^1.14.0 version: 1.17.1(hono@4.9.6) - '@privy-io/server-auth': - specifier: ^1.31.1 - version: 1.31.1(bufferutil@4.0.9)(encoding@0.1.13)(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typescript@5.8.3)(utf-8-validate@5.0.10)(viem@2.33.0(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@4.0.5)) + '@privy-io/node': + specifier: ^0.3.0 + version: 0.3.0(encoding@0.1.13)(viem@2.33.0(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@4.0.5)) commander: specifier: ^13.1.0 version: 13.1.0 @@ -272,12 +272,12 @@ importers: '@morpho-org/morpho-ts': specifier: ^2.4.1 version: 2.4.1 + '@privy-io/node': + specifier: '>=0.3.0' + version: 0.3.0(encoding@0.1.13)(viem@2.33.0(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@4.0.5)) '@privy-io/react-auth': specifier: '>=2.24.0' version: 2.24.0(@solana/spl-token@0.4.14(@solana/web3.js@1.98.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.8.3)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(encoding@0.1.13)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.3)(utf-8-validate@5.0.10))(@solana/web3.js@1.98.1(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.8.3)(utf-8-validate@5.0.10))(@types/react@18.3.23)(bs58@6.0.0)(bufferutil@4.0.9)(permissionless@0.2.54(ox@0.9.3(typescript@5.8.3)(zod@4.0.5))(viem@2.33.0(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@4.0.5)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)(use-sync-external-store@1.5.0(react@18.3.1))(utf-8-validate@5.0.10)(zod@4.0.5) - '@privy-io/server-auth': - specifier: '>=1.31.1' - version: 1.31.1(bufferutil@4.0.9)(encoding@0.1.13)(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typescript@5.8.3)(utf-8-validate@5.0.10)(viem@2.33.0(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@4.0.5)) '@turnkey/core': specifier: '>=1.1.1' version: 1.1.2(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@4.0.5) @@ -1678,26 +1678,14 @@ packages: peerDependencies: hono: ^4 - '@hpke/chacha20poly1305@1.6.3': - resolution: {integrity: sha512-74fKTe/hj1gB9Izi0cALZwnVDgGhNji3az/q38yXYi3wePdjKo6lFTo+HTTTp6Fmk0j4Z3u4+X8POge8cmjiOA==} - engines: {node: '>=16.0.0'} - '@hpke/chacha20poly1305@1.7.1': resolution: {integrity: sha512-Zp8IwRIkdCucu877wCNqDp3B8yOhAnAah/YDDkO94pPr/KKV7IGnBbpwIjDB3BsAySWBMrhhdE0JKYw3N4FCag==} engines: {node: '>=16.0.0'} - '@hpke/common@1.7.3': - resolution: {integrity: sha512-6wbbjlOPWZb2MnBETFNJRMcnEcB8jXMnjepGUs8412xEQX2Prds1n1yibvaxwUmQiKsHvjkGX+vTlWjqiiSiKw==} - engines: {node: '>=16.0.0'} - '@hpke/common@1.8.1': resolution: {integrity: sha512-PSI4QSxH8XDli0TqAsWycVfrLLCM/bBe+hVlJwtuJJiKIvCaFS3CXX/WtRfJceLJye9NHc2J7GvHVCY9B1BEbA==} engines: {node: '>=16.0.0'} - '@hpke/core@1.7.3': - resolution: {integrity: sha512-shx1PFyXM8nVOmylrb0O6bGSm7gFgq5rcvhbmVjT/Kch50XXFAm1DYlio3b5jdpzvyAr5CpcsEbfoIMMtJcnJQ==} - engines: {node: '>=16.0.0'} - '@hpke/core@1.7.4': resolution: {integrity: sha512-1BfPQB7unq/tNx7eznmoFgmJlMFnymdLNNt2CQX/L8oYOfmc1+cFEItpXZ90mgthW7tB2oksy/G7Xh1t569qzA==} engines: {node: '>=16.0.0'} @@ -2182,9 +2170,6 @@ packages: resolution: {integrity: sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==} engines: {node: '>=12'} - '@privy-io/api-base@1.6.0': - resolution: {integrity: sha512-ftlqjFw0Ww7Xn6Ad/1kEUsXRfKqNdmJYKat4ryJl2uPh60QXXlPfnf4y17dDFHJlnVb7qY10cCvKVz5ev5gAeg==} - '@privy-io/api-base@1.6.1': resolution: {integrity: sha512-GUGpW8FlwL+oTlKRNcuDRc5rfz2fPhfcqx2lHT58T4D3F54VxoXnX+NI8vsowogCddNq640d/p5FSKzBQJViZg==} @@ -2207,8 +2192,13 @@ packages: viem: optional: true - '@privy-io/public-api@2.43.1': - resolution: {integrity: sha512-zhGBTghZiwnqdA4YvrXXM7fsz3fWUltSkxNdnQTqKGb/IfV8aZ14ryuWvD4v5oPJGtqVcwKRfdDmW8TMPGZHog==} + '@privy-io/node@0.3.0': + resolution: {integrity: sha512-IKG1SghJuSpD9Jfr4ssRO2IBg6NFj/R8sllj17cbWBhxsd+5MoWKJNhwRgfxfBQxO5WTCOtCMg1jnjtB0x7glg==} + peerDependencies: + viem: ^2.24.1 + peerDependenciesMeta: + viem: + optional: true '@privy-io/public-api@2.44.2': resolution: {integrity: sha512-w+IpGmHIbCHyPka4fmcsIOO8IOaWRV8la/LRAZ1pDhkeKxMNR8zVVJu8jlLFbxu45tAhREvChpz0OGyDu40hmA==} @@ -2235,17 +2225,6 @@ packages: permissionless: optional: true - '@privy-io/server-auth@1.31.1': - resolution: {integrity: sha512-w0DT0VZCPcXa/Mxqzo7fhXoInX5i4J5BgvzjNsdtMuovgR790kMx/9+K/rSlgtQ/25/B7oDjoIk/f8kd5Ps6mA==} - peerDependencies: - ethers: ^6 - viem: ^2.24.1 - peerDependenciesMeta: - ethers: - optional: true - viem: - optional: true - '@react-aria/focus@3.21.1': resolution: {integrity: sha512-hmH1IhHlcQ2lSIxmki1biWzMbGgnhdxJUM0MFfzc71Rv6YAzhlx4kX3GYn4VNcjCeb6cdPv4RZ5vunV4kgMZYQ==} peerDependencies: @@ -4934,6 +4913,9 @@ packages: jose@4.15.9: resolution: {integrity: sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==} + jose@6.1.0: + resolution: {integrity: sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==} + joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} @@ -5186,6 +5168,10 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.2.2: + resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -5321,9 +5307,6 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - node-fetch-native@1.6.6: - resolution: {integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==} - node-fetch-native@1.6.7: resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} @@ -5939,9 +5922,6 @@ packages: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} - redaxios@0.5.1: - resolution: {integrity: sha512-FSD2AmfdbkYwl7KDExYQlVvIrFz6Yd83pGfaGjBzM9F6rpq8g652Q4Yq5QD4c+nf4g2AgeElv1y+8ajUPiOYMg==} - redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} @@ -6474,9 +6454,6 @@ packages: peerDependencies: typescript: '>=4.8.4' - ts-case-convert@2.1.0: - resolution: {integrity: sha512-Ye79el/pHYXfoew6kqhMwCoxp4NWjKNcm2kBzpmEMIU9dd9aBmHNNFtZ+WTm0rz1ngyDmfqDXDlyUnBXayiD0w==} - tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} @@ -6518,10 +6495,6 @@ packages: resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} engines: {node: '>=4'} - type-fest@3.13.1: - resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} - engines: {node: '>=14.16'} - typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -9031,23 +9004,12 @@ snapshots: dependencies: hono: 4.9.6 - '@hpke/chacha20poly1305@1.6.3': - dependencies: - '@hpke/common': 1.7.3 - '@noble/ciphers': 1.3.0 - '@hpke/chacha20poly1305@1.7.1': dependencies: '@hpke/common': 1.8.1 - '@hpke/common@1.7.3': {} - '@hpke/common@1.8.1': {} - '@hpke/core@1.7.3': - dependencies: - '@hpke/common': 1.7.3 - '@hpke/core@1.7.4': dependencies: '@hpke/common': 1.8.1 @@ -9637,10 +9599,6 @@ snapshots: '@pnpm/network.ca-file': 1.0.2 config-chain: 1.1.13 - '@privy-io/api-base@1.6.0': - dependencies: - zod: 3.25.76 - '@privy-io/api-base@1.6.1': dependencies: zod: 3.25.76 @@ -9678,17 +9636,21 @@ snapshots: - typescript - utf-8-validate - '@privy-io/public-api@2.43.1(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)': + '@privy-io/node@0.3.0(encoding@0.1.13)(viem@2.33.0(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@4.0.5))': dependencies: - '@privy-io/api-base': 1.6.0 - bs58: 5.0.0 - libphonenumber-js: 1.12.10 - viem: 2.37.3(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.25.76) - zod: 3.25.76 + '@hpke/chacha20poly1305': 1.7.1 + '@hpke/core': 1.7.4 + '@noble/curves': 1.9.7 + '@noble/hashes': 1.8.0 + '@scure/base': 1.2.6 + canonicalize: 2.1.0 + jose: 6.1.0 + lru-cache: 11.2.2 + svix: 1.69.0(encoding@0.1.13) + optionalDependencies: + viem: 2.33.0(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@4.0.5) transitivePeerDependencies: - - bufferutil - - typescript - - utf-8-validate + - encoding '@privy-io/public-api@2.44.2(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)': dependencies: @@ -9783,32 +9745,6 @@ snapshots: - utf-8-validate - zod - '@privy-io/server-auth@1.31.1(bufferutil@4.0.9)(encoding@0.1.13)(ethers@6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(typescript@5.8.3)(utf-8-validate@5.0.10)(viem@2.33.0(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@4.0.5))': - dependencies: - '@hpke/chacha20poly1305': 1.6.3 - '@hpke/core': 1.7.3 - '@noble/curves': 1.9.4 - '@noble/hashes': 1.8.0 - '@privy-io/public-api': 2.43.1(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10) - '@scure/base': 1.2.6 - '@solana/web3.js': 1.98.2(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.8.3)(utf-8-validate@5.0.10) - canonicalize: 2.1.0 - dotenv: 16.6.1 - jose: 4.15.9 - node-fetch-native: 1.6.6 - redaxios: 0.5.1 - svix: 1.69.0(encoding@0.1.13) - ts-case-convert: 2.1.0 - type-fest: 3.13.1 - optionalDependencies: - ethers: 6.15.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - viem: 2.33.0(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@4.0.5) - transitivePeerDependencies: - - bufferutil - - encoding - - typescript - - utf-8-validate - '@react-aria/focus@3.21.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@react-aria/interactions': 3.25.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -14319,6 +14255,8 @@ snapshots: jose@4.15.9: {} + jose@6.1.0: {} + joycon@3.1.1: {} js-cookie@3.0.5: {} @@ -14541,6 +14479,8 @@ snapshots: lru-cache@10.4.3: {} + lru-cache@11.2.2: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -14645,8 +14585,6 @@ snapshots: natural-compare@1.4.0: {} - node-fetch-native@1.6.6: {} - node-fetch-native@1.6.7: {} node-fetch@2.7.0(encoding@0.1.13): @@ -15404,8 +15342,6 @@ snapshots: real-require@0.2.0: {} - redaxios@0.5.1: {} - redent@3.0.0: dependencies: indent-string: 4.0.0 @@ -16056,8 +15992,6 @@ snapshots: dependencies: typescript: 5.8.3 - ts-case-convert@2.1.0: {} - tsconfig-paths@3.15.0: dependencies: '@types/json5': 0.0.29 @@ -16098,8 +16032,6 @@ snapshots: type-detect@4.1.0: {} - type-fest@3.13.1: {} - typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4