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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ NEXT_PUBLIC_KWIL_JS_SDK_DOCS_URL=https://docs.kwil.com/docs/sdks/js-ts/overview
NEXT_PUBLIC_KWIL_CLI_DOWNLOAD_URL=https://github.com/kwilteam/kwil-db/releases
NEXT_PUBLIC_GOOGLE_REDIRECT_URL=https://localhost:3000/firebird/deployments
NEXT_PUBLIC_GOOGLE_SIGN_IN_URL=https://dev-firebird.kwil.com/api/auth/googlebutton?redirect=${NEXT_PUBLIC_GOOGLE_REDIRECT_URL}
NEXT_PUBLIC_PRIVY_APP_ID=
5 changes: 4 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
{
"extends": "next/core-web-vitals"
"extends": "next/core-web-vitals",
"globals": {
"vi": true
}
}
9 changes: 8 additions & 1 deletion __tests__/components/UserAccount.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import { beforeEach, describe, expect, it } from "vitest"
import { beforeEach, describe, expect, it, vi } from "vitest"
import { act, render, screen } from "@testing-library/react"
import "@testing-library/jest-dom"
import { Provider } from "react-redux"
import { mockStore } from "../mocks/mock-store"
import UserAccount from "@/components/UserAccount"

vi.mock("@/hooks/use-privy-accounts", () => ({
usePrivyAccounts: () => ({
ready: true,
}),
}))


const mockActiveAccount = "0x1234567890abcdef1234567890abcdef12345678"

const storeData = mockStore({
Expand Down
5 changes: 4 additions & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ReduxProvider } from "@/providers/StoreProvider"
import "./globals.css"
import { IdbProvider } from "@/providers/IdbProvider"
import { WebKwilProvider } from "@/providers/WebKwilProvider"
import PrivyAuth from "@/providers/PrivyProvider"

const manrope = Manrope({ subsets: ["latin"] })

Expand All @@ -31,7 +32,9 @@ export default function RootLayout({
>
<ReduxProvider>
<WebKwilProvider>
<IdbProvider>{children}</IdbProvider>
<PrivyAuth>
<IdbProvider>{children}</IdbProvider>
</PrivyAuth>
</WebKwilProvider>
</ReduxProvider>
</body>
Expand Down
26 changes: 13 additions & 13 deletions components/Modal/ReadOnlyModal.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import { useAppDispatch, useAppSelector } from "@/store/hooks"
import {
ModalEnum,
selectModal,
setActiveAccount,
setModal,
} from "@/store/global"
import { getAddress } from "@/utils/wallet"
import { ModalEnum, selectModal, setModal } from "@/store/global"
import Button from "../Button"
import Base from "./Base"
import { usePrivyAccounts } from "@/hooks/use-privy-accounts"

export default function ReadOnlyModal({
activeAccount,
Expand All @@ -16,16 +11,15 @@ export default function ReadOnlyModal({
}) {
const dispatch = useAppDispatch()
const modal = useAppSelector(selectModal)
const { ready, connectOrCreateWallet } = usePrivyAccounts()

const continueReadOnly = () => {
dispatch(setModal(undefined))
}

const connectWallet = async () => {
try {
const address = await getAddress()
dispatch(setActiveAccount(address))
dispatch(setModal(undefined))
await connectOrCreateWallet()
} catch (e) {
console.log(e)
}
Expand All @@ -50,9 +44,15 @@ export default function ReadOnlyModal({
<Button context="secondary" size="md" onClick={continueReadOnly}>
Continue
</Button>
<Button context="primary" size="md" onClick={connectWallet}>
Connect Wallet
</Button>
{ready ? (
<Button context="primary" size="md" onClick={connectWallet}>
Connect Wallet
</Button>
) : (
<Button context="primary" size="md" disabled>
Loading...
</Button>
)}
</div>
)

Expand Down
30 changes: 14 additions & 16 deletions components/UserAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ import { ChevronDownIcon, ProfileIcon, SignOutIcon } from "@/utils/icons"
import { useKwilProvider } from "@/providers/WebKwilProvider"
import { usePathname } from "next/navigation"
import { formatEther } from "ethers"
import { getAddress } from "@/utils/wallet"
import { hexToBytes } from "@kwilteam/kwil-js/dist/utils/serial"
import { usePrivyAccounts } from "@/hooks/use-privy-accounts"

interface IUserInfoProps extends React.HTMLAttributes<HTMLDivElement> {
activeAccount: string | undefined
}

export default function UserAccount({ activeAccount }: IUserInfoProps) {
const { ready, connectOrCreateWallet, disconnectWallet } = usePrivyAccounts()

const dispatch = useAppDispatch()
const [abbreviatedAccount, setAbbreviatedAccount] = useState<
string | undefined
Expand All @@ -24,20 +26,6 @@ export default function UserAccount({ activeAccount }: IUserInfoProps) {
string | undefined
>()

const disconnectWallet = () => {
dispatch(setActiveAccount(undefined))
}

const connectWallet = async () => {
try {
const address = await getAddress()
dispatch(setActiveAccount(address))
dispatch(setModal(undefined))
} catch (e) {
console.log(e)
}
}

useEffect(() => {
if (activeAccount) {
const _abbreviatedUser =
Expand All @@ -63,12 +51,22 @@ export default function UserAccount({ activeAccount }: IUserInfoProps) {
}
}, [dispatch])

if (!ready) {
return <>
<button
className="inline-flex w-full items-center justify-center gap-2 rounded-md border border-slate-200 bg-white p-1 px-2 text-sm font-thin text-slate-800 hover:bg-slate-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-white/75"
>
<ProfileIcon className="h-4 w-4" />Loading...
</button>
</>
}

if (!activeAccount)
return (
<>
<button
className="inline-flex w-full items-center justify-center gap-2 rounded-md border border-slate-200 bg-white p-1 px-2 text-sm font-thin text-slate-800 hover:bg-slate-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-white/75"
onClick={connectWallet}
onClick={connectOrCreateWallet}
>
<ProfileIcon className="h-4 w-4" /> Connect
</button>
Expand Down
40 changes: 34 additions & 6 deletions hooks/use-kwil-signer.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
import { useEffect, useState } from "react"
import { useCallback, useEffect, useState } from "react"
import { KwilSigner } from "@kwilteam/kwil-js" // or NodeKwil
import { getAddress, getSigner } from "../utils/wallet"
import { useAppSelector } from "@/store/hooks"
import { selectActiveAccount } from "@/store/global"
import { usePrivyAccounts } from "./use-privy-accounts"
import { useSignMessage } from "@privy-io/react-auth"
import { bytesToString, hexToBytes } from "@kwilteam/kwil-js/dist/utils/serial"

export const useKwilSigner = (): KwilSigner | undefined => {
const activeAccount = useAppSelector(selectActiveAccount)
const { wallets } = usePrivyAccounts();
const { signMessage } = useSignMessage();
const [kwilSigner, setKwilSigner] = useState<KwilSigner | undefined>(
undefined,
)

const privySigner = useCallback(
async (m: Uint8Array) => {
const message = bytesToString(m)
const s = await signMessage({ message })
return hexToBytes(s.signature)
},
[signMessage]
)

useEffect(() => {
if (!activeAccount) {
setKwilSigner(undefined)
Expand All @@ -18,17 +32,31 @@ export const useKwilSigner = (): KwilSigner | undefined => {

const initKwilSigner = async () => {
try {
const signer = await getSigner()
const identifier = await getAddress()
const _kwilSigner = new KwilSigner(signer, identifier)
setKwilSigner(_kwilSigner)
const wallet = wallets.find((w) => w.address.toLowerCase() === activeAccount.toLowerCase())
if (!wallet) {
console.error("Wallet not found for the active account")
return
}

if(wallet.walletClientType === 'privy') {
// using privy wallet
const _kwilSigner = new KwilSigner(privySigner, wallet.address, "secp256k1_ep")
setKwilSigner(_kwilSigner)
} else {
// using injected wallet
const provider = await wallet.getEthereumProvider()
const identifier = await getAddress(provider)
const signer = await getSigner(provider)
const _kwilSigner = new KwilSigner(signer, identifier)
setKwilSigner(_kwilSigner)
}
} catch (error) {
console.error("Failed to initialize KwilSigner:", error)
}
}

initKwilSigner()
}, [activeAccount])
}, [activeAccount, wallets, privySigner])

return kwilSigner
}
42 changes: 42 additions & 0 deletions hooks/use-privy-accounts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { setActiveAccount, setModal } from "@/store/global";
import { useAppDispatch } from "@/store/hooks";
import { useLogin, usePrivy, useWallets } from "@privy-io/react-auth"
import { useEffect } from "react";

export const usePrivyAccounts = () => {
const { ready, authenticated, logout } = usePrivy();
const { login } = useLogin();
const { wallets } = useWallets();
const dispatch = useAppDispatch();

const connectOrCreateWallet = async () => {
login({
loginMethods: ["wallet", "email"],
walletChainType: "ethereum-only",
disableSignup: false
})
}

useEffect(() => {
if (authenticated) {
const wallet = wallets[0]
if (wallet) {
const address = wallet.address
dispatch(setActiveAccount(address))
dispatch(setModal(undefined))
}
}
}, [authenticated, wallets, dispatch])

const disconnectWallet = async () => {
await logout()
dispatch(setActiveAccount(undefined))
}

return {
ready,
connectOrCreateWallet,
disconnectWallet,
wallets
}
}
4 changes: 4 additions & 0 deletions next.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ const nextConfig = {
"/ide": ["./wasm/**/*"],
},
},
webpack: (config) => {
config.externals['@solana/web3.js'] = 'commonjs @solana/web3.js';
return config;
}
}

module.exports = nextConfig
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
"@headlessui/react": "^2.0.0-alpha.4",
"@kwilteam/kwil-js": "^0.9.0",
"@monaco-editor/react": "^4.5.2",
"@privy-io/react-auth": "^2.8.0",
"@reduxjs/toolkit": "^1.9.5",
"@solana/web3.js": "^1.98.0",
"@tailwindcss/forms": "^0.5.7",
"@types/js-cookie": "^3.0.6",
"@types/react": "^18.2.33",
Expand Down
25 changes: 25 additions & 0 deletions providers/PrivyProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use client';

import {PrivyProvider} from '@privy-io/react-auth';

export default function PrivyAuth({children}: {children: React.ReactNode}) {
return (
<PrivyProvider
appId={process.env.NEXT_PUBLIC_PRIVY_APP_ID || ""}
config={{
appearance: {
theme: 'light',
accentColor: '#418E9D',
logo: '/images/kwil.png',
walletChainType: "ethereum-only",
walletList: ['metamask', 'coinbase_wallet', 'detected_ethereum_wallets']
},
embeddedWallets: {
createOnLogin: "users-without-wallets"
},
}}
>
{children}
</PrivyProvider>
);
}
23 changes: 7 additions & 16 deletions utils/wallet.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
import { BrowserProvider, JsonRpcProvider } from "ethers"
import { BrowserProvider, Eip1193Provider } from "ethers"

declare global {
interface Window {
ethereum: any
}
export const getProvider = async (p: Eip1193Provider) => {
return new BrowserProvider(p)
}

export const getProvider = async () => {
if (window.ethereum) {
return new BrowserProvider(window.ethereum)
}
return new JsonRpcProvider()
}

export const getSigner = async () => {
const provider = await getProvider()
export const getSigner = async (p: Eip1193Provider) => {
const provider = await getProvider(p)
return provider.getSigner()
}

export const getAddress = async () => {
const signer = await getSigner()
export const getAddress = async (p: Eip1193Provider) => {
const signer = await getSigner(p)
return await signer.getAddress()
}
Loading