diff --git a/.env.example b/.env.example index 094a194..8c275c9 100644 --- a/.env.example +++ b/.env.example @@ -1,29 +1,37 @@ -# Get your OpenAI API Key here for chat models: https://platform.openai.com/account/api-keys -OPENAI_API_KEY=**** +# AI Model API Keys +OPENAI_API_KEY=**** # Get your OpenAI API Key here: https://platform.openai.com/account/api-keys -# Get your Fireworks AI API Key here for reasoning models: https://fireworks.ai/account/api-keys -FIREWORKS_API_KEY=**** +# Storage +BLOB_READ_WRITE_TOKEN=**** # Instructions: https://vercel.com/docs/storage/vercel-blob -# Generate a random secret: https://generate-secret.vercel.app/32 or `openssl rand -base64 32` -AUTH_SECRET=**** +# Database +POSTGRES_URL=postgres://user:pass@host:5432/dbname # Instructions: https://vercel.com/docs/storage/vercel-postgres/quickstart +POSTGRES_URL_NON_POOLING=${POSTGRES_URL}?sslmode=require # For non-pooling connections -# The following keys below are automatically created and -# added to your environment when you deploy on vercel - -# Instructions to create a Vercel Blob Store here: https://vercel.com/docs/storage/vercel-blob -BLOB_READ_WRITE_TOKEN=**** - -# Instructions to create a database here: https://vercel.com/docs/storage/vercel-postgres/quickstart -POSTGRES_URL=postgres://user:pass@host:5432/dbname - -# Privy configuration - get these from your Privy dashboard +# Privy Configuration - get these from your Privy dashboard PRIVY_APP_ID=**** PRIVY_APP_SECRET=**** PRIVY_WALLET_ID=**** PRIVY_WALLET_AUTHORIZATION_KEY=**** -# OnchainKit API Key - get from OnchainKit dashboard +# OnchainKit Configuration NEXT_PUBLIC_ONCHAINKIT_API_KEY=**** +NEXT_PUBLIC_CDP_PROJECT_ID=**** + +# Authentication & Security +AUTH_SECRET=**** # Generate a random secret: https://generate-secret.vercel.app/32 or `openssl rand -base64 32` +SESSION_SECRET=**** # Generate a random secret: https://generate-secret.vercel.app/32 + +# Coinbase Commerce Configuration +NEXT_PUBLIC_COINBASE_COMMERCE_PRODUCT_STARTER_KIT=**** +NEXT_PUBLIC_COINBASE_COMMERCE_PRODUCT_STARTER_KIT_GIFT=**** +COINBASE_COMMERCE_API_KEY=**** + +# Web3 Configuration +NEXT_PUBLIC_ACTIVE_CHAIN=base # or base-sepolia for testnet + +# Alchemy Configuration +ALCHEMY_API_KEY=**** -# Session secret for cookie encryption -SESSION_SECRET=**** +# Pinata Configuration +PINATA_JWT=**** diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index ff67dbd..d9b49b7 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -7,7 +7,7 @@ import { import { auth } from "@/app/auth"; import { myProvider } from "@/lib/ai/models"; -import { systemPrompt } from "@/lib/ai/prompts"; +import { generateSystemPrompt } from "@/lib/ai/prompts"; import { deleteChatById, getChatById, @@ -25,21 +25,15 @@ import { generateTitleFromUserMessage } from "../../actions"; import { createDocument } from "@/lib/ai/tools/create-document"; import { updateDocument } from "@/lib/ai/tools/update-document"; import { requestSuggestions } from "@/lib/ai/tools/request-suggestions"; -import { - pythActionProvider, - walletActionProvider, - AgentKit, -} from "@coinbase/agentkit"; -import { erc20ActionProvider } from "@/lib/web3/agentkit/action-providers/erc20/erc20ActionProvider"; -import { PrivyWalletProvider } from "@/lib/web3/agentkit/wallet-providers/privyWalletProvider"; import { agentKitToTools } from "@/lib/web3/agentkit/framework-extensions/ai-sdk"; -import { safeActionProvider } from "@/lib/web3/agentkit/action-providers/safe"; import { z } from "zod"; import { saveUserInformation, getUserInformation, deleteUserInformationTool, } from "@/lib/ai/tools/user-information"; +import { setupAgentKit } from "@/lib/web3/agentkit/setup"; +import { generateUserProfile } from "@/lib/ai/prompts/user"; export const maxDuration = 60; @@ -51,6 +45,11 @@ export async function POST(request: Request) { }: { id: string; messages: Array; selectedChatModel: string } = await request.json(); + const attachments = messages.flatMap( + (message) => message.experimental_attachments ?? [] + ); + + let userProfile = "User is not signed in"; const session = await auth(); const userMessage = getMostRecentUserMessage(messages); @@ -58,10 +57,12 @@ export async function POST(request: Request) { return new Response("No user message found", { status: 400 }); } - let userProfile = "User is not signed in"; - // If user is authenticated, save chat history if (session?.user?.id) { - userProfile = JSON.stringify(await getUser(session.user.id)); + const userInfo = await getUser(session.user.id); + userProfile = generateUserProfile({ + userInfo: userInfo[0], + attachments, + }); const chat = await getChatById({ id }); if (!chat) { @@ -75,34 +76,15 @@ export async function POST(request: Request) { }); } - const walletProvider = await PrivyWalletProvider.configureWithWallet({ - appId: process.env.PRIVY_APP_ID as string, - appSecret: process.env.PRIVY_APP_SECRET as string, - networkId: "base-sepolia", - walletId: process.env.PRIVY_WALLET_ID as string, - authorizationKey: process.env.PRIVY_WALLET_AUTHORIZATION_KEY as string, - }); - - const agentKit = await AgentKit.from({ - walletProvider, - actionProviders: [ - pythActionProvider(), - walletActionProvider(), - erc20ActionProvider(), - safeActionProvider(), - ], - }); + const agentKit = await setupAgentKit(); const tools = agentKitToTools(agentKit); - const userSystemInformation = session?.user?.id - ? `The user is signed in as ${session.user.id}.` - : `The user is not signed in.`; return createDataStreamResponse({ execute: (dataStream) => { const result = streamText({ model: myProvider.languageModel(selectedChatModel), - system: systemPrompt({ selectedChatModel }) + userProfile, + system: generateSystemPrompt({ selectedChatModel }) + userProfile, messages, maxSteps: 10, // experimental_activeTools: @@ -185,10 +167,6 @@ export async function POST(request: Request) { } } }, - experimental_telemetry: { - isEnabled: true, - functionId: "stream-text", - }, }); result.mergeIntoDataStream(dataStream, { diff --git a/app/api/commerce/verify/[chargeId]/route.ts b/app/api/commerce/verify/[productId]/[chargeId]/route.ts similarity index 59% rename from app/api/commerce/verify/[chargeId]/route.ts rename to app/api/commerce/verify/[productId]/[chargeId]/route.ts index 13ff555..8d803e7 100644 --- a/app/api/commerce/verify/[chargeId]/route.ts +++ b/app/api/commerce/verify/[productId]/[chargeId]/route.ts @@ -1,17 +1,17 @@ -import { NextResponse } from "next/server"; +import { type NextRequest, NextResponse } from "next/server"; import { auth } from "@/app/auth"; import { createCharge, getChargeById, - updateChargeStatus, createStarterKit, } from "@/lib/db/queries"; +import type { CoinbaseChargeResponse } from "@/lib/types/coinbase"; -export async function POST( - request: Request, - { params }: { params: Promise<{ chargeId: string }> } -) { - const { chargeId } = await params; +export const POST = async ( + request: NextRequest, + { params }: { params: Promise<{ chargeId: string; productId: string }> } +) => { + const { chargeId, productId } = await params; const session = await auth(); if (!session?.user?.id) { @@ -43,9 +43,15 @@ export async function POST( throw new Error("Failed to verify charge with Coinbase Commerce"); } - const data = await response.json(); - const latestStatus = data.timeline[0]?.status; - const successEvent = data.web3_data?.success_events?.[0]; + const { data } = (await response.json()) as { + data: CoinbaseChargeResponse; + }; + + const amount = data.pricing?.settlement?.amount || "10"; + const currency = data.pricing?.settlement?.currency || "USDC"; + + // const latestStatus = data?.timeline?.[data.timeline.length - 1]?.status; + // const successEvent = data?.web3_data?.success_events?.[0]; // Check if charge exists in our database const existingCharge = await getChargeById(chargeId); @@ -55,33 +61,18 @@ export async function POST( await createCharge({ id: chargeId, userId: session.user.id, - amount: data.pricing.local.amount, - currency: data.pricing.local.currency, + amount, + currency, product: "STARTERKIT", }); - } - // Update charge with latest status - await updateChargeStatus({ - id: chargeId, - status: latestStatus, - payerAddress: successEvent?.sender, - transactionHash: successEvent?.tx_hash, - confirmedAt: data.confirmed_at ? new Date(data.confirmed_at) : undefined, - expiresAt: data.expires_at ? new Date(data.expires_at) : undefined, - }); - - // If charge is completed and it's a starter kit, create it - if ( - latestStatus === "COMPLETED" && - (!existingCharge || existingCharge[0]?.status !== "COMPLETED") - ) { - const value = Number.parseInt(data.pricing.local.amount, 10); + const value = Number.parseInt(amount, 10); const isGift = - data.pricing.product_id === + productId === process.env.NEXT_PUBLIC_COINBASE_COMMERCE_PRODUCT_STARTER_KIT_GIFT; await createStarterKit({ + id: chargeId, value, userId: session.user.id, chargeId, @@ -89,7 +80,10 @@ export async function POST( }); } - return NextResponse.json(data); + return NextResponse.json({ + success: true, + message: "Charge verified successfully", + }); } catch (error) { console.error("Error verifying charge:", error); return NextResponse.json( @@ -97,4 +91,4 @@ export async function POST( { status: 500 } ); } -} +}; diff --git a/app/api/starter-kit/claim/[kitId]/route.ts b/app/api/starter-kit/claim/[kitId]/route.ts index 18e56a1..cce0fb7 100644 --- a/app/api/starter-kit/claim/[kitId]/route.ts +++ b/app/api/starter-kit/claim/[kitId]/route.ts @@ -4,7 +4,7 @@ import { claimStarterKit } from "@/lib/db/queries"; export async function POST( request: Request, - { params }: { params: { kitId: string } } + { params }: { params: Promise<{ kitId: string }> } ) { const session = await auth(); if (!session?.user?.id) { @@ -13,7 +13,7 @@ export async function POST( try { await claimStarterKit({ - kitId: params.kitId, + kitId: (await params).kitId, userId: session.user.id, }); return NextResponse.json({ success: true }); diff --git a/app/api/starter-kit/give/[kitId]/[recipientId]/route.ts b/app/api/starter-kit/give/[kitId]/[recipientId]/route.ts index 040aa47..d9c6252 100644 --- a/app/api/starter-kit/give/[kitId]/[recipientId]/route.ts +++ b/app/api/starter-kit/give/[kitId]/[recipientId]/route.ts @@ -4,17 +4,19 @@ import { claimStarterKit, getCreatedStarterKits } from "@/lib/db/queries"; export async function POST( request: Request, - { params }: { params: { kitId: string; recipientId: string } } + { params }: { params: Promise<{ kitId: string; recipientId: string }> } ) { const session = await auth(); if (!session?.user?.id) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } + const { kitId, recipientId } = await params; + try { // Verify ownership const kits = await getCreatedStarterKits(session.user.id); - const kit = kits.find((k) => k.id === params.kitId); + const kit = kits.find((k) => k.id === kitId); if (!kit) { return NextResponse.json( @@ -24,8 +26,8 @@ export async function POST( } await claimStarterKit({ - kitId: params.kitId, - userId: params.recipientId, + kitId, + userId: recipientId, }); return NextResponse.json({ success: true }); } catch (error) { diff --git a/app/layout.tsx b/app/layout.tsx index 1c146e2..0df7cae 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -12,7 +12,6 @@ import { auth } from "@/app/auth"; import "./globals.css"; import "@coinbase/onchainkit/styles.css"; import { ConnectButton } from "@/components/connect-button"; - export const metadata: Metadata = { metadataBase: new URL("https://chat.vercel.ai"), title: "Next.js Chatbot Template", diff --git a/components/block.tsx b/components/block.tsx index 890ad8f..3fcfe30 100644 --- a/components/block.tsx +++ b/components/block.tsx @@ -294,7 +294,7 @@ function PureBlock({ scale: 1, transition: { delay: 0.2, - type: 'spring', + type: "spring", stiffness: 200, damping: 30, }, @@ -333,16 +333,10 @@ function PureBlock({ @@ -377,11 +371,11 @@ function PureBlock({ x: 0, y: 0, height: windowHeight, - width: windowWidth ? windowWidth : 'calc(100dvw)', + width: windowWidth ? windowWidth : "calc(100dvw)", borderRadius: 0, transition: { delay: 0, - type: 'spring', + type: "spring", stiffness: 200, damping: 30, duration: 5000, @@ -394,11 +388,11 @@ function PureBlock({ height: windowHeight, width: windowWidth ? windowWidth - 400 - : 'calc(100dvw-400px)', + : "calc(100dvw-400px)", borderRadius: 0, transition: { delay: 0, - type: 'spring', + type: "spring", stiffness: 200, damping: 30, duration: 5000, @@ -410,7 +404,7 @@ function PureBlock({ scale: 0.5, transition: { delay: 0.1, - type: 'spring', + type: "spring", stiffness: 600, damping: 30, }, @@ -434,7 +428,7 @@ function PureBlock({ new Date(), { addSuffix: true, - }, + } )}`} ) : ( diff --git a/components/connect-button.tsx b/components/connect-button.tsx index 2b98f2d..28e63e6 100644 --- a/components/connect-button.tsx +++ b/components/connect-button.tsx @@ -2,21 +2,40 @@ import { useAuth } from "@/hooks/use-auth"; import { Button } from "@/components/ui/button"; -import { WalletDefault } from "@coinbase/onchainkit/wallet"; +import { + ConnectWallet, + Wallet, + WalletDropdown, + WalletDropdownDisconnect, +} from "@coinbase/onchainkit/wallet"; +import { Address, Avatar, Name, Identity } from "@coinbase/onchainkit/identity"; +import { color } from "@coinbase/onchainkit/theme"; + export function ConnectButton() { const { login, isAuthenticated, address, isLoading } = useAuth(); - if (!address) { - return ; - } - - if (!isAuthenticated) { - return ( - - ); - } - - return ; + return ( +
+ {!isAuthenticated && address && ( + + )} + + + + + + + + + +
+ + + + +
+ ); } + diff --git a/components/interactive-element.tsx b/components/interactive-element.tsx index 9044e92..7a35101 100644 --- a/components/interactive-element.tsx +++ b/components/interactive-element.tsx @@ -83,8 +83,6 @@ export function InteractiveElement({ const optionsAction = actions.find((a) => a.action === "options"); const helpAction = actions.find((a) => a.action === "help"); - console.log(actions); - return (
{connectWalletAction && } @@ -94,10 +92,7 @@ export function InteractiveElement({ {buyStarterKitAction && ( { - append({ - role: "user", - content: "I bought a starter kit!", - }); + console.log("successfully bought a starter kit"); }} /> )} @@ -106,10 +101,7 @@ export function InteractiveElement({ { - append({ - role: "user", - content: "I bought a starter kit as a gift!", - }); + console.log("successfully bought a starter kit as a gift"); }} /> )} diff --git a/components/message.tsx b/components/message.tsx index d8dfffd..a8e02b8 100644 --- a/components/message.tsx +++ b/components/message.tsx @@ -14,11 +14,10 @@ import { SparklesIcon, } from "./icons"; import { Markdown } from "./markdown"; -import { MessageActions } from "./message-actions"; import { PreviewAttachment } from "./preview-attachment"; import { Weather } from "./weather"; import equal from "fast-deep-equal"; -import { cn, } from "@/lib/utils"; +import { cn } from "@/lib/utils"; import { Button } from "./ui/button"; import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; import { MessageEditor } from "./message-editor"; @@ -220,16 +219,6 @@ const PurePreviewMessage = ({ })}
)} - - {!isReadonly && ( - - )} diff --git a/components/sign-out-form.tsx b/components/sign-out-form.tsx deleted file mode 100644 index 26b9eba..0000000 --- a/components/sign-out-form.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import Form from 'next/form'; - -import { signOut } from "@/app/auth"; - -export const SignOutForm = () => { - return ( -
{ - 'use server'; - - await signOut({ - redirectTo: '/', - }); - }} - > - -
- ); -}; diff --git a/components/starter-kit-checkout.tsx b/components/starter-kit-checkout.tsx index f813a19..cbedcf3 100644 --- a/components/starter-kit-checkout.tsx +++ b/components/starter-kit-checkout.tsx @@ -4,14 +4,22 @@ import { CheckoutStatus, type LifecycleStatus, } from "@coinbase/onchainkit/checkout"; -import { useCallback } from "react"; +import { useCallback, useRef } from "react"; import useSWRMutation from "swr/mutation"; +import { toast } from "sonner"; -async function verifyCharge(_url: string, { arg }: { arg: string }) { - const response = await fetch(`/api/commerce/verify/${arg}`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - }); +async function verifyCharge( + _url: string, + { arg }: { arg: { chargeId: string; productId: string } } +) { + const response = await fetch( + `/api/commerce/verify/${arg.productId}/${arg.chargeId}`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + credentials: "include", + } + ); if (!response.ok) throw new Error("Failed to verify charge"); return response.json(); } @@ -27,51 +35,89 @@ export function StarterKitCheckout({ }: StarterKitCheckoutProps) { const { trigger } = useSWRMutation("/api/commerce/verify", verifyCharge); + const productId = isGift + ? process.env.NEXT_PUBLIC_COINBASE_COMMERCE_PRODUCT_STARTER_KIT_GIFT + : process.env.NEXT_PUBLIC_COINBASE_COMMERCE_PRODUCT_STARTER_KIT; + + // Add a ref to track the last processed charge status + const processedChargeRef = useRef<{ + chargeId?: string; + status?: string; + lastProcessed?: number; + }>({}); + const statusHandler = useCallback( async (status: LifecycleStatus) => { const { statusName, statusData } = status; try { - if (statusData?.chargeId) { - const data = await trigger(statusData.chargeId); + if (statusName !== "error" && statusData?.chargeId && productId) { + // Check if we've recently processed this charge + const now = Date.now(); + const minInterval = 2000; // 2 seconds between calls + const lastProcessed = processedChargeRef.current.lastProcessed || 0; - switch (statusName) { - case "success": - console.log("Payment successful!", data); - onSuccess?.(); - break; - case "pending": - console.log("Payment pending...", data); - break; - case "error": - console.error("Payment failed:", data); - break; - default: - console.log("Payment initialized", data); + // Skip if: + // 1. It's the same charge we just processed within minInterval + // 2. Or if we've already seen this charge completed successfully + if ( + (statusData.chargeId === processedChargeRef.current.chargeId && + now - lastProcessed < minInterval) || + (statusData.chargeId === processedChargeRef.current.chargeId && + processedChargeRef.current.status === "success") + ) { + return; } + + // Update our tracking ref + processedChargeRef.current = { + chargeId: statusData.chargeId, + status: statusName, + lastProcessed: now, + }; + + await trigger({ + productId, + chargeId: statusData.chargeId, + }); + } + + switch (statusName) { + case "success": + toast.success("Payment successful!"); + onSuccess?.(); + break; + case "pending": + console.log("Payment pending..."); + break; + case "error": + toast.error("Something went wrong"); + break; + default: + console.log("Payment initialized"); } } catch (error) { console.error("Error handling charge status:", error); } }, - [trigger, onSuccess] + [trigger, onSuccess, productId] ); + if (!productId) { + console.error("Product ID is not defined in environment variables"); + return null; + } + return ( - - - - +
+ + + + +
); } diff --git a/hooks/use-auth.ts b/hooks/use-auth.ts index c43cbfe..b5dd976 100644 --- a/hooks/use-auth.ts +++ b/hooks/use-auth.ts @@ -30,9 +30,8 @@ export function useAuth() { }); async function login() { - if (!address) return; - try { + console.log("Logging in..."); setIsLoading(true); // Get challenge diff --git a/lib/ai/prompts.ts b/lib/ai/prompts-old.ts similarity index 91% rename from lib/ai/prompts.ts rename to lib/ai/prompts-old.ts index 8853a7b..df43ca0 100644 --- a/lib/ai/prompts.ts +++ b/lib/ai/prompts-old.ts @@ -41,7 +41,7 @@ The first thing a user has to do is get set up with a wallet. They might have on Once they have connected their wallet, they will need to sign in - this is signing a message with their connected wallet, to prove ownership. Once they are signed in, we can really get started! -As you are working with the user, you will learn information about them: interests, completed actions (by you or by them), and their goals. Let's keep track of these in our database as we are going, and let's fetch the data when it might be useful. +You should keep track of a user's actions, interests, and goals. If they say something like "I am interested in...", you should save that interest. If they complete an action, you should save that action. If they set a goal, you should save that goal. You can propose userActions as a part of your response: 1. "connect-wallet" - To ask users to connect their wallet: @@ -65,6 +65,16 @@ You can propose userActions as a part of your response: "Let me know if you need clarification! /help" You can propose multiple actions at once, just add multiple userActions to the array. + +You might receive attachments in the messages, as an array of objects in the following format: +[ + { + contentType: "image/jpeg", + name: "example-name.jpg", + url: "https://example.com/image.jpg" + } +] +These might prove useful in executing certain actions. `; export const systemPrompt = ({ diff --git a/lib/ai/prompts/constants/blocks.ts b/lib/ai/prompts/constants/blocks.ts new file mode 100644 index 0000000..e083a5a --- /dev/null +++ b/lib/ai/prompts/constants/blocks.ts @@ -0,0 +1,30 @@ +export const blocksPrompt = ` +Blocks is a special user interface mode that helps users with writing, editing, and other content creation tasks. When block is open, it is on the right side of the screen, while the conversation is on the left side. When creating or updating documents, changes are reflected in real-time on the blocks and visible to the user. + +When asked to write code, always use blocks. When writing code, specify the language in the backticks, e.g. \`\`\`python\`code here\`\`\`. The default language is Python. Other languages are not yet supported, so let the user know if they request a different language. + +DO NOT UPDATE DOCUMENTS IMMEDIATELY AFTER CREATING THEM. WAIT FOR USER FEEDBACK OR REQUEST TO UPDATE IT. + +This is a guide for using blocks tools: \`createDocument\` and \`updateDocument\`, which render content on a blocks beside the conversation. + +**When to use \`createDocument\`:** +- For substantial content (>10 lines) or code +- For content users will likely save/reuse (emails, code, essays, etc.) +- When explicitly requested to create a document +- For when content contains a single code snippet + +**When NOT to use \`createDocument\`:** +- For informational/explanatory content +- For conversational responses +- When asked to keep it in chat + +**Using \`updateDocument\`:** +- Default to full document rewrites for major changes +- Use targeted updates only for specific, isolated changes +- Follow user instructions for which parts to modify + +**When NOT to use \`updateDocument\`:** +- Immediately after creating a document + +Do not update document right after creating it. Wait for user feedback or request to update it. +`; diff --git a/lib/ai/prompts/constants/code.ts b/lib/ai/prompts/constants/code.ts new file mode 100644 index 0000000..df165ab --- /dev/null +++ b/lib/ai/prompts/constants/code.ts @@ -0,0 +1,27 @@ +export const codePrompt = ` +You are a Python code generator that creates self-contained, executable code snippets. When writing code: + +1. Each snippet should be complete and runnable on its own +2. Prefer using print() statements to display outputs +3. Include helpful comments explaining the code +4. Keep snippets concise (generally under 15 lines) +5. Avoid external dependencies - use Python standard library +6. Handle potential errors gracefully +7. Return meaningful output that demonstrates the code's functionality +8. Don't use input() or other interactive functions +9. Don't access files or network resources +10. Don't use infinite loops + +Examples of good snippets: + +\`\`\`python +# Calculate factorial iteratively +def factorial(n): + result = 1 + for i in range(1, n + 1): + result *= i + return result + +print(f"Factorial of 5 is: {factorial(5)}") +\`\`\` +`; diff --git a/lib/ai/prompts/constants/regular.ts b/lib/ai/prompts/constants/regular.ts new file mode 100644 index 0000000..58aa3ef --- /dev/null +++ b/lib/ai/prompts/constants/regular.ts @@ -0,0 +1,45 @@ +export const regularPrompt = ` +This is Hello World Computer, the most user-friendly dynamic way to get started on Ethereum. +You are a helpful assistant. +You have a web3 wallet of your own, which you can access using some of your tools. This will allow you to make transactions on their behalf! +You are deeply knowledgeable about web3, but you also have a sense of humour. Keep your responses concise and helpful. + +The first thing a user has to do is get set up with a wallet. They might have one of their own, or they might have to create one. +Once they have connected their wallet, they will need to sign in - this is signing a message with their connected wallet, to prove ownership. +Once they are signed in, we can really get started! + +You should keep track of a user's actions, interests, and goals. If they say something like "I am interested in...", you should save that interest. If they complete an action, you should save that action. If they set a goal, you should save that goal. + +You can propose userActions as a part of your response: +1. "connect-wallet" - To ask users to connect their wallet: +2. "fund-wallet" - To show funding options +3. "buy-starter-kit" - To show a checkout to buy a starter kit for yourself +4. "gift-starter-kit" - To show a checkout to buy a starter kit as a gift +5. "options" - To present multiple choice for the user to select from: + example args: [ + {"label": "DeFi", "value": "defi", "description": "Decentralized Finance protocols"}, + {"label": "NFTs", "value": "nfts", "description": "Digital collectibles and art"}, + {"label": "Gaming", "value": "gaming", "description": "Web3 games"}, + {"label": "Social", "value": "social", "description": "Decentralized social networks"} + ]" +6. "transaction" - To show a transaction for the user to execute: + example arguments: [{ + "to": "0x123...", + "value": "0.1", + "data": "0x..." + }]" +7. "help" - To add a help button: + "Let me know if you need clarification! /help" + +You can propose multiple actions at once, just add multiple userActions to the array. + +You might receive attachments in the messages, as an array of objects in the following format: +[ + { + contentType: "image/jpeg", + name: "example-name.jpg", + url: "https://example.com/image.jpg" + } +] +These might prove useful in executing certain actions. +`; diff --git a/lib/ai/prompts/constants/sheet.ts b/lib/ai/prompts/constants/sheet.ts new file mode 100644 index 0000000..7859260 --- /dev/null +++ b/lib/ai/prompts/constants/sheet.ts @@ -0,0 +1,3 @@ +export const sheetPrompt = ` +You are a spreadsheet creation assistant. Create a spreadsheet in csv format based on the given prompt. The spreadsheet should contain meaningful column headers and data. +`; diff --git a/lib/ai/prompts/constants/user-actions.ts b/lib/ai/prompts/constants/user-actions.ts new file mode 100644 index 0000000..29d651d --- /dev/null +++ b/lib/ai/prompts/constants/user-actions.ts @@ -0,0 +1,22 @@ +export const userActionsPrompt = `You can propose userActions as a part of your response: +1. "connect-wallet" - To ask users to connect their wallet: +2. "fund-wallet" - To show funding options +3. "buy-starter-kit" - To show a checkout to buy a starter kit for yourself +4. "gift-starter-kit" - To show a checkout to buy a starter kit as a gift +5. "options" - To present multiple choice for the user to select from: + example args: [ + {"label": "DeFi", "value": "defi", "description": "Decentralized Finance protocols"}, + {"label": "NFTs", "value": "nfts", "description": "Digital collectibles and art"}, + {"label": "Gaming", "value": "gaming", "description": "Web3 games"}, + {"label": "Social", "value": "social", "description": "Decentralized social networks"} + ]" +6. "transaction" - To show a transaction for the user to execute: + example arguments: [{ + "to": "0x123...", + "value": "0.1", + "data": "0x..." + }]" +7. "help" - To add a help button: + "Let me know if you need clarification! /help" + +You can propose multiple actions at once, just add multiple userActions to the array.`; diff --git a/lib/ai/prompts/index.ts b/lib/ai/prompts/index.ts new file mode 100644 index 0000000..2c22d82 --- /dev/null +++ b/lib/ai/prompts/index.ts @@ -0,0 +1,43 @@ +import { blocksPrompt } from "./constants/blocks"; +import { regularPrompt } from "./constants/regular"; +import { codePrompt } from "./constants/code"; +import { sheetPrompt } from "./constants/sheet"; +import { userActionsPrompt } from "./constants/user-actions"; +import type { BlockKind } from "@/components/block"; + +export const generateSystemPrompt = ({ + selectedChatModel, +}: { + selectedChatModel: string; +}) => { + if (selectedChatModel === "chat-model-reasoning") { + return regularPrompt; + } + return `${regularPrompt}\n\n${blocksPrompt}\n\n${userActionsPrompt}`; +}; + +export const updateDocumentPrompt = ( + currentContent: string | null, + type: BlockKind +) => + type === "text" + ? `\ +Improve the following contents of the document based on the given prompt. + +${currentContent} +` + : type === "code" + ? `\ +Improve the following code snippet based on the given prompt. + +${currentContent} +` + : type === "sheet" + ? `\ +Improve the following spreadsheet based on the given prompt. + +${currentContent} +` + : ""; + +export { blocksPrompt, regularPrompt, codePrompt, sheetPrompt }; diff --git a/lib/ai/prompts/user.ts b/lib/ai/prompts/user.ts new file mode 100644 index 0000000..cd34fd6 --- /dev/null +++ b/lib/ai/prompts/user.ts @@ -0,0 +1,94 @@ +import type { User } from "@/lib/db/queries"; + +interface Attachment { + name?: string; + contentType?: string; + url: string; +} + +const formatAttachments = (attachments: Attachment[]): string => { + if (!attachments.length) return ""; + + return `The user has shared the following files: +${attachments + .map( + (attachment, index) => + `${index + 1}. ${attachment.name} (${attachment.contentType}, ${ + attachment.url + })` + ) + .join("\n")}`; +}; + +const formatUserInterests = (information: User["information"]): string => { + if (!information.length) return ""; + + const activeInfo = information.filter((info) => !info.deletedAt); + if (!activeInfo.length) return ""; + + return `We have the following information about them: +${activeInfo + .map((interest) => `${interest.type}: ${interest.content}`) + .join("\n")}`; +}; + +const formatKitInfo = ( + claimedKits: User["claimedKits"], + createdKits: User["createdKits"] +): string => { + const parts = []; + + if (claimedKits.length > 0) { + const totalValue = claimedKits.reduce((sum, kit) => sum + kit.value, 0); + parts.push( + `They have claimed ${claimedKits.length} kits with a total value of ${totalValue}` + ); + } + + if (createdKits.length > 0) { + const unclaimedKits = createdKits.filter((kit) => !kit.claimedAt); + const totalValue = createdKits.reduce((sum, kit) => sum + kit.value, 0); + parts.push( + `They have created ${createdKits.length} kits with a total value of ${totalValue}, of which ${unclaimedKits.length} are unclaimed` + ); + } + + return parts.join("\n"); +}; + +const formatCharges = (charges: User["charges"]): string => { + if (!charges.length) return ""; + + const completedCharges = charges.filter( + (charge) => charge.status === "COMPLETED" + ); + if (!completedCharges.length) return ""; + + const totalSpent = completedCharges.reduce( + (sum, charge) => sum + Number.parseFloat(charge.amount), + 0 + ); + return `They have spent a total of ${totalSpent} ${completedCharges[0].currency}`; +}; + +export const generateUserProfile = ({ + userInfo, + attachments = [], +}: { + userInfo?: User; + attachments?: Attachment[]; +}): string => { + if (!userInfo) { + return "User is not signed in"; + } + + const sections = [ + formatAttachments(attachments), + `User's connected wallet is ${userInfo.id}`, + formatUserInterests(userInfo.information), + formatKitInfo(userInfo.claimedKits, userInfo.createdKits), + formatCharges(userInfo.charges), + ].filter(Boolean); // Remove empty sections + + return sections.join("\n\n"); +}; diff --git a/lib/ai/tools/create-document.ts b/lib/ai/tools/create-document.ts index 7f148b1..3d23fdd 100644 --- a/lib/ai/tools/create-document.ts +++ b/lib/ai/tools/create-document.ts @@ -8,7 +8,8 @@ import { tool, } from 'ai'; import { z } from 'zod'; -import { codePrompt, sheetPrompt } from '../prompts'; +import { codePrompt } from "../prompts/constants/code"; +import { sheetPrompt } from "../prompts/constants/sheet"; import { saveDocument } from '@/lib/db/queries'; import type { Session } from 'next-auth'; import { myProvider } from '../models'; diff --git a/lib/ai/tools/index.ts b/lib/ai/tools/index.ts new file mode 100644 index 0000000..782f2e2 --- /dev/null +++ b/lib/ai/tools/index.ts @@ -0,0 +1,37 @@ +import type { Session } from "next-auth"; +import type { DataStreamWriter } from "ai"; +import { createDocument } from "./create-document"; +import { updateDocument } from "./update-document"; +import { requestSuggestions } from "./request-suggestions"; +import { + saveUserInformation, + getUserInformation, + deleteUserInformationTool, +} from "./user-information"; +import { agentKitToTools } from "@/lib/web3/agentkit/framework-extensions/ai-sdk"; +import type { AgentKit } from "@coinbase/agentkit"; + +export const setupTools = async ({ + session, + dataStream, + agentKit, +}: { + session: Session; + dataStream: DataStreamWriter; + agentKit: AgentKit; +}) => { + const baseTools = agentKitToTools(agentKit); + + return { + ...baseTools, + createDocument: createDocument({ session, dataStream }), + updateDocument: updateDocument({ session, dataStream }), + requestSuggestions: requestSuggestions({ + session, + dataStream, + }), + saveUserInformation: saveUserInformation({ session }), + getUserInformation: getUserInformation({ session }), + deleteUserInformation: deleteUserInformationTool({ session }), + }; +}; diff --git a/lib/db/queries.ts b/lib/db/queries.ts index b6b9b77..730f187 100644 --- a/lib/db/queries.ts +++ b/lib/db/queries.ts @@ -29,7 +29,9 @@ import type { BlockKind } from "@/components/block"; const client = postgres(process.env.POSTGRES_URL!); const db = drizzle(client, { schema }); -export async function getUser(id: string): Promise> { +export type User = UserWithRelations; + +export async function getUser(id: string): Promise { try { const users = await db.select().from(user).where(eq(user.id, id)); @@ -405,25 +407,53 @@ export async function getUserInformation(userId: string) { } export async function createStarterKit({ + id, value, userId, chargeId, claimerId, }: { + id?: string; value: number; userId?: string; chargeId?: string; claimerId?: string; }) { try { - return await db.insert(starterKit).values({ - creatorId: userId, - claimerId, - value, - chargeId, - createdAt: new Date(), - ...(claimerId && { claimedAt: new Date() }), - }); + if (id) { + // If id is provided, do an upsert + return await db + .insert(starterKit) + .values({ + id, + creatorId: userId, + claimerId, + value, + chargeId, + createdAt: new Date(), + ...(claimerId && { claimedAt: new Date() }), + }) + .onConflictDoUpdate({ + target: starterKit.id, + set: { + creatorId: userId, + claimerId, + value, + chargeId, + ...(claimerId && { claimedAt: new Date() }), + }, + }); + } else { + // If no id is provided, just do a regular insert + return await db.insert(starterKit).values({ + creatorId: userId, + claimerId, + value, + chargeId, + createdAt: new Date(), + ...(claimerId && { claimedAt: new Date() }), + }); + } } catch (error) { console.error("Failed to create starter kit"); throw error; diff --git a/lib/types/coinbase.ts b/lib/types/coinbase.ts new file mode 100644 index 0000000..4e0b85d --- /dev/null +++ b/lib/types/coinbase.ts @@ -0,0 +1,44 @@ +export interface CoinbaseChargeResponse { + id: string; + code: string; + name: string; + description: string; + pricing_type: "fixed_price" | "no_price"; + pricing?: { + local: { + amount: string; + currency: string; + }; + settlement: { + amount: string; + currency: string; + }; + }; + timeline: Array<{ + status: "NEW" | "PENDING" | "COMPLETED" | "EXPIRED" | "FAILED" | "SIGNED"; + time: string; + }>; + web3_data?: { + success_events?: Array<{ + finalized: boolean; + input_token_address: string; + input_token_amount: string; + network_fee_paid: string; + recipient: string; + sender: string; + timestamp: string; + tx_hash: string; + }>; + failure_events?: Array<{ + input_token_address: string; + network_fee_paid: string; + reason: string; + sender: string; + timestamp: string; + tx_hash: string; + }>; + }; + created_at: string; + expires_at: string; + confirmed_at?: string; +} diff --git a/lib/web3/agentkit/action-providers/alchemy/alchemyActionProvider.ts b/lib/web3/agentkit/action-providers/alchemy/alchemyActionProvider.ts new file mode 100644 index 0000000..aade0dc --- /dev/null +++ b/lib/web3/agentkit/action-providers/alchemy/alchemyActionProvider.ts @@ -0,0 +1,201 @@ +import type { z } from "zod"; +import { + ActionProvider, + CreateAction, + EvmWalletProvider, +} from "@coinbase/agentkit"; +import { + getTokenBalancesSchema, + type TokenBalanceWithMetadata, + getNFTsForOwnerSchema, + type NFTWithMetadata, +} from "./schemas"; +import { Network } from "../types"; + +/** + * AlchemyActionProvider provides actions for interacting with Alchemy APIs. + */ +export class AlchemyActionProvider extends ActionProvider { + private apiKey: string; + + /** + * Constructor for the AlchemyActionProvider. + * @param apiKey - The Alchemy API key + */ + constructor(apiKey: string) { + super("alchemy", []); + this.apiKey = apiKey; + } + + /** + * Gets the appropriate Alchemy base URL for the given chain ID. + * @param chainId - The chain ID + * @returns The Alchemy base URL for the chain + */ + private getBaseUrl(chainId: string): string { + const urls: Record = { + "1": "eth-mainnet.g.alchemy.com", + "8453": "base-mainnet.g.alchemy.com", + "84532": "base-sepolia.g.alchemy.com", + }; + + const domain = urls[chainId]; + if (!domain) { + throw new Error(`Chain ID ${chainId} not supported by Alchemy`); + } + + return `https://${domain}/v2/${this.apiKey}`; + } + + /** + * Gets token balances for a wallet address. + * + * @param walletProvider - The wallet provider to get balances from. + * @param args - The query parameters. + * @returns Array of token balances with metadata. + */ + @CreateAction({ + name: "get_token_balances", + description: ` + This tool will get all ERC-20 token balances for a wallet address. + It takes the following inputs: + - address: The wallet address to get token balances for + - includeZeroBalances: (Optional) Whether to include tokens with zero balance + Returns an array of token balances with metadata including name, symbol, and balance. + `, + schema: getTokenBalancesSchema, + }) + async getTokenBalances( + walletProvider: EvmWalletProvider, + args: z.infer + ): Promise { + try { + const chainId = String(walletProvider.getNetwork().chainId); + const baseURL = this.getBaseUrl(chainId); + const balancesResponse = await fetch(baseURL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + jsonrpc: "2.0", + method: "alchemy_getTokenBalances", + params: [args.address], + id: 42, + }), + }); + + const balancesData = await balancesResponse.json(); + const balances = balancesData.result.tokenBalances; + + // Filter out zero balances if requested + const filteredBalances = args.includeZeroBalances + ? balances + : balances.filter((token: any) => token.tokenBalance !== "0"); + + // Get metadata for each token + const tokenBalancesWithMetadata: TokenBalanceWithMetadata[] = + await Promise.all( + filteredBalances.map(async (token: any) => { + const metadataResponse = await fetch(baseURL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + jsonrpc: "2.0", + method: "alchemy_getTokenMetadata", + params: [token.contractAddress], + id: 42, + }), + }); + + const metadataData = await metadataResponse.json(); + const metadata = metadataData.result; + + // Convert balance to human readable format + const balance = ( + parseInt(token.tokenBalance) / Math.pow(10, metadata.decimals) + ).toFixed(2); + + return { + name: metadata.name, + symbol: metadata.symbol, + balance, + decimals: metadata.decimals, + contractAddress: token.contractAddress, + }; + }) + ); + + return tokenBalancesWithMetadata; + } catch (error) { + return { + error: `Error getting token balances: ${JSON.stringify(error)}`, + }; + } + } + + /** + * Gets NFTs owned by a wallet address. + * + * @param walletProvider - The wallet provider to get NFTs from. + * @param args - The query parameters. + * @returns Array of NFTs with metadata. + */ + @CreateAction({ + name: "get_nfts_for_owner", + description: ` + This tool will get all NFTs owned by a specified wallet address. + It takes the following inputs: + - owner: The wallet address to get NFTs for + - withMetadata: (Optional) Whether to include NFT metadata (default: true) + - pageSize: (Optional) Number of NFTs to return per page (default: 100) + Returns an array of NFTs with metadata including contract info, token IDs, and media. + `, + schema: getNFTsForOwnerSchema, + }) + async getNFTsForOwner( + walletProvider: EvmWalletProvider, + args: z.infer + ): Promise { + try { + const chainId = String(walletProvider.getNetwork().chainId); + const baseURL = this.getBaseUrl(chainId); + const nftBaseUrl = baseURL.replace("/v2/", "/nft/v3/"); + + const queryParams = new URLSearchParams({ + owner: args.owner, + withMetadata: String(args.withMetadata), + pageSize: String(args.pageSize), + }); + + const response = await fetch( + `${nftBaseUrl}/getNFTsForOwner?${queryParams}`, + { + method: "GET", + headers: { accept: "application/json" }, + } + ); + + const data = await response.json(); + + if (data.error) { + throw new Error(data.error.message || "Failed to fetch NFTs"); + } + + return data.ownedNfts; + } catch (error) { + throw new Error(`Error getting NFTs: ${error}`); + } + } + + /** + * Checks if the network is supported by Alchemy. + * @param network - The network to check. + * @returns True if the network is supported. + */ + supportsNetwork = (network: Network) => { + // Supported networks: Ethereum mainnet, Base mainnet, Base Sepolia + const supportedChainIds = ["1", "8453", "84532"]; + return supportedChainIds.includes(String(network.chainId)); + }; +} + +export const alchemyActionProvider = (apiKey: string) => new AlchemyActionProvider(apiKey); \ No newline at end of file diff --git a/lib/web3/agentkit/action-providers/alchemy/index.ts b/lib/web3/agentkit/action-providers/alchemy/index.ts new file mode 100644 index 0000000..320092a --- /dev/null +++ b/lib/web3/agentkit/action-providers/alchemy/index.ts @@ -0,0 +1 @@ +export * from "./alchemyActionProvider"; \ No newline at end of file diff --git a/lib/web3/agentkit/action-providers/alchemy/schemas.ts b/lib/web3/agentkit/action-providers/alchemy/schemas.ts new file mode 100644 index 0000000..2be6382 --- /dev/null +++ b/lib/web3/agentkit/action-providers/alchemy/schemas.ts @@ -0,0 +1,65 @@ +import { z } from "zod"; + +/** + * Input schema for getTokenBalances action. + */ +export const getTokenBalancesSchema = z.object({ + address: z + .string() + .describe("The wallet address to get token balances for"), + includeZeroBalances: z + .boolean() + .optional() + .default(false) + .describe("Whether to include tokens with zero balance"), +}); + +/** + * Token balance with metadata response type + */ +export type TokenBalanceWithMetadata = { + name: string; + symbol: string; + balance: string; + decimals: number; + contractAddress: string; +}; + +/** + * Input schema for getNFTsForOwner action + */ +export const getNFTsForOwnerSchema = z.object({ + owner: z + .string() + .describe("The wallet address to get NFTs for"), + withMetadata: z + .boolean() + .optional() + .default(true) + .describe("Whether to include NFT metadata"), + pageSize: z + .number() + .optional() + .default(100) + .describe("Number of NFTs to return per page"), +}); + +/** + * NFT with metadata response type + */ +export type NFTWithMetadata = { + contract: { + address: string; + name?: string; + symbol?: string; + tokenType: string; + }; + tokenId: string; + tokenType: string; + title?: string; + description?: string; + image?: { + originalUrl?: string; + thumbnailUrl?: string; + }; +}; \ No newline at end of file diff --git a/lib/web3/agentkit/action-providers/erc20/erc20ActionProvider.ts b/lib/web3/agentkit/action-providers/erc20/erc20ActionProvider.ts index 2a15b5d..0429df8 100644 --- a/lib/web3/agentkit/action-providers/erc20/erc20ActionProvider.ts +++ b/lib/web3/agentkit/action-providers/erc20/erc20ActionProvider.ts @@ -2,9 +2,9 @@ import type { z } from "zod"; import { ActionProvider, CreateAction, - type EvmWalletProvider, + EvmWalletProvider, } from "@coinbase/agentkit"; -import type { Network } from "./types"; +import type { Network } from "../types"; import { GetBalanceSchema, TransferSchema } from "./schemas"; import { abi } from "./constants"; import { encodeFunctionData, type Hex } from "viem"; diff --git a/lib/web3/agentkit/action-providers/onchainkit/index.ts b/lib/web3/agentkit/action-providers/onchainkit/index.ts new file mode 100644 index 0000000..fbdee69 --- /dev/null +++ b/lib/web3/agentkit/action-providers/onchainkit/index.ts @@ -0,0 +1 @@ +export * from "./onchainKitActionProvider"; \ No newline at end of file diff --git a/lib/web3/agentkit/action-providers/onchainkit/onchainKitActionProvider.ts b/lib/web3/agentkit/action-providers/onchainkit/onchainKitActionProvider.ts new file mode 100644 index 0000000..229a2cd --- /dev/null +++ b/lib/web3/agentkit/action-providers/onchainkit/onchainKitActionProvider.ts @@ -0,0 +1,112 @@ +import type { z } from "zod"; +import { + ActionProvider, + CreateAction, +} from "@coinbase/agentkit"; +import type { Network } from "../types"; +import { searchBaseTokensSchema, getPortfoliosSchema } from "./schemas"; +import { getTokens, getPortfolios, GetTokensResponse, GetPortfoliosResponse, APIError } from "@coinbase/onchainkit/api"; +import { setOnchainKitConfig } from '@coinbase/onchainkit'; + +/** + * OnchainKitActionProvider provides actions for interacting with Base tokens. + */ +export class OnchainKitActionProvider extends ActionProvider { + /** + * Constructor for the OnchainKitActionProvider. + * @param apiKey - The API key for OnchainKit + */ + constructor(apiKey: string) { + super("onchainkit", []); + setOnchainKitConfig({ apiKey }); + } + + /** + * Gets tokens on Base by searching for name, symbol, or address. + * + * @param args - The search parameters. + * @returns A message containing the found tokens. + */ + @CreateAction({ + name: "search_base_tokens", + description: ` + This tool will search for tokens on Base by name, symbol, or address. + It takes the following inputs: + - search: Search term for token (name, symbol, or address) + - limit: (Optional) Maximum number of tokens to return + `, + schema: searchBaseTokensSchema, + }) + async searchBaseTokens( + args: z.infer + ): Promise { + try { + const tokens = await getTokens({ + search: args.search, + limit: args.limit || "5", + }); + + return tokens; + } catch (error) { + return { + error: `Error searching for tokens: ${error}`, + code: "error", + message: `Error searching for tokens`, + }; + } + } + + /** + * Gets portfolio information for specified wallet addresses. + * + * @param args - The addresses to get portfolios for. + * @returns Portfolio information for the specified addresses. + */ + @CreateAction({ + name: "get_portfolios", + description: ` + This tool will get portfolio information for specified wallet addresses. + It takes the following input: + - addresses: Array of wallet addresses to get portfolios for + + Returns portfolio information including token holdings and fiat values. + `, + schema: getPortfoliosSchema, + }) + async getPortfolios( + args: z.infer + ): Promise { + try { + console.log("getting portfolios",args.addresses) + const portfolios = await getPortfolios({ + addresses: args.addresses.map((address) => address as `0x${string}`), + }); + + return portfolios; + } catch (error) { + return { + error: `Error getting portfolios: ${error}`, + code: "error", + message: `Error getting portfolios`, + }; + } + } + + /** + * Checks if the OnchainKit action provider supports the given network. + * + * @param network - The network to check. + * @returns True if the network is Base, false otherwise. + */ + supportsNetwork = (network: Network) => { + // Base mainnet chainId is 8453 + console.log("checking",network.chainId) + return String(network.chainId) === "8453"; + }; +} + +/** + * Creates a new OnchainKitActionProvider instance. + * @param apiKey - The API key for OnchainKit + */ +export const onchainKitActionProvider = (apiKey: string) => new OnchainKitActionProvider(apiKey); \ No newline at end of file diff --git a/lib/web3/agentkit/action-providers/onchainkit/schemas.ts b/lib/web3/agentkit/action-providers/onchainkit/schemas.ts new file mode 100644 index 0000000..c5bf4d8 --- /dev/null +++ b/lib/web3/agentkit/action-providers/onchainkit/schemas.ts @@ -0,0 +1,25 @@ +import { z } from "zod"; + +/** + * Input schema for getBaseTokens action. + */ +export const searchBaseTokensSchema = z + .object({ + search: z + .string() + .describe("Search term for token (name, symbol, or address)"), + limit: z + .string() + .optional() + .describe("Maximum number of tokens to return"), + }) + +/** + * Input schema for getPortfolios action. + */ +export const getPortfoliosSchema = z + .object({ + addresses: z + .array(z.string()) + .describe("Array of wallet addresses to get portfolios for"), + }) \ No newline at end of file diff --git a/lib/web3/agentkit/action-providers/safe/index.ts b/lib/web3/agentkit/action-providers/safe/index.ts index f209a9c..92fd33b 100644 --- a/lib/web3/agentkit/action-providers/safe/index.ts +++ b/lib/web3/agentkit/action-providers/safe/index.ts @@ -77,11 +77,16 @@ export class SafeActionProvider extends ActionProvider { chain: baseSepolia }); + if (!tx) { + throw new Error("Failed to prepare transaction request"); + } + const transactionHash = await walletProvider.sendTransaction(tx); await waitForTransactionReceipt( - client!, - { hash: transactionHash } + // biome-ignore lint: client is not null + client!, + { hash: transactionHash } ); const newProtocolKit = await protocolKit.connect({ diff --git a/lib/web3/agentkit/action-providers/erc20/types.ts b/lib/web3/agentkit/action-providers/types.ts similarity index 99% rename from lib/web3/agentkit/action-providers/erc20/types.ts rename to lib/web3/agentkit/action-providers/types.ts index b39cb12..2615b0a 100644 --- a/lib/web3/agentkit/action-providers/erc20/types.ts +++ b/lib/web3/agentkit/action-providers/types.ts @@ -16,4 +16,4 @@ export interface Network { * The chain ID of the network. */ chainId?: string; -} +} \ No newline at end of file diff --git a/lib/web3/agentkit/action-providers/zora/index.ts b/lib/web3/agentkit/action-providers/zora/index.ts new file mode 100644 index 0000000..d48bb25 --- /dev/null +++ b/lib/web3/agentkit/action-providers/zora/index.ts @@ -0,0 +1 @@ +export * from "./zoraActionProvider"; \ No newline at end of file diff --git a/lib/web3/agentkit/action-providers/zora/schemas.ts b/lib/web3/agentkit/action-providers/zora/schemas.ts new file mode 100644 index 0000000..591770e --- /dev/null +++ b/lib/web3/agentkit/action-providers/zora/schemas.ts @@ -0,0 +1,70 @@ +import { z } from "zod"; + +/** + * Input schema for mint_1155 action. + */ +export const mint1155Schema = z + .object({ + tokenContract: z + .string() + .describe("The ERC-1155 contract address"), + tokenId: z + .string() + .describe("The ERC-1155 token id to mint"), + quantityToMint: z + .number() + .describe("Quantity of tokens to mint"), + mintRecipient: z + .string() + .optional() + .describe("Optional recipient address to mint to"), + }) + .strip() + .describe("Mint parameters for ERC-1155 tokens"); + +/** + * Response type for mint_1155 action + */ +export type Mint1155Response = { + success: boolean; + message: string; + data?: { + tokenContract: string; + tokenId: string; + quantity: number; + recipient: string; + transactionHash: string; + blockNumber: string; + value?: string; + }; + error?: string; +}; + +export const create1155Schema = z.object({ + contractAddress: z.string(), + name: z.string(), + description: z.string(), + imageFile: z.instanceof(File), + // Optional fields + attributes: z + .array( + z.object({ + trait_type: z.string(), + value: z.string(), + }) + ) + .optional(), +}); + +export type Create1155Response = { + success: boolean; + message: string; + data?: { + contractAddress: string; + tokenId: string; + tokenUri: string; + transactionHash: string; + blockNumber: string; + }; + error?: string; +}; \ No newline at end of file diff --git a/lib/web3/agentkit/action-providers/zora/zoraActionProvider.ts b/lib/web3/agentkit/action-providers/zora/zoraActionProvider.ts new file mode 100644 index 0000000..806b042 --- /dev/null +++ b/lib/web3/agentkit/action-providers/zora/zoraActionProvider.ts @@ -0,0 +1,122 @@ +import type { z } from "zod"; +import { + ActionProvider, + CreateAction, + EvmWalletProvider, + NETWORK_ID_TO_VIEM_CHAIN, +} from "@coinbase/agentkit"; +import type { Network } from "../types"; +import { mint1155Schema, type Mint1155Response } from "./schemas"; +import { mint } from "@zoralabs/protocol-sdk"; +import { createPublicClient, http, encodeFunctionData, type Hex } from "viem"; + +/** + * ZoraActionProvider provides actions for interacting with Zora Protocol. + */ +export class ZoraActionProvider extends ActionProvider { + /** + * Constructor for the ZoraActionProvider. + */ + constructor() { + super("zora", []); + } + + /** + * Mints tokens from a Zora 1155 contract. + * + * @param walletProvider - The wallet provider to mint from. + * @param args - The mint parameters. + * @returns The transaction hash of the mint. + */ + @CreateAction({ + name: "mint_1155", + description: ` + This tool will mint ERC-1155 tokens from a Zora contract. + It takes the following inputs: + - tokenContract: The ERC-1155 contract address + - tokenId: The ERC-1155 token id to mint + - quantityToMint: Quantity of tokens to mint + - mintRecipient: The address to mint the tokens to (optional, defaults to the wallet provider's address) + + Important notes: + - Ensure the token contract supports ERC-1155 + - Ensure sufficient balance for gas fees + `, + schema: mint1155Schema, + }) + async mint1155( + walletProvider: EvmWalletProvider, + args: z.infer + ): Promise { + try { + const network = walletProvider.getNetwork(); + const publicClient = createPublicClient({ + // biome-ignore lint: networkId is not null + chain: NETWORK_ID_TO_VIEM_CHAIN[network.networkId!], + transport: http(), + }); + + const recipient = args.mintRecipient || walletProvider.getAddress(); + + // Prepare mint parameters + const { parameters } = await mint({ + tokenContract: args.tokenContract as `0x${string}`, + mintType: "1155", + tokenId: BigInt(args.tokenId), + quantityToMint: args.quantityToMint, + minterAccount: recipient, + publicClient, + }); + + // Execute the mint transaction + const hash = await walletProvider.sendTransaction({ + to: parameters.address as Hex, + data: encodeFunctionData({ + abi: parameters.abi, + functionName: parameters.functionName, + args: parameters.args, + }), + value: parameters.value, + }); + + // Wait for transaction confirmation + const receipt = await walletProvider.waitForTransactionReceipt(hash); + + return { + success: true, + message: "Successfully minted tokens", + data: { + tokenContract: args.tokenContract, + tokenId: args.tokenId, + quantity: args.quantityToMint, + recipient, + transactionHash: hash, + blockNumber: String(receipt.blockNumber), + value: parameters.value ? String(parameters.value) : undefined, + }, + }; + } catch (error) { + console.log("Error minting tokens", error); + return { + success: false, + message: "Failed to mint tokens", + error: error instanceof Error ? error.message : String(error), + }; + } + } + + /** + * Checks if the Zora action provider supports the given network. + * + * @param network - The network to check. + * @returns True if the network is supported by Zora, false otherwise. + */ + supportsNetwork = (network: Network) => { + // Zora supports Base (8453) and Zora Network (7777777) + const supportedChainIds = ["8453", "7777777"]; + const chainId = String(network.chainId); + return supportedChainIds.includes(chainId); + }; +} + +export const zoraActionProvider = () => new ZoraActionProvider(); \ No newline at end of file diff --git a/lib/web3/agentkit/setup.ts b/lib/web3/agentkit/setup.ts new file mode 100644 index 0000000..4d6cf30 --- /dev/null +++ b/lib/web3/agentkit/setup.ts @@ -0,0 +1,37 @@ +import { PrivyWalletProvider } from "./wallet-providers/privyWalletProvider"; +import { + AgentKit, + pythActionProvider, + walletActionProvider, +} from "@coinbase/agentkit"; +import { erc20ActionProvider } from "./action-providers/erc20"; +import { safeActionProvider } from "./action-providers/safe"; +import { alchemyActionProvider } from "./action-providers/alchemy"; +import { zoraActionProvider } from "./action-providers/zora"; + +export const setupAgentKit = async () => { + const activeChain = + process.env.NEXT_PUBLIC_ACTIVE_CHAIN === "base" + ? "base-mainnet" + : "base-sepolia"; + + const walletProvider = await PrivyWalletProvider.configureWithWallet({ + appId: process.env.PRIVY_APP_ID as string, + appSecret: process.env.PRIVY_APP_SECRET as string, + networkId: activeChain, + walletId: process.env.PRIVY_WALLET_ID as string, + authorizationKey: process.env.PRIVY_WALLET_AUTHORIZATION_KEY as string, + }); + + return AgentKit.from({ + walletProvider, + actionProviders: [ + pythActionProvider(), + walletActionProvider(), + erc20ActionProvider(), + safeActionProvider(), + alchemyActionProvider(process.env.ALCHEMY_API_KEY as string), + zoraActionProvider(), + ], + }); +}; diff --git a/package.json b/package.json index ca1e1f7..f9015ef 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,8 @@ "@codemirror/state": "^6.5.0", "@codemirror/theme-one-dark": "^6.1.2", "@codemirror/view": "^6.35.3", - "@coinbase/agentkit": "0.1.1", - "@coinbase/onchainkit": "^0.36.9", + "@coinbase/agentkit": "0.1.2", + "@coinbase/onchainkit": "github:azf20/onchainkit#534f302c60da4ab9a2f95187c04a67be062ad4a9", "@privy-io/server-auth": "^1.18.4", "@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-dialog": "^1.1.2", @@ -43,6 +43,7 @@ "@vercel/analytics": "^1.3.1", "@vercel/blob": "^0.24.1", "@vercel/postgres": "^0.10.0", + "@zoralabs/protocol-sdk": "^0.13.2", "ai": "4.1.24", "bcrypt-ts": "^5.0.2", "class-variance-authority": "^0.7.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d7d6c5a..76d7f6c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,14 +30,14 @@ importers: specifier: ^6.35.3 version: 6.35.3 '@coinbase/agentkit': - specifier: 0.1.1 - version: 0.1.1(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10) + specifier: 0.1.2 + version: 0.1.2(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10) '@coinbase/onchainkit': - specifier: ^0.36.9 - version: 0.36.9(@tanstack/query-core@5.66.0)(@types/react@18.3.12)(@vercel/blob@0.24.1)(bufferutil@4.0.8)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021)(tailwindcss@3.4.14)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + specifier: github:azf20/onchainkit#534f302c60da4ab9a2f95187c04a67be062ad4a9 + version: https://codeload.github.com/azf20/onchainkit/tar.gz/534f302c60da4ab9a2f95187c04a67be062ad4a9(@tanstack/query-core@5.66.0)(@types/react@18.3.12)(@vercel/blob@0.24.1)(bufferutil@4.0.8)(encoding@0.1.13)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021)(tailwindcss@3.4.14)(typescript@5.6.3)(use-sync-external-store@1.4.0(react@19.0.0-rc-45804af1-20241021))(utf-8-validate@5.0.10)(zod@3.23.8) '@privy-io/server-auth': specifier: ^1.18.4 - version: 1.18.4(bufferutil@4.0.8)(utf-8-validate@5.0.10)(viem@2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)) + version: 1.18.4(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)(viem@2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)) '@radix-ui/react-alert-dialog': specifier: ^1.1.2 version: 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) @@ -83,6 +83,9 @@ importers: '@vercel/postgres': specifier: ^0.10.0 version: 0.10.0(utf-8-validate@5.0.10) + '@zoralabs/protocol-sdk': + specifier: ^0.13.2 + version: 0.13.2(abitype@1.0.8(typescript@5.6.3)(zod@3.23.8))(viem@2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)) ai: specifier: 4.1.24 version: 4.1.24(react@19.0.0-rc-45804af1-20241021)(zod@3.23.8) @@ -217,7 +220,7 @@ importers: version: 2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) wagmi: specifier: ^2.14.9 - version: 2.14.9(@tanstack/query-core@5.66.0)(@tanstack/react-query@5.66.0(react@19.0.0-rc-45804af1-20241021))(@types/react@18.3.12)(@vercel/blob@0.24.1)(bufferutil@4.0.8)(react@19.0.0-rc-45804af1-20241021)(typescript@5.6.3)(utf-8-validate@5.0.10)(viem@2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8) + version: 2.14.9(@tanstack/query-core@5.66.0)(@tanstack/react-query@5.66.0(react@19.0.0-rc-45804af1-20241021))(@types/react@18.3.12)(@vercel/blob@0.24.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@19.0.0-rc-45804af1-20241021)(typescript@5.6.3)(utf-8-validate@5.0.10)(viem@2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8) zod: specifier: ^3.23.8 version: 3.23.8 @@ -447,14 +450,15 @@ packages: '@codemirror/view@6.35.3': resolution: {integrity: sha512-ScY7L8+EGdPl4QtoBiOzE4FELp7JmNUsBvgBcCakXWM2uiv/K89VAzU3BMDscf0DsACLvTKePbd5+cFDTcei6g==} - '@coinbase/agentkit@0.1.1': - resolution: {integrity: sha512-jEI9Zif0NEhGHgTCm7KkWpU8XFJegETKRf3/iYWLtdqVQonbLwEgbGgGHH6FuRX8eWQUlccMxlBFv8hnDBBLmg==} + '@coinbase/agentkit@0.1.2': + resolution: {integrity: sha512-vG7crTvtkG/dovng5vSuOgHam7KXeirTPi3yzPbVyV+Xi3toGxB51ZYOXPiqp31tKI9avm2TSNuv7nCnPQEAVg==} '@coinbase/coinbase-sdk@0.17.0': resolution: {integrity: sha512-YdEO6kbi0EDTzmPBqKtvDR/78P2Zw72rLmM6LS6yGZuTnfrXRhYR7/xAY0Fesoc1BcCL4eodwc+txUxrpdYCcg==} - '@coinbase/onchainkit@0.36.9': - resolution: {integrity: sha512-kBA38YGyq6kAXG/OOMTepLdP/j8MBqCCOaPKMtVmqTVgbNqcyBkLZhepAvgMVEN22pkPf4YmkhLWyyz+vdmDew==} + '@coinbase/onchainkit@https://codeload.github.com/azf20/onchainkit/tar.gz/534f302c60da4ab9a2f95187c04a67be062ad4a9': + resolution: {tarball: https://codeload.github.com/azf20/onchainkit/tar.gz/534f302c60da4ab9a2f95187c04a67be062ad4a9} + version: 0.36.10 peerDependencies: '@types/react': ^18 || ^19 react: ^18 || ^19 @@ -2092,6 +2096,15 @@ packages: '@walletconnect/window-metadata@1.0.1': resolution: {integrity: sha512-9koTqyGrM2cqFRW517BPY/iEtUDx2r1+Pwwu5m7sJ7ka79wi3EyqhqcICk/yDmv6jAS1rjKgTKXlEhanYjijcA==} + '@zoralabs/protocol-deployments@0.5.1': + resolution: {integrity: sha512-D7xmVV4TbuDDsiieXd7EGxHQCPPHvqTG8xutRRFEZbj+IQpHuZdZgfwDJ9DAV6320Wh9lhDFuGaYyAiR4z+fWw==} + + '@zoralabs/protocol-sdk@0.13.2': + resolution: {integrity: sha512-3913Hd1iViWeDtpux/Z5Gik76mDbJPAxTuXJAdFH7/OqeU6OsmQ41lY0ZZcHMo5FPik46zO9ByH4N42aR1nyXw==} + peerDependencies: + abitype: ^1.0.2 + viem: ^2.21.21 + JSONStream@1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true @@ -2757,6 +2770,9 @@ packages: encode-utf8@1.0.3: resolution: {integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==} + encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} @@ -3269,6 +3285,10 @@ packages: humanize-ms@1.2.1: resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + idb-keyval@6.2.1: resolution: {integrity: sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==} @@ -4518,6 +4538,9 @@ packages: resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} engines: {node: '>=10'} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + scheduler@0.25.0-rc-45804af1-20241021: resolution: {integrity: sha512-8jyu/iy3tGFNakMMCWnKw/vsiTcapDyl0LKlZ3fUKBcBicZAkrrCC1bdqVFx0Ioxgry1SzOrCGcZLM7vtWK00A==} @@ -5398,7 +5421,7 @@ snapshots: style-mod: 4.1.2 w3c-keyname: 2.2.8 - '@coinbase/agentkit@0.1.1(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)': + '@coinbase/agentkit@0.1.2(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)': dependencies: '@coinbase/coinbase-sdk': 0.17.0(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) md5: 2.3.0 @@ -5434,20 +5457,21 @@ snapshots: - utf-8-validate - zod - '@coinbase/onchainkit@0.36.9(@tanstack/query-core@5.66.0)(@types/react@18.3.12)(@vercel/blob@0.24.1)(bufferutil@4.0.8)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021)(tailwindcss@3.4.14)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)': + '@coinbase/onchainkit@https://codeload.github.com/azf20/onchainkit/tar.gz/534f302c60da4ab9a2f95187c04a67be062ad4a9(@tanstack/query-core@5.66.0)(@types/react@18.3.12)(@vercel/blob@0.24.1)(bufferutil@4.0.8)(encoding@0.1.13)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021)(tailwindcss@3.4.14)(typescript@5.6.3)(use-sync-external-store@1.4.0(react@19.0.0-rc-45804af1-20241021))(utf-8-validate@5.0.10)(zod@3.23.8)': dependencies: '@tanstack/react-query': 5.66.0(react@19.0.0-rc-45804af1-20241021) '@types/react': 18.3.12 + '@wagmi/core': 2.16.3(@tanstack/query-core@5.66.0)(@types/react@18.3.12)(react@19.0.0-rc-45804af1-20241021)(typescript@5.6.3)(use-sync-external-store@1.4.0(react@19.0.0-rc-45804af1-20241021))(viem@2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)) clsx: 2.1.1 graphql: 16.10.0 - graphql-request: 6.1.0(graphql@16.10.0) + graphql-request: 6.1.0(encoding@0.1.13)(graphql@16.10.0) qrcode: 1.5.4 react: 19.0.0-rc-45804af1-20241021 react-dom: 19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021) tailwind-merge: 2.5.4 tailwindcss-animate: 1.0.7(tailwindcss@3.4.14) viem: 2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) - wagmi: 2.14.9(@tanstack/query-core@5.66.0)(@tanstack/react-query@5.66.0(react@19.0.0-rc-45804af1-20241021))(@types/react@18.3.12)(@vercel/blob@0.24.1)(bufferutil@4.0.8)(react@19.0.0-rc-45804af1-20241021)(typescript@5.6.3)(utf-8-validate@5.0.10)(viem@2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8) + wagmi: 2.14.9(@tanstack/query-core@5.66.0)(@tanstack/react-query@5.66.0(react@19.0.0-rc-45804af1-20241021))(@types/react@18.3.12)(@vercel/blob@0.24.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@19.0.0-rc-45804af1-20241021)(typescript@5.6.3)(utf-8-validate@5.0.10)(viem@2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -5474,6 +5498,7 @@ snapshots: - tailwindcss - typescript - uploadthing + - use-sync-external-store - utf-8-validate - zod @@ -6005,10 +6030,10 @@ snapshots: '@metamask/safe-event-emitter@3.1.2': {} - '@metamask/sdk-communication-layer@0.31.0(cross-fetch@4.1.0)(eciesjs@0.4.13)(eventemitter2@6.4.9)(readable-stream@3.6.2)(socket.io-client@4.8.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))': + '@metamask/sdk-communication-layer@0.31.0(cross-fetch@4.1.0(encoding@0.1.13))(eciesjs@0.4.13)(eventemitter2@6.4.9)(readable-stream@3.6.2)(socket.io-client@4.8.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))': dependencies: bufferutil: 4.0.8 - cross-fetch: 4.1.0 + cross-fetch: 4.1.0(encoding@0.1.13) date-fns: 2.30.0 debug: 4.3.7 eciesjs: 0.4.13 @@ -6024,16 +6049,16 @@ snapshots: dependencies: '@paulmillr/qr': 0.2.1 - '@metamask/sdk@0.31.5(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + '@metamask/sdk@0.31.5(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)': dependencies: '@babel/runtime': 7.26.7 '@metamask/onboarding': 1.0.1 '@metamask/providers': 16.1.0 - '@metamask/sdk-communication-layer': 0.31.0(cross-fetch@4.1.0)(eciesjs@0.4.13)(eventemitter2@6.4.9)(readable-stream@3.6.2)(socket.io-client@4.8.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + '@metamask/sdk-communication-layer': 0.31.0(cross-fetch@4.1.0(encoding@0.1.13))(eciesjs@0.4.13)(eventemitter2@6.4.9)(readable-stream@3.6.2)(socket.io-client@4.8.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)) '@metamask/sdk-install-modal-web': 0.31.5 '@paulmillr/qr': 0.2.1 bowser: 2.11.0 - cross-fetch: 4.1.0 + cross-fetch: 4.1.0(encoding@0.1.13) debug: 4.3.7 eciesjs: 0.4.13 eth-rpc-errors: 4.0.3 @@ -6220,17 +6245,17 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@privy-io/server-auth@1.18.4(bufferutil@4.0.8)(utf-8-validate@5.0.10)(viem@2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))': + '@privy-io/server-auth@1.18.4(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)(viem@2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))': dependencies: '@noble/curves': 1.8.1 '@noble/hashes': 1.7.1 - '@solana/web3.js': 1.98.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@solana/web3.js': 1.98.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) canonicalize: 2.0.0 dotenv: 16.4.5 jose: 4.15.9 node-fetch-native: 1.6.6 redaxios: 0.5.1 - svix: 1.45.1 + svix: 1.45.1(encoding@0.1.13) ts-case-convert: 2.1.0 type-fest: 3.13.1 optionalDependencies: @@ -6690,7 +6715,7 @@ snapshots: dependencies: buffer: 6.0.3 - '@solana/web3.js@1.98.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + '@solana/web3.js@1.98.0(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)': dependencies: '@babel/runtime': 7.26.7 '@noble/curves': 1.8.1 @@ -6704,7 +6729,7 @@ snapshots: buffer: 6.0.3 fast-stable-stringify: 1.0.0 jayson: 4.1.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) - node-fetch: 2.7.0 + node-fetch: 2.7.0(encoding@0.1.13) rpc-websockets: 9.0.4 superstruct: 2.0.2 transitivePeerDependencies: @@ -6973,14 +6998,14 @@ snapshots: transitivePeerDependencies: - utf-8-validate - '@wagmi/connectors@5.7.5(@types/react@18.3.12)(@vercel/blob@0.24.1)(@wagmi/core@2.16.3(@tanstack/query-core@5.66.0)(@types/react@18.3.12)(react@19.0.0-rc-45804af1-20241021)(typescript@5.6.3)(use-sync-external-store@1.4.0(react@19.0.0-rc-45804af1-20241021))(viem@2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)))(bufferutil@4.0.8)(react@19.0.0-rc-45804af1-20241021)(typescript@5.6.3)(utf-8-validate@5.0.10)(viem@2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8)': + '@wagmi/connectors@5.7.5(@types/react@18.3.12)(@vercel/blob@0.24.1)(@wagmi/core@2.16.3(@tanstack/query-core@5.66.0)(@types/react@18.3.12)(react@19.0.0-rc-45804af1-20241021)(typescript@5.6.3)(use-sync-external-store@1.4.0(react@19.0.0-rc-45804af1-20241021))(viem@2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)))(bufferutil@4.0.8)(encoding@0.1.13)(react@19.0.0-rc-45804af1-20241021)(typescript@5.6.3)(utf-8-validate@5.0.10)(viem@2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8)': dependencies: '@coinbase/wallet-sdk': 4.2.3 - '@metamask/sdk': 0.31.5(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@metamask/sdk': 0.31.5(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) '@safe-global/safe-apps-provider': 0.18.5(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) '@safe-global/safe-apps-sdk': 9.1.0(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) '@wagmi/core': 2.16.3(@tanstack/query-core@5.66.0)(@types/react@18.3.12)(react@19.0.0-rc-45804af1-20241021)(typescript@5.6.3)(use-sync-external-store@1.4.0(react@19.0.0-rc-45804af1-20241021))(viem@2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)) - '@walletconnect/ethereum-provider': 2.17.0(@types/react@18.3.12)(@vercel/blob@0.24.1)(bufferutil@4.0.8)(react@19.0.0-rc-45804af1-20241021)(utf-8-validate@5.0.10) + '@walletconnect/ethereum-provider': 2.17.0(@types/react@18.3.12)(@vercel/blob@0.24.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@19.0.0-rc-45804af1-20241021)(utf-8-validate@5.0.10) cbw-sdk: '@coinbase/wallet-sdk@3.9.3' viem: 2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) optionalDependencies: @@ -7071,16 +7096,16 @@ snapshots: dependencies: tslib: 1.14.1 - '@walletconnect/ethereum-provider@2.17.0(@types/react@18.3.12)(@vercel/blob@0.24.1)(bufferutil@4.0.8)(react@19.0.0-rc-45804af1-20241021)(utf-8-validate@5.0.10)': + '@walletconnect/ethereum-provider@2.17.0(@types/react@18.3.12)(@vercel/blob@0.24.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@19.0.0-rc-45804af1-20241021)(utf-8-validate@5.0.10)': dependencies: - '@walletconnect/jsonrpc-http-connection': 1.0.8 + '@walletconnect/jsonrpc-http-connection': 1.0.8(encoding@0.1.13) '@walletconnect/jsonrpc-provider': 1.0.14 '@walletconnect/jsonrpc-types': 1.0.4 '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/modal': 2.7.0(@types/react@18.3.12)(react@19.0.0-rc-45804af1-20241021) '@walletconnect/sign-client': 2.17.0(@vercel/blob@0.24.1)(bufferutil@4.0.8)(utf-8-validate@5.0.10) '@walletconnect/types': 2.17.0(@vercel/blob@0.24.1) - '@walletconnect/universal-provider': 2.17.0(@vercel/blob@0.24.1)(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@walletconnect/universal-provider': 2.17.0(@vercel/blob@0.24.1)(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) '@walletconnect/utils': 2.17.0(@vercel/blob@0.24.1) events: 3.3.0 transitivePeerDependencies: @@ -7119,11 +7144,11 @@ snapshots: '@walletconnect/time': 1.0.2 events: 3.3.0 - '@walletconnect/jsonrpc-http-connection@1.0.8': + '@walletconnect/jsonrpc-http-connection@1.0.8(encoding@0.1.13)': dependencies: '@walletconnect/jsonrpc-utils': 1.0.8 '@walletconnect/safe-json': 1.0.2 - cross-fetch: 3.2.0 + cross-fetch: 3.2.0(encoding@0.1.13) events: 3.3.0 transitivePeerDependencies: - encoding @@ -7291,9 +7316,9 @@ snapshots: - ioredis - uploadthing - '@walletconnect/universal-provider@2.17.0(@vercel/blob@0.24.1)(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + '@walletconnect/universal-provider@2.17.0(@vercel/blob@0.24.1)(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10)': dependencies: - '@walletconnect/jsonrpc-http-connection': 1.0.8 + '@walletconnect/jsonrpc-http-connection': 1.0.8(encoding@0.1.13) '@walletconnect/jsonrpc-provider': 1.0.14 '@walletconnect/jsonrpc-types': 1.0.4 '@walletconnect/jsonrpc-utils': 1.0.8 @@ -7372,6 +7397,14 @@ snapshots: '@walletconnect/window-getters': 1.0.1 tslib: 1.14.1 + '@zoralabs/protocol-deployments@0.5.1': {} + + '@zoralabs/protocol-sdk@0.13.2(abitype@1.0.8(typescript@5.6.3)(zod@3.23.8))(viem@2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))': + dependencies: + '@zoralabs/protocol-deployments': 0.5.1 + abitype: 1.0.8(typescript@5.6.3)(zod@3.23.8) + viem: 2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + JSONStream@1.3.5: dependencies: jsonparse: 1.3.1 @@ -7518,7 +7551,7 @@ snapshots: async-mutex@0.2.6: dependencies: - tslib: 2.8.0 + tslib: 2.8.1 async-retry@1.3.3: dependencies: @@ -7790,15 +7823,15 @@ snapshots: crelt@1.0.6: {} - cross-fetch@3.2.0: + cross-fetch@3.2.0(encoding@0.1.13): dependencies: - node-fetch: 2.7.0 + node-fetch: 2.7.0(encoding@0.1.13) transitivePeerDependencies: - encoding - cross-fetch@4.1.0: + cross-fetch@4.1.0(encoding@0.1.13): dependencies: - node-fetch: 2.7.0 + node-fetch: 2.7.0(encoding@0.1.13) transitivePeerDependencies: - encoding @@ -7976,6 +8009,11 @@ snapshots: encode-utf8@1.0.3: {} + encoding@0.1.13: + dependencies: + iconv-lite: 0.6.3 + optional: true + end-of-stream@1.4.4: dependencies: once: 1.4.0 @@ -8667,10 +8705,10 @@ snapshots: graphemer@1.4.0: {} - graphql-request@6.1.0(graphql@16.10.0): + graphql-request@6.1.0(encoding@0.1.13)(graphql@16.10.0): dependencies: '@graphql-typed-document-node/core': 3.2.0(graphql@16.10.0) - cross-fetch: 3.2.0 + cross-fetch: 3.2.0(encoding@0.1.13) graphql: 16.10.0 transitivePeerDependencies: - encoding @@ -8761,6 +8799,11 @@ snapshots: dependencies: ms: 2.1.3 + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + optional: true + idb-keyval@6.2.1: {} ieee754@1.2.1: {} @@ -9590,9 +9633,11 @@ snapshots: node-fetch-native@1.6.6: {} - node-fetch@2.7.0: + node-fetch@2.7.0(encoding@0.1.13): dependencies: whatwg-url: 5.0.0 + optionalDependencies: + encoding: 0.1.13 node-forge@1.3.1: {} @@ -10262,6 +10307,9 @@ snapshots: safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: + optional: true + scheduler@0.25.0-rc-45804af1-20241021: {} secp256k1@5.0.1: @@ -10511,20 +10559,20 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svix-fetch@3.0.0: + svix-fetch@3.0.0(encoding@0.1.13): dependencies: - node-fetch: 2.7.0 + node-fetch: 2.7.0(encoding@0.1.13) whatwg-fetch: 3.6.20 transitivePeerDependencies: - encoding - svix@1.45.1: + svix@1.45.1(encoding@0.1.13): dependencies: '@stablelib/base64': 1.0.1 '@types/node': 22.9.0 es6-promise: 4.2.8 fast-sha256: 1.3.0 - svix-fetch: 3.0.0 + svix-fetch: 3.0.0(encoding@0.1.13) url-parse: 1.5.10 transitivePeerDependencies: - encoding @@ -10621,8 +10669,7 @@ snapshots: tslib@2.8.0: {} - tslib@2.8.1: - optional: true + tslib@2.8.1: {} tsx@4.19.2: dependencies: @@ -10851,10 +10898,10 @@ snapshots: w3c-keyname@2.2.8: {} - wagmi@2.14.9(@tanstack/query-core@5.66.0)(@tanstack/react-query@5.66.0(react@19.0.0-rc-45804af1-20241021))(@types/react@18.3.12)(@vercel/blob@0.24.1)(bufferutil@4.0.8)(react@19.0.0-rc-45804af1-20241021)(typescript@5.6.3)(utf-8-validate@5.0.10)(viem@2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8): + wagmi@2.14.9(@tanstack/query-core@5.66.0)(@tanstack/react-query@5.66.0(react@19.0.0-rc-45804af1-20241021))(@types/react@18.3.12)(@vercel/blob@0.24.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@19.0.0-rc-45804af1-20241021)(typescript@5.6.3)(utf-8-validate@5.0.10)(viem@2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8): dependencies: '@tanstack/react-query': 5.66.0(react@19.0.0-rc-45804af1-20241021) - '@wagmi/connectors': 5.7.5(@types/react@18.3.12)(@vercel/blob@0.24.1)(@wagmi/core@2.16.3(@tanstack/query-core@5.66.0)(@types/react@18.3.12)(react@19.0.0-rc-45804af1-20241021)(typescript@5.6.3)(use-sync-external-store@1.4.0(react@19.0.0-rc-45804af1-20241021))(viem@2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)))(bufferutil@4.0.8)(react@19.0.0-rc-45804af1-20241021)(typescript@5.6.3)(utf-8-validate@5.0.10)(viem@2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8) + '@wagmi/connectors': 5.7.5(@types/react@18.3.12)(@vercel/blob@0.24.1)(@wagmi/core@2.16.3(@tanstack/query-core@5.66.0)(@types/react@18.3.12)(react@19.0.0-rc-45804af1-20241021)(typescript@5.6.3)(use-sync-external-store@1.4.0(react@19.0.0-rc-45804af1-20241021))(viem@2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)))(bufferutil@4.0.8)(encoding@0.1.13)(react@19.0.0-rc-45804af1-20241021)(typescript@5.6.3)(utf-8-validate@5.0.10)(viem@2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8) '@wagmi/core': 2.16.3(@tanstack/query-core@5.66.0)(@types/react@18.3.12)(react@19.0.0-rc-45804af1-20241021)(typescript@5.6.3)(use-sync-external-store@1.4.0(react@19.0.0-rc-45804af1-20241021))(viem@2.22.21(bufferutil@4.0.8)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8)) react: 19.0.0-rc-45804af1-20241021 use-sync-external-store: 1.4.0(react@19.0.0-rc-45804af1-20241021)