Update payment flow

This commit is contained in:
Shreerang Kale 2025-07-18 16:07:14 +05:30
parent 4c291dc307
commit f5d82d5292
9 changed files with 197 additions and 667 deletions

View File

@ -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

View File

@ -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.

427
package-lock.json generated
View File

@ -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",

View File

@ -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"

View File

@ -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',

View File

@ -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);

View File

@ -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!
};
};

View File

@ -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<SolanaWalletState> => {
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<PublicKey> {
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<SolanaPaymentResult> => {
): Promise<SolanaPaymentResult> {
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'

View File

@ -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<SolanaTransactionInfo> => {
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<string>();
@ -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;
};