diff --git a/package-lock.json b/package-lock.json index bb9f006..e9f1e91 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@noble/ed25519": "^3.0.0", + "@pump-fun/pump-sdk": "^1.32.0", "@react-three/drei": "^10.7.7", "@react-three/fiber": "^9.5.0", "@solana/web3.js": "^1.98.4", @@ -27,7 +28,8 @@ "remark-gfm": "^4.0.1", "tailwind-merge": "^3.4.0", "three": "^0.183.2", - "ws": "^8.18.3" + "ws": "^8.18.3", + "zod": "^4.3.6" }, "devDependencies": { "@playwright/test": "^1.58.0", @@ -382,6 +384,85 @@ "node": ">=6.9.0" } }, + "node_modules/@coral-xyz/anchor": { + "version": "0.31.1", + "resolved": "https://registry.npmjs.org/@coral-xyz/anchor/-/anchor-0.31.1.tgz", + "integrity": "sha512-QUqpoEK+gi2S6nlYc2atgT2r41TT3caWr/cPUEL8n8Md9437trZ68STknq897b82p5mW0XrTBNOzRbmIRJtfsA==", + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "@coral-xyz/anchor-errors": "^0.31.1", + "@coral-xyz/borsh": "^0.31.1", + "@noble/hashes": "^1.3.1", + "@solana/web3.js": "^1.69.0", + "bn.js": "^5.1.2", + "bs58": "^4.0.1", + "buffer-layout": "^1.2.2", + "camelcase": "^6.3.0", + "cross-fetch": "^3.1.5", + "eventemitter3": "^4.0.7", + "pako": "^2.0.3", + "superstruct": "^0.15.4", + "toml": "^3.0.0" + }, + "engines": { + "node": ">=17" + } + }, + "node_modules/@coral-xyz/anchor-errors": { + "version": "0.31.1", + "resolved": "https://registry.npmjs.org/@coral-xyz/anchor-errors/-/anchor-errors-0.31.1.tgz", + "integrity": "sha512-NhNEku4F3zzUSBtrYz84FzYWm48+9OvmT1Hhnwr6GnPQry2dsEqH/ti/7ASjjpoFTWRnPXrjAIT1qM6Isop+LQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/@coral-xyz/anchor/node_modules/base-x": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", + "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@coral-xyz/anchor/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "license": "MIT", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/@coral-xyz/anchor/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/@coral-xyz/anchor/node_modules/superstruct": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.15.5.tgz", + "integrity": "sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ==", + "license": "MIT" + }, + "node_modules/@coral-xyz/borsh": { + "version": "0.31.1", + "resolved": "https://registry.npmjs.org/@coral-xyz/borsh/-/borsh-0.31.1.tgz", + "integrity": "sha512-9N8AU9F0ubriKfNE3g1WF0/4dtlGXoBN/hd1PvbNBamBNwRgHxH4P+o3Zt7rSEloW1HUs6LfZEchlx9fW7POYw==", + "license": "Apache-2.0", + "dependencies": { + "bn.js": "^5.1.2", + "buffer-layout": "^1.2.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@solana/web3.js": "^1.69.0" + } + }, "node_modules/@csstools/color-helpers": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", @@ -2296,6 +2377,36 @@ "node": ">=18" } }, + "node_modules/@pump-fun/pump-sdk": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/@pump-fun/pump-sdk/-/pump-sdk-1.32.0.tgz", + "integrity": "sha512-ptqaz119m80w9ICjFFQLMO6WJw4DSZacj5Gzi4nSZEDlf7mLbgj5RbPzGZ3Eud+s+mTFVxJmztPjFVr/hpDI+Q==", + "license": "MIT", + "dependencies": { + "@coral-xyz/anchor": "^0.31.1", + "@pump-fun/pump-swap-sdk": "^1.13.0", + "@solana/spl-token": "^0.4.13", + "@solana/web3.js": "^1.98.2", + "bn.js": "^5.2.2", + "undici-types": "^6.20.0" + } + }, + "node_modules/@pump-fun/pump-swap-sdk": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@pump-fun/pump-swap-sdk/-/pump-swap-sdk-1.14.1.tgz", + "integrity": "sha512-U3vFL1Plhl/sBYUAHQhrFkmzXLxJp/gmuqHDn+odgwC1cRj15DJLVlRxnksGBMnf8dlScX9I+DdVng6rGbUnzg==", + "license": "MIT", + "dependencies": { + "@coral-xyz/anchor": "^0.31.1", + "@solana/spl-token": "^0.4.9", + "@solana/web3.js": "^1.98.2", + "bn.js": "^5.2.2", + "bs58": "^6.0.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@react-three/drei": { "version": "10.7.7", "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-10.7.7.tgz", @@ -2753,6 +2864,37 @@ "node": ">=5.10" } }, + "node_modules/@solana/buffer-layout-utils": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@solana/buffer-layout-utils/-/buffer-layout-utils-0.2.0.tgz", + "integrity": "sha512-szG4sxgJGktbuZYDg2FfNmkMi0DYQoVjN2h7ta1W1hPrwzarcFLBq9UpX1UjNXsNpT9dn+chgprtWGioUAr4/g==", + "license": "Apache-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" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@solana/codecs": { + "version": "2.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@solana/codecs/-/codecs-2.0.0-rc.1.tgz", + "integrity": "sha512-qxoR7VybNJixV51L0G1RD2boZTcxmwUWnKCaJJExQ5qNKwbpSyDdWfFJfM5JhGyKe9DnPVOZB+JHWXnpbZBqrQ==", + "license": "MIT", + "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" + } + }, "node_modules/@solana/codecs-core": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.3.0.tgz", @@ -2768,6 +2910,82 @@ "typescript": ">=5.3.3" } }, + "node_modules/@solana/codecs-data-structures": { + "version": "2.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@solana/codecs-data-structures/-/codecs-data-structures-2.0.0-rc.1.tgz", + "integrity": "sha512-rinCv0RrAVJ9rE/rmaibWJQxMwC5lSaORSZuwjopSUE6T0nb/MVg6Z1siNCXhh/HFTOg0l8bNvZHgBcN/yvXog==", + "license": "MIT", + "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" + } + }, + "node_modules/@solana/codecs-data-structures/node_modules/@solana/codecs-core": { + "version": "2.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.0.0-rc.1.tgz", + "integrity": "sha512-bauxqMfSs8EHD0JKESaNmNuNvkvHSuN3bbWAF5RjOfDu2PugxHrvRebmYauvSumZ3cTfQ4HJJX6PG5rN852qyQ==", + "license": "MIT", + "dependencies": { + "@solana/errors": "2.0.0-rc.1" + }, + "peerDependencies": { + "typescript": ">=5" + } + }, + "node_modules/@solana/codecs-data-structures/node_modules/@solana/codecs-numbers": { + "version": "2.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.0.0-rc.1.tgz", + "integrity": "sha512-J5i5mOkvukXn8E3Z7sGIPxsThRCgSdgTWJDQeZvucQ9PT6Y3HiVXJ0pcWiOWAoQ3RX8e/f4I3IC+wE6pZiJzDQ==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "2.0.0-rc.1", + "@solana/errors": "2.0.0-rc.1" + }, + "peerDependencies": { + "typescript": ">=5" + } + }, + "node_modules/@solana/codecs-data-structures/node_modules/@solana/errors": { + "version": "2.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.0.0-rc.1.tgz", + "integrity": "sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ==", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "commander": "^12.1.0" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "peerDependencies": { + "typescript": ">=5" + } + }, + "node_modules/@solana/codecs-data-structures/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@solana/codecs-data-structures/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@solana/codecs-numbers": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.3.0.tgz", @@ -2784,6 +3002,145 @@ "typescript": ">=5.3.3" } }, + "node_modules/@solana/codecs-strings": { + "version": "2.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@solana/codecs-strings/-/codecs-strings-2.0.0-rc.1.tgz", + "integrity": "sha512-9/wPhw8TbGRTt6mHC4Zz1RqOnuPTqq1Nb4EyuvpZ39GW6O2t2Q7Q0XxiB3+BdoEjwA2XgPw6e2iRfvYgqty44g==", + "license": "MIT", + "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" + } + }, + "node_modules/@solana/codecs-strings/node_modules/@solana/codecs-core": { + "version": "2.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.0.0-rc.1.tgz", + "integrity": "sha512-bauxqMfSs8EHD0JKESaNmNuNvkvHSuN3bbWAF5RjOfDu2PugxHrvRebmYauvSumZ3cTfQ4HJJX6PG5rN852qyQ==", + "license": "MIT", + "dependencies": { + "@solana/errors": "2.0.0-rc.1" + }, + "peerDependencies": { + "typescript": ">=5" + } + }, + "node_modules/@solana/codecs-strings/node_modules/@solana/codecs-numbers": { + "version": "2.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.0.0-rc.1.tgz", + "integrity": "sha512-J5i5mOkvukXn8E3Z7sGIPxsThRCgSdgTWJDQeZvucQ9PT6Y3HiVXJ0pcWiOWAoQ3RX8e/f4I3IC+wE6pZiJzDQ==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "2.0.0-rc.1", + "@solana/errors": "2.0.0-rc.1" + }, + "peerDependencies": { + "typescript": ">=5" + } + }, + "node_modules/@solana/codecs-strings/node_modules/@solana/errors": { + "version": "2.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.0.0-rc.1.tgz", + "integrity": "sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ==", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "commander": "^12.1.0" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "peerDependencies": { + "typescript": ">=5" + } + }, + "node_modules/@solana/codecs-strings/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@solana/codecs-strings/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@solana/codecs/node_modules/@solana/codecs-core": { + "version": "2.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.0.0-rc.1.tgz", + "integrity": "sha512-bauxqMfSs8EHD0JKESaNmNuNvkvHSuN3bbWAF5RjOfDu2PugxHrvRebmYauvSumZ3cTfQ4HJJX6PG5rN852qyQ==", + "license": "MIT", + "dependencies": { + "@solana/errors": "2.0.0-rc.1" + }, + "peerDependencies": { + "typescript": ">=5" + } + }, + "node_modules/@solana/codecs/node_modules/@solana/codecs-numbers": { + "version": "2.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.0.0-rc.1.tgz", + "integrity": "sha512-J5i5mOkvukXn8E3Z7sGIPxsThRCgSdgTWJDQeZvucQ9PT6Y3HiVXJ0pcWiOWAoQ3RX8e/f4I3IC+wE6pZiJzDQ==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "2.0.0-rc.1", + "@solana/errors": "2.0.0-rc.1" + }, + "peerDependencies": { + "typescript": ">=5" + } + }, + "node_modules/@solana/codecs/node_modules/@solana/errors": { + "version": "2.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.0.0-rc.1.tgz", + "integrity": "sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ==", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "commander": "^12.1.0" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "peerDependencies": { + "typescript": ">=5" + } + }, + "node_modules/@solana/codecs/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@solana/codecs/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@solana/errors": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.3.0.tgz", @@ -2815,6 +3172,133 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/@solana/options": { + "version": "2.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@solana/options/-/options-2.0.0-rc.1.tgz", + "integrity": "sha512-mLUcR9mZ3qfHlmMnREdIFPf9dpMc/Bl66tLSOOWxw4ml5xMT2ohFn7WGqoKcu/UHkT9CrC6+amEdqCNvUqI7AA==", + "license": "MIT", + "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" + } + }, + "node_modules/@solana/options/node_modules/@solana/codecs-core": { + "version": "2.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.0.0-rc.1.tgz", + "integrity": "sha512-bauxqMfSs8EHD0JKESaNmNuNvkvHSuN3bbWAF5RjOfDu2PugxHrvRebmYauvSumZ3cTfQ4HJJX6PG5rN852qyQ==", + "license": "MIT", + "dependencies": { + "@solana/errors": "2.0.0-rc.1" + }, + "peerDependencies": { + "typescript": ">=5" + } + }, + "node_modules/@solana/options/node_modules/@solana/codecs-numbers": { + "version": "2.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.0.0-rc.1.tgz", + "integrity": "sha512-J5i5mOkvukXn8E3Z7sGIPxsThRCgSdgTWJDQeZvucQ9PT6Y3HiVXJ0pcWiOWAoQ3RX8e/f4I3IC+wE6pZiJzDQ==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "2.0.0-rc.1", + "@solana/errors": "2.0.0-rc.1" + }, + "peerDependencies": { + "typescript": ">=5" + } + }, + "node_modules/@solana/options/node_modules/@solana/errors": { + "version": "2.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.0.0-rc.1.tgz", + "integrity": "sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ==", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "commander": "^12.1.0" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "peerDependencies": { + "typescript": ">=5" + } + }, + "node_modules/@solana/options/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@solana/options/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@solana/spl-token": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.4.14.tgz", + "integrity": "sha512-u09zr96UBpX4U685MnvQsNzlvw9TiY005hk1vJmJr7gMJldoPG1eYU5/wNEyOA5lkMLiR/gOi9SFD4MefOYEsA==", + "license": "Apache-2.0", + "dependencies": { + "@solana/buffer-layout": "^4.0.0", + "@solana/buffer-layout-utils": "^0.2.0", + "@solana/spl-token-group": "^0.0.7", + "@solana/spl-token-metadata": "^0.1.6", + "buffer": "^6.0.3" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@solana/web3.js": "^1.95.5" + } + }, + "node_modules/@solana/spl-token-group": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@solana/spl-token-group/-/spl-token-group-0.0.7.tgz", + "integrity": "sha512-V1N/iX7Cr7H0uazWUT2uk27TMqlqedpXHRqqAbVO2gvmJyT0E0ummMEAVQeXZ05ZhQ/xF39DLSdBp90XebWEug==", + "license": "Apache-2.0", + "dependencies": { + "@solana/codecs": "2.0.0-rc.1" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@solana/web3.js": "^1.95.3" + } + }, + "node_modules/@solana/spl-token-metadata": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@solana/spl-token-metadata/-/spl-token-metadata-0.1.6.tgz", + "integrity": "sha512-7sMt1rsm/zQOQcUWllQX9mD2O6KhSAtY1hFR2hfFwgqfFWzSY9E9GDvFVNYUI1F0iQKcm6HmePU9QbKRXTEBiA==", + "license": "Apache-2.0", + "dependencies": { + "@solana/codecs": "2.0.0-rc.1" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@solana/web3.js": "^1.95.3" + } + }, "node_modules/@solana/web3.js": { "version": "1.98.4", "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.98.4.tgz", @@ -2838,6 +3322,24 @@ "superstruct": "^2.0.2" } }, + "node_modules/@solana/web3.js/node_modules/base-x": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", + "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@solana/web3.js/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "license": "MIT", + "dependencies": { + "base-x": "^3.0.2" + } + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -4558,13 +5060,10 @@ "license": "MIT" }, "node_modules/base-x": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", - "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.0.1" - } + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz", + "integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==", + "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", @@ -4607,6 +5106,37 @@ "require-from-string": "^2.0.2" } }, + "node_modules/bigint-buffer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bigint-buffer/-/bigint-buffer-1.1.5.tgz", + "integrity": "sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "bindings": "^1.3.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, "node_modules/bn.js": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.3.tgz", @@ -4624,6 +5154,24 @@ "text-encoding-utf-8": "^1.0.2" } }, + "node_modules/borsh/node_modules/base-x": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", + "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/borsh/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "license": "MIT", + "dependencies": { + "base-x": "^3.0.2" + } + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -4683,12 +5231,12 @@ } }, "node_modules/bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz", + "integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==", "license": "MIT", "dependencies": { - "base-x": "^3.0.2" + "base-x": "^5.0.0" } }, "node_modules/buffer": { @@ -4715,6 +5263,15 @@ "ieee754": "^1.2.1" } }, + "node_modules/buffer-layout": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/buffer-layout/-/buffer-layout-1.2.2.tgz", + "integrity": "sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA==", + "license": "MIT", + "engines": { + "node": ">=4.5" + } + }, "node_modules/bufferutil": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz", @@ -4799,6 +5356,18 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/camera-controls": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-3.1.2.tgz", @@ -5024,6 +5593,15 @@ "yarn": ">=1" } }, + "node_modules/cross-fetch": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", + "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -6203,6 +6781,13 @@ "integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==", "license": "MIT" }, + "node_modules/fastestsmallesttextencoderdecoder": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz", + "integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==", + "license": "CC0-1.0", + "peer": true + }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", @@ -6232,6 +6817,12 @@ "node": ">=16.0.0" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -9333,6 +9924,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -10942,6 +11539,12 @@ "node": ">=8.0" } }, + "node_modules/toml": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", + "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==", + "license": "MIT" + }, "node_modules/tough-cookie": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", @@ -11978,7 +12581,6 @@ "version": "4.3.6", "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 7f2d079..de2fcb9 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ }, "dependencies": { "@noble/ed25519": "^3.0.0", + "@pump-fun/pump-sdk": "^1.32.0", "@react-three/drei": "^10.7.7", "@react-three/fiber": "^9.5.0", "@solana/web3.js": "^1.98.4", @@ -36,7 +37,8 @@ "remark-gfm": "^4.0.1", "tailwind-merge": "^3.4.0", "three": "^0.183.2", - "ws": "^8.18.3" + "ws": "^8.18.3", + "zod": "^4.3.6" }, "devDependencies": { "@playwright/test": "^1.58.0", diff --git a/src/app/api/crypto/launch/prepare/route.ts b/src/app/api/crypto/launch/prepare/route.ts new file mode 100644 index 0000000..56bec7e --- /dev/null +++ b/src/app/api/crypto/launch/prepare/route.ts @@ -0,0 +1,32 @@ +import { NextResponse } from "next/server"; +import { ZodError } from "zod"; +import { normalizeLaunchDraft, cryptoLaunchPrepareSchema } from "@/features/crypto/lib/launchSchema"; +import { prepareCryptoLaunch } from "@/features/crypto/server/launch/service"; + +export async function POST(request: Request) { + try { + const payload = cryptoLaunchPrepareSchema.parse(await request.json()); + const prepared = await prepareCryptoLaunch({ + draft: normalizeLaunchDraft(payload.draft), + creatorPublicKey: payload.creatorPublicKey, + }); + return NextResponse.json({ prepared }); + } catch (error) { + if (error instanceof ZodError) { + return NextResponse.json( + { + error: error.issues[0]?.message ?? "Invalid launch payload.", + issues: error.issues, + }, + { status: 400 }, + ); + } + return NextResponse.json( + { + error: + error instanceof Error ? error.message : "Unable to prepare the Pump.fun launch.", + }, + { status: 502 }, + ); + } +} diff --git a/src/app/api/crypto/launch/submit/route.ts b/src/app/api/crypto/launch/submit/route.ts new file mode 100644 index 0000000..5f6a7ff --- /dev/null +++ b/src/app/api/crypto/launch/submit/route.ts @@ -0,0 +1,29 @@ +import { NextResponse } from "next/server"; +import { ZodError } from "zod"; +import { cryptoLaunchSubmitSchema } from "@/features/crypto/lib/launchSchema"; +import { submitCryptoLaunch } from "@/features/crypto/server/launch/service"; + +export async function POST(request: Request) { + try { + const payload = cryptoLaunchSubmitSchema.parse(await request.json()); + const result = await submitCryptoLaunch(payload); + return NextResponse.json({ result }); + } catch (error) { + if (error instanceof ZodError) { + return NextResponse.json( + { + error: error.issues[0]?.message ?? "Invalid launch submission payload.", + issues: error.issues, + }, + { status: 400 }, + ); + } + return NextResponse.json( + { + error: + error instanceof Error ? error.message : "Unable to submit the Pump.fun launch.", + }, + { status: 502 }, + ); + } +} diff --git a/src/app/api/crypto/launch/tip-floor/route.ts b/src/app/api/crypto/launch/tip-floor/route.ts new file mode 100644 index 0000000..fe5d1ac --- /dev/null +++ b/src/app/api/crypto/launch/tip-floor/route.ts @@ -0,0 +1,20 @@ +import { NextResponse } from "next/server"; +import { fetchJitoTipFloor } from "@/features/crypto/server/launch/service"; + +export async function GET() { + try { + const tipFloor = await fetchJitoTipFloor(); + if (!tipFloor) { + return NextResponse.json({ error: "Tip floor unavailable." }, { status: 503 }); + } + return NextResponse.json(tipFloor); + } catch (error) { + return NextResponse.json( + { + error: + error instanceof Error ? error.message : "Unable to load the Jito tip floor.", + }, + { status: 502 }, + ); + } +} diff --git a/src/features/agents/operations/cryptoLaunchChat.ts b/src/features/agents/operations/cryptoLaunchChat.ts new file mode 100644 index 0000000..54e07aa --- /dev/null +++ b/src/features/agents/operations/cryptoLaunchChat.ts @@ -0,0 +1,356 @@ +import { + executeServerSideLaunch, + executeWalletApprovedLaunch, +} from "@/features/crypto/lib/launchClient"; +import { + buildInitialCryptoLaunchState, + loadCryptoLaunchState, + saveCryptoLaunchState, +} from "@/features/crypto/lib/launchStorage"; +import { + getLaunchFieldLabel, + getMissingRequiredLaunchField, + isSkipAnswer, + normalizeLaunchDraft, + parseExecutionModeAnswer, + parseNetworkAnswer, +} from "@/features/crypto/lib/launchSchema"; +import type { AgentState } from "@/features/agents/state/store"; +import type { + CryptoLaunchDraft, + CryptoLaunchDraftState, + CryptoLaunchQuestionId, +} from "@/features/crypto/types"; + +type ChatInteractionDispatchAction = + | { type: "updateAgent"; agentId: string; patch: Partial } + | { type: "appendOutput"; agentId: string; line: string }; + +const QUESTION_ORDER: readonly CryptoLaunchQuestionId[] = [ + "network", + "executionMode", + "name", + "symbol", + "description", + "logoUrl", + "website", + "twitter", + "telegram", + "discord", + "confirm", +] as const; + +const START_RE = + /\b(create|launch|make)\b[\s\S]*\b(token|coin|memecoin|meme coin)\b/i; + +const isAffirmative = (value: string) => /^(yes|y|launch|create it|ship it|do it|confirm)$/i.test(value.trim()); +const isNegative = (value: string) => /^(no|n|cancel|stop|abort|not now)$/i.test(value.trim()); +const wantsOpenRoom = (value: string) => + /(open|show).*(crypto room|launch tab)|crypto room|manual/i.test(value.trim()); + +function getNextQuestion(current: CryptoLaunchQuestionId | null): CryptoLaunchQuestionId { + if (!current) return QUESTION_ORDER[0]; + const index = QUESTION_ORDER.indexOf(current); + return QUESTION_ORDER[Math.min(index + 1, QUESTION_ORDER.length - 1)]!; +} + +function buildQuestion(field: CryptoLaunchQuestionId): string { + switch (field) { + case "network": + return "Which network should I use: `devnet` or `mainnet`?"; + case "executionMode": + return "Should I use `user-approved wallet` mode or `server-side` mode?"; + case "name": + return "What is the token name?"; + case "symbol": + return "What is the token symbol? Keep it under 10 characters."; + case "description": + return "What description should I publish for the token?"; + case "logoUrl": + return "What is the logo URL? If you want to upload an image manually instead, say `open crypto room`."; + case "website": + return "What website should I attach? Reply with a URL or `skip`."; + case "twitter": + return "What Twitter/X link or handle should I attach? Reply with a value or `skip`."; + case "telegram": + return "What Telegram link or handle should I attach? Reply with a value or `skip`."; + case "discord": + return "What Discord invite or handle should I attach? Reply with a value or `skip`."; + case "confirm": + return "Everything is collected. Reply `launch` to create the token now, or say `open crypto room` if you want to review it manually first."; + } +} + +function buildSummary(draft: CryptoLaunchDraft): string { + return [ + "Token draft ready:", + `- Network: ${draft.network}`, + `- Mode: ${draft.executionMode === "server_side" ? "server-side" : "user-approved wallet"}`, + `- Name: ${draft.name}`, + `- Symbol: ${draft.symbol}`, + `- Description: ${draft.description}`, + `- Logo: ${draft.logoUrl}`, + `- Website: ${draft.website || "skip"}`, + `- Twitter/X: ${draft.twitter || "skip"}`, + `- Telegram: ${draft.telegram || "skip"}`, + `- Discord: ${draft.discord || "skip"}`, + ].join("\n"); +} + +function persistState(state: CryptoLaunchDraftState) { + saveCryptoLaunchState(state); +} + +function appendAssistant(dispatch: (action: ChatInteractionDispatchAction) => void, agentId: string, line: string) { + dispatch({ type: "appendOutput", agentId, line }); +} + +function updateDraftForAnswer( + field: CryptoLaunchQuestionId, + answer: string, + draft: CryptoLaunchDraft, +): { draft: CryptoLaunchDraft; error: string | null } { + const trimmed = answer.trim(); + switch (field) { + case "network": { + const network = parseNetworkAnswer(trimmed); + if (!network) { + return { draft, error: "Please reply with `devnet` or `mainnet`." }; + } + return { draft: { ...draft, network }, error: null }; + } + case "executionMode": { + const executionMode = parseExecutionModeAnswer(trimmed); + if (!executionMode) { + return { + draft, + error: "Please reply with `user-approved wallet` or `server-side`.", + }; + } + return { draft: { ...draft, executionMode }, error: null }; + } + case "name": + return trimmed ? { draft: { ...draft, name: trimmed }, error: null } : { draft, error: "The token name cannot be empty." }; + case "symbol": + return trimmed + ? { draft: { ...draft, symbol: trimmed.toUpperCase() }, error: null } + : { draft, error: "The token symbol cannot be empty." }; + case "description": + return trimmed + ? { draft: { ...draft, description: trimmed }, error: null } + : { draft, error: "The token description cannot be empty." }; + case "logoUrl": + if (!trimmed) { + return { draft, error: "A logo URL is required for the chat launch flow." }; + } + return { draft: { ...draft, logoUrl: trimmed }, error: null }; + case "website": + return { draft: { ...draft, website: isSkipAnswer(trimmed) ? "" : trimmed }, error: null }; + case "twitter": + return { draft: { ...draft, twitter: isSkipAnswer(trimmed) ? "" : trimmed }, error: null }; + case "telegram": + return { draft: { ...draft, telegram: isSkipAnswer(trimmed) ? "" : trimmed }, error: null }; + case "discord": + return { draft: { ...draft, discord: isSkipAnswer(trimmed) ? "" : trimmed }, error: null }; + case "confirm": + return { draft, error: null }; + } +} + +async function launchFromDraft(params: { + state: CryptoLaunchDraftState; +}) { + const draft = normalizeLaunchDraft(params.state.draft); + const outcome = + draft.executionMode === "server_side" + ? await executeServerSideLaunch(draft) + : await executeWalletApprovedLaunch(draft); + const nextState: CryptoLaunchDraftState = { + ...params.state, + draft: { + ...draft, + creatorWallet: + draft.executionMode === "user_approved" ? outcome.result.creatorPublicKey : draft.creatorWallet, + }, + conversation: { + active: false, + agentId: null, + awaitingField: null, + lastUpdatedAt: Date.now(), + }, + lastPrepared: outcome.prepared, + lastResult: outcome.result, + }; + persistState(nextState); + return outcome.result; +} + +export async function maybeHandleCryptoLaunchChat(params: { + agentId: string; + message: string; + dispatch: (action: ChatInteractionDispatchAction) => void; +}): Promise { + const trimmed = params.message.trim(); + if (!trimmed) return false; + + const existing = loadCryptoLaunchState(); + const activeForAgent = + existing.conversation.active && existing.conversation.agentId === params.agentId; + const shouldStart = START_RE.test(trimmed); + + if (!activeForAgent && !shouldStart) { + return false; + } + + appendAssistant(params.dispatch, params.agentId, `> ${trimmed}`); + + if (shouldStart && !activeForAgent) { + const nextState: CryptoLaunchDraftState = { + ...buildInitialCryptoLaunchState(), + conversation: { + active: true, + agentId: params.agentId, + awaitingField: QUESTION_ORDER[0], + lastUpdatedAt: Date.now(), + }, + lastPrepared: existing.lastPrepared, + lastResult: existing.lastResult, + }; + persistState(nextState); + appendAssistant( + params.dispatch, + params.agentId, + `I can handle that. ${buildQuestion(QUESTION_ORDER[0])}`, + ); + return true; + } + + if (!activeForAgent) { + appendAssistant( + params.dispatch, + params.agentId, + "Another agent already owns the active token launch draft. Open the crypto room to review it or cancel that flow first.", + ); + return true; + } + + if (isNegative(trimmed)) { + const nextState: CryptoLaunchDraftState = { + ...existing, + conversation: { + active: false, + agentId: null, + awaitingField: null, + lastUpdatedAt: Date.now(), + }, + }; + persistState(nextState); + appendAssistant( + params.dispatch, + params.agentId, + "Token launch cancelled. The draft stays available in the crypto room if you want to resume manually.", + ); + return true; + } + + const awaitingField = existing.conversation.awaitingField ?? QUESTION_ORDER[0]; + if (awaitingField === "confirm") { + if (wantsOpenRoom(trimmed)) { + persistState({ + ...existing, + conversation: { + ...existing.conversation, + active: false, + agentId: null, + awaitingField: null, + lastUpdatedAt: Date.now(), + }, + }); + appendAssistant( + params.dispatch, + params.agentId, + "The launch draft is ready in the crypto room Launch tab. Review it there and launch whenever you want.", + ); + return true; + } + if (!isAffirmative(trimmed)) { + appendAssistant( + params.dispatch, + params.agentId, + "Reply `launch` to create the token now, or say `open crypto room` to finish it manually.", + ); + return true; + } + try { + const result = await launchFromDraft({ state: existing }); + appendAssistant( + params.dispatch, + params.agentId, + [ + `Token created on ${result.network}.`, + `Mint: ${result.mintAddress}`, + `Creator: ${result.creatorPublicKey}`, + `Transaction: ${result.explorerTxUrl}`, + ].join("\n"), + ); + } catch (error) { + appendAssistant( + params.dispatch, + params.agentId, + error instanceof Error + ? `Launch failed: ${error.message}` + : "Launch failed. Try again or open the crypto room to review the draft.", + ); + } + return true; + } + + if (awaitingField === "logoUrl" && wantsOpenRoom(trimmed)) { + appendAssistant( + params.dispatch, + params.agentId, + "The draft is waiting in the crypto room Launch tab. Upload the logo there, then finish the launch manually or come back here and say `launch`.", + ); + return true; + } + + const nextDraft = updateDraftForAnswer(awaitingField, trimmed, normalizeLaunchDraft(existing.draft)); + if (nextDraft.error) { + appendAssistant(params.dispatch, params.agentId, nextDraft.error); + return true; + } + + const candidateNextField = getNextQuestion(awaitingField); + const nextState: CryptoLaunchDraftState = { + ...existing, + draft: nextDraft.draft, + conversation: { + active: true, + agentId: params.agentId, + awaitingField: candidateNextField, + lastUpdatedAt: Date.now(), + }, + }; + + if (candidateNextField === "confirm") { + const missingField = getMissingRequiredLaunchField(nextState.draft); + if (missingField) { + nextState.conversation.awaitingField = missingField; + persistState(nextState); + appendAssistant( + params.dispatch, + params.agentId, + `I still need the ${getLaunchFieldLabel(missingField)}. ${buildQuestion(missingField)}`, + ); + return true; + } + persistState(nextState); + appendAssistant(params.dispatch, params.agentId, buildSummary(nextState.draft)); + appendAssistant(params.dispatch, params.agentId, buildQuestion("confirm")); + return true; + } + + persistState(nextState); + appendAssistant(params.dispatch, params.agentId, buildQuestion(candidateNextField)); + return true; +} diff --git a/src/features/agents/operations/useChatInteractionController.ts b/src/features/agents/operations/useChatInteractionController.ts index c692e0e..b6dbc88 100644 --- a/src/features/agents/operations/useChatInteractionController.ts +++ b/src/features/agents/operations/useChatInteractionController.ts @@ -7,6 +7,7 @@ import { planNewSessionIntent, planStopRunIntent, } from "@/features/agents/operations/chatInteractionWorkflow"; +import { maybeHandleCryptoLaunchChat } from "@/features/agents/operations/cryptoLaunchChat"; import { sendChatMessageViaStudio } from "@/features/agents/operations/chatSendOperation"; import { mergePendingLivePatch } from "@/features/agents/state/livePatchQueue"; import { buildNewSessionAgentPatch, type AgentState } from "@/features/agents/state/store"; @@ -207,6 +208,20 @@ export function useChatInteractionController( }); return; } + if ( + await maybeHandleCryptoLaunchChat({ + agentId, + message: trimmed, + dispatch: params.dispatch, + }) + ) { + params.dispatch({ + type: "updateAgent", + agentId, + patch: { draft: "", lastActivityAt: Date.now() }, + }); + return; + } if (agent.status === "running") { params.dispatch({ type: "enqueueQueuedMessage", diff --git a/src/features/crypto/components/CryptoImmersiveScreen.tsx b/src/features/crypto/components/CryptoImmersiveScreen.tsx index ad829c4..2d100bd 100644 --- a/src/features/crypto/components/CryptoImmersiveScreen.tsx +++ b/src/features/crypto/components/CryptoImmersiveScreen.tsx @@ -12,12 +12,14 @@ import { Sparkles, Wallet, } from "lucide-react"; -import { useMemo, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { CRYPTO_ROOM_DEXSCREENER_URL, CRYPTO_ROOM_PAIR_ADDRESS, } from "@/features/crypto/lib/constants"; import { useCryptoRoomState } from "@/features/crypto/hooks/useCryptoRoomState"; +import { useCryptoLaunchState } from "@/features/crypto/hooks/useCryptoLaunchState"; +import { getLaunchFieldLabel, getMissingRequiredLaunchField } from "@/features/crypto/lib/launchSchema"; import type { CryptoAgentTradeMode, CryptoTrackedPair } from "@/features/crypto/types"; import type { OfficeAgent } from "@/features/retro-office/core/types"; @@ -38,7 +40,7 @@ const number = new Intl.NumberFormat("en-US", { maximumFractionDigits: 4, }); -type TabKey = "market" | "trade" | "ledger" | "agents"; +type TabKey = "market" | "trade" | "ledger" | "agents" | "launch"; const DEXSCREENER_SOLANA_URL_RE = /^https?:\/\/(?:www\.)?dexscreener\.com\/solana\/([a-zA-Z0-9]{16,128})(?:[/?#].*)?$/i; @@ -79,6 +81,7 @@ export function CryptoImmersiveScreen({ agents }: { agents: OfficeAgent[] }) { const [browsedPairLoading, setBrowsedPairLoading] = useState(false); const [browsedPairError, setBrowsedPairError] = useState(null); const state = useCryptoRoomState(agents); + const launch = useCryptoLaunchState(); const pendingApprovals = useMemo( () => state.approvals.filter((approval) => approval.status === "pending"), @@ -380,6 +383,11 @@ export function CryptoImmersiveScreen({ agents }: { agents: OfficeAgent[] }) { label: "Agents", icon: , }, + { + key: "launch" as const, + label: "Launch", + icon: , + }, ].map((tab) => ( + + + } + > +
+ + + + + + + + + launch.setDraft((current) => ({ ...current, name: event.target.value })) + } + maxLength={32} + className="w-full rounded-2xl border border-white/12 bg-[#071019] px-4 py-3 text-[13px] text-white outline-none transition-colors placeholder:text-white/30 focus:border-cyan-300/35" + placeholder="Claw Coin" + /> + + + + launch.setDraft((current) => ({ + ...current, + symbol: event.target.value.toUpperCase(), + })) + } + maxLength={10} + className="w-full rounded-2xl border border-white/12 bg-[#071019] px-4 py-3 text-[13px] uppercase text-white outline-none transition-colors placeholder:text-white/30 focus:border-cyan-300/35" + placeholder="CLAW" + /> + +
+
+ +