Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
4 changes: 3 additions & 1 deletion apps/storybook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"@noble/hashes": "^1.6.1",
"@noble/secp256k1": "^2.1.0",
"@openzeppelin/merkle-tree": "^1.0.7",
"@privy-io/react-auth": "^1.95.2",
"@privy-io/react-auth": "^2.0.4",
"@radix-ui/colors": "^3.0.0",
"@radix-ui/react-accessible-icon": "^1.0.3",
"@radix-ui/react-accordion": "^1.2.0",
Expand Down Expand Up @@ -83,6 +83,8 @@
"lucide-react": "^0.446.0",
"micro-packed": "^0.6.3",
"multiformats": "^13.3.0",
"pako": "^2.1.0",
"permissionless": "^0.2.23",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-dropzone": "^14.2.9",
Expand Down
26 changes: 26 additions & 0 deletions apps/storybook/src/stories/decorators/privy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { PrivyAAProvider } from "@geist/ui-react/components/privy-aa/provider.js";
import { WAGMI_CONFIG } from "@geist/ui-react/lib/utils/wagmi-config.js";
import { QueryClient } from "@tanstack/react-query";

const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
},
},
});

export const withPrivyProvider = ({ appId }: { appId: string }) => {
return (Story: any) => (
<div>
<PrivyAAProvider
appId={appId}
queryClient={queryClient}
wagmiConfig={WAGMI_CONFIG}
privyConfig={{}}
>
{Story()}
</PrivyAAProvider>
</div>
);
};
20 changes: 20 additions & 0 deletions apps/storybook/src/stories/privy/privy-aa.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { PrivyAAFlow } from "@geist/ui-react/components/privy-aa/account-abstraction.js";

import config from "@geist/domain/config";
import type { Meta, StoryObj } from "@storybook/react";
import { withPrivyProvider } from "#stories/decorators/privy.tsx";

const meta = {
title: "Privy/Account Abstraction",
component: PrivyAAFlow,
argTypes: {},
decorators: [withPrivyProvider({ appId: config.privy.appId! })],
} satisfies Meta<typeof PrivyAAFlow>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {},
};
3 changes: 2 additions & 1 deletion apps/storybook/src/stories/privy/privy-login.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import config from "@geist/domain/config";
import {
PrivyLogin,
PrivyLoginProvider,
Expand Down Expand Up @@ -33,6 +34,6 @@ type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
appId: "cm2vi1gua0aukbq4p69w3rphl",
appId: config.privy.appId!,
},
};
6 changes: 5 additions & 1 deletion env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ AKAVE_API_KEY=
LIGHTHOUSE_API_KEY=

STORACHA_KEY=
STORACHA_PROOF=
STORACHA_PROOF=

PIMLICO_API_KEY=

PRIVY_APP_ID=cm2vi1gua0aukbq4p69w3rphl
2 changes: 1 addition & 1 deletion packages/domain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"@noble/hashes": "^1.6.1",
"@noble/secp256k1": "^2.1.0",
"@openzeppelin/merkle-tree": "^1.0.7",
"@privy-io/react-auth": "^1.95.2",
"@privy-io/react-auth": "^2.0.4",
"@geist/graphql": "workspace:*",
"@scure/base": "^1.1.9",
"@storybook/components": "^8.3.6",
Expand Down
6 changes: 6 additions & 0 deletions packages/domain/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,10 @@ export default {
privateKey: process.env.TEST_EAS_PRIVATE_KEY,
},
},
pimlico: {
apiKey: process.env.PIMLICO_API_KEY,
},
privy: {
appId: process.env.PRIVY_APP_ID,
},
};
37 changes: 20 additions & 17 deletions packages/ui-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,44 +18,44 @@
"generate:component": "turbo gen react-component"
},
"devDependencies": {
"@geist/typescript-config": "workspace:*",
"@turbo/gen": "^1.12.4",
"@types/node": "^22.10.2",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"typescript": "^5.7.2",
"vite": "^5.4.1",
"vite-plugin-node-polyfills": "^0.22.0",
"vitest": "^2.1.8",
"@biomejs/biome": "^1.9.4",
"@ethereum-attestation-service/eas-contracts": "1.7.1",
"@faker-js/faker": "^8.2.0",
"@geist/typescript-config": "workspace:*",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.0.1",
"@testing-library/react-hooks": "^8.0.1",
"@turbo/gen": "^1.12.4",
"@types/node": "^22.10.2",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.1",
"pako": "^2.1.0",
"react-test-renderer": "^18.3.1",
"typescript": "^5.7.2",
"typescript-eslint": "^8.0.1",
"pako": "^2.1.0"
"vite": "^5.4.1",
"vite-plugin-node-polyfills": "^0.22.0",
"vitest": "^2.1.8"
},
"dependencies": {
"@geist/domain": "workspace:*",
"@ensdomains/ensjs": "4.0.1-alpha.0",
"@ensdomains/ensjs-react": "^0.0.3",
"@ethereum-attestation-service/eas-sdk": "2.6.0",
"@ethereumjs/rlp": "^5.0.2",
"@geist/domain": "workspace:*",
"@geist/graphql": "workspace:*",
"@hookform/resolvers": "^3.9.0",
"@ipld/dag-ucan": "^3.4.0",
"@lighthouse-web3/kavach": "^0.1.9",
"@lighthouse-web3/sdk": "^0.3.7",
"postcss": "^8.4.24",
"autoprefixer": "10.4.20",
"@noble/ed25519": "2.1.0",
"@noble/hashes": "^1.6.1",
"@noble/secp256k1": "^2.1.0",
"@openzeppelin/merkle-tree": "^1.0.7",
"@radix-ui/themes": "^3.1.6",
"@privy-io/react-auth": "^1.95.2",
"@privy-io/react-auth": "^2.0.4",
"@privy-io/wagmi": "^1.0.2",
"@privy-io/wagmi-connector": "^0.1.13",
"@radix-ui/colors": "^3.0.0",
"@radix-ui/react-accessible-icon": "^1.0.3",
"@radix-ui/react-accordion": "^1.2.0",
Expand Down Expand Up @@ -88,7 +88,7 @@
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.0.4",
"@radix-ui/react-tooltip": "^1.1.2",
"@geist/graphql": "workspace:*",
"@radix-ui/themes": "^3.1.6",
"@scure/base": "^1.1.9",
"@storybook/components": "^8.3.6",
"@storybook/icons": "^1.2.12",
Expand All @@ -100,8 +100,8 @@
"@ucanto/interface": "^10.0.1",
"@ucanto/transport": "^9.1.1",
"@wagmi/core": "^2.13.8",
"wagmi": "^2.12.16",
"@web3-storage/w3up-client": "^16.5.1",
"autoprefixer": "10.4.20",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
Expand All @@ -118,6 +118,8 @@
"micro-eth-signer": "^0.12.0",
"micro-packed": "^0.6.3",
"multiformats": "^13.3.0",
"permissionless": "^0.2.23",
"postcss": "^8.4.24",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-dropzone": "^14.2.9",
Expand All @@ -128,6 +130,7 @@
"tailwindcss": "^3.4.13",
"tailwindcss-animate": "^1.0.7",
"viem": "2.x",
"wagmi": "^2.12.16",
"zod": "^3.23.8"
}
}
112 changes: 112 additions & 0 deletions packages/ui-react/src/components/privy-aa/account-abstraction.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import {
type ConnectedWallet,
usePrivy,
useWallets,
} from "@privy-io/react-auth";
import { useSetActiveWallet } from "@privy-io/wagmi";
import {
type SmartAccountClient,
createSmartAccountClient,
} from "permissionless";
import { toSimpleSmartAccount } from "permissionless/accounts";
import { useEffect, useMemo, useState } from "react";
import { zeroAddress } from "viem";
import { entryPoint07Address } from "viem/account-abstraction";
import { sepolia } from "viem/chains";
import { http, useAccount, usePublicClient, useWalletClient } from "wagmi";

import { Button } from "#components/shadcn/button";
import { TransferButton } from "#components/transfer/transfer-button";
import { usePrivyAA } from "./provider";

export function PrivyAAFlow() {
const { pimlicoRpcUrl, pimlicoClient } = usePrivyAA();
const { login, ready: isReady, authenticated: isAuthenticated } = usePrivy();

const { isConnected } = useAccount();
const [smartAccountClient, setSmartAccountClient] =
useState<SmartAccountClient | null>(null);

const publicClient = usePublicClient();
const { wallets, ready: isWalletsReady } = useWallets();
const { data: walletClient } = useWalletClient();

const embeddedWallet = useMemo(
() => wallets.find((wallet) => wallet.walletClientType === "privy"),
[wallets],
);

const { setActiveWallet } = useSetActiveWallet();

useEffect(() => {
(async () => {
if (isConnected && walletClient && publicClient && embeddedWallet) {
const owner = await embeddedWallet.getEthereumProvider();

if (!owner) {
throw new Error("No owner found");
}

const simpleSmartAccount = await toSimpleSmartAccount({
owner,
client: publicClient,
entryPoint: {
address: entryPoint07Address,
version: "0.7",
},
});

const smartAccountClient = createSmartAccountClient({
account: simpleSmartAccount,
chain: sepolia,
bundlerTransport: http(pimlicoRpcUrl),
paymaster: pimlicoClient,
userOperation: {
estimateFeesPerGas: async () => {
return (await pimlicoClient.getUserOperationGasPrice()).fast;
},
},
});

setSmartAccountClient(smartAccountClient);
}
})();
}, [isConnected, walletClient, publicClient]);

if (!isReady) {
return null;
}

if (isConnected && smartAccountClient && embeddedWallet) {
return (
<div>
<TransferButton
amount={0}
to={zeroAddress}
smartAccountClient={smartAccountClient}
/>
</div>
);
}

return (
<>
{!isAuthenticated && <Button onClick={login}>Sign in with Privy</Button>}

{isWalletsReady &&
wallets.map((wallet: ConnectedWallet) => {
return (
<Button
key={wallet.address}
onClick={async () => {
// @ts-ignore
await setActiveWallet(wallet);
}}
>
Make active: {wallet.address}
</Button>
);
})}
</>
);
}
Loading