From 452c4db5f8392186ba6dba7eff06c277a98ac873 Mon Sep 17 00:00:00 2001 From: shreerang Date: Fri, 25 Jul 2025 13:36:35 +0000 Subject: [PATCH] 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 Reviewed-on: https://git.vdb.to/LaconicNetwork/gor-deploy/pulls/4 Co-authored-by: shreerang Co-committed-by: shreerang --- .env.example | 15 +- .gitignore | 3 + CLAUDE.md | 6 +- README.md | 102 ++---- deploy/README.md | 56 +++- deploy/laconic-cli.sh | 50 +++ deploy/publish-pricing.md | 147 +++++++++ deploy/records/alnt-pricing.yml | 6 + deploy/records/webapp-deployment-pricing.yml | 6 + package-lock.json | 310 +++++++------------ package.json | 4 +- src/app/api/registry/route.ts | 26 +- src/app/page.tsx | 10 +- src/components/PaymentModal.tsx | 69 +++-- src/config/index.ts | 38 ++- src/constants/payments.ts | 2 - src/services/laconic-transfer.ts | 1 - src/services/registry.ts | 42 ++- src/types/index.ts | 11 +- src/utils/gorbagana.ts | 1 + 20 files changed, 555 insertions(+), 350 deletions(-) create mode 100755 deploy/laconic-cli.sh create mode 100644 deploy/publish-pricing.md create mode 100644 deploy/records/alnt-pricing.yml create mode 100644 deploy/records/webapp-deployment-pricing.yml create mode 100644 src/utils/gorbagana.ts diff --git a/.env.example b/.env.example index fb8fd7a..6f7943c 100644 --- a/.env.example +++ b/.env.example @@ -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= diff --git a/.gitignore b/.gitignore index 1ec5286..e6d23e2 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,6 @@ yarn-error.log* # typescript *.tsbuildinfo + +# Reveal file out dir +out \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index dc64720..10e3e28 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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= REGISTRY_AUTHORITY= REGISTRY_USER_KEY= diff --git a/README.md b/README.md index 67f91f7..135e5b8 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/deploy/README.md b/deploy/README.md index 5a22711..5ac2ef3 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -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: +- Check deployment logs on deployer UI: -* Visit deployed app: +- Visit deployed app: -### remove deployment +### Remove deployment -* Remove deployment: +- Remove deployment: ```bash # In gor-deploy/deploy dir diff --git a/deploy/laconic-cli.sh b/deploy/laconic-cli.sh new file mode 100755 index 0000000..2325582 --- /dev/null +++ b/deploy/laconic-cli.sh @@ -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 \ + "$@" diff --git a/deploy/publish-pricing.md b/deploy/publish-pricing.md new file mode 100644 index 0000000..c943dae --- /dev/null +++ b/deploy/publish-pricing.md @@ -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: ): + - 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 + ``` + +- Get record info: + + ```bash + ./laconic-cli.sh record get --id + ``` + +- Set record name for cost of alnt record: + + ```bash + ./laconic-cli.sh name set lrn://laconic/pricing/alnt + ``` + +### Publish Record for Cost of Deployment + +- Publish the record: + + ```bash + ./laconic-cli.sh record publish --filename records/webapp-deployment-pricing.yml --bond-id + ``` + +- Get record info: + + ```bash + ./laconic-cli.sh record get --id + ``` + +- Set record name for cost of deployment record + + ```bash + ./laconic-cli.sh name set lrn://laconic/pricing/webapp-deployment + ``` + +- 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 + ``` + +## 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 + ``` + +- Commit an auction bid: + + ```bash + # 5000000 alnt is the minimum bid amount for authority auction + + ./laconic-cli.sh auction bid commit 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 /app/deploy/out/.json + ``` + +- Set authority bond after winning auction as it is required to use the published authority: + + ```bash + ./laconic-cli.sh authority bond set laconic + ``` diff --git a/deploy/records/alnt-pricing.yml b/deploy/records/alnt-pricing.yml new file mode 100644 index 0000000..e3529e1 --- /dev/null +++ b/deploy/records/alnt-pricing.yml @@ -0,0 +1,6 @@ +record: + type: PricingRecord + for: "alnt" + amount: "0.000386" + currency: "USD" + version: 1.0.0 diff --git a/deploy/records/webapp-deployment-pricing.yml b/deploy/records/webapp-deployment-pricing.yml new file mode 100644 index 0000000..abfc3a6 --- /dev/null +++ b/deploy/records/webapp-deployment-pricing.yml @@ -0,0 +1,6 @@ +record: + type: PricingRecord + for: "webapp-deployment" + amount: "12960" + currency: "alnt" + version: 1.0.0 diff --git a/package-lock.json b/package-lock.json index 0b0c62b..fe3efda 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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": { diff --git a/package.json b/package.json index 24ac37b..223eb09 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/app/api/registry/route.ts b/src/app/api/registry/route.ts index 8e2b74c..4d0c429 100644 --- a/src/app/api/registry/route.ts +++ b/src/app/api/registry/route.ts @@ -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 diff --git a/src/app/page.tsx b/src/app/page.tsx index 0aa153e..bd80876 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -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() { diff --git a/src/components/PaymentModal.tsx b/src/components/PaymentModal.tsx index 9bcd753..654c193 100644 --- a/src/components/PaymentModal.tsx +++ b/src/components/PaymentModal.tsx @@ -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(0); const [tokenDecimals, setTokenDecimals] = useState(6); // Default fallback - const [loadingPrice, setLoadingPrice] = useState(false); + const [loadingPrice, setLoadingPrice] = useState(true); + const [deploymentCost, setDeploymentCost] = useState(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({
- + color: 'var(--muted)' + }}> + + + + +
+ ) : ( + + )}
USD
diff --git a/src/config/index.ts b/src/config/index.ts index 5a729d1..473f4fd 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -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', - }, }; }; diff --git a/src/constants/payments.ts b/src/constants/payments.ts index 4b67298..7e0c683 100644 --- a/src/constants/payments.ts +++ b/src/constants/payments.ts @@ -7,5 +7,3 @@ export const PAYMENT_METHOD_LABELS: Record = { }; export const SOLANA_GOR_MINT_ADDRESS = '71Jvq4Epe2FCJ7JFSF7jLXdNk1Wy4Bhqd9iL6bEFELvg'; - -export const IS_NAT_GOR_TRANSFER_ENABLED = process.env.NEXT_PUBLIC_ENABLE_NATIVE_GOR_TRANSFER === "true"; diff --git a/src/services/laconic-transfer.ts b/src/services/laconic-transfer.ts index 1a59d05..3d858b4 100644 --- a/src/services/laconic-transfer.ts +++ b/src/services/laconic-transfer.ts @@ -86,7 +86,6 @@ const getAccount = async (accountPrivateKey: string): Promise => { return account; } - const sendTokensToAccount = async ( senderPrivateKey: string, receiverAddress: string, diff --git a/src/services/registry.ts b/src/services/registry.ts index 7b65499..55b914e 100644 --- a/src/services/registry.ts +++ b/src/services/registry.ts @@ -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 => { + 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 => { + 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; +} diff --git a/src/types/index.ts b/src/types/index.ts index 86203f5..16258a6 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -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; +} diff --git a/src/utils/gorbagana.ts b/src/utils/gorbagana.ts new file mode 100644 index 0000000..8555e72 --- /dev/null +++ b/src/utils/gorbagana.ts @@ -0,0 +1 @@ +export const IS_NAT_GOR_TRANSFER_ENABLED = process.env.NEXT_PUBLIC_ENABLE_NATIVE_GOR_TRANSFER === "true";