Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions packages/app/src/hooks/use-auth.ts
Original file line number Diff line number Diff line change
@@ -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 [
Expand All @@ -18,7 +20,7 @@
].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;
Expand All @@ -27,10 +29,10 @@
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() {
Expand All @@ -39,20 +41,20 @@
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() {
try {
await AuthService.logout();
} catch {

Check warning on line 57 in packages/app/src/hooks/use-auth.ts

View workflow job for this annotation

GitHub Actions / Lint & Format

Empty block statement
} finally {
authLogout();
await walletDisconnect();
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/services/AuthService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ export class AuthService {
message: string,
signature: string,
email?: string,
walletProvider?: string,
): Promise<TokenResponse> {
const { data } = await httpClient.post<TokenResponse>('/v1/auth/wallet/verify', {
wallet_address: walletAddress,
message,
signature,
...(email && { email }),
...(walletProvider && { wallet_provider: walletProvider }),
});
return data;
}
Expand Down
39 changes: 32 additions & 7 deletions packages/app/src/stores/wallet-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,27 @@ 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<string> } {
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<void> | null = null;

Expand All @@ -14,18 +35,19 @@ interface WalletState {
error: string | null;
isConnected: () => boolean;
connect: (type: WalletProviderType) => Promise<string>;
register: (username: string) => Promise<string>;
register: (type: WalletProviderType, username: string) => Promise<string>;
disconnect: () => Promise<void>;
signMessage: (message: string) => Promise<string>;
sendUserOperation: (calls: Array<{ to: string; data: string; value?: bigint }>) => Promise<string>;
ensureConnected: () => Promise<void>;
}

async function reconnect(): Promise<void> {
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<WalletState>((set, get) => ({
Expand Down Expand Up @@ -53,7 +75,7 @@ export const useWalletStore = create<WalletState>((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 });
Expand All @@ -67,13 +89,16 @@ export const useWalletStore = create<WalletState>((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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof VerifyWalletDtoSchema>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
});
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/domain/auth/model/user.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type WalletProvider = 'zerodev' | 'walletconnect';
export type WalletProvider = string;

export interface UserParams {
id: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
Expand All @@ -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(),
},
Expand Down
Loading