Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TASK-6125] Feat: Peanut Wallet #464

Open
wants to merge 11 commits into
base: peanut-wallet
Choose a base branch
from
1 change: 1 addition & 0 deletions src/app/(setup)/setup/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const SetupPage = () => {
step.containerClassname
)}
>

<div className='w-full md:w-1/2 mg:1/3 mx-auto gap-8 lg:gap-12 h-full flex flex-col'>
<div className="flex h-[100px] flex-col gap-4">
<h1 className="text-center text-5xl font-bold">{step.title}</h1>
Expand Down
52 changes: 29 additions & 23 deletions src/context/authContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ interface AuthContextType {
updateUserName: (username: string) => Promise<void>
submitProfilePhoto: (file: File) => Promise<void>
updateBridgeCustomerId: (bridgeCustomerId: string) => Promise<void>
registerUserWithPasskey: (username: string) => Promise<void>
loginUserWithPasskey: (username: string) => Promise<void>
addAccount: ({
accountIdentifier,
accountType,
Expand All @@ -38,7 +40,6 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
// TODO: address here should be fetched from the walletContext
// TODO: all mentions of wallet in components should pull from that address
const { address } = useAccount()
const { signMessageAsync } = useSignMessage()

const { address: kernelClientAddress, isKernelClientReady, handleLogin } = useZeroDev()

Expand All @@ -62,12 +63,33 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
}
}, [user])

// TODO: this needs to be moved elsewhere (i.e. possible walletContext), was
// here for testing - POSSIBLY be removed bc the order is reversed:
// you first login w/ passkeys, so flow below would never happen
useEffect(() => {
if (kernelClientAddress != null && isKernelClientReady) {
authAndFetchUser(kernelClientAddress)
}
}, [kernelClientAddress, isKernelClientReady])

const registerUserWithPasskey = async (username: string) => {
// validatiion of @handle has happened before this function
await handleLogin(username) // TODO: replace this with handleRegister
const kernelClient = await handleLogin(username) // TODO: replace this with handleRegister
// TODO: case of failure on register
}

const loginUserWithPasskey = async (username: string) => {
// validatiion of @handle has happened before this function
const kernelClient = await handleLogin(username)
// TODO: case of failure on login
}

const authAndFetchUser = async (address: string) => {
await authUser(address)
await fetchUser()
}

const authUser = async (address: string) => {
const userIdResponse = await fetch('/api/peanut/user/get-user-id', {
method: 'POST',
headers: {
Expand All @@ -80,14 +102,9 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {

const response = await userIdResponse.json()

const siwemsg = utils.createSiweMessage({
address: address ?? '',
statement: `Sign in to peanut.to. This is your unique user identifier! ${response.userId}`,
})
const message = 'CHANGE THIS STRING WITH A MORE ROBUST THAT INCLUDES USER_ID'

const signature = await signMessageAsync({
message: siwemsg,
})
const signature = await signMessage(message)

await fetch('/api/peanut/user/get-jwt-token', {
method: 'POST',
Expand All @@ -96,29 +113,16 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
},
body: JSON.stringify({
signature: signature,
message: siwemsg,
message: message,
}),
})


// TODO: handle case where they try to register w/ pre-registered passkey
}


// TODO: document better
// used after register too (there is a login event then too)
const afterLoginUserSetup = async (): Promise<undefined> => {
// set isAuthed
setIsAuthed(true)

//TODO: REMOVE THIS - ONLY FOR TESTING
await handleLogin('hey2')

// // fetch user wallets
// // set PW as active wallet
// setupWalletsAfterLogin()


}


Expand Down Expand Up @@ -356,6 +360,8 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
fetchUser,
updateUserName,
submitProfilePhoto,
loginUserWithPasskey,
registerUserWithPasskey,
addAccount,
isFetchingUser,
logoutUser,
Expand Down
49 changes: 14 additions & 35 deletions src/context/walletContext/walletContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ const WalletContext = createContext<WalletContextType | undefined>(undefined)

// TODO: change description
/**
* Context provider to manage user authentication and profile interactions.
* It handles fetching the user profile, updating user details (e.g., username, profile photo),
* adding accounts and logging out. It also provides hooks for child components to access user data and auth-related functions.
*
*
*
*/
export const WalletProvider = ({ children }: { children: ReactNode }) => {
////// ZeroDev props
Expand All @@ -40,39 +40,18 @@ export const WalletProvider = ({ children }: { children: ReactNode }) => {
const [selectedWallet, setSelectedWallet] = useState<interfaces.IWallet | undefined>(undefined) // TODO: this is the var that should be exposed for the app to consume, instead of const { address } = useAccount() anywhere

////// Wallets
const { data: wallets } = useQuery({
queryKey: ["wallets", user?.user.userId, kernelClientAddress, wagmiAddress],
const { data: wallets } = useQuery<interfaces.IWallet[]>({
queryKey: ["wallets", user?.user.userId],
queryFn: async () => {
/**
* TODO: fetch wallets from backend
* TODO: 2: Remove fetch & pass user?.account ?
*/
const localPasskeys = PasskeyStorage.list()
// const walletsResponse = await fetch('/api/peanut/user/get-wallets')
// if (walletsResponse.ok) {
// // receive in backend format
// const { dbWallets }: { dbWallets: interfaces.IDBWallet[] } = await walletsResponse.json()
// // manipulate to frontend format (add connected attribute)
// const wallets: interfaces.IWallet[] = dbWallets.map((dbWallet: interfaces.IDBWallet) => ({
// ...dbWallet,
// connected: false // this property will be processed into accurate values later in the flow
// }))
// }
return [
// {
// walletProviderType: interfaces.WalletProviderType.BYOW,
// protocolType: interfaces.WalletProtocolType.EVM,
// connected: false,
// address: '0x7D4c7063E003CeB8B9413f63569e7AB968AF3714'
// },
...localPasskeys.map(({ handle, account }) => ({
walletProviderType: interfaces.WalletProviderType.PEANUT,
protocolType: interfaces.WalletProtocolType.EVM,
connected: false,
address: account,
handle
}))
]
const processedWallets = user?.accounts.filter(
account => Object.values(interfaces.WalletProviderType).includes(account.account_type)
).map(account=> ({
walletProviderType: account.account_type,
protocolType: account.chain,
address: account.account_identifier,
connected: false
}))
return processedWallets ? processedWallets : []
}
})

Expand Down
22 changes: 17 additions & 5 deletions src/context/walletContext/zeroDevContext.context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ interface ZeroDevContextType {
setIsLoggingIn: (loggingIn: boolean) => void
isSendingUserOp: boolean
setIsSendingUserOp: (sendingUserOp: boolean) => void
handleRegister: (handle: string) => Promise<AppSmartAccountClient>
handleLogin: (handle: string) => Promise<void>
handleRegister: (username: string) => Promise<AppSmartAccountClient>
handleLogin: (username: string) => Promise<AppSmartAccountClient>
signMessage: (message: any) => Promise<string>
handleSendUserOpEncoded: (
{
to,
Expand Down Expand Up @@ -144,7 +145,6 @@ export const ZeroDevProvider = ({ children }: { children: ReactNode }) => {
setKernelClient(kernelClient)
setAddress(kernelClient.account!.address)
setIsKernelClientReady(true)

return kernelClient
}

Expand Down Expand Up @@ -180,7 +180,7 @@ export const ZeroDevProvider = ({ children }: { children: ReactNode }) => {

////// Login functions
//
const handleLogin = async (handle: string) => {
const handleLogin = async (handle: string): Promise<AppSmartAccountClient | undefined> => {
setIsLoggingIn(true)
try {

Expand All @@ -198,19 +198,30 @@ export const ZeroDevProvider = ({ children }: { children: ReactNode }) => {
validatorContractVersion: PasskeyValidatorContractVersion.V0_0_2
})

await createKernelClient(passkeyValidator)
const client = await createKernelClient(passkeyValidator)

setIsLoggingIn(false)

return client

} catch (e) {
toast.error('Error logging in. Please try again.')
} finally {
setIsLoggingIn(false)
return undefined
}
}


////// UserOp functions
//

const signMessage = async (message: any) => {
return kernelClient!.account!.signMessage({
message
})
}

// TODO: better docstrings
// used when data is already encoded from Peanut
// but remains unsigned
Expand Down Expand Up @@ -316,6 +327,7 @@ export const ZeroDevProvider = ({ children }: { children: ReactNode }) => {
isSendingUserOp, setIsSendingUserOp,
handleRegister,
handleLogin,
signMessage,
handleSendUserOpEncoded,
handleSendUserOpNotEncoded,
address
Expand Down
1 change: 1 addition & 0 deletions src/interfaces/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ interface Account {
referrer: string | null
referred_users_points: number
totalReferralPoints: number
chain: string
}

export interface IUserProfile {
Expand Down