From 3e8ec0ef533a74e31bc39af2e3bb113fd9c825fa Mon Sep 17 00:00:00 2001 From: Dango Date: Sun, 1 Jun 2025 23:49:19 -0700 Subject: [PATCH 1/9] feat: WalletLinkForm support ens domain --- .../edit/components/WalletLinkForm.tsx | 40 +++++++++++++++---- src/lib/walletLinking/readmeUtils.ts | 2 + src/lib/walletLinking/viem.ts | 7 ++++ 3 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 src/lib/walletLinking/viem.ts diff --git a/src/app/profile/edit/components/WalletLinkForm.tsx b/src/app/profile/edit/components/WalletLinkForm.tsx index ea38e380a..7c1466bdd 100644 --- a/src/app/profile/edit/components/WalletLinkForm.tsx +++ b/src/app/profile/edit/components/WalletLinkForm.tsx @@ -5,7 +5,9 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; import { Loader2, Info, Shield, ArrowRight } from "lucide-react"; -import { isAddress } from "viem"; // For ETH address validation +import { isAddress as isEvmAddress } from "viem"; // For ETH address validation +import { normalize } from "viem/ens"; +import { viemClient } from "@/lib/walletLinking/viem"; import { LinkedWallet } from "@/lib/walletLinking/readmeUtils"; interface WalletLinkFormProps { @@ -18,6 +20,10 @@ interface WalletLinkFormProps { // For more robust validation, consider @solana/web3.js PublicKey.isOnCurve or similar const SOL_ADDRESS_REGEX = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/; +// ENS name regex (name.eth format) +// Matches names that end with .eth and contain valid characters +const ENS_NAME_REGEX = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.eth$/; + export function WalletLinkForm({ wallets = [], onSubmit, @@ -40,7 +46,7 @@ export function WalletLinkForm({ setEthAddress(ethWallet?.address || ""); setSolAddress(solWallet?.address || ""); - if (!ethWallet?.address || isAddress(ethWallet.address)) { + if (!ethWallet?.address || isEvmAddress(ethWallet.address)) { setIsEthValid(true); setEthAddressError(""); } else { @@ -59,11 +65,15 @@ export function WalletLinkForm({ if (ethAddress === "") { setIsEthValid(true); setEthAddressError(""); - } else { - const isValid = isAddress(ethAddress); - setIsEthValid(isValid); - setEthAddressError(isValid ? "" : "Invalid Ethereum address."); + return; } + + const isEVMValid = isEvmAddress(ethAddress); + const isENSValid = ENS_NAME_REGEX.test(ethAddress); + setIsEthValid(isEVMValid || isENSValid); + setEthAddressError( + isEVMValid || isENSValid ? "" : "Invalid Ethereum address or ENS name.", + ); }, [ethAddress]); useEffect(() => { @@ -87,9 +97,23 @@ export function WalletLinkForm({ const updatedWallets: LinkedWallet[] = []; if (ethAddress) { + const isENSValid = ENS_NAME_REGEX.test(ethAddress); + const address = isENSValid + ? await viemClient.getEnsAddress({ + name: normalize(ethAddress), + }) + : ethAddress; + + // If the address is not found, set the error and return + if (!address) { + setEthAddressError("Invalid Ethereum address or ENS name."); + return; + } + updatedWallets.push({ chain: "ethereum", - address: ethAddress, + address, + ...(isENSValid && { ensName: ethAddress }), }); } @@ -125,7 +149,7 @@ export function WalletLinkForm({ type="text" value={ethAddress} onChange={(e) => setEthAddress(e.target.value)} - placeholder="0x..." + placeholder="Your Ethereum address (e.g., 0x...) or ENS name (e.g., vitalik.eth)" disabled={isProcessing} className={ethAddressError ? "border-destructive" : ""} /> diff --git a/src/lib/walletLinking/readmeUtils.ts b/src/lib/walletLinking/readmeUtils.ts index dc01bbb0d..eae801fb6 100644 --- a/src/lib/walletLinking/readmeUtils.ts +++ b/src/lib/walletLinking/readmeUtils.ts @@ -7,6 +7,7 @@ import { export const LinkedWalletSchema = z.object({ chain: z.string().min(1).toLowerCase(), address: z.string().min(1), + ensName: z.string().min(1).optional(), signature: z.string().min(1).optional(), }); @@ -138,6 +139,7 @@ export function generateReadmeWalletSection(wallets: LinkedWallet[]): string { wallets: validatedWallets.map((wallet) => ({ chain: wallet.chain.toLowerCase().trim(), address: wallet.address.trim(), + ...(wallet.ensName ? { ensName: wallet.ensName.trim() } : {}), ...(wallet.signature ? { signature: wallet.signature.trim() } : {}), })), }; diff --git a/src/lib/walletLinking/viem.ts b/src/lib/walletLinking/viem.ts new file mode 100644 index 000000000..6a18846bd --- /dev/null +++ b/src/lib/walletLinking/viem.ts @@ -0,0 +1,7 @@ +import { createPublicClient, http } from "viem"; +import { mainnet } from "viem/chains"; + +export const viemClient = createPublicClient({ + chain: mainnet, + transport: http(), +}); From 5a242e1b6d74f85db8d447c86598638e89f0e754 Mon Sep 17 00:00:00 2001 From: Dango Date: Thu, 5 Jun 2025 01:11:32 -0700 Subject: [PATCH 2/9] feat: WalletLinkForm support sns domain --- bun.lock | 186 +++++++++++++++++- package.json | 2 + .../edit/components/WalletLinkForm.tsx | 31 ++- src/lib/walletLinking/readmeUtils.ts | 2 + src/lib/walletLinking/sns.ts | 52 +++++ 5 files changed, 266 insertions(+), 7 deletions(-) create mode 100644 src/lib/walletLinking/sns.ts diff --git a/bun.lock b/bun.lock index 83d4df9cb..10e2ccbc0 100644 --- a/bun.lock +++ b/bun.lock @@ -5,6 +5,7 @@ "name": "elizaos.github.io", "dependencies": { "@anthropic-ai/sdk": "^0.39.0", + "@bonfida/spl-name-service": "^3.0.11", "@commander-js/extra-typings": "^12.0.0", "@date-fns/utc": "^2.1.0", "@radix-ui/react-accordion": "^1.2.2", @@ -22,6 +23,7 @@ "@radix-ui/react-tabs": "^1.1.2", "@radix-ui/react-toast": "^1.2.13", "@radix-ui/react-tooltip": "^1.1.6", + "@solana/web3.js": "^1.98.2", "@tanstack/react-virtual": "^3.11.2", "@types/minimatch": "^5.1.2", "axios": "^1.8.4", @@ -97,6 +99,10 @@ "@babel/runtime": ["@babel/runtime@7.27.0", "", { "dependencies": { "regenerator-runtime": "^0.14.0" } }, "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw=="], + "@bonfida/sns-records": ["@bonfida/sns-records@0.0.1", "", { "dependencies": { "borsh": "1.0.0", "bs58": "5.0.0", "buffer": "^6.0.3" }, "peerDependencies": { "@solana/web3.js": "^1.87.3" } }, "sha512-i28w9+BMFufhhpmLQCNx1CKKXTsEn+5RT18VFpPqdGO3sqaYlnUWC1m3wDpOvlzGk498dljgRpRo5wmcsnuEMg=="], + + "@bonfida/spl-name-service": ["@bonfida/spl-name-service@3.0.11", "", { "dependencies": { "@bonfida/sns-records": "0.0.1", "@noble/curves": "^1.9.1", "@scure/base": "^1.2.5", "@solana/spl-token": "0.4.6", "borsh": "2.0.0", "buffer": "^6.0.3", "graphemesplit": "^2.6.0", "ipaddr.js": "^2.2.0", "punycode": "^2.3.1" }, "peerDependencies": { "@solana/web3.js": "^1.98.2" } }, "sha512-8k21dNF5UalHdlZa1Mqf9pwp4JMTRuY0ujsbWxcsb5hrXjcmYiXzyyMFAh1UHIqE1vRXOIqYGAt3av4XlQ2iQQ=="], + "@colors/colors": ["@colors/colors@1.5.0", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="], "@commander-js/extra-typings": ["@commander-js/extra-typings@12.1.0", "", { "peerDependencies": { "commander": "~12.1.0" } }, "sha512-wf/lwQvWAA0goIghcb91dQYpkLBcyhOhQNqG/VgWhnKzgt+UOMvra7EX/2fv70arm5RW+PUHoQHHDa6/p77Eqg=="], @@ -289,7 +295,7 @@ "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-SbnWkJmkS7Xl3kre8SdMF6F/XDh1DTFEhp0jRTj/uB8iPKoU2bb2NDfcu+iifv1+mxQEd1g2vvSxcZbXSKyWiQ=="], - "@noble/curves": ["@noble/curves@1.8.2", "", { "dependencies": { "@noble/hashes": "1.7.2" } }, "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g=="], + "@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], "@noble/hashes": ["@noble/hashes@1.7.2", "", {}, "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ=="], @@ -405,6 +411,34 @@ "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="], + "@solana/buffer-layout": ["@solana/buffer-layout@4.0.1", "", { "dependencies": { "buffer": "~6.0.3" } }, "sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA=="], + + "@solana/buffer-layout-utils": ["@solana/buffer-layout-utils@0.2.0", "", { "dependencies": { "@solana/buffer-layout": "^4.0.0", "@solana/web3.js": "^1.32.0", "bigint-buffer": "^1.1.5", "bignumber.js": "^9.0.1" } }, "sha512-szG4sxgJGktbuZYDg2FfNmkMi0DYQoVjN2h7ta1W1hPrwzarcFLBq9UpX1UjNXsNpT9dn+chgprtWGioUAr4/g=="], + + "@solana/codecs": ["@solana/codecs@2.0.0-preview.2", "", { "dependencies": { "@solana/codecs-core": "2.0.0-preview.2", "@solana/codecs-data-structures": "2.0.0-preview.2", "@solana/codecs-numbers": "2.0.0-preview.2", "@solana/codecs-strings": "2.0.0-preview.2", "@solana/options": "2.0.0-preview.2" } }, "sha512-4HHzCD5+pOSmSB71X6w9ptweV48Zj1Vqhe732+pcAQ2cMNnN0gMPMdDq7j3YwaZDZ7yrILVV/3+HTnfT77t2yA=="], + + "@solana/codecs-core": ["@solana/codecs-core@2.1.1", "", { "dependencies": { "@solana/errors": "2.1.1" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-iPQW3UZ2Vi7QFBo2r9tw0NubtH8EdrhhmZulx6lC8V5a+qjaxovtM/q/UW2BTNpqqHLfO0tIcLyBLrNH4HTWPg=="], + + "@solana/codecs-data-structures": ["@solana/codecs-data-structures@2.0.0-preview.2", "", { "dependencies": { "@solana/codecs-core": "2.0.0-preview.2", "@solana/codecs-numbers": "2.0.0-preview.2", "@solana/errors": "2.0.0-preview.2" } }, "sha512-Xf5vIfromOZo94Q8HbR04TbgTwzigqrKII0GjYr21K7rb3nba4hUW2ir8kguY7HWFBcjHGlU5x3MevKBOLp3Zg=="], + + "@solana/codecs-numbers": ["@solana/codecs-numbers@2.1.1", "", { "dependencies": { "@solana/codecs-core": "2.1.1", "@solana/errors": "2.1.1" }, "peerDependencies": { "typescript": ">=5.3.3" } }, "sha512-m20IUPJhPUmPkHSlZ2iMAjJ7PaYUvlMtFhCQYzm9BEBSI6OCvXTG3GAPpAnSGRBfg5y+QNqqmKn4QHU3B6zzCQ=="], + + "@solana/codecs-strings": ["@solana/codecs-strings@2.0.0-preview.2", "", { "dependencies": { "@solana/codecs-core": "2.0.0-preview.2", "@solana/codecs-numbers": "2.0.0-preview.2", "@solana/errors": "2.0.0-preview.2" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22" } }, "sha512-EgBwY+lIaHHgMJIqVOGHfIfpdmmUDNoNO/GAUGeFPf+q0dF+DtwhJPEMShhzh64X2MeCZcmSO6Kinx0Bvmmz2g=="], + + "@solana/errors": ["@solana/errors@2.1.1", "", { "dependencies": { "chalk": "^5.4.1", "commander": "^13.1.0" }, "peerDependencies": { "typescript": ">=5.3.3" }, "bin": { "errors": "bin/cli.mjs" } }, "sha512-sj6DaWNbSJFvLzT8UZoabMefQUfSW/8tXK7NTiagsDmh+Q87eyQDDC9L3z+mNmx9b6dEf6z660MOIplDD2nfEw=="], + + "@solana/options": ["@solana/options@2.0.0-preview.2", "", { "dependencies": { "@solana/codecs-core": "2.0.0-preview.2", "@solana/codecs-numbers": "2.0.0-preview.2" } }, "sha512-FAHqEeH0cVsUOTzjl5OfUBw2cyT8d5Oekx4xcn5hn+NyPAfQJgM3CEThzgRD6Q/4mM5pVUnND3oK/Mt1RzSE/w=="], + + "@solana/spl-token": ["@solana/spl-token@0.4.6", "", { "dependencies": { "@solana/buffer-layout": "^4.0.0", "@solana/buffer-layout-utils": "^0.2.0", "@solana/spl-token-group": "^0.0.4", "@solana/spl-token-metadata": "^0.1.4", "buffer": "^6.0.3" }, "peerDependencies": { "@solana/web3.js": "^1.91.6" } }, "sha512-1nCnUqfHVtdguFciVWaY/RKcQz1IF4b31jnKgAmjU9QVN1q7dRUkTEWJZgTYIEtsULjVnC9jRqlhgGN39WbKKA=="], + + "@solana/spl-token-group": ["@solana/spl-token-group@0.0.4", "", { "dependencies": { "@solana/codecs": "2.0.0-preview.2", "@solana/spl-type-length-value": "0.1.0" }, "peerDependencies": { "@solana/web3.js": "^1.91.6" } }, "sha512-7+80nrEMdUKlK37V6kOe024+T7J4nNss0F8LQ9OOPYdWCCfJmsGUzVx2W3oeizZR4IHM6N4yC9v1Xqwc3BTPWw=="], + + "@solana/spl-token-metadata": ["@solana/spl-token-metadata@0.1.6", "", { "dependencies": { "@solana/codecs": "2.0.0-rc.1" }, "peerDependencies": { "@solana/web3.js": "^1.95.3" } }, "sha512-7sMt1rsm/zQOQcUWllQX9mD2O6KhSAtY1hFR2hfFwgqfFWzSY9E9GDvFVNYUI1F0iQKcm6HmePU9QbKRXTEBiA=="], + + "@solana/spl-type-length-value": ["@solana/spl-type-length-value@0.1.0", "", { "dependencies": { "buffer": "^6.0.3" } }, "sha512-JBMGB0oR4lPttOZ5XiUGyvylwLQjt1CPJa6qQ5oM+MBCndfjz2TKKkw0eATlLLcYmq1jBVsNlJ2cD6ns2GR7lA=="], + + "@solana/web3.js": ["@solana/web3.js@1.98.2", "", { "dependencies": { "@babel/runtime": "^7.25.0", "@noble/curves": "^1.4.2", "@noble/hashes": "^1.4.0", "@solana/buffer-layout": "^4.0.1", "@solana/codecs-numbers": "^2.1.0", "agentkeepalive": "^4.5.0", "bn.js": "^5.2.1", "borsh": "^0.7.0", "bs58": "^4.0.1", "buffer": "6.0.3", "fast-stable-stringify": "^1.0.0", "jayson": "^4.1.1", "node-fetch": "^2.7.0", "rpc-websockets": "^9.0.2", "superstruct": "^2.0.2" } }, "sha512-BqVwEG+TaG2yCkBMbD3C4hdpustR4FpuUFRPUmqRZYYlPI9Hg4XMWxHWOWRzHE9Lkc9NDjzXFX7lDXSgzC7R1A=="], + "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], "@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="], @@ -425,6 +459,8 @@ "@types/bun": ["@types/bun@1.2.4", "", { "dependencies": { "bun-types": "1.2.4" } }, "sha512-QtuV5OMR8/rdKJs213iwXDpfVvnskPXY/S0ZiFbsTjQZycuqPbMW8Gf/XhLfwE5njW8sxI2WjISURXPlHypMFA=="], + "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], + "@types/d3-array": ["@types/d3-array@3.2.1", "", {}, "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="], "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], @@ -475,6 +511,8 @@ "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + "@types/uuid": ["@types/uuid@8.3.4", "", {}, "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw=="], + "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.20.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.20.0", "@typescript-eslint/type-utils": "8.20.0", "@typescript-eslint/utils": "8.20.0", "@typescript-eslint/visitor-keys": "8.20.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^2.0.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-naduuphVw5StFfqp4Gq4WhIBE2gN1GEmMUExpJYknZJdRnc+2gDzB8Z3+5+/Kv33hPQRDGzQO/0opHE72lZZ6A=="], @@ -565,18 +603,28 @@ "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "base-x": ["base-x@3.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA=="], + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], "better-sqlite3": ["better-sqlite3@11.9.1", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-Ba0KR+Fzxh2jDRhdg6TSH0SJGzb8C0aBY4hR8w8madIdIzzC6Y1+kx5qR6eS1Z+Gy20h6ZU28aeyg0z1VIrShQ=="], + "bigint-buffer": ["bigint-buffer@1.1.5", "", { "dependencies": { "bindings": "^1.3.0" } }, "sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA=="], + + "bignumber.js": ["bignumber.js@9.3.0", "", {}, "sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA=="], + "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], "bl": ["bl@5.1.0", "", { "dependencies": { "buffer": "^6.0.3", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ=="], + "bn.js": ["bn.js@5.2.2", "", {}, "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw=="], + "body-parser": ["body-parser@1.20.3", "", { "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" } }, "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g=="], + "borsh": ["borsh@2.0.0", "", {}, "sha512-kc9+BgR3zz9+cjbwM8ODoUB4fs3X3I5A/HtX7LZKxCLaMrEeDFoBpnhZY//DTS1VZBSs6S5v46RZRbZjRFspEg=="], + "boxen": ["boxen@7.1.1", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^7.0.1", "chalk": "^5.2.0", "cli-boxes": "^3.0.0", "string-width": "^5.1.2", "type-fest": "^2.13.0", "widest-line": "^4.0.1", "wrap-ansi": "^8.1.0" } }, "sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog=="], "brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], @@ -585,12 +633,16 @@ "browserslist": ["browserslist@4.24.4", "", { "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" } }, "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A=="], + "bs58": ["bs58@4.0.1", "", { "dependencies": { "base-x": "^3.0.2" } }, "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw=="], + "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + "bufferutil": ["bufferutil@4.0.9", "", { "dependencies": { "node-gyp-build": "^4.3.0" } }, "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw=="], + "bun-types": ["bun-types@1.2.4", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-nDPymR207ZZEoWD4AavvEaa/KZe/qlrbMSchqpQwovPZCKc7pwMoENjEtHgMKaAjJhy+x6vfqSBA1QU3bJgs0Q=="], "busboy": ["busboy@1.6.0", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="], @@ -731,6 +783,8 @@ "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], + "delay": ["delay@5.0.0", "", {}, "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw=="], + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], @@ -797,6 +851,10 @@ "es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="], + "es6-promise": ["es6-promise@4.2.8", "", {}, "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="], + + "es6-promisify": ["es6-promisify@5.0.0", "", { "dependencies": { "es6-promise": "^4.0.3" } }, "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ=="], + "esbuild": ["esbuild@0.19.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.19.12", "@esbuild/android-arm": "0.19.12", "@esbuild/android-arm64": "0.19.12", "@esbuild/android-x64": "0.19.12", "@esbuild/darwin-arm64": "0.19.12", "@esbuild/darwin-x64": "0.19.12", "@esbuild/freebsd-arm64": "0.19.12", "@esbuild/freebsd-x64": "0.19.12", "@esbuild/linux-arm": "0.19.12", "@esbuild/linux-arm64": "0.19.12", "@esbuild/linux-ia32": "0.19.12", "@esbuild/linux-loong64": "0.19.12", "@esbuild/linux-mips64el": "0.19.12", "@esbuild/linux-ppc64": "0.19.12", "@esbuild/linux-riscv64": "0.19.12", "@esbuild/linux-s390x": "0.19.12", "@esbuild/linux-x64": "0.19.12", "@esbuild/netbsd-x64": "0.19.12", "@esbuild/openbsd-x64": "0.19.12", "@esbuild/sunos-x64": "0.19.12", "@esbuild/win32-arm64": "0.19.12", "@esbuild/win32-ia32": "0.19.12", "@esbuild/win32-x64": "0.19.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg=="], "esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="], @@ -869,6 +927,8 @@ "external-editor": ["external-editor@3.1.0", "", { "dependencies": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", "tmp": "^0.0.33" } }, "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew=="], + "eyes": ["eyes@0.1.8", "", {}, "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ=="], + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], "fast-equals": ["fast-equals@5.2.2", "", {}, "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw=="], @@ -879,6 +939,10 @@ "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + "fast-stable-stringify": ["fast-stable-stringify@1.0.0", "", {}, "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag=="], + + "fastestsmallesttextencoderdecoder": ["fastestsmallesttextencoderdecoder@1.0.22", "", {}, "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw=="], + "fastmcp": ["fastmcp@1.21.0", "", { "dependencies": { "@modelcontextprotocol/sdk": "^1.6.0", "@standard-schema/spec": "^1.0.0", "execa": "^9.5.2", "file-type": "^20.3.0", "fuse.js": "^7.1.0", "mcp-proxy": "^2.10.4", "strict-event-emitter-types": "^2.0.0", "undici": "^7.4.0", "uri-templates": "^0.2.0", "xsschema": "0.2.0-beta.2", "yargs": "^17.7.2", "zod": "^3.24.2", "zod-to-json-schema": "^3.24.5" }, "bin": { "fastmcp": "dist/bin/fastmcp.js" } }, "sha512-q4edIA6Mxo8/63JCvdZqnD2IfWVGXfLteZRDwewnr4BVmdv9TylaV+KBBPngf73KdAWc47UtXzDqUQUPL9vxOA=="], "fastq": ["fastq@1.18.0", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw=="], @@ -977,6 +1041,8 @@ "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], + "graphemesplit": ["graphemesplit@2.6.0", "", { "dependencies": { "js-base64": "^3.6.0", "unicode-trie": "^2.0.0" } }, "sha512-rG9w2wAfkpg0DILa1pjnjNfucng3usON360shisqIMUBw/87pojcBSrHmeE4UwryAuBih7g8m1oilf5/u8EWdQ=="], + "gray-matter": ["gray-matter@4.0.3", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="], "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="], @@ -1031,7 +1097,7 @@ "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], - "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], + "ipaddr.js": ["ipaddr.js@2.2.0", "", {}, "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA=="], "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], @@ -1115,14 +1181,20 @@ "isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="], + "isomorphic-ws": ["isomorphic-ws@4.0.1", "", { "peerDependencies": { "ws": "*" } }, "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w=="], + "isows": ["isows@1.0.6", "", { "peerDependencies": { "ws": "*" } }, "sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw=="], "iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="], "jackspeak": ["jackspeak@4.1.0", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" } }, "sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw=="], + "jayson": ["jayson@4.2.0", "", { "dependencies": { "@types/connect": "^3.4.33", "@types/node": "^12.12.54", "@types/ws": "^7.4.4", "commander": "^2.20.3", "delay": "^5.0.0", "es6-promisify": "^5.0.0", "eyes": "^0.1.8", "isomorphic-ws": "^4.0.1", "json-stringify-safe": "^5.0.1", "stream-json": "^1.9.1", "uuid": "^8.3.2", "ws": "^7.5.10" }, "bin": { "jayson": "bin/jayson.js" } }, "sha512-VfJ9t1YLwacIubLhONk0KFeosUBwstRWQ0IRT1KDjEjnVnSOVHC3uwugyV7L0c7R9lpVyrUGT2XWiBA1UTtpyg=="], + "jiti": ["jiti@1.21.7", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="], + "js-base64": ["js-base64@3.7.7", "", {}, "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw=="], + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], "js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="], @@ -1133,6 +1205,8 @@ "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], + "json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="], "jsonwebtoken": ["jsonwebtoken@9.0.2", "", { "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ=="], @@ -1347,6 +1421,8 @@ "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="], + "node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="], "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], @@ -1401,6 +1477,8 @@ "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + "pako": ["pako@0.2.9", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="], + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], @@ -1543,6 +1621,8 @@ "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], + "rpc-websockets": ["rpc-websockets@9.1.1", "", { "dependencies": { "@swc/helpers": "^0.5.11", "@types/uuid": "^8.3.4", "@types/ws": "^8.2.2", "buffer": "^6.0.3", "eventemitter3": "^5.0.1", "uuid": "^8.3.2", "ws": "^8.5.0" }, "optionalDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" } }, "sha512-1IXGM/TfPT6nfYMIXkJdzn+L4JEsmb0FL1O2OBjaH03V3yuUDdKFulGLMFG6ErV+8pZ5HVC0limve01RyO+saA=="], + "run-async": ["run-async@3.0.0", "", {}, "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q=="], "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], @@ -1623,6 +1703,10 @@ "stdin-discarder": ["stdin-discarder@0.1.0", "", { "dependencies": { "bl": "^5.0.0" } }, "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ=="], + "stream-chain": ["stream-chain@2.2.5", "", {}, "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA=="], + + "stream-json": ["stream-json@1.9.1", "", { "dependencies": { "stream-chain": "^2.2.5" } }, "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw=="], + "streamsearch": ["streamsearch@1.1.0", "", {}, "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="], "strict-event-emitter-types": ["strict-event-emitter-types@2.0.0", "", {}, "sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA=="], @@ -1671,6 +1755,8 @@ "sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="], + "superstruct": ["superstruct@2.0.2", "", {}, "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A=="], + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], @@ -1689,10 +1775,14 @@ "task-master-ai": ["task-master-ai@0.12.1", "", { "dependencies": { "@anthropic-ai/sdk": "^0.39.0", "boxen": "^8.0.1", "chalk": "^4.1.2", "cli-table3": "^0.6.5", "commander": "^11.1.0", "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.21.2", "fastmcp": "^1.20.5", "figlet": "^1.8.0", "fuse.js": "^7.0.0", "gradient-string": "^3.0.0", "helmet": "^8.1.0", "inquirer": "^12.5.0", "jsonwebtoken": "^9.0.2", "lru-cache": "^10.2.0", "openai": "^4.89.0", "ora": "^8.2.0", "uuid": "^11.1.0" }, "bin": { "task-master": "bin/task-master.js", "task-master-mcp": "mcp-server/server.js", "task-master-ai": "mcp-server/server.js" } }, "sha512-s72tXsfoxV8S47UuN0m3mMftb2oR3QPTmbZqBaB6LiZVNIWq7CivIBJsxszcCeEss9I28OP3BqE1XoUDI+jQ/w=="], + "text-encoding-utf-8": ["text-encoding-utf-8@1.0.2", "", {}, "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg=="], + "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], + "tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="], + "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], "tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="], @@ -1747,6 +1837,8 @@ "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], + "unicode-trie": ["unicode-trie@2.0.0", "", { "dependencies": { "pako": "^0.2.5", "tiny-inflate": "^1.0.0" } }, "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ=="], + "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], @@ -1773,6 +1865,8 @@ "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], + "utf-8-validate": ["utf-8-validate@5.0.10", "", { "dependencies": { "node-gyp-build": "^4.3.0" } }, "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ=="], + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="], @@ -1841,6 +1935,10 @@ "@anthropic-ai/sdk/@types/node": ["@types/node@18.19.86", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-fifKayi175wLyKyc5qUfyENhQ1dCNI1UNjp653d8kuYcPQN5JhX3dGuP/XmvPTg/xRBn1VTLpbmi+H/Mr7tLfQ=="], + "@bonfida/sns-records/borsh": ["borsh@1.0.0", "", {}, "sha512-fSVWzzemnyfF89EPwlUNsrS5swF5CrtiN4e+h0/lLf4dz2he4L3ndM20PS9wj7ICSkXJe/TQUHdaPTq15b1mNQ=="], + + "@bonfida/sns-records/bs58": ["bs58@5.0.0", "", { "dependencies": { "base-x": "^4.0.0" } }, "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ=="], + "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], @@ -1863,6 +1961,8 @@ "@next/eslint-plugin-next/fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="], + "@noble/curves/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + "@radix-ui/react-arrow/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.2", "", { "dependencies": { "@radix-ui/react-slot": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw=="], "@radix-ui/react-checkbox/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], @@ -2021,6 +2121,36 @@ "@radix-ui/react-use-size/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="], + "@scure/bip32/@noble/curves": ["@noble/curves@1.8.2", "", { "dependencies": { "@noble/hashes": "1.7.2" } }, "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g=="], + + "@solana/codecs/@solana/codecs-core": ["@solana/codecs-core@2.0.0-preview.2", "", { "dependencies": { "@solana/errors": "2.0.0-preview.2" } }, "sha512-gLhCJXieSCrAU7acUJjbXl+IbGnqovvxQLlimztPoGgfLQ1wFYu+XJswrEVQqknZYK1pgxpxH3rZ+OKFs0ndQg=="], + + "@solana/codecs/@solana/codecs-numbers": ["@solana/codecs-numbers@2.0.0-preview.2", "", { "dependencies": { "@solana/codecs-core": "2.0.0-preview.2", "@solana/errors": "2.0.0-preview.2" } }, "sha512-aLZnDTf43z4qOnpTcDsUVy1Ci9im1Md8thWipSWbE+WM9ojZAx528oAql+Cv8M8N+6ALKwgVRhPZkto6E59ARw=="], + + "@solana/codecs-data-structures/@solana/codecs-core": ["@solana/codecs-core@2.0.0-preview.2", "", { "dependencies": { "@solana/errors": "2.0.0-preview.2" } }, "sha512-gLhCJXieSCrAU7acUJjbXl+IbGnqovvxQLlimztPoGgfLQ1wFYu+XJswrEVQqknZYK1pgxpxH3rZ+OKFs0ndQg=="], + + "@solana/codecs-data-structures/@solana/codecs-numbers": ["@solana/codecs-numbers@2.0.0-preview.2", "", { "dependencies": { "@solana/codecs-core": "2.0.0-preview.2", "@solana/errors": "2.0.0-preview.2" } }, "sha512-aLZnDTf43z4qOnpTcDsUVy1Ci9im1Md8thWipSWbE+WM9ojZAx528oAql+Cv8M8N+6ALKwgVRhPZkto6E59ARw=="], + + "@solana/codecs-data-structures/@solana/errors": ["@solana/errors@2.0.0-preview.2", "", { "dependencies": { "chalk": "^5.3.0", "commander": "^12.0.0" }, "bin": { "errors": "bin/cli.js" } }, "sha512-H2DZ1l3iYF5Rp5pPbJpmmtCauWeQXRJapkDg8epQ8BJ7cA2Ut/QEtC3CMmw/iMTcuS6uemFNLcWvlOfoQhvQuA=="], + + "@solana/codecs-strings/@solana/codecs-core": ["@solana/codecs-core@2.0.0-preview.2", "", { "dependencies": { "@solana/errors": "2.0.0-preview.2" } }, "sha512-gLhCJXieSCrAU7acUJjbXl+IbGnqovvxQLlimztPoGgfLQ1wFYu+XJswrEVQqknZYK1pgxpxH3rZ+OKFs0ndQg=="], + + "@solana/codecs-strings/@solana/codecs-numbers": ["@solana/codecs-numbers@2.0.0-preview.2", "", { "dependencies": { "@solana/codecs-core": "2.0.0-preview.2", "@solana/errors": "2.0.0-preview.2" } }, "sha512-aLZnDTf43z4qOnpTcDsUVy1Ci9im1Md8thWipSWbE+WM9ojZAx528oAql+Cv8M8N+6ALKwgVRhPZkto6E59ARw=="], + + "@solana/codecs-strings/@solana/errors": ["@solana/errors@2.0.0-preview.2", "", { "dependencies": { "chalk": "^5.3.0", "commander": "^12.0.0" }, "bin": { "errors": "bin/cli.js" } }, "sha512-H2DZ1l3iYF5Rp5pPbJpmmtCauWeQXRJapkDg8epQ8BJ7cA2Ut/QEtC3CMmw/iMTcuS6uemFNLcWvlOfoQhvQuA=="], + + "@solana/errors/commander": ["commander@13.1.0", "", {}, "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw=="], + + "@solana/options/@solana/codecs-core": ["@solana/codecs-core@2.0.0-preview.2", "", { "dependencies": { "@solana/errors": "2.0.0-preview.2" } }, "sha512-gLhCJXieSCrAU7acUJjbXl+IbGnqovvxQLlimztPoGgfLQ1wFYu+XJswrEVQqknZYK1pgxpxH3rZ+OKFs0ndQg=="], + + "@solana/options/@solana/codecs-numbers": ["@solana/codecs-numbers@2.0.0-preview.2", "", { "dependencies": { "@solana/codecs-core": "2.0.0-preview.2", "@solana/errors": "2.0.0-preview.2" } }, "sha512-aLZnDTf43z4qOnpTcDsUVy1Ci9im1Md8thWipSWbE+WM9ojZAx528oAql+Cv8M8N+6ALKwgVRhPZkto6E59ARw=="], + + "@solana/spl-token-metadata/@solana/codecs": ["@solana/codecs@2.0.0-rc.1", "", { "dependencies": { "@solana/codecs-core": "2.0.0-rc.1", "@solana/codecs-data-structures": "2.0.0-rc.1", "@solana/codecs-numbers": "2.0.0-rc.1", "@solana/codecs-strings": "2.0.0-rc.1", "@solana/options": "2.0.0-rc.1" }, "peerDependencies": { "typescript": ">=5" } }, "sha512-qxoR7VybNJixV51L0G1RD2boZTcxmwUWnKCaJJExQ5qNKwbpSyDdWfFJfM5JhGyKe9DnPVOZB+JHWXnpbZBqrQ=="], + + "@solana/web3.js/@noble/curves": ["@noble/curves@1.8.2", "", { "dependencies": { "@noble/hashes": "1.7.2" } }, "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g=="], + + "@solana/web3.js/borsh": ["borsh@0.7.0", "", { "dependencies": { "bn.js": "^5.2.0", "bs58": "^4.0.0", "text-encoding-utf-8": "^1.0.2" } }, "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA=="], + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], "ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], @@ -2075,6 +2205,16 @@ "gradient-string/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "jayson/@types/node": ["@types/node@12.20.55", "", {}, "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="], + + "jayson/@types/ws": ["@types/ws@7.4.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww=="], + + "jayson/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + + "jayson/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + + "jayson/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], + "lint-staged/commander": ["commander@13.1.0", "", {}, "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw=="], "listr2/eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], @@ -2101,6 +2241,8 @@ "ora/string-width": ["string-width@6.1.0", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^10.2.1", "strip-ansi": "^7.0.1" } }, "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ=="], + "ox/@noble/curves": ["@noble/curves@1.8.2", "", { "dependencies": { "@noble/hashes": "1.7.2" } }, "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g=="], + "ox/eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], @@ -2111,6 +2253,8 @@ "prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], + "proxy-addr/ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], + "rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], "restore-cursor/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], @@ -2119,6 +2263,10 @@ "router/path-to-regexp": ["path-to-regexp@8.2.0", "", {}, "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ=="], + "rpc-websockets/eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], + + "rpc-websockets/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + "send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "send/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="], @@ -2151,6 +2299,8 @@ "task-master-ai/ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="], + "viem/@noble/curves": ["@noble/curves@1.8.2", "", { "dependencies": { "@noble/hashes": "1.7.2" } }, "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g=="], + "widest-line/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], "wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], @@ -2161,6 +2311,8 @@ "@anthropic-ai/sdk/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + "@bonfida/sns-records/bs58/base-x": ["base-x@4.0.1", "", {}, "sha512-uAZ8x6r6S3aUM9rbHGVOIsR15U/ZSc82b3ymnCPsT45Gk1DDvhDPdIgB5MrhirZWt+5K0EEPQH985kNqZgNPFw=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], @@ -2305,6 +2457,24 @@ "@radix-ui/react-tooltip/@radix-ui/react-popper/@radix-ui/rect": ["@radix-ui/rect@1.1.0", "", {}, "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg=="], + "@solana/codecs/@solana/codecs-core/@solana/errors": ["@solana/errors@2.0.0-preview.2", "", { "dependencies": { "chalk": "^5.3.0", "commander": "^12.0.0" }, "bin": { "errors": "bin/cli.js" } }, "sha512-H2DZ1l3iYF5Rp5pPbJpmmtCauWeQXRJapkDg8epQ8BJ7cA2Ut/QEtC3CMmw/iMTcuS6uemFNLcWvlOfoQhvQuA=="], + + "@solana/codecs/@solana/codecs-numbers/@solana/errors": ["@solana/errors@2.0.0-preview.2", "", { "dependencies": { "chalk": "^5.3.0", "commander": "^12.0.0" }, "bin": { "errors": "bin/cli.js" } }, "sha512-H2DZ1l3iYF5Rp5pPbJpmmtCauWeQXRJapkDg8epQ8BJ7cA2Ut/QEtC3CMmw/iMTcuS6uemFNLcWvlOfoQhvQuA=="], + + "@solana/options/@solana/codecs-core/@solana/errors": ["@solana/errors@2.0.0-preview.2", "", { "dependencies": { "chalk": "^5.3.0", "commander": "^12.0.0" }, "bin": { "errors": "bin/cli.js" } }, "sha512-H2DZ1l3iYF5Rp5pPbJpmmtCauWeQXRJapkDg8epQ8BJ7cA2Ut/QEtC3CMmw/iMTcuS6uemFNLcWvlOfoQhvQuA=="], + + "@solana/options/@solana/codecs-numbers/@solana/errors": ["@solana/errors@2.0.0-preview.2", "", { "dependencies": { "chalk": "^5.3.0", "commander": "^12.0.0" }, "bin": { "errors": "bin/cli.js" } }, "sha512-H2DZ1l3iYF5Rp5pPbJpmmtCauWeQXRJapkDg8epQ8BJ7cA2Ut/QEtC3CMmw/iMTcuS6uemFNLcWvlOfoQhvQuA=="], + + "@solana/spl-token-metadata/@solana/codecs/@solana/codecs-core": ["@solana/codecs-core@2.0.0-rc.1", "", { "dependencies": { "@solana/errors": "2.0.0-rc.1" }, "peerDependencies": { "typescript": ">=5" } }, "sha512-bauxqMfSs8EHD0JKESaNmNuNvkvHSuN3bbWAF5RjOfDu2PugxHrvRebmYauvSumZ3cTfQ4HJJX6PG5rN852qyQ=="], + + "@solana/spl-token-metadata/@solana/codecs/@solana/codecs-data-structures": ["@solana/codecs-data-structures@2.0.0-rc.1", "", { "dependencies": { "@solana/codecs-core": "2.0.0-rc.1", "@solana/codecs-numbers": "2.0.0-rc.1", "@solana/errors": "2.0.0-rc.1" }, "peerDependencies": { "typescript": ">=5" } }, "sha512-rinCv0RrAVJ9rE/rmaibWJQxMwC5lSaORSZuwjopSUE6T0nb/MVg6Z1siNCXhh/HFTOg0l8bNvZHgBcN/yvXog=="], + + "@solana/spl-token-metadata/@solana/codecs/@solana/codecs-numbers": ["@solana/codecs-numbers@2.0.0-rc.1", "", { "dependencies": { "@solana/codecs-core": "2.0.0-rc.1", "@solana/errors": "2.0.0-rc.1" }, "peerDependencies": { "typescript": ">=5" } }, "sha512-J5i5mOkvukXn8E3Z7sGIPxsThRCgSdgTWJDQeZvucQ9PT6Y3HiVXJ0pcWiOWAoQ3RX8e/f4I3IC+wE6pZiJzDQ=="], + + "@solana/spl-token-metadata/@solana/codecs/@solana/codecs-strings": ["@solana/codecs-strings@2.0.0-rc.1", "", { "dependencies": { "@solana/codecs-core": "2.0.0-rc.1", "@solana/codecs-numbers": "2.0.0-rc.1", "@solana/errors": "2.0.0-rc.1" }, "peerDependencies": { "fastestsmallesttextencoderdecoder": "^1.0.22", "typescript": ">=5" } }, "sha512-9/wPhw8TbGRTt6mHC4Zz1RqOnuPTqq1Nb4EyuvpZ39GW6O2t2Q7Q0XxiB3+BdoEjwA2XgPw6e2iRfvYgqty44g=="], + + "@solana/spl-token-metadata/@solana/codecs/@solana/options": ["@solana/options@2.0.0-rc.1", "", { "dependencies": { "@solana/codecs-core": "2.0.0-rc.1", "@solana/codecs-data-structures": "2.0.0-rc.1", "@solana/codecs-numbers": "2.0.0-rc.1", "@solana/codecs-strings": "2.0.0-rc.1", "@solana/errors": "2.0.0-rc.1" }, "peerDependencies": { "typescript": ">=5" } }, "sha512-mLUcR9mZ3qfHlmMnREdIFPf9dpMc/Bl66tLSOOWxw4ml5xMT2ohFn7WGqoKcu/UHkT9CrC6+amEdqCNvUqI7AA=="], + "body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "boxen/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], @@ -2337,6 +2507,8 @@ "finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "jayson/@types/ws/@types/node": ["@types/node@22.10.5", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ=="], + "listr2/wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], "listr2/wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], @@ -2421,6 +2593,16 @@ "@radix-ui/react-progress/@radix-ui/react-primitive/@radix-ui/react-slot/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], + "@solana/spl-token-metadata/@solana/codecs/@solana/codecs-core/@solana/errors": ["@solana/errors@2.0.0-rc.1", "", { "dependencies": { "chalk": "^5.3.0", "commander": "^12.1.0" }, "peerDependencies": { "typescript": ">=5" }, "bin": { "errors": "bin/cli.mjs" } }, "sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ=="], + + "@solana/spl-token-metadata/@solana/codecs/@solana/codecs-data-structures/@solana/errors": ["@solana/errors@2.0.0-rc.1", "", { "dependencies": { "chalk": "^5.3.0", "commander": "^12.1.0" }, "peerDependencies": { "typescript": ">=5" }, "bin": { "errors": "bin/cli.mjs" } }, "sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ=="], + + "@solana/spl-token-metadata/@solana/codecs/@solana/codecs-numbers/@solana/errors": ["@solana/errors@2.0.0-rc.1", "", { "dependencies": { "chalk": "^5.3.0", "commander": "^12.1.0" }, "peerDependencies": { "typescript": ">=5" }, "bin": { "errors": "bin/cli.mjs" } }, "sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ=="], + + "@solana/spl-token-metadata/@solana/codecs/@solana/codecs-strings/@solana/errors": ["@solana/errors@2.0.0-rc.1", "", { "dependencies": { "chalk": "^5.3.0", "commander": "^12.1.0" }, "peerDependencies": { "typescript": ">=5" }, "bin": { "errors": "bin/cli.mjs" } }, "sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ=="], + + "@solana/spl-token-metadata/@solana/codecs/@solana/options/@solana/errors": ["@solana/errors@2.0.0-rc.1", "", { "dependencies": { "chalk": "^5.3.0", "commander": "^12.1.0" }, "peerDependencies": { "typescript": ">=5" }, "bin": { "errors": "bin/cli.mjs" } }, "sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ=="], + "fastmcp/execa/npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], "listr2/wrap-ansi/string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="], diff --git a/package.json b/package.json index af440a8a1..0503af1d1 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ }, "dependencies": { "@anthropic-ai/sdk": "^0.39.0", + "@bonfida/spl-name-service": "^3.0.11", "@commander-js/extra-typings": "^12.0.0", "@date-fns/utc": "^2.1.0", "@radix-ui/react-accordion": "^1.2.2", @@ -39,6 +40,7 @@ "@radix-ui/react-tabs": "^1.1.2", "@radix-ui/react-toast": "^1.2.13", "@radix-ui/react-tooltip": "^1.1.6", + "@solana/web3.js": "^1.98.2", "@tanstack/react-virtual": "^3.11.2", "@types/minimatch": "^5.1.2", "axios": "^1.8.4", diff --git a/src/app/profile/edit/components/WalletLinkForm.tsx b/src/app/profile/edit/components/WalletLinkForm.tsx index 7c1466bdd..d231b2f47 100644 --- a/src/app/profile/edit/components/WalletLinkForm.tsx +++ b/src/app/profile/edit/components/WalletLinkForm.tsx @@ -9,6 +9,7 @@ import { isAddress as isEvmAddress } from "viem"; // For ETH address validation import { normalize } from "viem/ens"; import { viemClient } from "@/lib/walletLinking/viem"; import { LinkedWallet } from "@/lib/walletLinking/readmeUtils"; +import { resolveSolDomain } from "@/lib/walletLinking/sns"; interface WalletLinkFormProps { wallets: LinkedWallet[]; @@ -24,6 +25,10 @@ const SOL_ADDRESS_REGEX = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/; // Matches names that end with .eth and contain valid characters const ENS_NAME_REGEX = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.eth$/; +// SNS name regex (name.sol format) +// Matches names that end with .sol and contain valid characters +const SNS_NAME_REGEX = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.sol$/; + export function WalletLinkForm({ wallets = [], onSubmit, @@ -80,11 +85,15 @@ export function WalletLinkForm({ if (solAddress === "") { setIsSolValid(true); setSolAddressError(""); - } else { - const isValid = SOL_ADDRESS_REGEX.test(solAddress); - setIsSolValid(isValid); - setSolAddressError(isValid ? "" : "Invalid Solana address format."); + return; } + + const isSOLValid = SOL_ADDRESS_REGEX.test(solAddress); + const isSNSValid = SNS_NAME_REGEX.test(solAddress); + setIsSolValid(isSNSValid || isSOLValid); + setSolAddressError( + isSNSValid || isSOLValid ? "" : "Invalid Solana address or SNS name.", + ); }, [solAddress]); const handleSubmit = async (e: FormEvent) => { @@ -118,9 +127,21 @@ export function WalletLinkForm({ } if (solAddress) { + const isSNSValid = SNS_NAME_REGEX.test(solAddress); + const address = isSNSValid + ? await resolveSolDomain(solAddress) + : solAddress; + + // If the address is not found, set the error and return + if (!address) { + setSolAddressError("Invalid Solana address or SNS name."); + return; + } + updatedWallets.push({ chain: "solana", - address: solAddress, + address, + ...(isSNSValid && { snsName: solAddress }), }); } diff --git a/src/lib/walletLinking/readmeUtils.ts b/src/lib/walletLinking/readmeUtils.ts index eae801fb6..d648c94c9 100644 --- a/src/lib/walletLinking/readmeUtils.ts +++ b/src/lib/walletLinking/readmeUtils.ts @@ -8,6 +8,7 @@ export const LinkedWalletSchema = z.object({ chain: z.string().min(1).toLowerCase(), address: z.string().min(1), ensName: z.string().min(1).optional(), + snsName: z.string().min(1).optional(), signature: z.string().min(1).optional(), }); @@ -140,6 +141,7 @@ export function generateReadmeWalletSection(wallets: LinkedWallet[]): string { chain: wallet.chain.toLowerCase().trim(), address: wallet.address.trim(), ...(wallet.ensName ? { ensName: wallet.ensName.trim() } : {}), + ...(wallet.snsName ? { snsName: wallet.snsName.trim() } : {}), ...(wallet.signature ? { signature: wallet.signature.trim() } : {}), })), }; diff --git a/src/lib/walletLinking/sns.ts b/src/lib/walletLinking/sns.ts new file mode 100644 index 000000000..4e80018d1 --- /dev/null +++ b/src/lib/walletLinking/sns.ts @@ -0,0 +1,52 @@ +import { Connection } from "@solana/web3.js"; +import { getDomainKeySync, NameRegistryState } from "@bonfida/spl-name-service"; + +// Array of RPC endpoints to try in order +const RPC_ENDPOINTS = [ + "https://api.mainnet-beta.solana.com", // Public Solana RPC + "https://solana-mainnet.g.alchemy.com/v2/lqe31XHZcBd-8FsgmYnHJ", // Alchemy endpoint, domain restricted to https://elizaos.github.io + process.env.NEXT_PUBLIC_SOLANA_RPC_URL || "", // Local/custom endpoint from environment +]; + +/** + * Gets the first working RPC endpoint by testing connectivity + * @returns Promise The first working RPC endpoint URL + * @throws Will throw an error if no endpoints are accessible + */ +async function getWorkingRpcEndpoint(): Promise { + for (const endpoint of RPC_ENDPOINTS) { + try { + const connection = new Connection(endpoint, "confirmed"); + await connection.getLatestBlockhash(); // Test connectivity + console.log(`Using RPC endpoint: ${endpoint}`); + return endpoint; + } catch (error) { + console.warn(`Failed to connect to RPC endpoint ${endpoint}:`, error); + continue; // Try next endpoint + } + } + + throw new Error("No working RPC endpoints available"); +} + +/** + * Resolves a Solana Name Service (SNS) domain to its owner's public key. + * @param domain The SNS domain name to resolve (e.g., "alice.sol") + * @returns A Promise that resolves to the PublicKey of the domain owner + * @throws Will throw an error if the domain doesn't exist or if there's a network issue + */ +export async function resolveSolDomain(domain: string) { + try { + const workingEndpoint = await getWorkingRpcEndpoint(); + const connection = new Connection(workingEndpoint); + + const { pubkey } = getDomainKeySync(domain); + + const { registry } = await NameRegistryState.retrieve(connection, pubkey); + const owner = registry.owner?.toString(); + return owner; + } catch (error) { + console.error(`Failed to resolve SNS domain ${domain}:`, error); + return null; + } +} From 2ba7935a109669236ef64457589583a4c0598cc4 Mon Sep 17 00:00:00 2001 From: Dango Date: Thu, 5 Jun 2025 14:25:06 -0700 Subject: [PATCH 3/9] update form SNS placeholder --- src/app/profile/edit/components/WalletLinkForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/profile/edit/components/WalletLinkForm.tsx b/src/app/profile/edit/components/WalletLinkForm.tsx index d231b2f47..b39101124 100644 --- a/src/app/profile/edit/components/WalletLinkForm.tsx +++ b/src/app/profile/edit/components/WalletLinkForm.tsx @@ -185,7 +185,7 @@ export function WalletLinkForm({ type="text" value={solAddress} onChange={(e) => setSolAddress(e.target.value)} - placeholder="Your Solana address (e.g., So1...)" + placeholder="Your Solana address (e.g., So1...) or SNS name (e.g., example.sol)" disabled={isProcessing} className={solAddressError ? "border-destructive" : ""} /> From 80f6353870e3b97242ec059c51660d62db642dd0 Mon Sep 17 00:00:00 2001 From: Dango Date: Tue, 24 Jun 2025 11:23:21 -0700 Subject: [PATCH 4/9] refactor dynamic import for ens & sns --- .../edit/components/WalletLinkForm.tsx | 67 ++++++++++++------- src/lib/walletLinking/sns.ts | 19 ++++-- src/lib/walletLinking/viem.ts | 31 +++++++-- 3 files changed, 79 insertions(+), 38 deletions(-) diff --git a/src/app/profile/edit/components/WalletLinkForm.tsx b/src/app/profile/edit/components/WalletLinkForm.tsx index b39101124..6ed0dba6d 100644 --- a/src/app/profile/edit/components/WalletLinkForm.tsx +++ b/src/app/profile/edit/components/WalletLinkForm.tsx @@ -5,9 +5,11 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; import { Loader2, Info, Shield, ArrowRight } from "lucide-react"; -import { isAddress as isEvmAddress } from "viem"; // For ETH address validation -import { normalize } from "viem/ens"; -import { viemClient } from "@/lib/walletLinking/viem"; +import { + getViemClient, + isAddress as isEvmAddressAsync, + normalizeEns, +} from "@/lib/walletLinking/viem"; import { LinkedWallet } from "@/lib/walletLinking/readmeUtils"; import { resolveSolDomain } from "@/lib/walletLinking/sns"; @@ -51,19 +53,26 @@ export function WalletLinkForm({ setEthAddress(ethWallet?.address || ""); setSolAddress(solWallet?.address || ""); - if (!ethWallet?.address || isEvmAddress(ethWallet.address)) { - setIsEthValid(true); - setEthAddressError(""); - } else { - setIsEthValid(false); - } + // Validate initial addresses asynchronously + const validateInitialAddresses = async () => { + if (ethWallet?.address) { + const isValid = await isEvmAddressAsync(ethWallet.address); + setIsEthValid(isValid); + if (!isValid) { + setEthAddressError("Invalid Ethereum address"); + } + } - if (!solWallet?.address || SOL_ADDRESS_REGEX.test(solWallet.address)) { - setIsSolValid(true); - setSolAddressError(""); - } else { - setIsSolValid(false); - } + if (solWallet?.address) { + const isValid = SOL_ADDRESS_REGEX.test(solWallet.address); + setIsSolValid(isValid); + if (!isValid) { + setSolAddressError("Invalid Solana address"); + } + } + }; + + validateInitialAddresses(); }, [wallets]); useEffect(() => { @@ -73,12 +82,16 @@ export function WalletLinkForm({ return; } - const isEVMValid = isEvmAddress(ethAddress); - const isENSValid = ENS_NAME_REGEX.test(ethAddress); - setIsEthValid(isEVMValid || isENSValid); - setEthAddressError( - isEVMValid || isENSValid ? "" : "Invalid Ethereum address or ENS name.", - ); + const validateEthAddress = async () => { + const isEVMValid = await isEvmAddressAsync(ethAddress); + const isENSValid = ENS_NAME_REGEX.test(ethAddress); + setIsEthValid(isEVMValid || isENSValid); + setEthAddressError( + isEVMValid || isENSValid ? "" : "Invalid Ethereum address or ENS name.", + ); + }; + + validateEthAddress(); }, [ethAddress]); useEffect(() => { @@ -107,11 +120,13 @@ export function WalletLinkForm({ if (ethAddress) { const isENSValid = ENS_NAME_REGEX.test(ethAddress); - const address = isENSValid - ? await viemClient.getEnsAddress({ - name: normalize(ethAddress), - }) - : ethAddress; + let address: string | null = ethAddress; + + if (isENSValid) { + const viemClient = await getViemClient(); + const normalizedName = await normalizeEns(ethAddress); + address = await viemClient.getEnsAddress({ name: normalizedName }); + } // If the address is not found, set the error and return if (!address) { diff --git a/src/lib/walletLinking/sns.ts b/src/lib/walletLinking/sns.ts index 4e80018d1..88849b60f 100644 --- a/src/lib/walletLinking/sns.ts +++ b/src/lib/walletLinking/sns.ts @@ -1,6 +1,3 @@ -import { Connection } from "@solana/web3.js"; -import { getDomainKeySync, NameRegistryState } from "@bonfida/spl-name-service"; - // Array of RPC endpoints to try in order const RPC_ENDPOINTS = [ "https://api.mainnet-beta.solana.com", // Public Solana RPC @@ -10,10 +7,13 @@ const RPC_ENDPOINTS = [ /** * Gets the first working RPC endpoint by testing connectivity + * @param Connection - The Solana Connection class from dynamic import * @returns Promise The first working RPC endpoint URL * @throws Will throw an error if no endpoints are accessible */ -async function getWorkingRpcEndpoint(): Promise { +async function getWorkingRpcEndpoint( + Connection: typeof import("@solana/web3.js").Connection, +): Promise { for (const endpoint of RPC_ENDPOINTS) { try { const connection = new Connection(endpoint, "confirmed"); @@ -35,9 +35,16 @@ async function getWorkingRpcEndpoint(): Promise { * @returns A Promise that resolves to the PublicKey of the domain owner * @throws Will throw an error if the domain doesn't exist or if there's a network issue */ -export async function resolveSolDomain(domain: string) { +export async function resolveSolDomain(domain: string): Promise { try { - const workingEndpoint = await getWorkingRpcEndpoint(); + // Import all Solana dependencies dynamically + const [{ Connection }, { getDomainKeySync, NameRegistryState }] = + await Promise.all([ + import("@solana/web3.js"), + import("@bonfida/spl-name-service"), + ]); + + const workingEndpoint = await getWorkingRpcEndpoint(Connection); const connection = new Connection(workingEndpoint); const { pubkey } = getDomainKeySync(domain); diff --git a/src/lib/walletLinking/viem.ts b/src/lib/walletLinking/viem.ts index 6a18846bd..cb6e11bc3 100644 --- a/src/lib/walletLinking/viem.ts +++ b/src/lib/walletLinking/viem.ts @@ -1,7 +1,26 @@ -import { createPublicClient, http } from "viem"; -import { mainnet } from "viem/chains"; +import type { PublicClient } from "viem"; -export const viemClient = createPublicClient({ - chain: mainnet, - transport: http(), -}); +let viemClient: PublicClient | null = null; + +export async function getViemClient(): Promise { + if (!viemClient) { + const { createPublicClient, http } = await import("viem"); + const { mainnet } = await import("viem/chains"); + + viemClient = createPublicClient({ + chain: mainnet, + transport: http(), + }); + } + return viemClient; +} + +export async function isAddress(address: string): Promise { + const { isAddress } = await import("viem"); + return isAddress(address); +} + +export async function normalizeEns(name: string): Promise { + const { normalize } = await import("viem/ens"); + return normalize(name); +} From 5fbe0572762cc76cfd6bdcfc1532a78a679d4a05 Mon Sep 17 00:00:00 2001 From: Dango Date: Wed, 25 Jun 2025 21:34:43 -0700 Subject: [PATCH 5/9] refactor domain resolution & optimize validateAddress --- .../edit/components/WalletLinkForm.tsx | 65 ++++++++----------- .../profile/edit/hooks/useProfileWallets.ts | 3 +- .../pipelines/ingest/fetchWalletAddresses.ts | 2 +- src/lib/walletLinking/chainUtils.ts | 49 ++++++++++++-- src/lib/walletLinking/{sns.ts => domain.ts} | 50 ++++++++++++-- .../fetchWalletDataFromGithub.ts | 4 +- src/lib/walletLinking/readmeUtils.ts | 24 +++++-- src/lib/walletLinking/viem.ts | 26 -------- 8 files changed, 139 insertions(+), 84 deletions(-) rename src/lib/walletLinking/{sns.ts => domain.ts} (54%) delete mode 100644 src/lib/walletLinking/viem.ts diff --git a/src/app/profile/edit/components/WalletLinkForm.tsx b/src/app/profile/edit/components/WalletLinkForm.tsx index 6ed0dba6d..9a34fe41b 100644 --- a/src/app/profile/edit/components/WalletLinkForm.tsx +++ b/src/app/profile/edit/components/WalletLinkForm.tsx @@ -5,13 +5,14 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; import { Loader2, Info, Shield, ArrowRight } from "lucide-react"; -import { - getViemClient, - isAddress as isEvmAddressAsync, - normalizeEns, -} from "@/lib/walletLinking/viem"; import { LinkedWallet } from "@/lib/walletLinking/readmeUtils"; -import { resolveSolDomain } from "@/lib/walletLinking/sns"; +import { validateAddress } from "@/lib/walletLinking/chainUtils"; +import { + resolveSnsDomain, + resolveEnsDomain, + validateEnsFormat, + validateSnsFormat, +} from "@/lib/walletLinking/domain"; interface WalletLinkFormProps { wallets: LinkedWallet[]; @@ -19,18 +20,6 @@ interface WalletLinkFormProps { isProcessing: boolean; } -// Basic regex for Solana address (Base58, 32-44 chars) -// For more robust validation, consider @solana/web3.js PublicKey.isOnCurve or similar -const SOL_ADDRESS_REGEX = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/; - -// ENS name regex (name.eth format) -// Matches names that end with .eth and contain valid characters -const ENS_NAME_REGEX = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.eth$/; - -// SNS name regex (name.sol format) -// Matches names that end with .sol and contain valid characters -const SNS_NAME_REGEX = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.sol$/; - export function WalletLinkForm({ wallets = [], onSubmit, @@ -56,7 +45,7 @@ export function WalletLinkForm({ // Validate initial addresses asynchronously const validateInitialAddresses = async () => { if (ethWallet?.address) { - const isValid = await isEvmAddressAsync(ethWallet.address); + const isValid = await validateAddress(ethWallet.address, "ethereum"); setIsEthValid(isValid); if (!isValid) { setEthAddressError("Invalid Ethereum address"); @@ -64,7 +53,7 @@ export function WalletLinkForm({ } if (solWallet?.address) { - const isValid = SOL_ADDRESS_REGEX.test(solWallet.address); + const isValid = await validateAddress(solWallet.address, "solana"); setIsSolValid(isValid); if (!isValid) { setSolAddressError("Invalid Solana address"); @@ -83,8 +72,8 @@ export function WalletLinkForm({ } const validateEthAddress = async () => { - const isEVMValid = await isEvmAddressAsync(ethAddress); - const isENSValid = ENS_NAME_REGEX.test(ethAddress); + const isEVMValid = await validateAddress(ethAddress, "ethereum"); + const isENSValid = validateEnsFormat(ethAddress); setIsEthValid(isEVMValid || isENSValid); setEthAddressError( isEVMValid || isENSValid ? "" : "Invalid Ethereum address or ENS name.", @@ -101,12 +90,16 @@ export function WalletLinkForm({ return; } - const isSOLValid = SOL_ADDRESS_REGEX.test(solAddress); - const isSNSValid = SNS_NAME_REGEX.test(solAddress); - setIsSolValid(isSNSValid || isSOLValid); - setSolAddressError( - isSNSValid || isSOLValid ? "" : "Invalid Solana address or SNS name.", - ); + const validateSolAddress = async () => { + const isSOLValid = await validateAddress(solAddress, "solana"); + const isSNSValid = validateSnsFormat(solAddress); + setIsSolValid(isSNSValid || isSOLValid); + setSolAddressError( + isSNSValid || isSOLValid ? "" : "Invalid Solana address or SNS name.", + ); + }; + + validateSolAddress(); }, [solAddress]); const handleSubmit = async (e: FormEvent) => { @@ -119,14 +112,10 @@ export function WalletLinkForm({ const updatedWallets: LinkedWallet[] = []; if (ethAddress) { - const isENSValid = ENS_NAME_REGEX.test(ethAddress); - let address: string | null = ethAddress; - - if (isENSValid) { - const viemClient = await getViemClient(); - const normalizedName = await normalizeEns(ethAddress); - address = await viemClient.getEnsAddress({ name: normalizedName }); - } + const isENSValid = validateEnsFormat(ethAddress); + const address = isENSValid + ? await resolveEnsDomain(ethAddress) + : ethAddress; // If the address is not found, set the error and return if (!address) { @@ -142,9 +131,9 @@ export function WalletLinkForm({ } if (solAddress) { - const isSNSValid = SNS_NAME_REGEX.test(solAddress); + const isSNSValid = validateSnsFormat(solAddress); const address = isSNSValid - ? await resolveSolDomain(solAddress) + ? await resolveSnsDomain(solAddress) : solAddress; // If the address is not found, set the error and return diff --git a/src/app/profile/edit/hooks/useProfileWallets.ts b/src/app/profile/edit/hooks/useProfileWallets.ts index 5323284fa..b347edcb5 100644 --- a/src/app/profile/edit/hooks/useProfileWallets.ts +++ b/src/app/profile/edit/hooks/useProfileWallets.ts @@ -60,7 +60,8 @@ export function useProfileWallets() { setReadmeContent(decodedReadmeText); // parse Readme content for Wallet data - const walletData = parseWalletLinkingDataFromReadme(decodedReadmeText); + const walletData = + await parseWalletLinkingDataFromReadme(decodedReadmeText); setWalletData(walletData); } catch (err: unknown) { console.error("Error in fetchProfileData:", err); diff --git a/src/lib/pipelines/ingest/fetchWalletAddresses.ts b/src/lib/pipelines/ingest/fetchWalletAddresses.ts index cd0f70780..713748f2c 100644 --- a/src/lib/pipelines/ingest/fetchWalletAddresses.ts +++ b/src/lib/pipelines/ingest/fetchWalletAddresses.ts @@ -111,7 +111,7 @@ const ingestWalletAddresses = createStep( for (const wallet of freshWalletData.wallets) { if ( !SUPPORTED_CHAINS_NAMES.includes(wallet.chain.toLowerCase()) || - !validateAddress(wallet.address, wallet.chain) + !(await validateAddress(wallet.address, wallet.chain)) ) { logger?.warn( `Skipping invalid wallet for ${username}: ${wallet.address} on chain ${wallet.chain}`, diff --git a/src/lib/walletLinking/chainUtils.ts b/src/lib/walletLinking/chainUtils.ts index 086bc408d..1f75ea925 100644 --- a/src/lib/walletLinking/chainUtils.ts +++ b/src/lib/walletLinking/chainUtils.ts @@ -1,10 +1,13 @@ import EthereumIcon from "@/components/icons/EthereumIcon"; import SolanaIcon from "@/components/icons/SolanaIcon"; -import { isAddress } from "viem"; + +// Module-level caches for imported validators to optimize repeated calls +let viemValidator: ((address: string) => boolean) | null = null; +let solanaValidator: ((address: string) => boolean) | null = null; interface ChainConfig { chainId: string; - validator: (address: string) => boolean; + validator: (address: string) => Promise; icon: React.ElementType; } @@ -21,13 +24,44 @@ interface ChainConfig { export const SUPPORTED_CHAINS: Record = { ethereum: { chainId: "eip155:1", - validator: (address: string) => isAddress(address), + validator: async (address: string) => { + // Use cached validator if available, otherwise import and cache + if (!viemValidator) { + const { isAddress } = await import("viem"); + viemValidator = isAddress; + } + + try { + return viemValidator(address); + } catch (error) { + console.error(`Failed to validate Ethereum address ${address}:`, error); + return false; + } + }, icon: EthereumIcon, }, solana: { chainId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", - validator: (address: string) => - /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address), + validator: async (address: string) => { + // Use cached validator if available, otherwise import and cache + if (!solanaValidator) { + const { PublicKey } = await import("@solana/web3.js"); + solanaValidator = (addr: string) => { + try { + return PublicKey.isOnCurve(new PublicKey(addr).toBytes()); + } catch { + return false; + } + }; + } + + try { + return solanaValidator(address); + } catch (error) { + console.error(`Failed to validate Solana address ${address}:`, error); + return false; + } + }, icon: SolanaIcon, }, }; @@ -79,7 +113,10 @@ export function createAccountId(chainId: string, address: string): string { * @param chain The blockchain name (e.g., "ethereum", "solana") * @returns True if the address is valid for the chain, false otherwise */ -export function validateAddress(address: string, chain: string): boolean { +export async function validateAddress( + address: string, + chain: string, +): Promise { const chainConfig = SUPPORTED_CHAINS[chain.toLowerCase() as keyof typeof SUPPORTED_CHAINS]; if (!chainConfig) { diff --git a/src/lib/walletLinking/sns.ts b/src/lib/walletLinking/domain.ts similarity index 54% rename from src/lib/walletLinking/sns.ts rename to src/lib/walletLinking/domain.ts index 88849b60f..7e9b86eaf 100644 --- a/src/lib/walletLinking/sns.ts +++ b/src/lib/walletLinking/domain.ts @@ -1,4 +1,4 @@ -// Array of RPC endpoints to try in order +// Array of solana RPC endpoints to try in order const RPC_ENDPOINTS = [ "https://api.mainnet-beta.solana.com", // Public Solana RPC "https://solana-mainnet.g.alchemy.com/v2/lqe31XHZcBd-8FsgmYnHJ", // Alchemy endpoint, domain restricted to https://elizaos.github.io @@ -11,7 +11,7 @@ const RPC_ENDPOINTS = [ * @returns Promise The first working RPC endpoint URL * @throws Will throw an error if no endpoints are accessible */ -async function getWorkingRpcEndpoint( +async function getWorkingRpcEndpointForSolana( Connection: typeof import("@solana/web3.js").Connection, ): Promise { for (const endpoint of RPC_ENDPOINTS) { @@ -35,7 +35,7 @@ async function getWorkingRpcEndpoint( * @returns A Promise that resolves to the PublicKey of the domain owner * @throws Will throw an error if the domain doesn't exist or if there's a network issue */ -export async function resolveSolDomain(domain: string): Promise { +export async function resolveSnsDomain(domain: string): Promise { try { // Import all Solana dependencies dynamically const [{ Connection }, { getDomainKeySync, NameRegistryState }] = @@ -44,7 +44,7 @@ export async function resolveSolDomain(domain: string): Promise { import("@bonfida/spl-name-service"), ]); - const workingEndpoint = await getWorkingRpcEndpoint(Connection); + const workingEndpoint = await getWorkingRpcEndpointForSolana(Connection); const connection = new Connection(workingEndpoint); const { pubkey } = getDomainKeySync(domain); @@ -57,3 +57,45 @@ export async function resolveSolDomain(domain: string): Promise { return null; } } + +/** + * Resolves an Ethereum Name Service (ENS) domain to its owner's address. + * @param name The ENS domain name to resolve (e.g., "alice.eth") + * @returns A Promise that resolves to the Ethereum address of the domain owner + * @throws Will throw an error if the domain doesn't exist or if there's a network issue + */ +export async function resolveEnsDomain(name: string): Promise { + try { + const { createPublicClient, http } = await import("viem"); + const { normalize } = await import("viem/ens"); + const { mainnet } = await import("viem/chains"); + + const viemClient = createPublicClient({ + chain: mainnet, + transport: http(), + }); + + const normalizedName = await normalize(name); + const address = await viemClient.getEnsAddress({ name: normalizedName }); + return address; + } catch (error) { + console.error(`Failed to resolve ENS domain ${name}:`, error); + return null; + } +} + +// ENS name regex (name.eth format) +// Matches names that end with .eth and contain valid characters +const ENS_NAME_REGEX = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.eth$/; + +export function validateEnsFormat(name: string): boolean { + return ENS_NAME_REGEX.test(name); +} + +// SNS name regex (name.sol format) +// Matches names that end with .sol and contain valid characters +const SNS_NAME_REGEX = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.sol$/; + +export function validateSnsFormat(name: string): boolean { + return SNS_NAME_REGEX.test(name); +} diff --git a/src/lib/walletLinking/fetchWalletDataFromGithub.ts b/src/lib/walletLinking/fetchWalletDataFromGithub.ts index 2b2a2a4bd..cf8f8ba83 100644 --- a/src/lib/walletLinking/fetchWalletDataFromGithub.ts +++ b/src/lib/walletLinking/fetchWalletDataFromGithub.ts @@ -37,7 +37,9 @@ export async function batchFetchWalletDataFromGithub( const result = fileContents[i]; if (result.content) { - const walletData = parseWalletLinkingDataFromReadme(result.content); + const walletData = await parseWalletLinkingDataFromReadme( + result.content, + ); results[username] = { walletData, profileRepoExists: true, diff --git a/src/lib/walletLinking/readmeUtils.ts b/src/lib/walletLinking/readmeUtils.ts index d648c94c9..b155ac64a 100644 --- a/src/lib/walletLinking/readmeUtils.ts +++ b/src/lib/walletLinking/readmeUtils.ts @@ -29,9 +29,9 @@ const WALLET_SECTION_END_MARKER = "WALLET-LINKING-END -->"; * @param readmeContent The string content of the README file. * @returns The parsed and validated wallet linking data, or null if no valid data found. */ -export function parseWalletLinkingDataFromReadme( +export async function parseWalletLinkingDataFromReadme( readmeContent: string, -): WalletLinkingData | null { +): Promise { const startIndex = readmeContent.indexOf(WALLET_SECTION_BEGIN_MARKER); const endIndex = readmeContent.indexOf(WALLET_SECTION_END_MARKER); @@ -56,13 +56,23 @@ export function parseWalletLinkingDataFromReadme( } // Make sure to only return wallets for supported chains + const validWallets = []; + for (const wallet of result.data.wallets) { + const isChainSupported = SUPPORTED_CHAINS_NAMES.includes( + wallet.chain.toLowerCase(), + ); + const isAddressValid = await validateAddress( + wallet.address, + wallet.chain, + ); + if (isAddressValid && isChainSupported) { + validWallets.push(wallet); + } + } + const walletLinkingData: WalletLinkingData = { lastUpdated: result.data.lastUpdated, - wallets: result.data.wallets.filter( - (wallet) => - SUPPORTED_CHAINS_NAMES.includes(wallet.chain.toLowerCase()) && - validateAddress(wallet.address, wallet.chain), - ), + wallets: validWallets, }; return walletLinkingData; diff --git a/src/lib/walletLinking/viem.ts b/src/lib/walletLinking/viem.ts deleted file mode 100644 index cb6e11bc3..000000000 --- a/src/lib/walletLinking/viem.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { PublicClient } from "viem"; - -let viemClient: PublicClient | null = null; - -export async function getViemClient(): Promise { - if (!viemClient) { - const { createPublicClient, http } = await import("viem"); - const { mainnet } = await import("viem/chains"); - - viemClient = createPublicClient({ - chain: mainnet, - transport: http(), - }); - } - return viemClient; -} - -export async function isAddress(address: string): Promise { - const { isAddress } = await import("viem"); - return isAddress(address); -} - -export async function normalizeEns(name: string): Promise { - const { normalize } = await import("viem/ens"); - return normalize(name); -} From 09d101ff1ca38d5622c0655049cf82db808d581c Mon Sep 17 00:00:00 2001 From: Dango Date: Thu, 26 Jun 2025 21:25:02 -0700 Subject: [PATCH 6/9] refactor dynamic import from function level to component level for ProfileEditor --- .../edit/components/ProfileEditorSkeleton.tsx | 31 +++++++++++++++ .../edit/components/WalletLinkForm.tsx | 10 ++--- src/app/profile/edit/page.tsx | 25 +++++------- .../pipelines/ingest/fetchWalletAddresses.ts | 2 +- src/lib/walletLinking/chainUtils.ts | 39 ++++--------------- .../{domain.ts => domainUtils.ts} | 25 +++++------- src/lib/walletLinking/readmeUtils.ts | 20 +++------- 7 files changed, 69 insertions(+), 83 deletions(-) create mode 100644 src/app/profile/edit/components/ProfileEditorSkeleton.tsx rename src/lib/walletLinking/{domain.ts => domainUtils.ts} (81%) diff --git a/src/app/profile/edit/components/ProfileEditorSkeleton.tsx b/src/app/profile/edit/components/ProfileEditorSkeleton.tsx new file mode 100644 index 000000000..c795b31da --- /dev/null +++ b/src/app/profile/edit/components/ProfileEditorSkeleton.tsx @@ -0,0 +1,31 @@ +import { Card, CardContent, CardHeader } from "@/components/ui/card"; +import { Skeleton } from "@/components/ui/skeleton"; + +export default function ProfileEditorSkeleton() { + return ( +
+ + + + + + + +
+ + +
+
+ + +
+
+ + +
+ +
+
+
+ ); +} diff --git a/src/app/profile/edit/components/WalletLinkForm.tsx b/src/app/profile/edit/components/WalletLinkForm.tsx index 9a34fe41b..cbe9c5b14 100644 --- a/src/app/profile/edit/components/WalletLinkForm.tsx +++ b/src/app/profile/edit/components/WalletLinkForm.tsx @@ -12,7 +12,7 @@ import { resolveEnsDomain, validateEnsFormat, validateSnsFormat, -} from "@/lib/walletLinking/domain"; +} from "@/lib/walletLinking/domainUtils"; interface WalletLinkFormProps { wallets: LinkedWallet[]; @@ -45,7 +45,7 @@ export function WalletLinkForm({ // Validate initial addresses asynchronously const validateInitialAddresses = async () => { if (ethWallet?.address) { - const isValid = await validateAddress(ethWallet.address, "ethereum"); + const isValid = validateAddress(ethWallet.address, "ethereum"); setIsEthValid(isValid); if (!isValid) { setEthAddressError("Invalid Ethereum address"); @@ -53,7 +53,7 @@ export function WalletLinkForm({ } if (solWallet?.address) { - const isValid = await validateAddress(solWallet.address, "solana"); + const isValid = validateAddress(solWallet.address, "solana"); setIsSolValid(isValid); if (!isValid) { setSolAddressError("Invalid Solana address"); @@ -72,7 +72,7 @@ export function WalletLinkForm({ } const validateEthAddress = async () => { - const isEVMValid = await validateAddress(ethAddress, "ethereum"); + const isEVMValid = validateAddress(ethAddress, "ethereum"); const isENSValid = validateEnsFormat(ethAddress); setIsEthValid(isEVMValid || isENSValid); setEthAddressError( @@ -91,7 +91,7 @@ export function WalletLinkForm({ } const validateSolAddress = async () => { - const isSOLValid = await validateAddress(solAddress, "solana"); + const isSOLValid = validateAddress(solAddress, "solana"); const isSNSValid = validateSnsFormat(solAddress); setIsSolValid(isSNSValid || isSOLValid); setSolAddressError( diff --git a/src/app/profile/edit/page.tsx b/src/app/profile/edit/page.tsx index 55613d8d2..4d47825fe 100644 --- a/src/app/profile/edit/page.tsx +++ b/src/app/profile/edit/page.tsx @@ -1,18 +1,13 @@ -import { Suspense } from "react"; -import { Loader2 } from "lucide-react"; // For Suspense fallback -import ProfileEditor from "./components/ProfileEditor"; // Adjusted path +"use client"; + +import dynamic from "next/dynamic"; +import ProfileEditorSkeleton from "./components/ProfileEditorSkeleton"; + +const ProfileEditor = dynamic(() => import("./components/ProfileEditor"), { + loading: () => , + ssr: false, +}); export default function ProfileEditPage() { - return ( - - - - } - > - - - ); + return ; } diff --git a/src/lib/pipelines/ingest/fetchWalletAddresses.ts b/src/lib/pipelines/ingest/fetchWalletAddresses.ts index 713748f2c..cd0f70780 100644 --- a/src/lib/pipelines/ingest/fetchWalletAddresses.ts +++ b/src/lib/pipelines/ingest/fetchWalletAddresses.ts @@ -111,7 +111,7 @@ const ingestWalletAddresses = createStep( for (const wallet of freshWalletData.wallets) { if ( !SUPPORTED_CHAINS_NAMES.includes(wallet.chain.toLowerCase()) || - !(await validateAddress(wallet.address, wallet.chain)) + !validateAddress(wallet.address, wallet.chain) ) { logger?.warn( `Skipping invalid wallet for ${username}: ${wallet.address} on chain ${wallet.chain}`, diff --git a/src/lib/walletLinking/chainUtils.ts b/src/lib/walletLinking/chainUtils.ts index 1f75ea925..9729cde0c 100644 --- a/src/lib/walletLinking/chainUtils.ts +++ b/src/lib/walletLinking/chainUtils.ts @@ -1,13 +1,11 @@ import EthereumIcon from "@/components/icons/EthereumIcon"; import SolanaIcon from "@/components/icons/SolanaIcon"; - -// Module-level caches for imported validators to optimize repeated calls -let viemValidator: ((address: string) => boolean) | null = null; -let solanaValidator: ((address: string) => boolean) | null = null; +import { isAddress } from "viem"; +import { PublicKey } from "@solana/web3.js"; interface ChainConfig { chainId: string; - validator: (address: string) => Promise; + validator: (address: string) => boolean; icon: React.ElementType; } @@ -24,15 +22,9 @@ interface ChainConfig { export const SUPPORTED_CHAINS: Record = { ethereum: { chainId: "eip155:1", - validator: async (address: string) => { - // Use cached validator if available, otherwise import and cache - if (!viemValidator) { - const { isAddress } = await import("viem"); - viemValidator = isAddress; - } - + validator: (address: string) => { try { - return viemValidator(address); + return isAddress(address); } catch (error) { console.error(`Failed to validate Ethereum address ${address}:`, error); return false; @@ -42,21 +34,9 @@ export const SUPPORTED_CHAINS: Record = { }, solana: { chainId: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", - validator: async (address: string) => { - // Use cached validator if available, otherwise import and cache - if (!solanaValidator) { - const { PublicKey } = await import("@solana/web3.js"); - solanaValidator = (addr: string) => { - try { - return PublicKey.isOnCurve(new PublicKey(addr).toBytes()); - } catch { - return false; - } - }; - } - + validator: (address: string) => { try { - return solanaValidator(address); + return PublicKey.isOnCurve(new PublicKey(address).toBytes()); } catch (error) { console.error(`Failed to validate Solana address ${address}:`, error); return false; @@ -113,10 +93,7 @@ export function createAccountId(chainId: string, address: string): string { * @param chain The blockchain name (e.g., "ethereum", "solana") * @returns True if the address is valid for the chain, false otherwise */ -export async function validateAddress( - address: string, - chain: string, -): Promise { +export function validateAddress(address: string, chain: string): boolean { const chainConfig = SUPPORTED_CHAINS[chain.toLowerCase() as keyof typeof SUPPORTED_CHAINS]; if (!chainConfig) { diff --git a/src/lib/walletLinking/domain.ts b/src/lib/walletLinking/domainUtils.ts similarity index 81% rename from src/lib/walletLinking/domain.ts rename to src/lib/walletLinking/domainUtils.ts index 7e9b86eaf..6be857aef 100644 --- a/src/lib/walletLinking/domain.ts +++ b/src/lib/walletLinking/domainUtils.ts @@ -1,7 +1,13 @@ +import { Connection } from "@solana/web3.js"; +import { getDomainKeySync, NameRegistryState } from "@bonfida/spl-name-service"; +import { createPublicClient, http } from "viem"; +import { normalize } from "viem/ens"; +import { mainnet } from "viem/chains"; + // Array of solana RPC endpoints to try in order const RPC_ENDPOINTS = [ "https://api.mainnet-beta.solana.com", // Public Solana RPC - "https://solana-mainnet.g.alchemy.com/v2/lqe31XHZcBd-8FsgmYnHJ", // Alchemy endpoint, domain restricted to https://elizaos.github.io + process.env.NEXT_PUBLIC_ALCHEMY_SOLANA_URL || "", // Alchemy endpoint process.env.NEXT_PUBLIC_SOLANA_RPC_URL || "", // Local/custom endpoint from environment ]; @@ -11,9 +17,7 @@ const RPC_ENDPOINTS = [ * @returns Promise The first working RPC endpoint URL * @throws Will throw an error if no endpoints are accessible */ -async function getWorkingRpcEndpointForSolana( - Connection: typeof import("@solana/web3.js").Connection, -): Promise { +async function getWorkingRpcEndpointForSolana(): Promise { for (const endpoint of RPC_ENDPOINTS) { try { const connection = new Connection(endpoint, "confirmed"); @@ -37,14 +41,7 @@ async function getWorkingRpcEndpointForSolana( */ export async function resolveSnsDomain(domain: string): Promise { try { - // Import all Solana dependencies dynamically - const [{ Connection }, { getDomainKeySync, NameRegistryState }] = - await Promise.all([ - import("@solana/web3.js"), - import("@bonfida/spl-name-service"), - ]); - - const workingEndpoint = await getWorkingRpcEndpointForSolana(Connection); + const workingEndpoint = await getWorkingRpcEndpointForSolana(); const connection = new Connection(workingEndpoint); const { pubkey } = getDomainKeySync(domain); @@ -66,10 +63,6 @@ export async function resolveSnsDomain(domain: string): Promise { */ export async function resolveEnsDomain(name: string): Promise { try { - const { createPublicClient, http } = await import("viem"); - const { normalize } = await import("viem/ens"); - const { mainnet } = await import("viem/chains"); - const viemClient = createPublicClient({ chain: mainnet, transport: http(), diff --git a/src/lib/walletLinking/readmeUtils.ts b/src/lib/walletLinking/readmeUtils.ts index b155ac64a..a1b5cffcf 100644 --- a/src/lib/walletLinking/readmeUtils.ts +++ b/src/lib/walletLinking/readmeUtils.ts @@ -56,23 +56,13 @@ export async function parseWalletLinkingDataFromReadme( } // Make sure to only return wallets for supported chains - const validWallets = []; - for (const wallet of result.data.wallets) { - const isChainSupported = SUPPORTED_CHAINS_NAMES.includes( - wallet.chain.toLowerCase(), - ); - const isAddressValid = await validateAddress( - wallet.address, - wallet.chain, - ); - if (isAddressValid && isChainSupported) { - validWallets.push(wallet); - } - } - const walletLinkingData: WalletLinkingData = { lastUpdated: result.data.lastUpdated, - wallets: validWallets, + wallets: result.data.wallets.filter( + (wallet) => + SUPPORTED_CHAINS_NAMES.includes(wallet.chain.toLowerCase()) && + validateAddress(wallet.address, wallet.chain), + ), }; return walletLinkingData; From d7c8d046f5e8fe3de91decae42c7b3c0a13bc92d Mon Sep 17 00:00:00 2001 From: Dango Date: Fri, 27 Jun 2025 11:19:48 -0700 Subject: [PATCH 7/9] refactor WalletLinkForm with shadcn form --- bun.lock | 42 ++- package.json | 8 +- .../profile/edit/components/ProfileEditor.tsx | 5 +- .../edit/components/WalletLinkForm.tsx | 334 ++++++++---------- .../profile/edit/hooks/useProfileWallets.ts | 84 ++++- src/components/ui/form.tsx | 179 ++++++++++ src/lib/walletLinking/chainUtils.ts | 6 +- src/lib/walletLinking/domainUtils.ts | 4 +- 8 files changed, 461 insertions(+), 201 deletions(-) create mode 100644 src/components/ui/form.tsx diff --git a/bun.lock b/bun.lock index 10e2ccbc0..a117edacb 100644 --- a/bun.lock +++ b/bun.lock @@ -8,18 +8,19 @@ "@bonfida/spl-name-service": "^3.0.11", "@commander-js/extra-typings": "^12.0.0", "@date-fns/utc": "^2.1.0", + "@hookform/resolvers": "^5.1.1", "@radix-ui/react-accordion": "^1.2.2", "@radix-ui/react-avatar": "^1.1.2", "@radix-ui/react-checkbox": "^1.3.1", "@radix-ui/react-dialog": "^1.1.13", "@radix-ui/react-dropdown-menu": "^2.1.4", "@radix-ui/react-icons": "^1.3.2", - "@radix-ui/react-label": "^2.1.6", + "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-popover": "^1.1.13", "@radix-ui/react-progress": "^1.1.3", "@radix-ui/react-scroll-area": "^1.2.8", "@radix-ui/react-select": "^2.1.4", - "@radix-ui/react-slot": "^1.1.1", + "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tabs": "^1.1.2", "@radix-ui/react-toast": "^1.2.13", "@radix-ui/react-tooltip": "^1.1.6", @@ -51,6 +52,7 @@ "p-retry": "^6.2.1", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-hook-form": "^7.58.1", "react-markdown": "^9.0.1", "recharts": "^2.15.2", "remark-frontmatter": "^5.0.0", @@ -63,7 +65,7 @@ "unist-util-visit": "^5.0.0", "viem": "^2.29.2", "yaml": "^2.4.1", - "zod": "^3.24.1", + "zod": "^3.25.67", }, "devDependencies": { "@tailwindcss/typography": "^0.5.16", @@ -187,6 +189,8 @@ "@floating-ui/utils": ["@floating-ui/utils@0.2.9", "", {}, "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="], + "@hookform/resolvers": ["@hookform/resolvers@5.1.1", "", { "dependencies": { "@standard-schema/utils": "^0.3.0" }, "peerDependencies": { "react-hook-form": "^7.55.0" } }, "sha512-J/NVING3LMAEvexJkyTLjruSm7aOFx7QX21pzkiJfMoNG0wl5aFEjLTl7ay7IQb9EWY6AkrBy7tHL2Alijpdcg=="], + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], "@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="], @@ -347,7 +351,7 @@ "@radix-ui/react-id": ["@radix-ui/react-id@1.1.0", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA=="], - "@radix-ui/react-label": ["@radix-ui/react-label@2.1.6", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-S/hv1mTlgcPX2gCTJrWuTjSXf7ER3Zf7zWGtOprxhIIY93Qin3n5VgNA0Ez9AgrK/lEtlYgzLd4f5x6AVar4Yw=="], + "@radix-ui/react-label": ["@radix-ui/react-label@2.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ=="], "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.4", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-dismissable-layer": "1.1.3", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.1", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.1", "@radix-ui/react-portal": "1.1.3", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-roving-focus": "1.1.1", "@radix-ui/react-slot": "1.1.1", "@radix-ui/react-use-callback-ref": "1.1.0", "aria-hidden": "^1.1.1", "react-remove-scroll": "^2.6.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-BnOgVoL6YYdHAG6DtXONaR29Eq4nvbi8rutrV/xlr3RQCMMb3yqP85Qiw/3NReozrSW+4dfLkK+rc1hb4wPU/A=="], @@ -369,7 +373,7 @@ "@radix-ui/react-select": ["@radix-ui/react-select@2.1.4", "", { "dependencies": { "@radix-ui/number": "1.1.0", "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-dismissable-layer": "1.1.3", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.1", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.1", "@radix-ui/react-portal": "1.1.3", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-slot": "1.1.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-visually-hidden": "1.1.1", "aria-hidden": "^1.1.1", "react-remove-scroll": "^2.6.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-pOkb2u8KgO47j/h7AylCj7dJsm69BXcjkrvTqMptFqsE2i0p8lHkfgneXKjAgPzBMivnoMyt8o4KiV4wYzDdyQ=="], - "@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], + "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.2", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-roving-focus": "1.1.1", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-9u/tQJMcC2aGq7KXpGivMm1mgq7oRJKXphDwdypPd/j21j/2znamPU8WkXgnhUaTrSFNIt8XhOyCAupg8/GbwQ=="], @@ -441,6 +445,8 @@ "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], + "@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="], + "@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="], "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], @@ -1563,6 +1569,8 @@ "react-dom": ["react-dom@19.0.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ=="], + "react-hook-form": ["react-hook-form@7.58.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-Lml/KZYEEFfPhUVgE0RdCVpnC4yhW+PndRhbiTtdvSlQTL8IfVR+iQkBjLIvmmc6+GGoVeM11z37ktKFPAb0FA=="], + "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], "react-markdown": ["react-markdown@9.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw=="], @@ -1927,7 +1935,7 @@ "yoctocolors-cjs": ["yoctocolors-cjs@2.1.2", "", {}, "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA=="], - "zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="], + "zod": ["zod@3.25.67", "", {}, "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw=="], "zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="], @@ -1959,6 +1967,8 @@ "@modelcontextprotocol/sdk/raw-body": ["raw-body@3.0.0", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.6.3", "unpipe": "1.0.0" } }, "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g=="], + "@modelcontextprotocol/sdk/zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="], + "@next/eslint-plugin-next/fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="], "@noble/curves/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], @@ -1977,6 +1987,8 @@ "@radix-ui/react-collapsible/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg=="], + "@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], + "@radix-ui/react-dialog/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], "@radix-ui/react-dialog/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], @@ -2005,7 +2017,7 @@ "@radix-ui/react-focus-scope/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="], - "@radix-ui/react-label/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.2", "", { "dependencies": { "@radix-ui/react-slot": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw=="], + "@radix-ui/react-label/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], "@radix-ui/react-menu/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg=="], @@ -2019,6 +2031,8 @@ "@radix-ui/react-menu/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg=="], + "@radix-ui/react-menu/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], + "@radix-ui/react-menu/react-remove-scroll": ["react-remove-scroll@2.6.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.1", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw=="], "@radix-ui/react-popover/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], @@ -2053,6 +2067,8 @@ "@radix-ui/react-presence/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="], + "@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], + "@radix-ui/react-progress/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], "@radix-ui/react-progress/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Pf/t/GkndH7CQ8wE2hbkXA+WyZ83fhQQn5DDmwDiDo6AwN/fhaH8oqZ0jRjMrO2iaMhDi6P1HRx6AZwyMinY1g=="], @@ -2083,10 +2099,14 @@ "@radix-ui/react-select/@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.3", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw=="], + "@radix-ui/react-select/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], + "@radix-ui/react-select/@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og=="], "@radix-ui/react-select/react-remove-scroll": ["react-remove-scroll@2.6.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.1", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw=="], + "@radix-ui/react-slot/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], + "@radix-ui/react-tabs/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg=="], "@radix-ui/react-toast/@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], @@ -2115,6 +2135,8 @@ "@radix-ui/react-tooltip/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg=="], + "@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], + "@radix-ui/react-use-effect-event/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="], "@radix-ui/react-use-escape-keydown/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="], @@ -2199,6 +2221,8 @@ "fastmcp/execa": ["execa@9.5.2", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.3", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.0", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.0.0" } }, "sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q=="], + "fastmcp/zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="], + "figures/is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], @@ -2409,8 +2433,6 @@ "@radix-ui/react-focus-scope/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ=="], - "@radix-ui/react-label/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ=="], - "@radix-ui/react-menu/@radix-ui/react-dismissable-layer/@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.0", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw=="], "@radix-ui/react-menu/@radix-ui/react-popper/@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.1", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w=="], @@ -2587,8 +2609,6 @@ "@radix-ui/react-arrow/@radix-ui/react-primitive/@radix-ui/react-slot/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], - "@radix-ui/react-label/@radix-ui/react-primitive/@radix-ui/react-slot/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], - "@radix-ui/react-portal/@radix-ui/react-primitive/@radix-ui/react-slot/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], "@radix-ui/react-progress/@radix-ui/react-primitive/@radix-ui/react-slot/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], diff --git a/package.json b/package.json index 0503af1d1..a340fccfb 100644 --- a/package.json +++ b/package.json @@ -25,18 +25,19 @@ "@bonfida/spl-name-service": "^3.0.11", "@commander-js/extra-typings": "^12.0.0", "@date-fns/utc": "^2.1.0", + "@hookform/resolvers": "^5.1.1", "@radix-ui/react-accordion": "^1.2.2", "@radix-ui/react-avatar": "^1.1.2", "@radix-ui/react-checkbox": "^1.3.1", "@radix-ui/react-dialog": "^1.1.13", "@radix-ui/react-dropdown-menu": "^2.1.4", "@radix-ui/react-icons": "^1.3.2", - "@radix-ui/react-label": "^2.1.6", + "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-popover": "^1.1.13", "@radix-ui/react-progress": "^1.1.3", "@radix-ui/react-scroll-area": "^1.2.8", "@radix-ui/react-select": "^2.1.4", - "@radix-ui/react-slot": "^1.1.1", + "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tabs": "^1.1.2", "@radix-ui/react-toast": "^1.2.13", "@radix-ui/react-tooltip": "^1.1.6", @@ -68,6 +69,7 @@ "p-retry": "^6.2.1", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-hook-form": "^7.58.1", "react-markdown": "^9.0.1", "recharts": "^2.15.2", "remark-frontmatter": "^5.0.0", @@ -80,7 +82,7 @@ "unist-util-visit": "^5.0.0", "viem": "^2.29.2", "yaml": "^2.4.1", - "zod": "^3.24.1" + "zod": "^3.25.67" }, "devDependencies": { "@tailwindcss/typography": "^0.5.16", diff --git a/src/app/profile/edit/components/ProfileEditor.tsx b/src/app/profile/edit/components/ProfileEditor.tsx index 88b0dec0d..b56cdb610 100644 --- a/src/app/profile/edit/components/ProfileEditor.tsx +++ b/src/app/profile/edit/components/ProfileEditor.tsx @@ -22,11 +22,13 @@ export default function ProfileEditor() { profileRepoExists, walletData, pageLoading, + isProcessingWallets, error, successMessage, walletSection, readmeContent, handleCreateProfileRepo, + processWallets, handleGenerateWalletSection, defaultBranch, } = useProfileWallets(); @@ -98,8 +100,9 @@ export default function ProfileEditor() { <> )} diff --git a/src/app/profile/edit/components/WalletLinkForm.tsx b/src/app/profile/edit/components/WalletLinkForm.tsx index cbe9c5b14..96cd35891 100644 --- a/src/app/profile/edit/components/WalletLinkForm.tsx +++ b/src/app/profile/edit/components/WalletLinkForm.tsx @@ -1,228 +1,206 @@ "use client"; -import { useState, useEffect, FormEvent } from "react"; +import { useEffect } from "react"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; import { Loader2, Info, Shield, ArrowRight } from "lucide-react"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; import { LinkedWallet } from "@/lib/walletLinking/readmeUtils"; import { validateAddress } from "@/lib/walletLinking/chainUtils"; import { - resolveSnsDomain, - resolveEnsDomain, validateEnsFormat, validateSnsFormat, } from "@/lib/walletLinking/domainUtils"; +// Zod schema for form validation +const walletFormSchema = z.object({ + ethereum: z + .string() + .optional() + .refine( + (value) => { + if (!value || value.trim() === "") return true; + const isEVMValid = validateAddress(value, "ethereum"); + const isENSValid = validateEnsFormat(value); + return isEVMValid || isENSValid; + }, + { + message: "Invalid Ethereum address or ENS name.", + }, + ), + solana: z + .string() + .optional() + .refine( + (value) => { + if (!value || value.trim() === "") return true; + const isSOLValid = validateAddress(value, "solana"); + const isSNSValid = validateSnsFormat(value); + return isSOLValid || isSNSValid; + }, + { + message: "Invalid Solana address or SNS name.", + }, + ), +}); + +type WalletFormValues = z.infer; + interface WalletLinkFormProps { wallets: LinkedWallet[]; + processWallets: (values: WalletFormValues) => Promise; onSubmit: (wallets: LinkedWallet[]) => Promise; isProcessing: boolean; } export function WalletLinkForm({ wallets = [], + processWallets, onSubmit, isProcessing, }: WalletLinkFormProps) { - const [ethAddress, setEthAddress] = useState(""); - const [solAddress, setSolAddress] = useState(""); - - const [ethAddressError, setEthAddressError] = useState(""); - const [solAddressError, setSolAddressError] = useState(""); - - const [isEthValid, setIsEthValid] = useState(true); - const [isSolValid, setIsSolValid] = useState(true); + const form = useForm({ + resolver: zodResolver(walletFormSchema), + defaultValues: { + ethereum: "", + solana: "", + }, + mode: "onChange", + }); // Initialize form with existing wallet addresses useEffect(() => { const ethWallet = wallets.find((w) => w.chain === "ethereum"); const solWallet = wallets.find((w) => w.chain === "solana"); - setEthAddress(ethWallet?.address || ""); - setSolAddress(solWallet?.address || ""); - - // Validate initial addresses asynchronously - const validateInitialAddresses = async () => { - if (ethWallet?.address) { - const isValid = validateAddress(ethWallet.address, "ethereum"); - setIsEthValid(isValid); - if (!isValid) { - setEthAddressError("Invalid Ethereum address"); - } - } - - if (solWallet?.address) { - const isValid = validateAddress(solWallet.address, "solana"); - setIsSolValid(isValid); - if (!isValid) { - setSolAddressError("Invalid Solana address"); - } - } - }; - - validateInitialAddresses(); - }, [wallets]); - - useEffect(() => { - if (ethAddress === "") { - setIsEthValid(true); - setEthAddressError(""); - return; - } - - const validateEthAddress = async () => { - const isEVMValid = validateAddress(ethAddress, "ethereum"); - const isENSValid = validateEnsFormat(ethAddress); - setIsEthValid(isEVMValid || isENSValid); - setEthAddressError( - isEVMValid || isENSValid ? "" : "Invalid Ethereum address or ENS name.", - ); - }; - - validateEthAddress(); - }, [ethAddress]); - - useEffect(() => { - if (solAddress === "") { - setIsSolValid(true); - setSolAddressError(""); - return; - } - - const validateSolAddress = async () => { - const isSOLValid = validateAddress(solAddress, "solana"); - const isSNSValid = validateSnsFormat(solAddress); - setIsSolValid(isSNSValid || isSOLValid); - setSolAddressError( - isSNSValid || isSOLValid ? "" : "Invalid Solana address or SNS name.", - ); - }; - - validateSolAddress(); - }, [solAddress]); - - const handleSubmit = async (e: FormEvent) => { - e.preventDefault(); - // Double check validity before submitting - if (!isEthValid || !isSolValid) { - return; - } - - const updatedWallets: LinkedWallet[] = []; - - if (ethAddress) { - const isENSValid = validateEnsFormat(ethAddress); - const address = isENSValid - ? await resolveEnsDomain(ethAddress) - : ethAddress; - - // If the address is not found, set the error and return - if (!address) { - setEthAddressError("Invalid Ethereum address or ENS name."); - return; - } - - updatedWallets.push({ - chain: "ethereum", - address, - ...(isENSValid && { ensName: ethAddress }), - }); - } - - if (solAddress) { - const isSNSValid = validateSnsFormat(solAddress); - const address = isSNSValid - ? await resolveSnsDomain(solAddress) - : solAddress; - - // If the address is not found, set the error and return - if (!address) { - setSolAddressError("Invalid Solana address or SNS name."); + form.reset({ + ethereum: ethWallet?.address || "", + solana: solWallet?.address || "", + }); + }, [wallets, form]); + + const handleFormSubmit = async (values: WalletFormValues) => { + try { + const processedWallets = await processWallets(values); + await onSubmit(processedWallets); + } catch (error) { + // Handle specific field errors from wallet processing + if (error instanceof Error && "field" in error) { + const fieldError = error as { + field: "ethereum" | "solana"; + message: string; + }; + form.setError(fieldError.field, { message: fieldError.message }); return; } - updatedWallets.push({ - chain: "solana", - address, - ...(isSNSValid && { snsName: solAddress }), + // Handle general errors + form.setError("root", { + message: + error instanceof Error + ? error.message + : "Failed to process wallet addresses. Please try again.", }); } - - await onSubmit(updatedWallets); }; - const hasValuesChanged = - ethAddress !== - (wallets.find((w) => w.chain === "ethereum")?.address || "") || - solAddress !== (wallets.find((w) => w.chain === "solana")?.address || ""); + // Button text logic (keep separate due to complexity) const canSubmit = - isEthValid && isSolValid && !isProcessing && hasValuesChanged; - + form.formState.isValid && !isProcessing && form.formState.isDirty; const isUpdateOperation = wallets.length > 0; - const buttonTextBase = isUpdateOperation ? "Update" : "Save"; const buttonText = isProcessing - ? `${buttonTextBase === "Update" ? "Updating" : "Saving"}...` - : `${buttonTextBase} Wallet Addresses`; + ? `${isUpdateOperation ? "Updating" : "Saving"}...` + : `${isUpdateOperation ? "Update" : "Save"} Wallet Addresses`; return ( -
-
- - setEthAddress(e.target.value)} - placeholder="Your Ethereum address (e.g., 0x...) or ENS name (e.g., vitalik.eth)" - disabled={isProcessing} - className={ethAddressError ? "border-destructive" : ""} + + + ( + + Ethereum Address + + + + + + )} /> - {ethAddressError && ( -

{ethAddressError}

- )} -
-
- - setSolAddress(e.target.value)} - placeholder="Your Solana address (e.g., So1...) or SNS name (e.g., example.sol)" - disabled={isProcessing} - className={solAddressError ? "border-destructive" : ""} + + ( + + Solana Address + + + + + + )} /> - {solAddressError && ( -

{solAddressError}

+ + {form.formState.errors.root && ( +

+ {form.formState.errors.root.message} +

)} -
- -
-
- -
-

- Public addresses only: Enter - your wallet addresses to link them to your GitHub profile. -

-
- - Never share private keys or seed phrases -
-
- - Submit to generate README comment for copying + + +
+
+ +
+

+ Public addresses only:{" "} + Enter your wallet addresses to link them to your GitHub profile. +

+
+ + Never share private keys or seed phrases +
+
+ + Submit to generate README comment for copying +
-
- + + ); } diff --git a/src/app/profile/edit/hooks/useProfileWallets.ts b/src/app/profile/edit/hooks/useProfileWallets.ts index b347edcb5..e66313576 100644 --- a/src/app/profile/edit/hooks/useProfileWallets.ts +++ b/src/app/profile/edit/hooks/useProfileWallets.ts @@ -13,6 +13,12 @@ import { } from "@/lib/walletLinking/readmeUtils"; import { z } from "zod"; import { decodeBase64 } from "@/lib/decode"; +import { + resolveSnsDomain, + resolveEnsDomain, + validateEnsFormat, + validateSnsFormat, +} from "@/lib/walletLinking/domainUtils"; export function useProfileWallets() { const router = useRouter(); @@ -27,6 +33,7 @@ export function useProfileWallets() { const [defaultBranch, setDefaultBranch] = useState("main"); const [pageLoading, setPageLoading] = useState(true); + const [isProcessingWallets, setIsProcessingWallets] = useState(false); const [error, setError] = useState(null); const [successMessage, setSuccessMessage] = useState(null); @@ -124,6 +131,79 @@ export function useProfileWallets() { } }, [user]); + /** + * Processes form values and returns array of processed wallet objects + * with wallet addresses and/or domain names + */ + const processWallets = useCallback( + async ( + values: Record, + ): Promise => { + setIsProcessingWallets(true); + const wallets: LinkedWallet[] = []; + + try { + for (const chainName of Object.keys(values)) { + const addressValue = values[chainName]; + + if (!addressValue?.trim()) { + continue; + } + + // Determine if this is a domain name based on chain type + const isEns = + chainName === "ethereum" && validateEnsFormat(addressValue); + const isSns = + chainName === "solana" && validateSnsFormat(addressValue); + const isDomain = isEns || isSns; + + let finalAddress = addressValue; + let ensName: string | undefined; + let snsName: string | undefined; + + if (isDomain) { + // Domain address - resolve to actual address + const resolvedAddress = isEns + ? await resolveEnsDomain(addressValue) + : await resolveSnsDomain(addressValue); + + if (!resolvedAddress) { + // Resolution failed - throw error with field information + const domainType = isEns ? "ENS" : "SNS"; + const error = new Error( + `Failed to resolve ${domainType} name. Please check the domain.`, + ) as Error & { field: string }; + error.field = chainName; + throw error; + } + + finalAddress = resolvedAddress; + if (isEns) ensName = addressValue; + if (isSns) snsName = addressValue; + } + + // Create wallet object + const wallet: LinkedWallet = { + chain: chainName, + address: finalAddress, + ...(ensName && { ensName }), + ...(snsName && { snsName }), + }; + + wallets.push(wallet); + } + + return wallets; + } catch (error) { + // Re-throw errors (including ones with field property) for the component to handle + throw error; + } finally { + setIsProcessingWallets(false); + } + }, + [], + ); + const handleGenerateWalletSection = useCallback( async (wallets: LinkedWallet[]) => { if (!user || !user.login) { @@ -160,7 +240,7 @@ export function useProfileWallets() { setPageLoading(false); } }, - [user, setError, setSuccessMessage, setPageLoading], + [user], ); return { @@ -171,12 +251,14 @@ export function useProfileWallets() { walletSection, walletData, pageLoading, + isProcessingWallets, error, successMessage, setError, setSuccessMessage, getWalletAddress, handleCreateProfileRepo, + processWallets, handleGenerateWalletSection, defaultBranch, }; diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx new file mode 100644 index 000000000..28bb2fe35 --- /dev/null +++ b/src/components/ui/form.tsx @@ -0,0 +1,179 @@ +"use client"; + +import * as React from "react"; +import * as LabelPrimitive from "@radix-ui/react-label"; +import { Slot } from "@radix-ui/react-slot"; +import { + Controller, + FormProvider, + useFormContext, + type ControllerProps, + type FieldPath, + type FieldValues, +} from "react-hook-form"; + +import { cn } from "@/lib/utils"; +import { Label } from "@/components/ui/label"; + +const Form = FormProvider; + +type FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +> = { + name: TName; +}; + +const FormFieldContext = React.createContext( + {} as FormFieldContextValue, +); + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +>({ + ...props +}: ControllerProps) => { + return ( + + + + ); +}; + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext); + const itemContext = React.useContext(FormItemContext); + const { getFieldState, formState } = useFormContext(); + + const fieldState = getFieldState(fieldContext.name, formState); + + if (!fieldContext) { + throw new Error("useFormField should be used within "); + } + + const { id } = itemContext; + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + }; +}; + +type FormItemContextValue = { + id: string; +}; + +const FormItemContext = React.createContext( + {} as FormItemContextValue, +); + +const FormItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const id = React.useId(); + + return ( + +
+ + ); +}); +FormItem.displayName = "FormItem"; + +const FormLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + const { error, formItemId } = useFormField(); + + return ( +