Add steps to publish PricingRecord and use it for determining cost of deployments (#4)

Part of https://www.notion.so/Laconic-Mainnet-Plan-1eca6b22d47280569cd0d1e6d711d949

Co-authored-by: Shreerang Kale <shreerangkale@gmail.com>
Reviewed-on: #4
Co-authored-by: shreerang <shreerang@noreply.git.vdb.to>
Co-committed-by: shreerang <shreerang@noreply.git.vdb.to>
This commit is contained in:
shreerang 2025-07-25 13:36:35 +00:00 committed by nabarun
parent fbe4eed31d
commit 452c4db5f8
20 changed files with 555 additions and 350 deletions

View File

@ -1,11 +1,10 @@
# Client-side environment variables (must be prefixed with NEXT_PUBLIC_)
# Client-side environment variables must be prefixed with NEXT_PUBLIC_
# Solana Payment Configuration
# TODO: Use different RPC URL or use browser wallet
# TODO: Use different RPC URL
NEXT_PUBLIC_SOLANA_RPC_URL=https://skilled-prettiest-seed.solana-mainnet.quiknode.pro/eeecfebd04e345f69f1900cc3483cbbfea02a158
NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS=71Jvq4Epe2FCJ7JFSF7jLXdNk1Wy4Bhqd9iL6bEFELvg
NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL=GOR
NEXT_PUBLIC_SOLANA_PAYMENT_AMOUNT_USD=5
# Gorbagana Chain Configuration
NEXT_PUBLIC_GORBAGANA_RPC_URL=https://rpc.gorbagana.wtf
@ -17,12 +16,12 @@ NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS=FFDx3SdAEeXrp6BTmStB4BDHpctGsaasZq4FF
# UI Configuration
NEXT_PUBLIC_EXAMPLE_URL=https://git.vdb.to/cerc-io/test-progressive-web-app
# Server-side environment variables
# Laconic Registry Configuration
REGISTRY_CHAIN_ID=laconic-mainnet
REGISTRY_RPC_ENDPOINT=https://laconicd-mainnet-1.laconic.com
REGISTRY_GQL_ENDPOINT=https://laconicd-mainnet-1.laconic.com/graphql
NEXT_PUBLIC_REGISTRY_CHAIN_ID=laconic-mainnet
NEXT_PUBLIC_REGISTRY_RPC_ENDPOINT=https://laconicd-mainnet-1.laconic.com
NEXT_PUBLIC_REGISTRY_GQL_ENDPOINT=https://laconicd-mainnet-1.laconic.com/graphql
NEXT_PUBLIC_ALNT_COST_LRN=lrn://laconic/pricing/alnt
NEXT_PUBLIC_DEPLOYMENT_COST_LRN=lrn://laconic/pricing/webapp-deployment
REGISTRY_GAS_PRICE=0.001
REGISTRY_BOND_ID=
REGISTRY_AUTHORITY=

3
.gitignore vendored
View File

@ -43,3 +43,6 @@ yarn-error.log*
# typescript
*.tsbuildinfo
# Reveal file out dir
out

View File

@ -106,9 +106,9 @@ NEXT_PUBLIC_EXAMPLE_URL=https://git.vdb.to/cerc-io/test-progressive-web-app
### Server-side Variables
```bash
REGISTRY_CHAIN_ID=laconic-mainnet
REGISTRY_GQL_ENDPOINT=https://laconicd-mainnet-1.laconic.com/api
REGISTRY_RPC_ENDPOINT=https://laconicd-mainnet-1.laconic.com
NEXT_PUBLIC_REGISTRY_CHAIN_ID=laconic-mainnet
NEXT_PUBLIC_REGISTRY_GQL_ENDPOINT=https://laconicd-mainnet-1.laconic.com/api
NEXT_PUBLIC_REGISTRY_RPC_ENDPOINT=https://laconicd-mainnet-1.laconic.com
REGISTRY_BOND_ID=<BOND_ID>
REGISTRY_AUTHORITY=<AUTHORITY_NAME>
REGISTRY_USER_KEY=<PRIVATE_KEY>

102
README.md
View File

@ -20,35 +20,9 @@ A simple Next.js frontend that allows users to pay in GOR tokens (configurable S
- Solana wallet browser extension (Phantom or Solflare)
- Access to a Laconic Registry node
## Environment Variables
## Deploy to production
Copy the `.env.local.example` file to `.env.local` and fill in the required variables:
```bash
cp .env.local.example .env.local
```
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_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_DECIMALS` - The number of decimals for the token (e.g., 6)
- `NEXT_PUBLIC_SOLANA_PAYMENT_AMOUNT` - The fixed payment amount required (e.g., 400)
- `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")
Server-side:
- `REGISTRY_CHAIN_ID` - The chain ID for the Laconic Registry
- `REGISTRY_GQL_ENDPOINT` - The GraphQL endpoint for the Laconic Registry
- `REGISTRY_RPC_ENDPOINT` - The RPC endpoint for the Laconic Registry
- `REGISTRY_BOND_ID` - The bond ID to use for Laconic Registry records
- `REGISTRY_AUTHORITY` - The authority for Laconic Registry LRNs
- `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
Follow [these steps](./deploy/README.md) to deploy this app to production
## Installation
@ -58,6 +32,35 @@ npm install
## Development
Copy the `.env.example` file to `.env.local` and fill in the required variables:
```bash
cp .env.example .env.local
```
Required environment variables:
Client-side (must be prefixed with NEXT_PUBLIC_):
- `NEXT_PUBLIC_SOLANA_RPC_URL` - The RPC URL for the Solana blockchain (SPL token transactions)
- `NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS` - The mint address of the SPL token to accept
- `NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL` - The token symbol to display (e.g., "GOR")
- `NEXT_PUBLIC_GORBAGANA_RPC_URL` - The RPC URL for the Gorbagana blockchain (native GOR transactions)
- `NEXT_PUBLIC_ENABLE_NATIVE_GOR_TRANSFER` - Enable native GOR token transfers (true/false)
- `NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS` - The Solana address that will receive token payments
- `NEXT_PUBLIC_EXAMPLE_URL` - Example URL to pre-fill in the URL form
- `NEXT_PUBLIC_REGISTRY_CHAIN_ID` - The laconicd chain ID for the Laconic Registry
- `NEXT_PUBLIC_REGISTRY_RPC_ENDPOINT` - The laconicd RPC endpoint for the Laconic Registry
- `NEXT_PUBLIC_REGISTRY_GQL_ENDPOINT` - The laconicd GraphQL endpoint for the Laconic Registry
- `NEXT_PUBLIC_ALNT_COST_LRN` - LRN for ALNT token pricing
- `NEXT_PUBLIC_DEPLOYMENT_COST_LRN` - LRN for deployment cost pricing
- `NEXT_PUBLIC_DOMAIN_SUFFIX` - Optional suffix to append to DNS names in the UI (e.g. ".example.com")
Server-side:
- `REGISTRY_BOND_ID` - The bond ID to use for Laconic Registry records
- `REGISTRY_AUTHORITY` - The authority for Laconic Registry LRNs
- `REGISTRY_USER_KEY` - The private key for Laconic Registry transactions (also used for LNT transfers)
- `DEPLOYER_LRN` - The LRN of the deployer
```bash
npm run dev
```
@ -130,43 +133,6 @@ This application was built with reference to:
- `snowballtools-base/packages/backend/src/registry.ts`
- Original `hosted-frontends/deploy-atom.sh` (adapted for Solana/GOR)
## Deployment to Production
To deploy this application to production, follow these steps:
1. Clone the repository
2. Install dependencies: `npm install`
3. Create a production build: `npm run build`
4. Set up all required environment variables in your production environment
5. Start the production server: `npm start`
For containerized deployments, you can use the following Dockerfile:
```dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
EXPOSE 3000
ENV NODE_ENV=production
CMD ["npm", "start"]
```
Build and run the Docker container:
```bash
docker build -t gor-deploy .
docker run -p 3000:3000 --env-file .env.production gor-deploy
```
## Known Issues
- You may see a deprecated Buffer() warning during build. This comes from dependencies in the registry-sdk. This doesn't affect functionality.
@ -181,12 +147,6 @@ docker run -p 3000:3000 --env-file .env.production gor-deploy
- **Connection issues**: Ensure the wallet is unlocked and try refreshing the page.
- **Transaction failures**: Check that you have sufficient SOL for transaction fees and enough tokens for the payment.
### Token Configuration
- **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.
### Laconic Registry Issues
- **Failed to create record**: Check that your REGISTRY_USER_KEY and REGISTRY_BOND_ID are correctly set.

View File

@ -4,14 +4,14 @@
### gor-deploy
* Clone the repo:
- Clone the repo:
```bash
git clone git@git.vdb.to:LaconicNetwork/gor-deploy.git
cd gor-deploy/deploy
```
* Build registry CLI image:
- Build registry CLI image:
```bash
docker build -t cerc/laconic-registry-cli .
@ -19,13 +19,31 @@
# Builds image cerc/laconic-registry-cli:latest
```
* Configure `userKey` and `bondId` in the [registry CLI config](./config.yml):
- Configure `userKey` and `bondId` in the [registry CLI config](./config.yml):
- User key should be of the account that owns the `laconic-deploy` authority (owner account address: `laconic1kwx2jm6vscz38qlyujvq6msujmk8l3zangqahs`)
- The bond should also be owned by same user (owned bond's ID: `230cfedda15e78edc8986dfcb870e1b618f65c56e38d2735476d2a8cb3f25e38`)
- If the authority is not available, follow [these steps to reserve a new authority](./publish-pricing.md#reserve-a-new-authority-optional)
```bash
nano config.yml
```
* Add configuration for registry operations:
- This project requires pricing records for cost of deployment and cost of alnt to be published
- Check if these records are available by running following commands:
```bash
# Check if `lrn://laconic/pricing/webapp-deployment` is available
./laconic-cli.sh name resolve lrn://laconic/pricing/webapp-deployment
# Check if `lrn://laconic/pricing/alnt` is available
./laconic-cli.sh name resolve lrn://laconic/pricing/alnt
```
- If these records are not available, [follow these steps to publish them](./publish-pricing.md)
- Add configuration for registry operations:
```bash
cp .registry.env.example .registry.env
@ -34,7 +52,7 @@
nano .registry.env
```
* Add configuration for the app:
- Add configuration for the app:
```bash
curl -s https://git.vdb.to/LaconicNetwork/gor-deploy/src/branch/main/.env.example -o .app.env
@ -43,11 +61,27 @@
nano .app.env
```
- Required environment variables:
Client-side (must be prefixed with NEXT_PUBLIC_):
- `NEXT_PUBLIC_SOLANA_RPC_URL` - The RPC URL for the Solana blockchain (SPL token transactions)
- `NEXT_PUBLIC_GORBAGANA_RPC_URL` - The RPC URL for the Gorbagana blockchain (native GOR transactions)
- `NEXT_PUBLIC_ENABLE_NATIVE_GOR_TRANSFER` - Enable native GOR token transfers (true/false)
- `NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS` - The Solana address that will receive token payments
- `NEXT_PUBLIC_ALNT_COST_LRN` - LRN for ALNT token pricing
- `NEXT_PUBLIC_DEPLOYMENT_COST_LRN` - LRN for deployment cost pricing
- `NEXT_PUBLIC_DOMAIN_SUFFIX` - Suffix to append to DNS names in the UI (e.g. ".example.com")
Server-side:
- `REGISTRY_BOND_ID` - The bond ID to use for Laconic Registry records
- `REGISTRY_AUTHORITY` - The authority for Laconic Registry LRNs
- `REGISTRY_USER_KEY` - The private key for Laconic Registry transactions (also used for LNT transfers)
- `DEPLOYER_LRN` - The LRN of the deployer
## Run
### gor-deploy
- Deploy `gor-deploy` App:
* Deploy `gor-deploy` App:
```bash
# In gor-deploy/deploy dir
docker run -it \
@ -57,13 +91,13 @@
./deploy.sh
```
* Check deployment logs on deployer UI: <https://webapp-deployer-ui.apps.vaasl.io/>
- Check deployment logs on deployer UI: <https://webapp-deployer-ui.apps.vaasl.io/>
* Visit deployed app: <https://gor-deploy.apps.vaasl.io>
- Visit deployed app: <https://gor-deploy.apps.vaasl.io>
### remove deployment
### Remove deployment
* Remove deployment:
- Remove deployment:
```bash
# In gor-deploy/deploy dir

50
deploy/laconic-cli.sh Executable file
View File

@ -0,0 +1,50 @@
#!/bin/bash
# Laconic Registry CLI Docker wrapper script
# This script wraps the Docker command to run laconic registry CLI commands
# Run this script from the deploy directory
# Check if docker is available
if ! command -v docker &> /dev/null; then
echo "Error: Docker is not installed or not in PATH"
exit 1
fi
# Check if the cerc/laconic-registry-cli image exists
if ! docker image inspect cerc/laconic-registry-cli &> /dev/null; then
echo "Error: cerc/laconic-registry-cli Docker image not found"
echo "Please build the image first: docker build -t cerc/laconic-registry-cli ."
exit 1
fi
# Get current directory (should be deploy directory)
CURRENT_DIR="$(pwd)"
PROJECT_ROOT="$(dirname "$CURRENT_DIR")"
# Verify we're in the deploy directory
if [ ! -f "config.yml" ] || [ ! -f "laconic-cli.sh" ]; then
echo "Error: This script must be run from the deploy directory"
echo "Current directory: $CURRENT_DIR"
echo "Please cd to the deploy directory and run: ./laconic-cli.sh"
exit 1
fi
# Set up volume mounts
DEPLOY_MOUNT="-v $CURRENT_DIR:/app/deploy"
OUT_MOUNT=""
# Create out directory if it doesn't exist and always mount it
if [ ! -d "out" ]; then
mkdir -p "out"
fi
OUT_MOUNT="-v $CURRENT_DIR/out:/app/out"
# Run the Docker command with processed arguments
docker run --rm \
--add-host=host.docker.internal:host-gateway \
$DEPLOY_MOUNT \
$OUT_MOUNT \
-w /app/deploy \
cerc/laconic-registry-cli \
laconic registry -c config.yml \
"$@"

147
deploy/publish-pricing.md Normal file
View File

@ -0,0 +1,147 @@
# publish-pricing
## Setup
- Clone the repo:
```bash
git clone git@git.vdb.to:LaconicNetwork/gor-deploy.git
cd gor-deploy/deploy
```
- Build the Docker container:
```bash
docker build -t cerc/laconic-registry-cli .
# Builds image cerc/laconic-registry-cli:latest
```
- Configure `userKey` in the [registry CLI config](./config.yml):
NOTE: The `laconic` authority is required to set the published record names so the user key should be of the account that owns the `laconic` authority (owner account address: `laconic13maulvmjxnyx3g855vk0lsv5aptf3rpxskynef`). If you don't have an authority check [steps to reserve a new authority](#reserve-a-new-authority-optional)
```bash
nano config.yml
```
## Publish Record and Set Record Name
Publishing record requires a bond with enough funds as rent is taken for each record from the bond every year
The rent amount taken is `1000000alnt` so maintaining a bond with about 10x the rent amount i.e `10000000alnt` is recommended
Make sure, the record names are under `laconic` authority
### Publish Record for Cost of alnt
- alnt price calculation (reference: <https://store.laconic.com>):
- Cost of 1 deployment is `12960` in terms of `alnt` or `5` in terms of `USD`
- Hence, cost of 1 `alnt` comes out be `0.000386 USD` rounded off to 6 decimals
- Publish the record:
```bash
./laconic-cli.sh record publish --filename records/alnt-pricing.yml --bond-id <bond-id>
```
- Get record info:
```bash
./laconic-cli.sh record get --id <record-id>
```
- Set record name for cost of alnt record:
```bash
./laconic-cli.sh name set lrn://laconic/pricing/alnt <record-id>
```
### Publish Record for Cost of Deployment
- Publish the record:
```bash
./laconic-cli.sh record publish --filename records/webapp-deployment-pricing.yml --bond-id <bond-id>
```
- Get record info:
```bash
./laconic-cli.sh record get --id <record-id>
```
- Set record name for cost of deployment record
```bash
./laconic-cli.sh name set lrn://laconic/pricing/webapp-deployment <record-id>
```
- Now you should be able to use these records in the app
- You can now continue with [steps to deploy the app](./README.md)
## Create Bond (optional)
- Create bond:
```bash
./laconic-cli.sh bond create --type alnt --quantity 10000000
```
- Get bond info:
```bash
./laconic-cli.sh bond get --id <bond-id>
```
## Reserve a New Authority (optional)
Below steps are used to reserve `laconic` authority
- Reserve authority:
```bash
./laconic-cli.sh authority reserve laconic
```
- After reserving authority, commit phase begins which lasts for 1 minute so please commit the bid following below steps within that time period
- Check authority info:
```bash
./laconic-cli.sh authority whois laconic
```
- Note down auction ID from authority info as it is required in next steps
- Get auction info:
```bash
./laconic-cli.sh auction get <auction-id>
```
- Commit an auction bid:
```bash
# 5000000 alnt is the minimum bid amount for authority auction
./laconic-cli.sh auction bid commit <auction-id> 5000000 alnt
# Example file path inside container
Reveal file: /app/deploy/out/bafyreiay2rccax64yn4ljhvzvm3jkbebvzheyucuma5jlbpzpzd5i5gjuy.json
```
- The reveal phase starts as soon as commit phase ends and lasts for 1 minute so please reveal the bid within this time period
- Reveal bid:
```bash
./laconic-cli.sh auction bid reveal <auction-id> /app/deploy/out/<reaveal-file>.json
```
- Set authority bond after winning auction as it is required to use the published authority:
```bash
./laconic-cli.sh authority bond set laconic <bond-id>
```

View File

@ -0,0 +1,6 @@
record:
type: PricingRecord
for: "alnt"
amount: "0.000386"
currency: "USD"
version: 1.0.0

View File

@ -0,0 +1,6 @@
record:
type: PricingRecord
for: "webapp-deployment"
amount: "12960"
currency: "alnt"
version: 1.0.0

310
package-lock.json generated
View File

@ -25,6 +25,7 @@
"react-dom": "^19.0.0"
},
"devDependencies": {
"@cerc-io/laconic-registry-cli": "^0.2.9",
"@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
@ -515,72 +516,47 @@
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": {
"version": "7.27.2",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
"license": "MIT",
"peer": true,
"node_modules/@cerc-io/laconic-registry-cli": {
"version": "0.2.10",
"resolved": "https://git.vdb.to/api/packages/cerc-io/npm/%40cerc-io%2Flaconic-registry-cli/-/0.2.10/laconic-registry-cli-0.2.10.tgz",
"integrity": "sha512-rwrZhFgYZiMh2k+9E/aiyRhFLApydRUwclATb0f6hsFAkxARuaibHsJNy7eF2N/AQ4d6HAvpkXACJoVrGOppmw==",
"dev": true,
"license": "UNLICENSED",
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/parser": "^7.27.2",
"@babel/types": "^7.27.1"
"@cerc-io/registry-sdk": "^0.2.11",
"@cosmjs/stargate": "^0.32.2",
"fs-extra": "^10.1.0",
"js-yaml": "^3.14.1",
"lodash": "^4.17.21",
"lodash-clean": "^2.2.3",
"yargs": "^17.4.1"
},
"engines": {
"node": ">=6.9.0"
"bin": {
"laconic": "bin/laconic"
}
},
"node_modules/@babel/traverse": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz",
"integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==",
"node_modules/@cerc-io/laconic-registry-cli/node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.0",
"@babel/helper-globals": "^7.28.0",
"@babel/parser": "^7.28.0",
"@babel/template": "^7.27.2",
"@babel/types": "^7.28.0",
"debug": "^4.3.1"
},
"engines": {
"node": ">=6.9.0"
"sprintf-js": "~1.0.2"
}
},
"node_modules/@babel/traverse--for-generate-function-map": {
"name": "@babel/traverse",
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz",
"integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==",
"node_modules/@cerc-io/laconic-registry-cli/node_modules/js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.0",
"@babel/helper-globals": "^7.28.0",
"@babel/parser": "^7.28.0",
"@babel/template": "^7.27.2",
"@babel/types": "^7.28.0",
"debug": "^4.3.1"
"argparse": "^1.0.7",
"esprima": "^4.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/types": {
"version": "7.28.1",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz",
"integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/@cerc-io/registry-sdk": {
@ -5176,17 +5152,11 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/anser": {
"version": "1.4.10",
"resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz",
"integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==",
"license": "MIT",
"peer": true
},
"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==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@ -6072,14 +6042,18 @@
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
},
"node_modules/cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"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==",
"dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/co": {
@ -6794,19 +6768,12 @@
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6"
}
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT",
"peer": true
},
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@ -7638,14 +7605,19 @@
"node": ">= 6"
}
},
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
"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==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">= 0.6"
"node": ">=12"
}
},
"node_modules/fs.realpath": {
@ -7705,20 +7677,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6.9.0"
}
},
"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==",
"dev": true,
"license": "ISC",
"engines": {
"node": "6.* || 8.* || >= 10.*"
@ -8380,6 +8343,7 @@
"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==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@ -9017,6 +8981,19 @@
"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==",
"dev": true,
"license": "MIT",
"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",
@ -9484,6 +9461,16 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash-clean": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/lodash-clean/-/lodash-clean-2.2.3.tgz",
"integrity": "sha512-ioRhn/L0NNKq220nba58FPvjZ+bTdlUCb37+mhlDe4kzIzuPC/prUHLwDM9izeicr/rcnWrn0EanzNxhAbo8oA==",
"dev": true,
"license": "ISC",
"dependencies": {
"lodash": "^4.17.21"
}
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@ -11124,17 +11111,12 @@
"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==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"license": "ISC"
},
"node_modules/resolve": {
"version": "1.22.10",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
@ -11940,6 +11922,7 @@
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
@ -11954,6 +11937,7 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true,
"license": "MIT"
},
"node_modules/string.prototype.includes": {
@ -12067,6 +12051,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
@ -12518,14 +12503,14 @@
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.8"
"node": ">= 10.0.0"
}
},
"node_modules/unrs-resolver": {
@ -12800,9 +12785,10 @@
}
},
"node_modules/wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
@ -12810,7 +12796,10 @@
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=8"
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrappy": {
@ -12862,103 +12851,42 @@
}
},
"node_modules/y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
"license": "ISC"
},
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"dev": true,
"license": "ISC",
"peer": true
"engines": {
"node": ">=10"
}
},
"node_modules/yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"dev": true,
"license": "MIT",
"dependencies": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.2"
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=8"
"node": ">=12"
}
},
"node_modules/yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true,
"license": "ISC",
"dependencies": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/yargs/node_modules/find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"license": "MIT",
"dependencies": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yargs/node_modules/locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"license": "MIT",
"dependencies": {
"p-locate": "^4.1.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yargs/node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"license": "MIT",
"dependencies": {
"p-try": "^2.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/yargs/node_modules/p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"license": "MIT",
"dependencies": {
"p-limit": "^2.2.0"
},
"engines": {
"node": ">=8"
"node": ">=12"
}
},
"node_modules/yocto-queue": {

View File

@ -6,7 +6,8 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"laconic": "laconic registry -c deploy/config.yml"
},
"dependencies": {
"@cerc-io/registry-sdk": "^0.2.11",
@ -28,6 +29,7 @@
"devDependencies": {
"@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4",
"@cerc-io/laconic-registry-cli": "^0.2.9",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",

View File

@ -3,16 +3,16 @@ import { NextRequest, NextResponse } from 'next/server';
import axios from 'axios';
import assert from 'assert';
import { GasPrice } from '@cosmjs/stargate';
import { Connection } from '@solana/web3.js';
import { DENOM as ALNT_DENOM } from '@cerc-io/registry-sdk';
import { verifyUnusedSolanaPayment } from '@/utils/solana-verify';
import { transferLNTTokens } from '@/services/laconic-transfer';
import { getRegistry, getRegistryConfig } from '@/config';
import { getRequiredTokenInfo, RequiredTokenInfo } from '@/services/jupiter-price';
import { IS_NAT_GOR_TRANSFER_ENABLED, SOLANA_GOR_MINT_ADDRESS } from '@/constants/payments';
import { SOLANA_GOR_MINT_ADDRESS } from '@/constants/payments';
import { PaymentMethod } from '@/types';
import { getCostOfDeployment } from '@/services/registry';
import { IS_NAT_GOR_TRANSFER_ENABLED } from '@/utils/gorbagana';
assert(process.env.NEXT_PUBLIC_SOLANA_RPC_URL, 'SOLANA_RPC_URL is required');
assert(!IS_NAT_GOR_TRANSFER_ENABLED || process.env.NEXT_PUBLIC_GORBAGANA_RPC_URL, 'GORBAGANA_RPC_URL is required when NAT GOR transfer is enabled');
@ -215,14 +215,10 @@ export async function POST(request: NextRequest) {
}, { status: 400 });
}
// Verify Solana payment based on method
console.log(`Step 0: Verifying Solana ${paymentMethod} payment...`);
// Calculate expected token amount based on current price
// Verify Solana payment
console.log('Step 0: Verifying Solana token payment...');
let requiredTokenInfo: RequiredTokenInfo;
const targetUsdAmount = parseFloat(process.env.NEXT_PUBLIC_SOLANA_PAYMENT_AMOUNT_USD!);
const targetUsdAmount = await getCostOfDeployment();
const mintAddress = process.env.NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS!;
try {
@ -284,9 +280,9 @@ export async function POST(request: NextRequest) {
// Validate required environment variables for Solana payments
const requiredEnvVars = [
'REGISTRY_CHAIN_ID',
'REGISTRY_GQL_ENDPOINT',
'REGISTRY_RPC_ENDPOINT',
'NEXT_PUBLIC_REGISTRY_CHAIN_ID',
'NEXT_PUBLIC_REGISTRY_GQL_ENDPOINT',
'NEXT_PUBLIC_REGISTRY_RPC_ENDPOINT',
'REGISTRY_BOND_ID',
'REGISTRY_AUTHORITY',
'REGISTRY_USER_KEY', // This is the same as the prefilled account for LNT transfers
@ -354,10 +350,6 @@ export async function POST(request: NextRequest) {
const deployerLrn = process.env.DEPLOYER_LRN!;
// Create Registry client instance
const gasPrice = GasPrice.fromString(config.fee.gasPrice + ALNT_DENOM);
console.log('Using manual gas price:', gasPrice);
const registry = getRegistry()
// Create LRN for the application with commit hash

View File

@ -10,13 +10,21 @@ import { BackpackWalletName } from '@solana/wallet-adapter-backpack';
import URLForm from '@/components/URLForm';
import StatusDisplay from '@/components/StatusDisplay';
import { createApplicationDeploymentRequest } from '@/services/registry';
import { IS_NAT_GOR_TRANSFER_ENABLED, PAYMENT_METHOD_LABELS } from '@/constants/payments';
import { PAYMENT_METHOD_LABELS } from '@/constants/payments';
import { usePaymentMethod } from '@/contexts/PaymentMethodContext';
import { PaymentMethod } from '@/types';
import { IS_NAT_GOR_TRANSFER_ENABLED } from '@/utils/gorbagana';
// Dynamically import components to avoid SSR issues with browser APIs
const PaymentModal = dynamic(() => import('@/components/PaymentModal'), { ssr: false });
// Use following curl request to get Gorbagana chain genesis hash:
// curl https://rpc.gorbagana.wtf \
// -X POST \
// -H "Content-Type: application/json" \
// --data '{"jsonrpc":"2.0","id":1,"method":"getGenesisHash"}'
//
// RPC endpoint reference: https://docs.gorbagana.wtf/testnet-v2-devnet.html
const GORBAGANA_GENESIS_HASH = '533uBE9RRquhTBqEX58oV52FdTTsReMdAvaUvP6hNjsn';
export default function Home() {

View File

@ -9,8 +9,10 @@ import { useConnection, useWallet } from '@solana/wallet-adapter-react';
import { sendSolanaPayment } from '@/services/solana';
import { getRequiredTokenInfo, RequiredTokenInfo } from '@/services/jupiter-price';
import { PaymentMethod, PaymentModalProps, PaymentRequest } from '@/types';
import { IS_NAT_GOR_TRANSFER_ENABLED, PAYMENT_METHOD_LABELS, SOLANA_GOR_MINT_ADDRESS } from '@/constants/payments';
import { PAYMENT_METHOD_LABELS, SOLANA_GOR_MINT_ADDRESS } from '@/constants/payments';
import { usePaymentMethod } from '@/contexts/PaymentMethodContext';
import { getCostOfDeployment } from '@/services/registry';
import { IS_NAT_GOR_TRANSFER_ENABLED } from '@/utils/gorbagana';
assert(!IS_NAT_GOR_TRANSFER_ENABLED || process.env.NEXT_PUBLIC_GORBAGANA_RPC_URL, 'GORBAGANA_RPC_URL is required when NAT GOR transfer is enabled');
@ -23,28 +25,32 @@ export default function PaymentModal({
onPaymentComplete,
}: PaymentModalProps) {
const { selectedPaymentMethod: paymentMethod } = usePaymentMethod();
const { connection: solanaConnection } = useConnection();
const { wallet, publicKey } = useWallet();
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [tokenAmount, setTokenAmount] = useState<number>(0);
const [tokenDecimals, setTokenDecimals] = useState<number>(6); // Default fallback
const [loadingPrice, setLoadingPrice] = useState(false);
const [loadingPrice, setLoadingPrice] = useState(true);
const [deploymentCost, setDeploymentCost] = useState<number | null>(null);
const { wallet, publicKey } = useWallet();
useEffect(() => {
const getDeploymentCostInfo = async () => {
const cost = await getCostOfDeployment();
setDeploymentCost(cost);
}
getDeploymentCostInfo();
}, []);
// Get configuration from environment variables
const targetUsdAmount = parseFloat(process.env.NEXT_PUBLIC_SOLANA_PAYMENT_AMOUNT_USD!);
const mintAddress = process.env.NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS!;
const tokenSymbol = process.env.NEXT_PUBLIC_SOLANA_TOKEN_SYMBOL;
// Fetch payment amount based on USD price for both payment methods
useEffect(() => {
if (!isOpen || !paymentMethod) {
setLoadingPrice(false);
return;
}
if (!isOpen || !deploymentCost || !paymentMethod) return;
const fetchPaymentAmount = async () => {
setLoadingPrice(true);
@ -54,10 +60,10 @@ export default function PaymentModal({
let requiredTokenInfo: RequiredTokenInfo
if (paymentMethod === PaymentMethod.NAT_GOR) {
// Fetch native GOR amount using solana GOR token price
requiredTokenInfo = await getRequiredTokenInfo(targetUsdAmount, SOLANA_GOR_MINT_ADDRESS);
requiredTokenInfo = await getRequiredTokenInfo(deploymentCost, SOLANA_GOR_MINT_ADDRESS);
} else if (paymentMethod === PaymentMethod.SPL_TOKEN) {
// Fetch SPL token amount using token mint price
requiredTokenInfo = await getRequiredTokenInfo(targetUsdAmount, mintAddress);
requiredTokenInfo = await getRequiredTokenInfo(deploymentCost, mintAddress);
} else {
setError('Invalid payment method');
return;
@ -74,7 +80,7 @@ export default function PaymentModal({
};
fetchPaymentAmount();
}, [isOpen, paymentMethod, targetUsdAmount, mintAddress]);
}, [isOpen, paymentMethod, deploymentCost, mintAddress]);
// Initialize state when modal opens
useEffect(() => {
@ -184,19 +190,32 @@ export default function PaymentModal({
</label>
<div className="space-y-3">
<div className="relative">
<input
type="text"
value={targetUsdAmount}
disabled={true}
className="w-full p-3 pr-12 rounded-md"
style={{
background: 'var(--card-bg)',
{loadingPrice ? (
<div className="w-full p-3 rounded-md flex items-center" style={{
background: 'var(--muted-light)',
border: '1px solid var(--input-border)',
color: 'var(--foreground)',
opacity: '0.7'
}}
readOnly
/>
color: 'var(--muted)'
}}>
<svg className="animate-spin h-4 w-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</div>
) : (
<input
type="text"
value={`$${deploymentCost ? deploymentCost.toPrecision(2) : null}`}
disabled={true}
className="w-full p-3 pr-12 rounded-md"
style={{
background: 'var(--card-bg)',
border: '1px solid var(--input-border)',
color: 'var(--foreground)',
opacity: '0.7'
}}
readOnly
/>
)}
<div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
<span className="text-sm font-medium" style={{ color: 'var(--muted)' }}>USD</span>
</div>

View File

@ -1,30 +1,43 @@
import { Registry, DENOM as ALNT_DENOM } from '@cerc-io/registry-sdk';
import { GasPrice } from '@cosmjs/stargate';
import { DENOM as ALNT_DENOM, Registry } from '@cerc-io/registry-sdk';
import { RegistryConfig } from '../types';
import { GasPrice } from '@cosmjs/stargate';
let registryInstance: Registry | null = null;
export const getRegistry = (): Registry => {
if (!registryInstance) {
const config = getRegistryConfig();
const gasPrice = GasPrice.fromString(config.fee.gasPrice + ALNT_DENOM);
const config = getClientRegistryConfig();
const REGISTRY_GAS_PRICE = process.env.REGISTRY_GAS_PRICE;
const gasPrice = REGISTRY_GAS_PRICE ? GasPrice.fromString( REGISTRY_GAS_PRICE + ALNT_DENOM) : undefined;
registryInstance = new Registry(
config.gqlEndpoint,
config.rpcEndpoint,
{ chainId: config.chainId, gasPrice }
{
chainId: config.chainId,
gasPrice,
}
);
}
return registryInstance;
};
export const getClientRegistryConfig = () => {
return {
chainId: process.env.NEXT_PUBLIC_REGISTRY_CHAIN_ID!,
rpcEndpoint: process.env.NEXT_PUBLIC_REGISTRY_RPC_ENDPOINT!,
gqlEndpoint: process.env.NEXT_PUBLIC_REGISTRY_GQL_ENDPOINT!,
};
};
export const getRegistryConfig = (): RegistryConfig => {
// Validate required environment variables
const requiredEnvVars = [
'REGISTRY_CHAIN_ID',
'REGISTRY_GQL_ENDPOINT',
'REGISTRY_RPC_ENDPOINT',
'NEXT_PUBLIC_REGISTRY_CHAIN_ID',
'NEXT_PUBLIC_REGISTRY_GQL_ENDPOINT',
'NEXT_PUBLIC_REGISTRY_RPC_ENDPOINT',
'REGISTRY_BOND_ID',
'REGISTRY_AUTHORITY',
'REGISTRY_USER_KEY'
@ -37,14 +50,11 @@ export const getRegistryConfig = (): RegistryConfig => {
}
return {
chainId: process.env.REGISTRY_CHAIN_ID!,
rpcEndpoint: process.env.REGISTRY_RPC_ENDPOINT!,
gqlEndpoint: process.env.REGISTRY_GQL_ENDPOINT!,
chainId: process.env.NEXT_PUBLIC_REGISTRY_CHAIN_ID!,
rpcEndpoint: process.env.NEXT_PUBLIC_REGISTRY_RPC_ENDPOINT!,
gqlEndpoint: process.env.NEXT_PUBLIC_REGISTRY_GQL_ENDPOINT!,
bondId: process.env.REGISTRY_BOND_ID!,
authority: process.env.REGISTRY_AUTHORITY!,
privateKey: process.env.REGISTRY_USER_KEY!,
fee: {
gasPrice: process.env.REGISTRY_GAS_PRICE || '0.001',
},
};
};

View File

@ -7,5 +7,3 @@ export const PAYMENT_METHOD_LABELS: Record<PaymentMethod, string> = {
};
export const SOLANA_GOR_MINT_ADDRESS = '71Jvq4Epe2FCJ7JFSF7jLXdNk1Wy4Bhqd9iL6bEFELvg';
export const IS_NAT_GOR_TRANSFER_ENABLED = process.env.NEXT_PUBLIC_ENABLE_NATIVE_GOR_TRANSFER === "true";

View File

@ -86,7 +86,6 @@ const getAccount = async (accountPrivateKey: string): Promise<Account> => {
return account;
}
const sendTokensToAccount = async (
senderPrivateKey: string,
receiverAddress: string,

View File

@ -1,5 +1,13 @@
import { CreateRecordResponse } from '../types';
import { PaymentMethod } from '../types';
import assert from 'assert';
import { getRegistry } from '@/config';
import { CreateRecordResponse, PricingRecordAttributes, PaymentMethod } from '../types';
assert(process.env.NEXT_PUBLIC_DEPLOYMENT_COST_LRN, 'DEPLOYMENT_COST_LRN is required');
assert(process.env.NEXT_PUBLIC_ALNT_COST_LRN, 'ALNT_COST_LRN is required');
const DEPLOYMENT_COST_LRN = process.env.NEXT_PUBLIC_DEPLOYMENT_COST_LRN;
const ALNT_COST_LRN = process.env.NEXT_PUBLIC_ALNT_COST_LRN;
export const createApplicationDeploymentRequest = async (
url: string,
@ -51,3 +59,33 @@ export const createApplicationDeploymentRequest = async (
}
};
const resolvePricingRecordLrns = async (lrns: string[]): Promise<PricingRecordAttributes[]> => {
const registry = getRegistry();
const result = await registry.resolveNames(lrns);
const pricingRecordsAttributes: PricingRecordAttributes[] = result.map((record: any) => {
return record.attributes
});
return pricingRecordsAttributes;
};
export const getCostOfDeployment = async (): Promise<number> => {
const resolvedRecords = await resolvePricingRecordLrns([ALNT_COST_LRN, DEPLOYMENT_COST_LRN]);
console.log('resolvedRecords:', resolvedRecords);
// Find the ALNT price record (USD per ALNT)
const alntPriceRecord = resolvedRecords[0];
// Find the deployment cost record (ALNT cost for webapp-deployment)
const deploymentCostRecord = resolvedRecords[1];
if (!alntPriceRecord || !deploymentCostRecord) {
throw new Error('Required pricing records not found');
}
// Convert strings to numbers for calculation
const alntPriceUsd = parseFloat(alntPriceRecord.amount); // USD per ALNT
const deploymentCostAlnt = parseFloat(deploymentCostRecord.amount); // ALNT required
// Calculate deployment cost in USD: (ALNT required) * (USD per ALNT)
return deploymentCostAlnt * alntPriceUsd;
}

View File

@ -10,9 +10,6 @@ export interface RegistryConfig {
bondId: string;
authority: string;
privateKey: string;
fee: {
gasPrice: string;
};
}
export interface CreateRecordResponse {
@ -53,3 +50,11 @@ export interface LaconicTransferResult {
transactionHash?: string;
error?: string;
}
export interface PricingRecordAttributes {
amount: string;
currency: string;
for: string;
type: string;
version: string;
}

1
src/utils/gorbagana.ts Normal file
View File

@ -0,0 +1 @@
export const IS_NAT_GOR_TRANSFER_ENABLED = process.env.NEXT_PUBLIC_ENABLE_NATIVE_GOR_TRANSFER === "true";