From fd65d48d9d6442d5667a76cd764aec4dc374941d Mon Sep 17 00:00:00 2001 From: "v.snigerev" Date: Tue, 21 Apr 2026 21:50:30 +0300 Subject: [PATCH 1/2] refactor(wallet): make wallet provider extensible end-to-end Frontend: - Extract createProvider(type) factory in wallet-store; reconnect() reads persisted provider type instead of hardcoding zerodev - Parameterize providerType through use-auth login/register flows - AuthService.verifyWallet sends optional wallet_provider field Backend: - VerifyWalletDto accepts optional wallet_provider (<=64 chars) - Persist dto.wallet_provider on user creation (was hardcoded to 'walletconnect', mismatching the frontend's 'zerodev') - Open WalletProvider domain type to string; replace Postgres enum with text column so new providers don't require migration --- packages/app/src/hooks/use-auth.ts | 22 ++++++----- packages/app/src/services/AuthService.ts | 2 + packages/app/src/stores/wallet-store.ts | 37 +++++++++++++++---- .../application/dto/auth/verify-wallet.dto.ts | 1 + .../use-case/auth/verify-wallet.use-case.ts | 2 +- .../backend/src/domain/auth/model/user.ts | 2 +- .../repository/postgres/schema.ts | 4 +- 7 files changed, 48 insertions(+), 22 deletions(-) diff --git a/packages/app/src/hooks/use-auth.ts b/packages/app/src/hooks/use-auth.ts index ffb7a61..26cb138 100644 --- a/packages/app/src/hooks/use-auth.ts +++ b/packages/app/src/hooks/use-auth.ts @@ -1,7 +1,9 @@ import { useAuthStore } from '@/stores/auth-store'; -import { useWalletStore } from '@/stores/wallet-store'; +import { useWalletStore, type WalletProviderType } from '@/stores/wallet-store'; import { AuthService } from '@/services/AuthService'; +const DEFAULT_PROVIDER: WalletProviderType = 'zerodev'; + function buildSiweMessage(domain: string, address: string, statement: string, uri: string, nonce: string): string { const now = new Date().toISOString(); return [ @@ -18,7 +20,7 @@ function buildSiweMessage(domain: string, address: string, statement: string, ur ].join('\n'); } -async function authenticateWithSiwe(address: string) { +async function authenticateWithSiwe(address: string, providerType: WalletProviderType) { const { nonce } = await AuthService.requestNonce(address); const domain = window.location.host; @@ -27,10 +29,10 @@ async function authenticateWithSiwe(address: string) { const message = buildSiweMessage(domain, address, statement, origin, nonce); const signature = await useWalletStore.getState().signMessage(message); - const tokenResponse = await AuthService.verifyWallet(address, message, signature); + const tokenResponse = await AuthService.verifyWallet(address, message, signature, undefined, providerType); useAuthStore.getState().setTokens(tokenResponse.access_token, tokenResponse.refresh_token); - useAuthStore.getState().setWallet(address, 'zerodev'); + useAuthStore.getState().setWallet(address, providerType); } export function useAuth() { @@ -39,14 +41,14 @@ export function useAuth() { const walletDisconnect = useWalletStore((s) => s.disconnect); const authLogout = useAuthStore((s) => s.logout); - async function login() { - const address = await walletConnect('zerodev'); - await authenticateWithSiwe(address); + async function login(providerType: WalletProviderType = DEFAULT_PROVIDER) { + const address = await walletConnect(providerType); + await authenticateWithSiwe(address, providerType); } - async function register(username: string) { - const address = await walletRegister(username); - await authenticateWithSiwe(address); + async function register(username: string, providerType: WalletProviderType = DEFAULT_PROVIDER) { + const address = await walletRegister(providerType, username); + await authenticateWithSiwe(address, providerType); } async function logout() { diff --git a/packages/app/src/services/AuthService.ts b/packages/app/src/services/AuthService.ts index 3597678..6d75050 100644 --- a/packages/app/src/services/AuthService.ts +++ b/packages/app/src/services/AuthService.ts @@ -20,12 +20,14 @@ export class AuthService { message: string, signature: string, email?: string, + walletProvider?: string, ): Promise { const { data } = await httpClient.post('/v1/auth/wallet/verify', { wallet_address: walletAddress, message, signature, ...(email && { email }), + ...(walletProvider && { wallet_provider: walletProvider }), }); return data; } diff --git a/packages/app/src/stores/wallet-store.ts b/packages/app/src/stores/wallet-store.ts index daf2e88..c8a3fa5 100644 --- a/packages/app/src/stores/wallet-store.ts +++ b/packages/app/src/stores/wallet-store.ts @@ -4,6 +4,25 @@ import { ZeroDevProvider } from '@/providers/zerodev/zerodev.provider'; export type WalletProviderType = 'zerodev'; +const DEFAULT_PROVIDER_TYPE: WalletProviderType = 'zerodev'; + +function createProvider(type: WalletProviderType): IWalletProvider & { register?: (username: string) => Promise } { + switch (type) { + case 'zerodev': + return new ZeroDevProvider(); + default: { + const exhaustive: never = type; + throw new Error(`Unsupported wallet provider: ${exhaustive as string}`); + } + } +} + +function readPersistedProviderType(): WalletProviderType { + const stored = localStorage.getItem('wallet_provider'); + if (stored === 'zerodev') return 'zerodev'; + return DEFAULT_PROVIDER_TYPE; +} + let _provider: IWalletProvider | null = null; let _reconnectPromise: Promise | null = null; @@ -14,7 +33,7 @@ interface WalletState { error: string | null; isConnected: () => boolean; connect: (type: WalletProviderType) => Promise; - register: (username: string) => Promise; + register: (type: WalletProviderType, username: string) => Promise; disconnect: () => Promise; signMessage: (message: string) => Promise; sendUserOperation: (calls: Array<{ to: string; data: string; value?: bigint }>) => Promise; @@ -22,10 +41,11 @@ interface WalletState { } async function reconnect(): Promise { - const p = new ZeroDevProvider(); + const type = readPersistedProviderType(); + const p = createProvider(type); const addr = await p.connect(); _provider = p; - useWalletStore.setState({ activeProviderType: 'zerodev', address: addr }); + useWalletStore.setState({ activeProviderType: type, address: addr }); } export const useWalletStore = create((set, get) => ({ @@ -53,7 +73,7 @@ export const useWalletStore = create((set, get) => ({ connect: async (type) => { set({ connecting: true, error: null }); try { - const p = new ZeroDevProvider(); + const p = createProvider(type); const addr = await p.connect(); _provider = p; set({ activeProviderType: type, address: addr }); @@ -67,13 +87,16 @@ export const useWalletStore = create((set, get) => ({ } }, - register: async (username) => { + register: async (type, username) => { set({ connecting: true, error: null }); try { - const p = new ZeroDevProvider(); + const p = createProvider(type); + if (typeof p.register !== 'function') { + throw new Error(`Provider ${type} does not support register`); + } const addr = await p.register(username); _provider = p; - set({ activeProviderType: 'zerodev', address: addr }); + set({ activeProviderType: type, address: addr }); return addr; } catch (e) { const msg = e instanceof Error ? e.message : 'Registration failed'; diff --git a/packages/backend/src/application/dto/auth/verify-wallet.dto.ts b/packages/backend/src/application/dto/auth/verify-wallet.dto.ts index 8b46e39..11a5dcc 100644 --- a/packages/backend/src/application/dto/auth/verify-wallet.dto.ts +++ b/packages/backend/src/application/dto/auth/verify-wallet.dto.ts @@ -6,6 +6,7 @@ export const VerifyWalletDtoSchema = z.object({ message: z.string().min(1), signature: z.string().regex(/^0x/, 'Signature must start with 0x'), email: z.string().email().optional(), + wallet_provider: z.string().min(1).max(64).optional(), }); export type VerifyWalletDto = z.infer; diff --git a/packages/backend/src/application/use-case/auth/verify-wallet.use-case.ts b/packages/backend/src/application/use-case/auth/verify-wallet.use-case.ts index 5c31052..e03ff6b 100644 --- a/packages/backend/src/application/use-case/auth/verify-wallet.use-case.ts +++ b/packages/backend/src/application/use-case/auth/verify-wallet.use-case.ts @@ -37,7 +37,7 @@ export class VerifyWalletUseCase { user = new User({ id: randomUUID(), walletAddress: dto.wallet_address, - walletProvider: 'walletconnect', + walletProvider: dto.wallet_provider ?? 'unknown', email: dto.email, createdAt: new Date(), }); diff --git a/packages/backend/src/domain/auth/model/user.ts b/packages/backend/src/domain/auth/model/user.ts index 64b2699..6580cb0 100644 --- a/packages/backend/src/domain/auth/model/user.ts +++ b/packages/backend/src/domain/auth/model/user.ts @@ -1,4 +1,4 @@ -export type WalletProvider = 'zerodev' | 'walletconnect'; +export type WalletProvider = string; export interface UserParams { id: string; diff --git a/packages/backend/src/infrastructure/repository/postgres/schema.ts b/packages/backend/src/infrastructure/repository/postgres/schema.ts index bcbb8a2..fa00cdf 100644 --- a/packages/backend/src/infrastructure/repository/postgres/schema.ts +++ b/packages/backend/src/infrastructure/repository/postgres/schema.ts @@ -19,8 +19,6 @@ export const withdrawalStatusEnum = pgEnum('withdrawal_status', [ 'FAILED', ]); -export const walletProviderEnum = pgEnum('wallet_provider', ['zerodev', 'walletconnect']); - export const businessTypeEnum = pgEnum('business_type', ['RETAIL', 'SERVICE']); export const credentialStatusEnum = pgEnum('credential_status', ['active', 'revoked']); @@ -32,7 +30,7 @@ export const users = pgTable( { id: text('id').primaryKey(), walletAddress: text('wallet_address').unique().notNull(), - walletProvider: walletProviderEnum('wallet_provider').notNull(), + walletProvider: text('wallet_provider').notNull(), email: text('email'), createdAt: timestamp('created_at').notNull().defaultNow(), }, From 76672ab81988577ad52eeaddf5cc1910abbfd2f6 Mon Sep 17 00:00:00 2001 From: "v.snigerev" Date: Tue, 21 Apr 2026 21:56:53 +0300 Subject: [PATCH 2/2] style: prettier format on touched files --- packages/app/src/stores/wallet-store.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/app/src/stores/wallet-store.ts b/packages/app/src/stores/wallet-store.ts index c8a3fa5..e7160ce 100644 --- a/packages/app/src/stores/wallet-store.ts +++ b/packages/app/src/stores/wallet-store.ts @@ -6,7 +6,9 @@ export type WalletProviderType = 'zerodev'; const DEFAULT_PROVIDER_TYPE: WalletProviderType = 'zerodev'; -function createProvider(type: WalletProviderType): IWalletProvider & { register?: (username: string) => Promise } { +function createProvider( + type: WalletProviderType, +): IWalletProvider & { register?: (username: string) => Promise } { switch (type) { case 'zerodev': return new ZeroDevProvider();