diff --git a/.env.example b/.env.example index 26b84f4..9c951dd 100644 --- a/.env.example +++ b/.env.example @@ -1,14 +1,12 @@ # Client-side environment variables (must be prefixed with NEXT_PUBLIC_) # Solana Token Payment Configuration -NEXT_PUBLIC_SOLANA_RPC_URL=https://skilled-prettiest-seed.solana-mainnet.quiknode.pro/eeecfebd04e345f69f1900cc3483cbbfea02a158 -NEXT_PUBLIC_SOLANA_WEBSOCKET_URL=wss://skilled-prettiest-seed.solana-mainnet.quiknode.pro/ +NEXT_PUBLIC_SOLANA_RPC_URL=https://api.mainnet-beta.solana.com NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS=71Jvq4Epe2FCJ7JFSF7jLXdNk1Wy4Bhqd9iL6bEFELvg NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS= NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL=GOR NEXT_PUBLIC_SOLANA_TOKEN_NAME=GOR Token -NEXT_PUBLIC_SOLANA_TOKEN_DECIMALS=6 -NEXT_PUBLIC_SOLANA_PAYMENT_AMOUNT=50 +NEXT_PUBLIC_MIN_SOLANA_PAYMENT_AMOUNT=50 # UI Configuration (optional) NEXT_PUBLIC_DOMAIN_SUFFIX= @@ -30,7 +28,4 @@ REGISTRY_GAS_PRICE=0.001 # Application Configuration APP_NAME=gor-deploy DEPLOYER_LRN= - -# LNT Transfer Configuration (required for Solana flow) -# Note: REGISTRY_USER_KEY is used as the prefilled account for LNT transfers LACONIC_TRANSFER_AMOUNT=1000000alnt diff --git a/README.md b/README.md index 274f468..2aff3dc 100644 --- a/README.md +++ b/README.md @@ -32,13 +32,12 @@ Required environment variables: Client-side (must be prefixed with NEXT_PUBLIC_): - `NEXT_PUBLIC_SOLANA_RPC_URL` - The RPC URL for the Solana blockchain -- `NEXT_PUBLIC_SOLANA_WEBSOCKET_URL` - The WebSocket URL for Solana (optional) - `NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS` - The mint address of the SPL token to accept - `NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS` - The Solana address that will receive token payments - `NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL` - The token symbol to display (e.g., "GOR") - `NEXT_PUBLIC_SOLANA_TOKEN_NAME` - The full token name (e.g., "GOR Token") - `NEXT_PUBLIC_SOLANA_TOKEN_DECIMALS` - The number of decimals for the token (e.g., 6) -- `NEXT_PUBLIC_SOLANA_PAYMENT_AMOUNT` - The fixed payment amount required (e.g., 50) +- `NEXT_PUBLIC_MIN_SOLANA_PAYMENT_AMOUNT` - The fixed payment amount required (e.g., 50) - `NEXT_PUBLIC_DOMAIN_SUFFIX` - Optional suffix to append to DNS names in the UI (e.g. ".example.com") - `NEXT_PUBLIC_EXAMPLE_URL` - Example URL to pre-fill in the URL form (e.g. "https://github.com/cerc-io/laconic-registry-cli") @@ -51,7 +50,6 @@ Server-side: - `REGISTRY_USER_KEY` - The private key for Laconic Registry transactions (also used for LNT transfers) - `APP_NAME` - The name of the application (used in record creation, defaults to "gor-deploy") - `DEPLOYER_LRN` - The LRN of the deployer -- `LACONIC_SERVICE_PROVIDER_ADDRESS` - The Laconic address to receive LNT transfers - `LACONIC_TRANSFER_AMOUNT` - The amount of LNT to transfer (e.g., "1000000alnt") ## Installation @@ -189,10 +187,10 @@ docker run -p 3000:3000 --env-file .env.production gor-deploy - **Wrong token**: Verify the `NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS` matches your desired SPL token. - **Incorrect decimals**: Ensure `NEXT_PUBLIC_SOLANA_TOKEN_DECIMALS` matches the token's actual decimal count. -- **Payment amount**: Adjust `NEXT_PUBLIC_SOLANA_PAYMENT_AMOUNT` to the desired payment amount. +- **Payment amount**: Adjust `NEXT_PUBLIC_MIN_SOLANA_PAYMENT_AMOUNT` to the desired payment amount. ### Laconic Registry Issues - **Failed to create record**: Check that your REGISTRY_USER_KEY and REGISTRY_BOND_ID are correctly set. -- **LNT transfer errors**: Ensure your REGISTRY_USER_KEY has sufficient LNT balance and the LACONIC_SERVICE_PROVIDER_ADDRESS is valid. +- **LNT transfer errors**: Ensure your REGISTRY_USER_KEY has sufficient LNT balance. - **Transaction verification errors**: Ensure your SOLANA_RPC_URL is accessible and returns correct transaction data. diff --git a/package-lock.json b/package-lock.json index 49103be..75007f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,15 @@ { - "name": "atom-deploy", + "name": "gor-deploy", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "atom-deploy", + "name": "gor-deploy", "version": "0.1.0", "dependencies": { "@cerc-io/registry-sdk": "^0.2.11", "@cosmjs/stargate": "^0.32.3", - "@keplr-wallet/types": "^0.12.71", "@solana/spl-token": "^0.4.13", "@solana/web3.js": "^1.98.2", "axios": "^1.6.8", @@ -1754,17 +1753,6 @@ "multiformats": "^9.5.4" } }, - "node_modules/@keplr-wallet/types": { - "version": "0.12.230", - "resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.230.tgz", - "integrity": "sha512-fyd/Wt2accJ/avOHOWc8kOtJCLKrHtyVjMNvmxmIZw5uFYPLKFxeVUSLy1AB4JmhDupcZvshocFO1hOe+WkUMQ==", - "dependencies": { - "long": "^4.0.0" - }, - "peerDependencies": { - "starknet": "^6" - } - }, "node_modules/@metamask/eth-sig-util": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz", @@ -2080,40 +2068,6 @@ "integrity": "sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==", "dev": true }, - "node_modules/@scure/base": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.1.tgz", - "integrity": "sha512-DGmGtC8Tt63J5GfHgfl5CuAXh96VF/LD8K9Hr/Gv0J2lAoRGlPOMpqMpMbCTOoOJMZCk2Xt+DskdDyn6dEFdzQ==", - "peer": true, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/starknet": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@scure/starknet/-/starknet-1.1.0.tgz", - "integrity": "sha512-83g3M6Ix2qRsPN4wqLDqiRZ2GBNbjVWfboJE/9UjfG+MHr6oDSu/CWgy8hsBSJejr09DkkL+l0Ze4KVrlCIdtQ==", - "peer": true, - "dependencies": { - "@noble/curves": "~1.7.0", - "@noble/hashes": "~1.6.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/starknet/node_modules/@noble/hashes": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.1.tgz", - "integrity": "sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w==", - "peer": true, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@solana/buffer-layout": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz", @@ -3304,21 +3258,6 @@ "win32" ] }, - "node_modules/abi-wan-kanabi": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/abi-wan-kanabi/-/abi-wan-kanabi-2.2.4.tgz", - "integrity": "sha512-0aA81FScmJCPX+8UvkXLki3X1+yPQuWxEkqXBVKltgPAK79J+NB+Lp5DouMXa7L6f+zcRlIA/6XO7BN/q9fnvg==", - "peer": true, - "dependencies": { - "ansicolors": "^0.3.2", - "cardinal": "^2.1.1", - "fs-extra": "^10.0.0", - "yargs": "^17.7.2" - }, - "bin": { - "generate": "dist/generate.js" - } - }, "node_modules/acorn": { "version": "8.14.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", @@ -3373,19 +3312,11 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -3396,12 +3327,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/ansicolors": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", - "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==", - "peer": true - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -3945,19 +3870,6 @@ "resolved": "https://registry.npmjs.org/canonical-json/-/canonical-json-0.0.4.tgz", "integrity": "sha512-2sW7x0m/P7dqEnO0O87U7RTVQAaa7MELcd+Jd9FA6CYgYtwJ1TlDWIYMD8nuMkH1KoThsJogqgLyklrt9d/Azw==" }, - "node_modules/cardinal": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", - "integrity": "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==", - "peer": true, - "dependencies": { - "ansicolors": "~0.3.2", - "redeyed": "~2.1.0" - }, - "bin": { - "cdl": "bin/cdl.js" - } - }, "node_modules/cborg": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/cborg/-/cborg-1.10.2.tgz", @@ -3999,20 +3911,6 @@ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "peer": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -4039,6 +3937,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -4049,7 +3948,8 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "devOptional": true }, "node_modules/color-string": { "version": "1.9.1", @@ -4531,15 +4431,6 @@ "es6-promise": "^4.0.3" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -5174,16 +5065,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fetch-cookie": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-3.0.1.tgz", - "integrity": "sha512-ZGXe8Y5Z/1FWqQ9q/CrJhkUD73DyBU9VF0hBQmEO/wPHe4A9PKTjplFDLeFX8aOsYypZUcX5Ji/eByn3VCVO3Q==", - "peer": true, - "dependencies": { - "set-cookie-parser": "^2.4.8", - "tough-cookie": "^4.0.0" - } - }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -5296,20 +5177,6 @@ "node": ">= 6" } }, - "node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "peer": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -5352,15 +5219,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "peer": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -5503,7 +5361,8 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true }, "node_modules/graphemer": { "version": "1.4.0", @@ -5895,15 +5754,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-generator-function": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", @@ -6128,16 +5978,6 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "node_modules/isomorphic-fetch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", - "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", - "peer": true, - "dependencies": { - "node-fetch": "^2.6.1", - "whatwg-fetch": "^3.4.1" - } - }, "node_modules/isomorphic-ws": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", @@ -6274,18 +6114,6 @@ "json5": "lib/cli.js" } }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "peer": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -6738,12 +6566,6 @@ "loose-envify": "cli.js" } }, - "node_modules/lossless-json": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/lossless-json/-/lossless-json-4.0.2.tgz", - "integrity": "sha512-+z0EaLi2UcWi8MZRxA5iTb6m4Ys4E80uftGY+yG5KNFJb5EceQXOhdW/pWJZ8m97s26u7yZZAYMcKWNztSZssA==", - "peer": true - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -7185,12 +7007,6 @@ "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==", - "peer": true - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -7364,32 +7180,15 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, - "node_modules/psl": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", - "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", - "peer": true, - "dependencies": { - "punycode": "^2.3.1" - }, - "funding": { - "url": "https://github.com/sponsors/lupomontero" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, "engines": { "node": ">=6" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "peer": true - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -7472,15 +7271,6 @@ "node": ">= 0.10" } }, - "node_modules/redeyed": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", - "integrity": "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==", - "peer": true, - "dependencies": { - "esprima": "~4.0.0" - } - }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -7523,21 +7313,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "peer": true - }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -7800,12 +7575,6 @@ "node": ">=10" } }, - "node_modules/set-cookie-parser": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", - "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", - "peer": true - }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -8069,44 +7838,6 @@ "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", "dev": true }, - "node_modules/starknet": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/starknet/-/starknet-6.24.1.tgz", - "integrity": "sha512-g7tiCt73berhcNi41otlN3T3kxZnIvZhMi8WdC21Y6GC6zoQgbI2z1t7JAZF9c4xZiomlanwVnurcpyfEdyMpg==", - "peer": true, - "dependencies": { - "@noble/curves": "1.7.0", - "@noble/hashes": "1.6.0", - "@scure/base": "1.2.1", - "@scure/starknet": "1.1.0", - "abi-wan-kanabi": "^2.2.3", - "fetch-cookie": "~3.0.0", - "isomorphic-fetch": "~3.0.0", - "lossless-json": "^4.0.1", - "pako": "^2.0.4", - "starknet-types-07": "npm:@starknet-io/types-js@^0.7.10", - "ts-mixer": "^6.0.3" - } - }, - "node_modules/starknet-types-07": { - "name": "@starknet-io/types-js", - "version": "0.7.10", - "resolved": "https://registry.npmjs.org/@starknet-io/types-js/-/types-js-0.7.10.tgz", - "integrity": "sha512-1VtCqX4AHWJlRRSYGSn+4X1mqolI1Tdq62IwzoU2vUuEE72S1OlEeGhpvd6XsdqXcfHmVzYfj8k1XtKBQqwo9w==", - "peer": true - }, - "node_modules/starknet/node_modules/@noble/hashes": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.0.tgz", - "integrity": "sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==", - "peer": true, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/stream-chain": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", @@ -8138,26 +7869,6 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "peer": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "peer": true - }, "node_modules/string.prototype.includes": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", @@ -8265,18 +7976,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "peer": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -8467,30 +8166,6 @@ "node": ">=8.0" } }, - "node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", - "peer": true, - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "peer": true, - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -8508,12 +8183,6 @@ "typescript": ">=4.8.4" } }, - "node_modules/ts-mixer": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", - "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", - "peer": true - }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -8667,15 +8336,6 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "peer": true, - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/unrs-resolver": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.7.2.tgz", @@ -8717,16 +8377,6 @@ "punycode": "^2.1.0" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "peer": true, - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/utf-8-validate": { "version": "5.0.10", "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", @@ -8760,12 +8410,6 @@ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, - "node_modules/whatwg-fetch": { - "version": "3.6.20", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", - "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", - "peer": true - }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -8892,23 +8536,6 @@ "node": ">=0.10.0" } }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "peer": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -8943,42 +8570,6 @@ "symbol-observable": "^2.0.3" } }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "peer": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "peer": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "peer": true, - "engines": { - "node": ">=12" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 519bd9d..3989ba3 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@solana/spl-token": "^0.4.13", "@solana/web3.js": "^1.98.2", "axios": "^1.6.8", + "bn.js": "^5.2.2", "next": "15.3.1", "react": "^19.0.0", "react-dom": "^19.0.0" diff --git a/src/app/api/registry/route.ts b/src/app/api/registry/route.ts index 85cfe0e..d3790a7 100644 --- a/src/app/api/registry/route.ts +++ b/src/app/api/registry/route.ts @@ -1,3 +1,4 @@ +import BN from 'bn.js'; import { NextRequest, NextResponse } from 'next/server'; import { Registry } from '@cerc-io/registry-sdk'; import { GasPrice } from '@cosmjs/stargate'; @@ -143,7 +144,9 @@ export async function POST(request: NextRequest) { // Verify Solana payment console.log('Step 0: Verifying Solana token payment...'); - const solanaPaymentResult = await verifyUnusedSolanaPayment(txHash); + const paymentAmount = parseInt(process.env.NEXT_PUBLIC_MIN_SOLANA_PAYMENT_AMOUNT || '50'); + const tokenAmount = new BN(paymentAmount); + const solanaPaymentResult = await verifyUnusedSolanaPayment(txHash, tokenAmount); if (!solanaPaymentResult.valid) { console.error('Solana token payment verification failed:', solanaPaymentResult.reason); @@ -183,7 +186,6 @@ export async function POST(request: NextRequest) { 'REGISTRY_USER_KEY', // This is the same as the prefilled account for LNT transfers 'DEPLOYER_LRN', // LNT transfer variables - 'LACONIC_SERVICE_PROVIDER_ADDRESS', 'LACONIC_TRANSFER_AMOUNT', // Solana/GOR specific variables 'NEXT_PUBLIC_SOLANA_RPC_URL', diff --git a/src/components/PaymentModal.tsx b/src/components/PaymentModal.tsx index e2ea4a9..88398ad 100644 --- a/src/components/PaymentModal.tsx +++ b/src/components/PaymentModal.tsx @@ -1,10 +1,10 @@ 'use client'; import { useState } from 'react'; +import BN from 'bn.js'; import { sendSolanaTokenPayment } from '@/services/solana'; import { PaymentModalProps } from '@/types'; -import { getSolanaConfig } from '@/config'; export default function PaymentModal({ isOpen, @@ -15,10 +15,9 @@ export default function PaymentModal({ const [loading, setLoading] = useState(false); const [error, setError] = useState(''); - // Get configuration - const solanaConfig = getSolanaConfig(); - const amount = solanaConfig.paymentAmount; - const recipientAddress = solanaConfig.tokenRecipientAddress; + // Get configuration from environment variables directly + const amount = parseInt(process.env.NEXT_PUBLIC_MIN_SOLANA_PAYMENT_AMOUNT || '50'); + const recipientAddress = process.env.NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS; const handlePayment = async () => { setLoading(true); @@ -33,7 +32,8 @@ export default function PaymentModal({ return; } - const result = await sendSolanaTokenPayment(walletInfo.publicKey, walletInfo.walletType); + const tokenAmount = new BN(amount); + const result = await sendSolanaTokenPayment(walletInfo.publicKey, tokenAmount, walletInfo.walletType); if (result.success && result.transactionSignature) { onPaymentComplete(result.transactionSignature); diff --git a/src/config/index.ts b/src/config/index.ts index 5381b09..58f8f14 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -43,39 +43,10 @@ export const getAppName = (): string => { return process.env.APP_NAME || 'gor-deploy'; }; -// Solana Token Configuration (configurable) -export const getSolanaConfig = () => { - // Direct validation without loop to avoid the strange undefined behavior - const rpcUrl = process.env.NEXT_PUBLIC_SOLANA_RPC_URL; - const tokenMintAddress = process.env.NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS; - const tokenRecipientAddress = process.env.NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS; - - if (!rpcUrl) { - throw new Error(`Missing environment variable: NEXT_PUBLIC_SOLANA_RPC_URL`); - } - if (!tokenMintAddress) { - throw new Error(`Missing environment variable: NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS`); - } - if (!tokenRecipientAddress) { - throw new Error(`Missing environment variable: NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS`); - } - - return { - rpcUrl, - websocketUrl: process.env.NEXT_PUBLIC_SOLANA_WEBSOCKET_URL, - tokenMintAddress, - tokenRecipientAddress, - tokenSymbol: process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL || 'TOKEN', - tokenName: process.env.NEXT_PUBLIC_SOLANA_TOKEN_NAME || 'Solana Token', - paymentAmount: parseInt(process.env.NEXT_PUBLIC_SOLANA_PAYMENT_AMOUNT || '50'), - tokenDecimals: parseInt(process.env.NEXT_PUBLIC_SOLANA_TOKEN_DECIMALS || '9') - }; -}; export const getLaconicTransferConfig = () => { const requiredEnvVars = [ 'REGISTRY_USER_KEY', // Same account as the registry user - 'LACONIC_SERVICE_PROVIDER_ADDRESS', 'LACONIC_TRANSFER_AMOUNT' ]; @@ -87,7 +58,6 @@ export const getLaconicTransferConfig = () => { return { prefilledPrivateKey: process.env.REGISTRY_USER_KEY!, // Use the same key as registry operations - serviceProviderAddress: process.env.LACONIC_SERVICE_PROVIDER_ADDRESS!, transferAmount: process.env.LACONIC_TRANSFER_AMOUNT! }; }; diff --git a/src/services/solana.ts b/src/services/solana.ts index 1a86653..5c0b8a4 100644 --- a/src/services/solana.ts +++ b/src/services/solana.ts @@ -1,58 +1,53 @@ +import assert from 'assert'; +import BN from 'bn.js'; import { Connection, PublicKey, Transaction } from '@solana/web3.js'; import { TOKEN_PROGRAM_ID, createTransferInstruction, createAssociatedTokenAccountInstruction, - getAssociatedTokenAddress + ASSOCIATED_TOKEN_PROGRAM_ID } from '@solana/spl-token'; -import { getSolanaConfig } from '../config'; import { SolanaPaymentResult, SolanaWalletType, SolanaWalletState } from '../types'; -let connection: Connection | null = null; +assert(process.env.NEXT_PUBLIC_SOLANA_RPC_URL, 'SOLANA_RPC_URL is required'); +assert(process.env.NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS, 'SOLANA_TOKEN_MINT_ADDRESS is required'); +assert(process.env.NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS, 'SOLANA_TOKEN_RECIPIENT_ADDRESS is required'); -const getConnection = (): Connection => { - if (!connection) { - const config = getSolanaConfig(); - connection = new Connection( - config.rpcUrl, - { - commitment: 'confirmed', - wsEndpoint: config.websocketUrl, - confirmTransactionInitialTimeout: 60000, - } - ); - } - return connection; -}; +const TOKEN_MINT = process.env.NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS; +const PAYMENT_RECEIVER_ADDRESS = process.env.NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS; +const SOLANA_RPC_URL = process.env.NEXT_PUBLIC_SOLANA_RPC_URL; + +const connection = new Connection(SOLANA_RPC_URL); export const connectSolanaWallet = async (walletType: SolanaWalletType): Promise => { try { - let wallet: any = null; - if (walletType === 'phantom') { if (!window.phantom?.solana) { throw new Error('Phantom wallet not found. Please install Phantom browser extension.'); } - wallet = window.phantom.solana; + const response = await window.phantom.solana.connect(); + return { + connected: true, + publicKey: response.publicKey.toString(), + walletType + }; } else if (walletType === 'solflare') { if (!window.solflare) { throw new Error('Solflare wallet not found. Please install Solflare browser extension.'); } - wallet = window.solflare; + await window.solflare.connect(); + const publicKey = window.solflare.publicKey?.toString(); + if (!publicKey) { + throw new Error('Failed to get public key from Solflare wallet'); + } + return { + connected: true, + publicKey, + walletType + }; } - if (!wallet) { - throw new Error(`${walletType} wallet not available`); - } - - const response = await wallet.connect(); - const publicKey = response.publicKey.toString(); - - return { - connected: true, - publicKey, - walletType - }; + throw new Error(`Unsupported wallet type: ${walletType}`); } catch (error) { console.error('Failed to connect to Solana wallet:', error); throw error; @@ -77,49 +72,60 @@ export const disconnectSolanaWallet = async (walletType: SolanaWalletType): Prom } }; -export const sendSolanaTokenPayment = async ( +async function findAssociatedTokenAddress( + walletAddress: PublicKey, + tokenMintAddress: PublicKey +): Promise { + return PublicKey.findProgramAddressSync( + [ + walletAddress.toBuffer(), + TOKEN_PROGRAM_ID.toBuffer(), + tokenMintAddress.toBuffer(), + ], + ASSOCIATED_TOKEN_PROGRAM_ID + )[0]; +} + +interface WalletAdapter { + signAndSendTransaction(transaction: Transaction): Promise<{ signature: string }>; +} + +export async function sendSolanaTokenPayment( walletPublicKey: string, + tokenAmount: BN, walletType: SolanaWalletType -): Promise => { +): Promise { try { - const config = getSolanaConfig(); - let wallet: any = null; + let wallet: WalletAdapter | null = null; if (walletType === 'phantom') { - wallet = window.phantom?.solana; + wallet = window.phantom?.solana || null; } else if (walletType === 'solflare') { - wallet = window.solflare; + wallet = window.solflare || null; } if (!wallet) { - return { - success: false, - error: `${walletType} wallet not found` - }; + throw new Error(`${walletType} wallet not found`); } - const connection = getConnection(); const senderPublicKey = new PublicKey(walletPublicKey); - const mintPublicKey = new PublicKey(config.tokenMintAddress); - const receiverPublicKey = new PublicKey(config.tokenRecipientAddress); + const mintPublicKey = new PublicKey(TOKEN_MINT); + const receiverPublicKey = new PublicKey(PAYMENT_RECEIVER_ADDRESS); - console.log(`Processing ${config.tokenSymbol} payment with keys:`, { + console.log('Processing payment with keys:', { sender: senderPublicKey.toBase58(), mint: mintPublicKey.toBase58(), receiver: receiverPublicKey.toBase58(), - amount: config.paymentAmount, - token: config.tokenSymbol }); - // Get associated token addresses - const senderATA = await getAssociatedTokenAddress( - mintPublicKey, - senderPublicKey + const senderATA = await findAssociatedTokenAddress( + senderPublicKey, + mintPublicKey ); - const receiverATA = await getAssociatedTokenAddress( - mintPublicKey, - receiverPublicKey + const receiverATA = await findAssociatedTokenAddress( + receiverPublicKey, + mintPublicKey ); console.log('Token accounts:', { @@ -161,8 +167,7 @@ export const sendSolanaTokenPayment = async ( ); } - // Calculate amount in smallest units (considering decimals) - const amountToSend = BigInt(config.paymentAmount * Math.pow(10, config.tokenDecimals)); + const amountToSend = BigInt(tokenAmount.toString()); // Add transfer instruction transaction.add( @@ -179,7 +184,7 @@ export const sendSolanaTokenPayment = async ( transaction.recentBlockhash = latestBlockhash.blockhash; transaction.feePayer = senderPublicKey; - console.log(`Sending ${config.tokenSymbol} payment transaction...`); + console.log('Sending transaction...'); const { signature } = await wallet.signAndSendTransaction(transaction); console.log('Transaction sent:', signature); @@ -200,7 +205,7 @@ export const sendSolanaTokenPayment = async ( transactionSignature: signature }; } catch (error) { - console.error('Solana token payment error:', error); + console.error('Payment error:', error); return { success: false, error: error instanceof Error ? error.message : 'Payment failed' diff --git a/src/utils/solanaVerify.ts b/src/utils/solanaVerify.ts index fc20581..d5d7288 100644 --- a/src/utils/solanaVerify.ts +++ b/src/utils/solanaVerify.ts @@ -1,32 +1,18 @@ +import assert from 'assert'; +import BN from 'bn.js'; import { Connection } from '@solana/web3.js'; import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; -import { getSolanaConfig } from '../config'; -let connection: Connection | null = null; +assert(process.env.NEXT_PUBLIC_SOLANA_RPC_URL, 'SOLANA_RPC_URL is required'); -const getConnection = (): Connection => { - if (!connection) { - const config = getSolanaConfig(); - connection = new Connection( - config.rpcUrl, - { - commitment: 'confirmed', - confirmTransactionInitialTimeout: 60000, - } - ); - } - return connection; -}; +const SOLANA_RPC_URL = process.env.NEXT_PUBLIC_SOLANA_RPC_URL; -interface SolanaTransactionInfo { - authority: string; - amount: string; -} +const connection = new Connection(SOLANA_RPC_URL); -const extractSolanaTransactionInfo = async (transactionSignature: string): Promise => { - const connection = getConnection(); +// Simplified transaction info extraction following reference implementation +const extractTxInfo = async (transactionSignature: string): Promise<{ authority: string; amount: string }> => { const result = await connection.getParsedTransaction(transactionSignature, 'confirmed'); - + if (!result) { throw new Error('Transaction not found'); } @@ -43,107 +29,6 @@ const extractSolanaTransactionInfo = async (transactionSignature: string): Promi return { authority, amount }; }; -export const verifySolanaPayment = async ( - transactionSignature: string -): Promise<{ - valid: boolean, - reason?: string, - amount?: string, - sender?: string -}> => { - try { - const config = getSolanaConfig(); - const requiredAmountInSmallestUnits = (config.paymentAmount * Math.pow(10, config.tokenDecimals)).toString(); - - const connection = getConnection(); - - // Fetch transaction details - const transactionResult = await connection.getParsedTransaction(transactionSignature, 'confirmed'); - - if (!transactionResult) { - return { - valid: false, - reason: 'Transaction not found on Solana blockchain' - }; - } - - // Check if transaction was successful - if (transactionResult.meta?.err) { - return { - valid: false, - reason: `Transaction failed: ${JSON.stringify(transactionResult.meta.err)}` - }; - } - - // Check transaction timestamp (5-minute window) - const txTimestamp = transactionResult.blockTime ? new Date(transactionResult.blockTime * 1000) : null; - if (!txTimestamp) { - return { - valid: false, - reason: 'Transaction timestamp not available' - }; - } - - const now = new Date(); - const timeDiffMs = now.getTime() - txTimestamp.getTime(); - const timeWindowMs = 5 * 60 * 1000; // 5 minutes - - if (timeDiffMs > timeWindowMs) { - return { - valid: false, - reason: `Transaction is older than 5 minutes (${Math.round(timeDiffMs / 60000)} minutes old)` - }; - } - - // Extract transaction info - const { amount, authority } = await extractSolanaTransactionInfo(transactionSignature); - - // Verify amount - if (parseInt(amount) < parseInt(requiredAmountInSmallestUnits)) { - return { - valid: false, - reason: `Payment amount (${amount}) is less than required (${requiredAmountInSmallestUnits})` - }; - } - - // Verify recipient address by checking the transaction instructions - let foundValidTransfer = false; - - for (const instruction of transactionResult.transaction.message.instructions) { - if ('parsed' in instruction && instruction.programId.equals(TOKEN_PROGRAM_ID)) { - const parsed = instruction.parsed; - if (parsed.type === 'transferChecked' || parsed.type === 'transfer') { - const destination = parsed.info.destination; - - // Verify both amount and destination address - if (parsed.info.amount === amount && destination === config.tokenRecipientAddress) { - foundValidTransfer = true; - break; - } - } - } - } - - if (!foundValidTransfer) { - return { - valid: false, - reason: 'Valid Solana token transfer not found in transaction' - }; - } - - return { - valid: true, - amount, - sender: authority - }; - } catch (error) { - console.error('Error verifying Solana payment:', error); - return { - valid: false, - reason: `Failed to verify transaction: ${error instanceof Error ? error.message : 'Unknown error'}` - }; - } -}; // Helper to track used signatures (simple in-memory store for now) const usedSignatures = new Set(); @@ -157,28 +42,111 @@ export const markSignatureAsUsed = (transactionSignature: string): void => { }; export const verifyUnusedSolanaPayment = async ( - transactionSignature: string -): Promise<{ - valid: boolean, + transactionSignature: string, + tokenAmount: BN +): Promise<{ + valid: boolean, reason?: string, amount?: string, sender?: string }> => { - // Check if signature is already used - if (isSignatureUsed(transactionSignature)) { + try { + // Check if signature is already used + if (isSignatureUsed(transactionSignature)) { + return { + valid: false, + reason: 'Transaction signature has already been used' + }; + } + + // Fetch transaction details + const transactionResult = await connection.getParsedTransaction(transactionSignature, 'confirmed'); + + if (!transactionResult) { + return { + valid: false, + reason: 'Transaction not found on Solana blockchain' + }; + } + + // Check if transaction was successful + if (transactionResult.meta?.err) { + return { + valid: false, + reason: `Transaction failed: ${JSON.stringify(transactionResult.meta.err)}` + }; + } + + // Check transaction timestamp (5-minute window) + const txTimestamp = transactionResult.blockTime ? new Date(transactionResult.blockTime * 1000) : null; + if (!txTimestamp) { + return { + valid: false, + reason: 'Transaction timestamp not available' + }; + } + + const now = new Date(); + const timeDiffMs = now.getTime() - txTimestamp.getTime(); + const timeWindowMs = 5 * 60 * 1000; // 5 minutes + + if (timeDiffMs > timeWindowMs) { + return { + valid: false, + reason: `Transaction is older than 5 minutes (${Math.round(timeDiffMs / 60000)} minutes old)` + }; + } + + // Extract transaction info using simplified approach + const { amount, authority } = await extractTxInfo(transactionSignature); + + // Verify amount using BN comparison like in reference + const transactionAmount = new BN(amount); + if (transactionAmount.lt(tokenAmount)) { + return { + valid: false, + reason: `Payment amount (${amount}) is less than required (${tokenAmount.toString()})` + }; + } + + // Verify recipient address by checking the transaction instructions + let foundValidTransfer = false; + + for (const instruction of transactionResult.transaction.message.instructions) { + if ('parsed' in instruction && instruction.programId.equals(TOKEN_PROGRAM_ID)) { + const parsed = instruction.parsed; + if (parsed.type === 'transferChecked' || parsed.type === 'transfer') { + const destination = parsed.info.destination; + + // Verify both amount and destination address + if (parsed.info.amount === amount && destination === process.env.NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS) { + foundValidTransfer = true; + break; + } + } + } + } + + if (!foundValidTransfer) { + return { + valid: false, + reason: 'Valid Solana token transfer not found in transaction' + }; + } + + // Mark signature as used + markSignatureAsUsed(transactionSignature); + + return { + valid: true, + amount, + sender: authority + }; + } catch (error) { + console.error('Error verifying Solana payment:', error); return { valid: false, - reason: 'Transaction signature has already been used' + reason: `Failed to verify transaction: ${error instanceof Error ? error.message : 'Unknown error'}` }; } - - // Verify the payment - const result = await verifySolanaPayment(transactionSignature); - - // If valid, mark as used - if (result.valid) { - markSignatureAsUsed(transactionSignature); - } - - return result; }; \ No newline at end of file