Integrate solana token payments
This commit is contained in:
parent
765fe3d49a
commit
982232545a
40
.env.local.example
Normal file
40
.env.local.example
Normal file
@ -0,0 +1,40 @@
|
||||
# Client-side environment variables (must be prefixed with NEXT_PUBLIC_)
|
||||
|
||||
# ATOM Payment Configuration
|
||||
NEXT_PUBLIC_RECIPIENT_ADDRESS=cosmos1yourrealaddress
|
||||
NEXT_PUBLIC_COSMOS_RPC_URL=https://rpc.cosmos.network
|
||||
NEXT_PUBLIC_COSMOS_API_URL=https://api.cosmos.network
|
||||
NEXT_PUBLIC_COSMOS_CHAIN_ID=cosmoshub-4
|
||||
|
||||
# Solana/GOR Payment Configuration
|
||||
NEXT_PUBLIC_SOLANA_RPC_URL=https://skilled-prettiest-seed.solana-mainnet.quiknode.pro/eeecfebd04e345f69f1900cc3483cbbfea02a158
|
||||
NEXT_PUBLIC_SOLANA_WEBSOCKET_URL=wss://skilled-prettiest-seed.solana-mainnet.quiknode.pro/
|
||||
NEXT_PUBLIC_GOR_MINT_ADDRESS=71Jvq4Epe2FCJ7JFSF7jLXdNk1Wy4Bhqd9iL6bEFELvg
|
||||
NEXT_PUBLIC_GOR_RECIPIENT_ADDRESS=
|
||||
|
||||
# UI Configuration (optional)
|
||||
NEXT_PUBLIC_DOMAIN_SUFFIX=
|
||||
NEXT_PUBLIC_EXAMPLE_URL=https://github.com/cerc-io/laconic-registry-cli
|
||||
|
||||
# Server-side environment variables
|
||||
|
||||
# Laconic Registry Configuration
|
||||
REGISTRY_CHAIN_ID=
|
||||
REGISTRY_GQL_ENDPOINT=
|
||||
REGISTRY_RPC_ENDPOINT=
|
||||
REGISTRY_BOND_ID=
|
||||
REGISTRY_AUTHORITY=
|
||||
REGISTRY_USER_KEY=
|
||||
REGISTRY_GAS=900000
|
||||
REGISTRY_FEES=900000alnt
|
||||
REGISTRY_GAS_PRICE=0.001
|
||||
|
||||
# Application Configuration
|
||||
APP_NAME=atom-deploy
|
||||
DEPLOYER_LRN=
|
||||
|
||||
# LNT Transfer Configuration (required for both ATOM and GOR flows)
|
||||
# Note: REGISTRY_USER_KEY is used as the prefilled account for LNT transfers
|
||||
# TODO: Use deployer lrn to determine the address
|
||||
LACONIC_SERVICE_PROVIDER_ADDRESS=
|
||||
LACONIC_TRANSFER_AMOUNT=1000000alnt
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -31,7 +31,7 @@ yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
.env.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
@ -102,7 +102,7 @@ The DNS names are generated with the following format:
|
||||
{sanitized-url-name}-{short-commit-hash}-{random-salt}{domain-suffix}
|
||||
```
|
||||
|
||||
For example:
|
||||
For example:
|
||||
- Basic DNS: `github-abc123-xyz789`
|
||||
- With domain suffix: `github-abc123-xyz789.example.com`
|
||||
|
||||
|
||||
617
package-lock.json
generated
617
package-lock.json
generated
@ -11,7 +11,10 @@
|
||||
"@cerc-io/registry-sdk": "^0.2.11",
|
||||
"@cosmjs/stargate": "^0.32.3",
|
||||
"@keplr-wallet/types": "^0.12.71",
|
||||
"@solana/spl-token": "^0.4.13",
|
||||
"@solana/web3.js": "^1.98.2",
|
||||
"axios": "^1.6.8",
|
||||
"bn.js": "^5.2.2",
|
||||
"next": "15.3.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
@ -40,6 +43,15 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.27.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
|
||||
"integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cerc-io/registry-sdk": {
|
||||
"version": "0.2.11",
|
||||
"resolved": "https://git.vdb.to/api/packages/cerc-io/npm/%40cerc-io%2Fregistry-sdk/-/0.2.11/registry-sdk-0.2.11.tgz",
|
||||
@ -1918,7 +1930,6 @@
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.7.0.tgz",
|
||||
"integrity": "sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@noble/hashes": "1.6.0"
|
||||
},
|
||||
@ -1933,7 +1944,6 @@
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.0.tgz",
|
||||
"integrity": "sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^14.21.3 || >=16"
|
||||
},
|
||||
@ -2104,6 +2114,290 @@
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@solana/buffer-layout": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz",
|
||||
"integrity": "sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer": "~6.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=5.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@solana/buffer-layout-utils": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@solana/buffer-layout-utils/-/buffer-layout-utils-0.2.0.tgz",
|
||||
"integrity": "sha512-szG4sxgJGktbuZYDg2FfNmkMi0DYQoVjN2h7ta1W1hPrwzarcFLBq9UpX1UjNXsNpT9dn+chgprtWGioUAr4/g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@solana/buffer-layout": "^4.0.0",
|
||||
"@solana/web3.js": "^1.32.0",
|
||||
"bigint-buffer": "^1.1.5",
|
||||
"bignumber.js": "^9.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@solana/codecs": {
|
||||
"version": "2.0.0-rc.1",
|
||||
"resolved": "https://registry.npmjs.org/@solana/codecs/-/codecs-2.0.0-rc.1.tgz",
|
||||
"integrity": "sha512-qxoR7VybNJixV51L0G1RD2boZTcxmwUWnKCaJJExQ5qNKwbpSyDdWfFJfM5JhGyKe9DnPVOZB+JHWXnpbZBqrQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@solana/codecs-core": "2.0.0-rc.1",
|
||||
"@solana/codecs-data-structures": "2.0.0-rc.1",
|
||||
"@solana/codecs-numbers": "2.0.0-rc.1",
|
||||
"@solana/codecs-strings": "2.0.0-rc.1",
|
||||
"@solana/options": "2.0.0-rc.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/@solana/codecs-core": {
|
||||
"version": "2.0.0-rc.1",
|
||||
"resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.0.0-rc.1.tgz",
|
||||
"integrity": "sha512-bauxqMfSs8EHD0JKESaNmNuNvkvHSuN3bbWAF5RjOfDu2PugxHrvRebmYauvSumZ3cTfQ4HJJX6PG5rN852qyQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@solana/errors": "2.0.0-rc.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/@solana/codecs-data-structures": {
|
||||
"version": "2.0.0-rc.1",
|
||||
"resolved": "https://registry.npmjs.org/@solana/codecs-data-structures/-/codecs-data-structures-2.0.0-rc.1.tgz",
|
||||
"integrity": "sha512-rinCv0RrAVJ9rE/rmaibWJQxMwC5lSaORSZuwjopSUE6T0nb/MVg6Z1siNCXhh/HFTOg0l8bNvZHgBcN/yvXog==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@solana/codecs-core": "2.0.0-rc.1",
|
||||
"@solana/codecs-numbers": "2.0.0-rc.1",
|
||||
"@solana/errors": "2.0.0-rc.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/@solana/codecs-numbers": {
|
||||
"version": "2.0.0-rc.1",
|
||||
"resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.0.0-rc.1.tgz",
|
||||
"integrity": "sha512-J5i5mOkvukXn8E3Z7sGIPxsThRCgSdgTWJDQeZvucQ9PT6Y3HiVXJ0pcWiOWAoQ3RX8e/f4I3IC+wE6pZiJzDQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@solana/codecs-core": "2.0.0-rc.1",
|
||||
"@solana/errors": "2.0.0-rc.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/@solana/codecs-strings": {
|
||||
"version": "2.0.0-rc.1",
|
||||
"resolved": "https://registry.npmjs.org/@solana/codecs-strings/-/codecs-strings-2.0.0-rc.1.tgz",
|
||||
"integrity": "sha512-9/wPhw8TbGRTt6mHC4Zz1RqOnuPTqq1Nb4EyuvpZ39GW6O2t2Q7Q0XxiB3+BdoEjwA2XgPw6e2iRfvYgqty44g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@solana/codecs-core": "2.0.0-rc.1",
|
||||
"@solana/codecs-numbers": "2.0.0-rc.1",
|
||||
"@solana/errors": "2.0.0-rc.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"fastestsmallesttextencoderdecoder": "^1.0.22",
|
||||
"typescript": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/@solana/errors": {
|
||||
"version": "2.0.0-rc.1",
|
||||
"resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.0.0-rc.1.tgz",
|
||||
"integrity": "sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chalk": "^5.3.0",
|
||||
"commander": "^12.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"errors": "bin/cli.mjs"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/@solana/errors/node_modules/chalk": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
|
||||
"integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@solana/options": {
|
||||
"version": "2.0.0-rc.1",
|
||||
"resolved": "https://registry.npmjs.org/@solana/options/-/options-2.0.0-rc.1.tgz",
|
||||
"integrity": "sha512-mLUcR9mZ3qfHlmMnREdIFPf9dpMc/Bl66tLSOOWxw4ml5xMT2ohFn7WGqoKcu/UHkT9CrC6+amEdqCNvUqI7AA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@solana/codecs-core": "2.0.0-rc.1",
|
||||
"@solana/codecs-data-structures": "2.0.0-rc.1",
|
||||
"@solana/codecs-numbers": "2.0.0-rc.1",
|
||||
"@solana/codecs-strings": "2.0.0-rc.1",
|
||||
"@solana/errors": "2.0.0-rc.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/@solana/spl-token": {
|
||||
"version": "0.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.4.13.tgz",
|
||||
"integrity": "sha512-cite/pYWQZZVvLbg5lsodSovbetK/eA24gaR0eeUeMuBAMNrT8XFCwaygKy0N2WSg3gSyjjNpIeAGBAKZaY/1w==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@solana/buffer-layout": "^4.0.0",
|
||||
"@solana/buffer-layout-utils": "^0.2.0",
|
||||
"@solana/spl-token-group": "^0.0.7",
|
||||
"@solana/spl-token-metadata": "^0.1.6",
|
||||
"buffer": "^6.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@solana/web3.js": "^1.95.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@solana/spl-token-group": {
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@solana/spl-token-group/-/spl-token-group-0.0.7.tgz",
|
||||
"integrity": "sha512-V1N/iX7Cr7H0uazWUT2uk27TMqlqedpXHRqqAbVO2gvmJyT0E0ummMEAVQeXZ05ZhQ/xF39DLSdBp90XebWEug==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@solana/codecs": "2.0.0-rc.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@solana/web3.js": "^1.95.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@solana/spl-token-metadata": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@solana/spl-token-metadata/-/spl-token-metadata-0.1.6.tgz",
|
||||
"integrity": "sha512-7sMt1rsm/zQOQcUWllQX9mD2O6KhSAtY1hFR2hfFwgqfFWzSY9E9GDvFVNYUI1F0iQKcm6HmePU9QbKRXTEBiA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@solana/codecs": "2.0.0-rc.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@solana/web3.js": "^1.95.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@solana/web3.js": {
|
||||
"version": "1.98.2",
|
||||
"resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.98.2.tgz",
|
||||
"integrity": "sha512-BqVwEG+TaG2yCkBMbD3C4hdpustR4FpuUFRPUmqRZYYlPI9Hg4XMWxHWOWRzHE9Lkc9NDjzXFX7lDXSgzC7R1A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.25.0",
|
||||
"@noble/curves": "^1.4.2",
|
||||
"@noble/hashes": "^1.4.0",
|
||||
"@solana/buffer-layout": "^4.0.1",
|
||||
"@solana/codecs-numbers": "^2.1.0",
|
||||
"agentkeepalive": "^4.5.0",
|
||||
"bn.js": "^5.2.1",
|
||||
"borsh": "^0.7.0",
|
||||
"bs58": "^4.0.1",
|
||||
"buffer": "6.0.3",
|
||||
"fast-stable-stringify": "^1.0.0",
|
||||
"jayson": "^4.1.1",
|
||||
"node-fetch": "^2.7.0",
|
||||
"rpc-websockets": "^9.0.2",
|
||||
"superstruct": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@solana/web3.js/node_modules/@solana/codecs-core": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.3.0.tgz",
|
||||
"integrity": "sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@solana/errors": "2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.18.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@solana/web3.js/node_modules/@solana/codecs-numbers": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.3.0.tgz",
|
||||
"integrity": "sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@solana/codecs-core": "2.3.0",
|
||||
"@solana/errors": "2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.18.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@solana/web3.js/node_modules/@solana/errors": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.3.0.tgz",
|
||||
"integrity": "sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chalk": "^5.4.1",
|
||||
"commander": "^14.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"errors": "bin/cli.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.18.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@solana/web3.js/node_modules/chalk": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
|
||||
"integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@solana/web3.js/node_modules/commander": {
|
||||
"version": "14.0.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz",
|
||||
"integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/counter": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||
@ -2468,6 +2762,15 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/connect": {
|
||||
"version": "3.4.38",
|
||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
|
||||
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
|
||||
@ -2538,6 +2841,21 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/uuid": {
|
||||
"version": "8.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
||||
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "7.4.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz",
|
||||
"integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.31.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.1.tgz",
|
||||
@ -3027,6 +3345,18 @@
|
||||
"resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz",
|
||||
"integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw=="
|
||||
},
|
||||
"node_modules/agentkeepalive": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz",
|
||||
"integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"humanize-ms": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
@ -3345,6 +3675,28 @@
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/bigint-buffer": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/bigint-buffer/-/bigint-buffer-1.1.5.tgz",
|
||||
"integrity": "sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"bindings": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bignumber.js": {
|
||||
"version": "9.3.1",
|
||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
|
||||
"integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/bindings": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
@ -3390,7 +3742,19 @@
|
||||
"node_modules/bn.js": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz",
|
||||
"integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw=="
|
||||
"integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/borsh": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz",
|
||||
"integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"bn.js": "^5.2.0",
|
||||
"bs58": "^4.0.0",
|
||||
"text-encoding-utf-8": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
@ -3477,6 +3841,20 @@
|
||||
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
|
||||
"integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ=="
|
||||
},
|
||||
"node_modules/bufferutil": {
|
||||
"version": "4.0.9",
|
||||
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.9.tgz",
|
||||
"integrity": "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"node-gyp-build": "^4.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/busboy": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||
@ -3694,6 +4072,15 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
|
||||
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@ -3875,6 +4262,18 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/delay": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz",
|
||||
"integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
@ -4117,6 +4516,21 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/es6-promise": {
|
||||
"version": "4.2.8",
|
||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
|
||||
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/es6-promisify": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
|
||||
"integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es6-promise": "^4.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||
@ -4670,6 +5084,12 @@
|
||||
"npm": ">=3"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/evp_bytestokey": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
|
||||
@ -4679,6 +5099,14 @@
|
||||
"safe-buffer": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/eyes": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz",
|
||||
"integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==",
|
||||
"engines": {
|
||||
"node": "> 0.1.90"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
@ -4724,6 +5152,19 @@
|
||||
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-stable-stringify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz",
|
||||
"integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fastestsmallesttextencoderdecoder": {
|
||||
"version": "1.0.22",
|
||||
"resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz",
|
||||
"integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==",
|
||||
"license": "CC0-1.0",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.19.1",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
|
||||
@ -5190,6 +5631,15 @@
|
||||
"minimalistic-crypto-utils": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/humanize-ms": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
|
||||
"integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
@ -5713,6 +6163,44 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/jayson": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/jayson/-/jayson-4.2.0.tgz",
|
||||
"integrity": "sha512-VfJ9t1YLwacIubLhONk0KFeosUBwstRWQ0IRT1KDjEjnVnSOVHC3uwugyV7L0c7R9lpVyrUGT2XWiBA1UTtpyg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/connect": "^3.4.33",
|
||||
"@types/node": "^12.12.54",
|
||||
"@types/ws": "^7.4.4",
|
||||
"commander": "^2.20.3",
|
||||
"delay": "^5.0.0",
|
||||
"es6-promisify": "^5.0.0",
|
||||
"eyes": "^0.1.8",
|
||||
"isomorphic-ws": "^4.0.1",
|
||||
"json-stringify-safe": "^5.0.1",
|
||||
"stream-json": "^1.9.1",
|
||||
"uuid": "^8.3.2",
|
||||
"ws": "^7.5.10"
|
||||
},
|
||||
"bin": {
|
||||
"jayson": "bin/jayson.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jayson/node_modules/@types/node": {
|
||||
"version": "12.20.55",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
|
||||
"integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jayson/node_modules/commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jiti": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
|
||||
@ -5768,6 +6256,12 @@
|
||||
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/json-stringify-safe": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
||||
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
@ -6341,8 +6835,7 @@
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node_modules/multiformats": {
|
||||
"version": "9.9.0",
|
||||
@ -6481,7 +6974,6 @@
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
@ -7121,6 +7613,59 @@
|
||||
"rlp": "bin/rlp"
|
||||
}
|
||||
},
|
||||
"node_modules/rpc-websockets": {
|
||||
"version": "9.1.1",
|
||||
"resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-9.1.1.tgz",
|
||||
"integrity": "sha512-1IXGM/TfPT6nfYMIXkJdzn+L4JEsmb0FL1O2OBjaH03V3yuUDdKFulGLMFG6ErV+8pZ5HVC0limve01RyO+saA==",
|
||||
"license": "LGPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@swc/helpers": "^0.5.11",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/ws": "^8.2.2",
|
||||
"buffer": "^6.0.3",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"uuid": "^8.3.2",
|
||||
"ws": "^8.5.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/kozjak"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": "^5.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/rpc-websockets/node_modules/@types/ws": {
|
||||
"version": "8.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
||||
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/rpc-websockets/node_modules/ws": {
|
||||
"version": "8.18.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
|
||||
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/run-parallel": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||
@ -7562,6 +8107,21 @@
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/stream-chain": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz",
|
||||
"integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/stream-json": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz",
|
||||
"integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"stream-chain": "^2.2.5"
|
||||
}
|
||||
},
|
||||
"node_modules/streamsearch": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
|
||||
@ -7772,6 +8332,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/superstruct": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz",
|
||||
"integrity": "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
@ -7818,6 +8387,11 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/text-encoding-utf-8": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz",
|
||||
"integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg=="
|
||||
},
|
||||
"node_modules/tiny-secp256k1": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.7.tgz",
|
||||
@ -7920,8 +8494,7 @@
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||
"peer": true
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"node_modules/ts-api-utils": {
|
||||
"version": "2.1.0",
|
||||
@ -8063,7 +8636,6 @@
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@ -8155,16 +8727,38 @@
|
||||
"requires-port": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/utf-8-validate": {
|
||||
"version": "5.0.10",
|
||||
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
|
||||
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"node-gyp-build": "^4.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||
"peer": true
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||
},
|
||||
"node_modules/whatwg-fetch": {
|
||||
"version": "3.6.20",
|
||||
@ -8176,7 +8770,6 @@
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
|
||||
@ -12,7 +12,10 @@
|
||||
"@cerc-io/registry-sdk": "^0.2.11",
|
||||
"@cosmjs/stargate": "^0.32.3",
|
||||
"@keplr-wallet/types": "^0.12.71",
|
||||
"@solana/spl-token": "^0.4.13",
|
||||
"@solana/web3.js": "^1.98.2",
|
||||
"axios": "^1.6.8",
|
||||
"bn.js": "^5.2.2",
|
||||
"next": "15.3.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
|
||||
@ -2,13 +2,15 @@ import { NextRequest, NextResponse } from 'next/server';
|
||||
import { Account, Registry, parseGasAndFees } from '@cerc-io/registry-sdk';
|
||||
import { GasPrice } from '@cosmjs/stargate';
|
||||
import axios from 'axios';
|
||||
import { verifyUnusedSolanaPayment } from '@/utils/solanaVerify';
|
||||
import { transferLNTTokens } from '@/services/laconicTransfer';
|
||||
|
||||
// Sleep helper function
|
||||
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
// ATOM payment verification function
|
||||
const verifyAtomPayment = async (txHash: string): Promise<{
|
||||
valid: boolean,
|
||||
const verifyAtomPayment = async (txHash: string): Promise<{
|
||||
valid: boolean,
|
||||
reason?: string,
|
||||
amount?: string,
|
||||
sender?: string
|
||||
@ -16,18 +18,18 @@ const verifyAtomPayment = async (txHash: string): Promise<{
|
||||
try {
|
||||
const apiEndpoint = process.env.NEXT_PUBLIC_COSMOS_API_URL;
|
||||
const recipientAddress = process.env.NEXT_PUBLIC_RECIPIENT_ADDRESS;
|
||||
const minPaymentUAtom = '100000'; // 0.1 ATOM in uatom
|
||||
|
||||
const minPaymentUAtom = '100000'; // 0.1 ATOM in uatom
|
||||
|
||||
if (!apiEndpoint) {
|
||||
return {
|
||||
valid: false,
|
||||
return {
|
||||
valid: false,
|
||||
reason: 'ATOM API endpoint not configured'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (!recipientAddress) {
|
||||
return {
|
||||
valid: false,
|
||||
return {
|
||||
valid: false,
|
||||
reason: 'ATOM recipient address not configured'
|
||||
};
|
||||
}
|
||||
@ -36,8 +38,8 @@ const verifyAtomPayment = async (txHash: string): Promise<{
|
||||
const response = await axios.get(`${apiEndpoint}/cosmos/tx/v1beta1/txs/${txHash}`);
|
||||
|
||||
if (!response.data || !response.data.tx || !response.data.tx_response) {
|
||||
return {
|
||||
valid: false,
|
||||
return {
|
||||
valid: false,
|
||||
reason: 'Invalid transaction data from API endpoint'
|
||||
};
|
||||
}
|
||||
@ -45,8 +47,8 @@ const verifyAtomPayment = async (txHash: string): Promise<{
|
||||
// Check if transaction was successful
|
||||
const txResponse = response.data.tx_response;
|
||||
if (txResponse.code !== 0) {
|
||||
return {
|
||||
valid: false,
|
||||
return {
|
||||
valid: false,
|
||||
reason: `Transaction failed with code ${txResponse.code}: ${txResponse.raw_log}`
|
||||
};
|
||||
}
|
||||
@ -56,10 +58,10 @@ const verifyAtomPayment = async (txHash: string): Promise<{
|
||||
const now = new Date();
|
||||
const timeDiffMs = now.getTime() - txTimestamp.getTime();
|
||||
const timeWindowMs = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
|
||||
if (timeDiffMs > timeWindowMs) {
|
||||
return {
|
||||
valid: false,
|
||||
return {
|
||||
valid: false,
|
||||
reason: `Transaction is older than 5 minutes (${Math.round(timeDiffMs / 60000)} minutes old)`
|
||||
};
|
||||
}
|
||||
@ -69,7 +71,7 @@ const verifyAtomPayment = async (txHash: string): Promise<{
|
||||
let foundValidPayment = false;
|
||||
let paymentAmountUAtom = '';
|
||||
let sender = '';
|
||||
|
||||
|
||||
// Get the sender address from the first signer
|
||||
if (tx.auth_info && tx.auth_info.signer_infos && tx.auth_info.signer_infos.length > 0) {
|
||||
sender = tx.auth_info.signer_infos[0].public_key.address || '';
|
||||
@ -80,10 +82,10 @@ const verifyAtomPayment = async (txHash: string): Promise<{
|
||||
if (msg['@type'] === '/cosmos.bank.v1beta1.MsgSend') {
|
||||
if (msg.to_address === recipientAddress) {
|
||||
for (const coin of msg.amount) {
|
||||
if (coin.denom === 'uatom') {
|
||||
if (coin.denom === 'uatom') {
|
||||
// Get the amount in uatom
|
||||
paymentAmountUAtom = coin.amount;
|
||||
|
||||
|
||||
if (parseInt(paymentAmountUAtom) >= parseInt(minPaymentUAtom)) {
|
||||
foundValidPayment = true;
|
||||
}
|
||||
@ -95,8 +97,8 @@ const verifyAtomPayment = async (txHash: string): Promise<{
|
||||
}
|
||||
|
||||
if (!foundValidPayment) {
|
||||
return {
|
||||
valid: false,
|
||||
return {
|
||||
valid: false,
|
||||
reason: `Payment amount (${paymentAmountUAtom || '0'}uatom) is less than required (${minPaymentUAtom}uatom) or not sent to the correct address (${recipientAddress})`
|
||||
};
|
||||
}
|
||||
@ -108,9 +110,9 @@ const verifyAtomPayment = async (txHash: string): Promise<{
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error verifying ATOM payment:', error);
|
||||
return {
|
||||
valid: false,
|
||||
reason: `Failed to verify transaction: ${error.message || 'Unknown error'}`
|
||||
return {
|
||||
valid: false,
|
||||
reason: `Failed to verify transaction: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
};
|
||||
}
|
||||
};
|
||||
@ -120,16 +122,16 @@ const extractRepoInfo = (url: string): { repoName: string, repoUrl: string, prov
|
||||
try {
|
||||
const parsedUrl = new URL(url);
|
||||
const pathParts = parsedUrl.pathname.split('/').filter(part => part);
|
||||
|
||||
|
||||
// GitHub repository URL pattern
|
||||
if (parsedUrl.hostname === 'github.com' && pathParts.length >= 2) {
|
||||
return {
|
||||
repoName: pathParts[1],
|
||||
repoName: pathParts[1],
|
||||
repoUrl: `https://github.com/${pathParts[0]}/${pathParts[1]}`,
|
||||
provider: 'github'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// GitLab repository URL pattern
|
||||
if ((parsedUrl.hostname === 'gitlab.com' || parsedUrl.hostname.includes('gitlab')) && pathParts.length >= 2) {
|
||||
return {
|
||||
@ -138,7 +140,7 @@ const extractRepoInfo = (url: string): { repoName: string, repoUrl: string, prov
|
||||
provider: 'gitlab'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Bitbucket repository URL pattern
|
||||
if (parsedUrl.hostname === 'bitbucket.org' && pathParts.length >= 2) {
|
||||
return {
|
||||
@ -147,7 +149,7 @@ const extractRepoInfo = (url: string): { repoName: string, repoUrl: string, prov
|
||||
provider: 'bitbucket'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// For other URLs, try to extract a meaningful name from the hostname
|
||||
const hostnameWithoutTLD = parsedUrl.hostname.split('.')[0];
|
||||
return {
|
||||
@ -175,7 +177,7 @@ const fetchLatestCommitHash = async (repoUrl: string, provider: string): Promise
|
||||
if (match) {
|
||||
const [, owner, repo] = match;
|
||||
const apiUrl = `https://api.github.com/repos/${owner}/${repo}/commits/main`;
|
||||
|
||||
|
||||
const response = await axios.get(apiUrl);
|
||||
if (response.data && response.data.sha) {
|
||||
// Return both full hash and short hash (7 characters)
|
||||
@ -186,7 +188,7 @@ const fetchLatestCommitHash = async (repoUrl: string, provider: string): Promise
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// For non-GitHub repositories or if fetching fails, return a default value
|
||||
return {
|
||||
fullHash: 'main',
|
||||
@ -208,100 +210,160 @@ const registryTransactionWithRetry = async (
|
||||
delay = 1000
|
||||
): Promise<unknown> => {
|
||||
let lastError;
|
||||
|
||||
|
||||
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
||||
try {
|
||||
return await txFn();
|
||||
} catch (error) {
|
||||
console.error(`Transaction attempt ${attempt + 1} failed:`, error);
|
||||
lastError = error;
|
||||
|
||||
|
||||
if (attempt < maxRetries - 1) {
|
||||
await sleep(delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
throw lastError;
|
||||
};
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
// First check if the request body is valid JSON
|
||||
let url, txHash;
|
||||
let url, txHash, paymentType;
|
||||
try {
|
||||
const body = await request.json();
|
||||
url = body.url;
|
||||
txHash = body.txHash;
|
||||
|
||||
paymentType = body.paymentType || 'ATOM'; // Default to ATOM for backward compatibility
|
||||
|
||||
if (!url || !txHash) {
|
||||
return NextResponse.json({
|
||||
status: 'error',
|
||||
message: 'Missing required fields: url and txHash are required'
|
||||
return NextResponse.json({
|
||||
status: 'error',
|
||||
message: 'Missing required fields: url and txHash are required'
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
if (!['ATOM', 'GOR'].includes(paymentType)) {
|
||||
return NextResponse.json({
|
||||
status: 'error',
|
||||
message: 'Invalid payment type. Must be ATOM or GOR'
|
||||
}, { status: 400 });
|
||||
}
|
||||
} catch (error) {
|
||||
return NextResponse.json({
|
||||
status: 'error',
|
||||
message: 'Invalid JSON in request body'
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
// First, verify the ATOM payment before doing anything else
|
||||
console.log('Step 0: Verifying ATOM payment...');
|
||||
const paymentVerificationResult = await verifyAtomPayment(txHash);
|
||||
|
||||
if (!paymentVerificationResult.valid) {
|
||||
console.error('ATOM payment verification failed:', paymentVerificationResult.reason);
|
||||
return NextResponse.json({
|
||||
status: 'error',
|
||||
message: `Payment verification failed: ${paymentVerificationResult.reason}`
|
||||
message: 'Invalid JSON in request body'
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
console.log('ATOM payment verified successfully:', {
|
||||
amount: paymentVerificationResult.amount,
|
||||
sender: paymentVerificationResult.sender
|
||||
});
|
||||
|
||||
// Validate required environment variables
|
||||
const requiredEnvVars = [
|
||||
// Verify payment based on type
|
||||
if (paymentType === 'ATOM') {
|
||||
console.log('Step 0: Verifying ATOM payment...');
|
||||
const paymentVerificationResult = await verifyAtomPayment(txHash);
|
||||
|
||||
if (!paymentVerificationResult.valid) {
|
||||
console.error('ATOM payment verification failed:', paymentVerificationResult.reason);
|
||||
return NextResponse.json({
|
||||
status: 'error',
|
||||
message: `Payment verification failed: ${paymentVerificationResult.reason}`
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
console.log('ATOM payment verified successfully:', {
|
||||
amount: paymentVerificationResult.amount,
|
||||
sender: paymentVerificationResult.sender
|
||||
});
|
||||
} else if (paymentType === 'GOR') {
|
||||
console.log('Step 0: Verifying GOR payment...');
|
||||
const gorPaymentResult = await verifyUnusedSolanaPayment(txHash);
|
||||
|
||||
if (!gorPaymentResult.valid) {
|
||||
console.error('GOR payment verification failed:', gorPaymentResult.reason);
|
||||
return NextResponse.json({
|
||||
status: 'error',
|
||||
message: `Payment verification failed: ${gorPaymentResult.reason}`
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
console.log('GOR payment verified successfully:', {
|
||||
amount: gorPaymentResult.amount,
|
||||
sender: gorPaymentResult.sender
|
||||
});
|
||||
}
|
||||
|
||||
// For both payment types, perform LNT transfer after payment verification
|
||||
console.log('Step 0.5: Performing LNT transfer from prefilled account to service provider...');
|
||||
const lntTransferResult = await transferLNTTokens();
|
||||
|
||||
if (!lntTransferResult.success) {
|
||||
console.error('LNT transfer failed:', lntTransferResult.error);
|
||||
return NextResponse.json({
|
||||
status: 'error',
|
||||
message: `LNT transfer failed: ${lntTransferResult.error}`
|
||||
}, { status: 500 });
|
||||
}
|
||||
|
||||
console.log('LNT transfer completed:', lntTransferResult.transactionHash);
|
||||
const finalTxHash = lntTransferResult.transactionHash!; // Use LNT transfer hash for registry
|
||||
|
||||
// Validate required environment variables based on payment type
|
||||
const baseRequiredEnvVars = [
|
||||
'REGISTRY_CHAIN_ID',
|
||||
'REGISTRY_GQL_ENDPOINT',
|
||||
'REGISTRY_RPC_ENDPOINT',
|
||||
'REGISTRY_BOND_ID',
|
||||
'REGISTRY_AUTHORITY',
|
||||
'REGISTRY_USER_KEY',
|
||||
'REGISTRY_USER_KEY', // This is the same as the prefilled account for LNT transfers
|
||||
'DEPLOYER_LRN',
|
||||
// LNT transfer variables now required for both payment types
|
||||
'LACONIC_SERVICE_PROVIDER_ADDRESS',
|
||||
'LACONIC_TRANSFER_AMOUNT'
|
||||
];
|
||||
|
||||
const atomRequiredEnvVars = [
|
||||
'NEXT_PUBLIC_RECIPIENT_ADDRESS',
|
||||
'NEXT_PUBLIC_COSMOS_API_URL'
|
||||
];
|
||||
|
||||
const gorRequiredEnvVars = [
|
||||
'NEXT_PUBLIC_SOLANA_RPC_URL',
|
||||
'NEXT_PUBLIC_GOR_MINT_ADDRESS',
|
||||
'NEXT_PUBLIC_GOR_RECIPIENT_ADDRESS'
|
||||
];
|
||||
|
||||
let requiredEnvVars = [...baseRequiredEnvVars];
|
||||
if (paymentType === 'ATOM') {
|
||||
requiredEnvVars = [...requiredEnvVars, ...atomRequiredEnvVars];
|
||||
} else if (paymentType === 'GOR') {
|
||||
requiredEnvVars = [...requiredEnvVars, ...gorRequiredEnvVars];
|
||||
}
|
||||
|
||||
for (const envVar of requiredEnvVars) {
|
||||
if (!process.env[envVar]) {
|
||||
console.error(`Missing environment variable: ${envVar}`);
|
||||
return NextResponse.json({
|
||||
status: 'error',
|
||||
message: `Server configuration error: Missing environment variable: ${envVar}`
|
||||
return NextResponse.json({
|
||||
status: 'error',
|
||||
message: `Server configuration error: Missing environment variable: ${envVar}`
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Extract repository information from URL
|
||||
const { repoName, repoUrl, provider } = extractRepoInfo(url);
|
||||
console.log(`Extracted repo info - Name: ${repoName}, URL: ${repoUrl}, Provider: ${provider}`);
|
||||
|
||||
|
||||
// Fetch latest commit hash (or default to 'main' if unable to fetch)
|
||||
const { fullHash, shortHash } = await fetchLatestCommitHash(repoUrl, provider);
|
||||
console.log(`Using commit hash - Full: ${fullHash}, Short: ${shortHash}`);
|
||||
|
||||
|
||||
// Use the repository name as the app name
|
||||
const appName = repoName;
|
||||
console.log(`Using app name: ${appName}`);
|
||||
|
||||
|
||||
// Sanitize the app name to ensure it's DNS-compatible (only alphanumeric and dashes)
|
||||
const sanitizedAppName = appName.replace(/[^a-zA-Z0-9-]/g, '-').toLowerCase();
|
||||
|
||||
|
||||
// Generate a random salt (6 alphanumeric characters) to prevent name collisions
|
||||
const generateSalt = (): string => {
|
||||
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||
@ -309,16 +371,16 @@ export async function POST(request: NextRequest) {
|
||||
};
|
||||
const salt = generateSalt();
|
||||
console.log(`Generated salt: ${salt}`);
|
||||
|
||||
|
||||
// Create DNS name in format: app_name-shortcommithash-salt
|
||||
const dnsName = `${sanitizedAppName}-${shortHash}-${salt}`;
|
||||
console.log(`DNS name with salt: ${dnsName} (sanitized from: ${appName})`);
|
||||
|
||||
|
||||
// Ensure the DNS name doesn't have consecutive dashes or start/end with a dash
|
||||
let cleanDnsName = dnsName
|
||||
.replace(/--+/g, '-') // Replace consecutive dashes with a single dash
|
||||
.replace(/^-+|-+$/g, ''); // Remove leading and trailing dashes
|
||||
|
||||
|
||||
// Ensure DNS name is valid (63 chars max per label, all lowercase, starts with a letter)
|
||||
if (cleanDnsName.length > 63) {
|
||||
// If too long, truncate but preserve both the commit hash and salt parts
|
||||
@ -326,15 +388,15 @@ export async function POST(request: NextRequest) {
|
||||
const maxAppNameLength = 63 - suffixPart.length;
|
||||
cleanDnsName = sanitizedAppName.substring(0, maxAppNameLength) + suffixPart;
|
||||
}
|
||||
|
||||
|
||||
// If the DNS name ended up empty (unlikely) or doesn't start with a letter (possible),
|
||||
// add a prefix to make it valid
|
||||
if (!cleanDnsName || !/^[a-z]/.test(cleanDnsName)) {
|
||||
cleanDnsName = `app-${cleanDnsName}`;
|
||||
}
|
||||
|
||||
|
||||
console.log(`Final DNS name with salt: ${cleanDnsName}`);
|
||||
|
||||
|
||||
// Set up Registry config
|
||||
const config = {
|
||||
chainId: process.env.REGISTRY_CHAIN_ID!,
|
||||
@ -349,31 +411,31 @@ export async function POST(request: NextRequest) {
|
||||
gasPrice: '0.001alnt', // Hardcoded valid gas price string with denomination
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
console.log('Registry config:', {
|
||||
...config,
|
||||
privateKey: '[REDACTED]', // Don't log the private key
|
||||
});
|
||||
|
||||
|
||||
const deployerLrn = process.env.DEPLOYER_LRN!;
|
||||
|
||||
|
||||
// Create Registry client instance
|
||||
const gasPrice = GasPrice.fromString('0.001alnt');
|
||||
console.log('Using manual gas price:', gasPrice);
|
||||
|
||||
|
||||
const registry = new Registry(
|
||||
config.gqlEndpoint,
|
||||
config.rpcEndpoint,
|
||||
{ chainId: config.chainId, gasPrice }
|
||||
);
|
||||
|
||||
|
||||
// Create LRN for the application with commit hash and salt
|
||||
// We already have the salt from earlier, so we use it directly
|
||||
const lrn = `lrn://${config.authority}/applications/${appName}-${shortHash}-${salt}`;
|
||||
|
||||
|
||||
// Get current timestamp for the meta note
|
||||
const timestamp = new Date().toUTCString();
|
||||
|
||||
|
||||
// Step 1: Create and publish ApplicationRecord first
|
||||
console.log('Step 1: Publishing ApplicationRecord...');
|
||||
const applicationRecord = {
|
||||
@ -385,19 +447,19 @@ export async function POST(request: NextRequest) {
|
||||
repository_ref: fullHash,
|
||||
app_version: '0.0.1'
|
||||
};
|
||||
|
||||
|
||||
// Create fee for transaction directly
|
||||
const fee = {
|
||||
amount: [{ denom: 'alnt', amount: process.env.REGISTRY_FEES?.replace('alnt', '') || '900000' }],
|
||||
gas: process.env.REGISTRY_GAS || '900000',
|
||||
};
|
||||
|
||||
|
||||
console.log('Application record data:', applicationRecord);
|
||||
|
||||
|
||||
// Publish the application record
|
||||
let applicationRecordId;
|
||||
try {
|
||||
const appRecordResult = await registryTransactionWithRetry(() =>
|
||||
const appRecordResult = await registryTransactionWithRetry(() =>
|
||||
registry.setRecord(
|
||||
{
|
||||
privateKey: config.privateKey,
|
||||
@ -408,10 +470,10 @@ export async function POST(request: NextRequest) {
|
||||
fee
|
||||
)
|
||||
) as { id?: string };
|
||||
|
||||
|
||||
applicationRecordId = appRecordResult.id;
|
||||
console.log('Application record published with ID:', applicationRecordId);
|
||||
|
||||
|
||||
if (!applicationRecordId) {
|
||||
return NextResponse.json({
|
||||
status: 'error',
|
||||
@ -425,7 +487,7 @@ export async function POST(request: NextRequest) {
|
||||
message: err instanceof Error ? err.message : 'Unknown error publishing ApplicationRecord'
|
||||
}, { status: 500 });
|
||||
}
|
||||
|
||||
|
||||
// Step 2: Set name mappings
|
||||
console.log('Step 2: Setting name mappings...');
|
||||
try {
|
||||
@ -441,7 +503,7 @@ export async function POST(request: NextRequest) {
|
||||
)
|
||||
);
|
||||
console.log(`Set name mapping: ${lrn} -> ${applicationRecordId}`);
|
||||
|
||||
|
||||
// Set the versioned LRN (with repository_ref)
|
||||
await registryTransactionWithRetry(() =>
|
||||
registry.setName(
|
||||
@ -461,7 +523,7 @@ export async function POST(request: NextRequest) {
|
||||
message: err instanceof Error ? err.message : 'Unknown error setting name mappings'
|
||||
}, { status: 500 });
|
||||
}
|
||||
|
||||
|
||||
// Step 3: Create ApplicationDeploymentRequest
|
||||
console.log('Step 3: Creating ApplicationDeploymentRequest...');
|
||||
// Prepare record data for deployment request
|
||||
@ -482,15 +544,15 @@ export async function POST(request: NextRequest) {
|
||||
repository: repoUrl,
|
||||
repository_ref: fullHash,
|
||||
},
|
||||
payment: txHash,
|
||||
payment: finalTxHash,
|
||||
};
|
||||
|
||||
|
||||
console.log('Deployment request data:', deploymentRequestData);
|
||||
|
||||
|
||||
// Publish the deployment request
|
||||
let deploymentRequestId;
|
||||
try {
|
||||
const deployRequestResult = await registryTransactionWithRetry(() =>
|
||||
const deployRequestResult = await registryTransactionWithRetry(() =>
|
||||
registry.setRecord(
|
||||
{
|
||||
privateKey: config.privateKey,
|
||||
@ -501,10 +563,10 @@ export async function POST(request: NextRequest) {
|
||||
fee
|
||||
)
|
||||
) as { id?: string };
|
||||
|
||||
|
||||
deploymentRequestId = deployRequestResult.id;
|
||||
console.log('Deployment request published with ID:', deploymentRequestId);
|
||||
|
||||
|
||||
if (!deploymentRequestId) {
|
||||
return NextResponse.json({
|
||||
status: 'error',
|
||||
@ -518,7 +580,7 @@ export async function POST(request: NextRequest) {
|
||||
message: err instanceof Error ? err.message : 'Unknown error publishing deployment request'
|
||||
}, { status: 500 });
|
||||
}
|
||||
|
||||
|
||||
// Return combined results
|
||||
return NextResponse.json({
|
||||
id: deploymentRequestId,
|
||||
|
||||
@ -1,17 +1,24 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
// Dynamically import Keplr component to avoid SSR issues with browser APIs
|
||||
// Dynamically import components to avoid SSR issues with browser APIs
|
||||
const KeplrConnect = dynamic(() => import('@/components/KeplrConnect'), { ssr: false });
|
||||
import URLForm from '@/components/URLForm';
|
||||
// Dynamically import PaymentModal component to avoid SSR issues with browser APIs
|
||||
const SolanaConnect = dynamic(() => import('@/components/SolanaConnect'), { ssr: false });
|
||||
const PaymentModal = dynamic(() => import('@/components/PaymentModal'), { ssr: false });
|
||||
import URLForm from '@/components/URLForm';
|
||||
import StatusDisplay from '@/components/StatusDisplay';
|
||||
import { createApplicationDeploymentRequest } from '@/services/registry';
|
||||
import { PaymentType, SolanaWalletState } from '@/types';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
export default function Home() {
|
||||
const [paymentType, setPaymentType] = useState<PaymentType>('ATOM');
|
||||
const [walletAddress, setWalletAddress] = useState<string | null>(null);
|
||||
const [solanaWalletState, setSolanaWalletState] = useState<SolanaWalletState>({
|
||||
connected: false,
|
||||
publicKey: null,
|
||||
walletType: null
|
||||
});
|
||||
const [url, setUrl] = useState<string | null>(null);
|
||||
const [showPaymentModal, setShowPaymentModal] = useState(false);
|
||||
const [status, setStatus] = useState<'idle' | 'creating' | 'success' | 'error'>('idle');
|
||||
@ -30,6 +37,27 @@ export default function Home() {
|
||||
setWalletAddress(address);
|
||||
};
|
||||
|
||||
const handleSolanaConnect = (walletState: SolanaWalletState) => {
|
||||
setSolanaWalletState(walletState);
|
||||
// Store wallet info globally for PaymentModal access (simplified approach)
|
||||
if (typeof window !== 'undefined') {
|
||||
(window as any).solanaWalletInfo = {
|
||||
publicKey: walletState.publicKey,
|
||||
walletType: walletState.walletType
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const handlePaymentTypeChange = (type: PaymentType) => {
|
||||
setPaymentType(type);
|
||||
// Reset wallet states when switching payment types
|
||||
if (type === 'ATOM') {
|
||||
setSolanaWalletState({ connected: false, publicKey: null, walletType: null });
|
||||
} else {
|
||||
setWalletAddress(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUrlSubmit = (submittedUrl: string) => {
|
||||
setUrl(submittedUrl);
|
||||
setShowPaymentModal(true);
|
||||
@ -43,7 +71,7 @@ export default function Home() {
|
||||
try {
|
||||
// Create the Laconic Registry record (payment verification is done in the API)
|
||||
if (url) {
|
||||
const result = await createApplicationDeploymentRequest(url, hash);
|
||||
const result = await createApplicationDeploymentRequest(url, hash, paymentType);
|
||||
|
||||
if (result.status === 'success') {
|
||||
setRecordId(result.id);
|
||||
@ -89,27 +117,68 @@ export default function Home() {
|
||||
<div style={{ background: 'var(--card-bg)', borderColor: 'var(--card-border)' }}
|
||||
className="max-w-xl w-full p-8 rounded-xl shadow-lg border">
|
||||
<h1 className="text-2xl font-bold mb-8 text-center" style={{ color: 'var(--foreground)' }}>
|
||||
Deploy Frontends with ATOM and Laconic
|
||||
Deploy Frontends with ATOM/GOR + Laconic
|
||||
</h1>
|
||||
|
||||
{/* Payment Type Selection */}
|
||||
<div className="mb-6 p-6 rounded-lg" style={{ background: 'var(--muted-light)', borderLeft: '4px solid var(--primary)' }}>
|
||||
<h2 className="text-lg font-semibold mb-4 flex items-center">
|
||||
<span className="inline-flex items-center justify-center mr-3 w-7 h-7 rounded-full text-sm font-bold"
|
||||
style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}>1</span>
|
||||
Select Payment Method
|
||||
</h2>
|
||||
<div className="flex space-x-4">
|
||||
<label className="flex items-center cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
name="paymentType"
|
||||
value="ATOM"
|
||||
checked={paymentType === 'ATOM'}
|
||||
onChange={(e) => handlePaymentTypeChange(e.target.value as PaymentType)}
|
||||
className="mr-2"
|
||||
/>
|
||||
<span style={{ color: 'var(--foreground)' }}>ATOM (Cosmos)</span>
|
||||
</label>
|
||||
<label className="flex items-center cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
name="paymentType"
|
||||
value="GOR"
|
||||
checked={paymentType === 'GOR'}
|
||||
onChange={(e) => handlePaymentTypeChange(e.target.value as PaymentType)}
|
||||
className="mr-2"
|
||||
/>
|
||||
<span style={{ color: 'var(--foreground)' }}>GOR (Solana)</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-10 p-6 rounded-lg" style={{ background: 'var(--muted-light)', borderLeft: '4px solid var(--primary)' }}>
|
||||
<h2 className="text-lg font-semibold mb-4 flex items-center">
|
||||
<span className="inline-flex items-center justify-center mr-3 w-7 h-7 rounded-full text-sm font-bold"
|
||||
style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}>1</span>
|
||||
style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}>2</span>
|
||||
Connect Your Wallet
|
||||
</h2>
|
||||
<KeplrConnect onConnect={handleConnect} />
|
||||
{paymentType === 'ATOM' ? (
|
||||
<KeplrConnect onConnect={handleConnect} />
|
||||
) : (
|
||||
<SolanaConnect onConnect={handleSolanaConnect} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mb-8 p-6 rounded-lg" style={{ background: 'var(--muted-light)', borderLeft: '4px solid var(--primary)', opacity: walletAddress ? '1' : '0.6' }}>
|
||||
<div className="mb-8 p-6 rounded-lg" style={{
|
||||
background: 'var(--muted-light)',
|
||||
borderLeft: '4px solid var(--primary)',
|
||||
opacity: (paymentType === 'ATOM' ? walletAddress : solanaWalletState.connected) ? '1' : '0.6'
|
||||
}}>
|
||||
<h2 className="text-lg font-semibold mb-4 flex items-center">
|
||||
<span className="inline-flex items-center justify-center mr-3 w-7 h-7 rounded-full text-sm font-bold"
|
||||
style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}>2</span>
|
||||
style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}>3</span>
|
||||
Enter URL to Deploy
|
||||
</h2>
|
||||
<URLForm
|
||||
onSubmit={handleUrlSubmit}
|
||||
disabled={!walletAddress || status === 'creating'}
|
||||
disabled={!(paymentType === 'ATOM' ? walletAddress : solanaWalletState.connected) || status === 'creating'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -117,7 +186,7 @@ export default function Home() {
|
||||
<div className="p-6 rounded-lg" style={{ background: 'var(--muted-light)', borderLeft: '4px solid var(--primary)' }}>
|
||||
<h2 className="text-lg font-semibold mb-4 flex items-center">
|
||||
<span className="inline-flex items-center justify-center mr-3 w-7 h-7 rounded-full text-sm font-bold"
|
||||
style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}>3</span>
|
||||
style={{ background: 'var(--primary)', color: 'var(--primary-foreground)' }}>4</span>
|
||||
Deployment Status
|
||||
</h2>
|
||||
<StatusDisplay
|
||||
@ -137,11 +206,12 @@ export default function Home() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{showPaymentModal && walletAddress && url && (
|
||||
{showPaymentModal && url && (paymentType === 'ATOM' ? walletAddress : solanaWalletState.connected) && (
|
||||
<PaymentModal
|
||||
isOpen={showPaymentModal}
|
||||
onClose={handleClosePaymentModal}
|
||||
url={url}
|
||||
paymentType={paymentType}
|
||||
onPaymentComplete={handlePaymentComplete}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -2,26 +2,27 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { sendAtomPayment } from '@/services/keplr';
|
||||
|
||||
interface PaymentModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
url: string;
|
||||
onPaymentComplete: (txHash: string) => void;
|
||||
}
|
||||
import { sendGorPayment } from '@/services/solana';
|
||||
import { PaymentModalProps } from '@/types';
|
||||
import { getSolanaConfig, GOR_PAYMENT_AMOUNT } from '@/config';
|
||||
|
||||
export default function PaymentModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
url,
|
||||
paymentType,
|
||||
onPaymentComplete,
|
||||
}: PaymentModalProps) {
|
||||
const [amount, setAmount] = useState('0.01');
|
||||
const [amount, setAmount] = useState(paymentType === 'ATOM' ? '0.01' : GOR_PAYMENT_AMOUNT.toString());
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
// Get recipient address from environment variables
|
||||
const recipientAddress = process.env.NEXT_PUBLIC_RECIPIENT_ADDRESS || 'cosmos1yourrealaddress';
|
||||
// Get recipient addresses from environment variables
|
||||
const atomRecipientAddress = process.env.NEXT_PUBLIC_RECIPIENT_ADDRESS || 'cosmos1yourrealaddress';
|
||||
const gorRecipientAddress = paymentType === 'GOR' ?
|
||||
(process.env.NEXT_PUBLIC_GOR_RECIPIENT_ADDRESS || 'solana_recipient_address') : '';
|
||||
|
||||
const recipientAddress = paymentType === 'ATOM' ? atomRecipientAddress : gorRecipientAddress;
|
||||
|
||||
// Validate amount on change
|
||||
const handleAmountChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@ -46,12 +47,30 @@ export default function PaymentModal({
|
||||
setError('');
|
||||
|
||||
try {
|
||||
const result = await sendAtomPayment(recipientAddress, amount);
|
||||
|
||||
if (result.status === 'success' && result.hash) {
|
||||
onPaymentComplete(result.hash);
|
||||
} else {
|
||||
setError(result.message || 'Payment failed. Please try again.');
|
||||
if (paymentType === 'ATOM') {
|
||||
const result = await sendAtomPayment(recipientAddress, amount);
|
||||
|
||||
if (result.status === 'success' && result.hash) {
|
||||
onPaymentComplete(result.hash);
|
||||
} else {
|
||||
setError(result.message || 'ATOM payment failed. Please try again.');
|
||||
}
|
||||
} else if (paymentType === 'GOR') {
|
||||
// For GOR payments, we need wallet info from parent component
|
||||
// This is a simplified approach - in a real implementation, you'd pass wallet state
|
||||
const walletInfo = (window as any).solanaWalletInfo;
|
||||
if (!walletInfo || !walletInfo.publicKey || !walletInfo.walletType) {
|
||||
setError('Solana wallet not connected. Please connect your wallet first.');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await sendGorPayment(walletInfo.publicKey, walletInfo.walletType);
|
||||
|
||||
if (result.success && result.transactionSignature) {
|
||||
onPaymentComplete(result.transactionSignature);
|
||||
} else {
|
||||
setError(result.error || 'GOR payment failed. Please try again.');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
setError(error instanceof Error ? error.message : 'Payment failed. Please try again.');
|
||||
@ -67,7 +86,9 @@ export default function PaymentModal({
|
||||
<div className="max-w-md w-full rounded-xl shadow-xl animate-appear"
|
||||
style={{ background: 'var(--card-bg)', border: '1px solid var(--card-border)' }}>
|
||||
<div className="p-6 border-b" style={{ borderColor: 'var(--card-border)' }}>
|
||||
<h2 className="text-xl font-semibold" style={{ color: 'var(--foreground)' }}>Complete Payment</h2>
|
||||
<h2 className="text-xl font-semibold" style={{ color: 'var(--foreground)' }}>
|
||||
Complete {paymentType} Payment
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="p-6 space-y-6">
|
||||
@ -87,27 +108,34 @@ export default function PaymentModal({
|
||||
|
||||
<div>
|
||||
<label htmlFor="amount" className="block text-sm font-medium mb-2" style={{ color: 'var(--foreground)' }}>
|
||||
Amount (ATOM)
|
||||
Amount ({paymentType})
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
id="amount"
|
||||
type="number"
|
||||
min="0.01"
|
||||
step="0.01"
|
||||
min={paymentType === 'ATOM' ? "0.01" : "1"}
|
||||
step={paymentType === 'ATOM' ? "0.01" : "1"}
|
||||
value={amount}
|
||||
onChange={handleAmountChange}
|
||||
disabled={paymentType === 'GOR'} // Fixed amount for GOR
|
||||
className="w-full p-3 pr-12 rounded-md"
|
||||
style={{
|
||||
background: 'var(--card-bg)',
|
||||
border: '1px solid var(--input-border)',
|
||||
color: 'var(--foreground)'
|
||||
color: 'var(--foreground)',
|
||||
opacity: paymentType === 'GOR' ? '0.7' : '1'
|
||||
}}
|
||||
/>
|
||||
<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)' }}>ATOM</span>
|
||||
<span className="text-sm font-medium" style={{ color: 'var(--muted)' }}>{paymentType}</span>
|
||||
</div>
|
||||
</div>
|
||||
{paymentType === 'GOR' && (
|
||||
<p className="text-xs mt-1" style={{ color: 'var(--muted)' }}>
|
||||
Fixed amount required for deployment
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
@ -146,7 +174,7 @@ export default function PaymentModal({
|
||||
<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>
|
||||
)}
|
||||
{loading ? 'Processing...' : 'Pay with Keplr'}
|
||||
{loading ? 'Processing...' : `Pay with ${paymentType === 'ATOM' ? 'Keplr' : 'Solana Wallet'}`}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
148
src/components/SolanaConnect.tsx
Normal file
148
src/components/SolanaConnect.tsx
Normal file
@ -0,0 +1,148 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { connectSolanaWallet, disconnectSolanaWallet, checkSolanaWalletConnection } from '@/services/solana';
|
||||
import { SolanaWalletType, SolanaWalletState } from '@/types';
|
||||
|
||||
interface SolanaConnectProps {
|
||||
onConnect: (walletState: SolanaWalletState) => void;
|
||||
}
|
||||
|
||||
export default function SolanaConnect({ onConnect }: SolanaConnectProps) {
|
||||
const [connecting, setConnecting] = useState(false);
|
||||
const [walletState, setWalletState] = useState<SolanaWalletState>({
|
||||
connected: false,
|
||||
publicKey: null,
|
||||
walletType: null
|
||||
});
|
||||
|
||||
const handleConnect = async (walletType: SolanaWalletType) => {
|
||||
setConnecting(true);
|
||||
try {
|
||||
const newWalletState = await connectSolanaWallet(walletType);
|
||||
setWalletState(newWalletState);
|
||||
onConnect(newWalletState);
|
||||
} catch (error) {
|
||||
console.error('Failed to connect to Solana wallet:', error);
|
||||
alert(error instanceof Error ? error.message : 'Failed to connect wallet');
|
||||
} finally {
|
||||
setConnecting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDisconnect = async () => {
|
||||
if (walletState.walletType) {
|
||||
try {
|
||||
await disconnectSolanaWallet(walletState.walletType);
|
||||
const disconnectedState = {
|
||||
connected: false,
|
||||
publicKey: null,
|
||||
walletType: null
|
||||
};
|
||||
setWalletState(disconnectedState);
|
||||
onConnect(disconnectedState);
|
||||
} catch (error) {
|
||||
console.error('Failed to disconnect wallet:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Check for auto-connection on page load
|
||||
const checkConnection = () => {
|
||||
if (typeof window !== 'undefined') {
|
||||
// Check Phantom
|
||||
if (window.phantom?.solana && checkSolanaWalletConnection('phantom')) {
|
||||
handleConnect('phantom');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check Solflare
|
||||
if (window.solflare && checkSolanaWalletConnection('solflare')) {
|
||||
handleConnect('solflare');
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
checkConnection();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center p-4 rounded-lg">
|
||||
{walletState.connected ? (
|
||||
<div className="flex flex-col items-center w-full">
|
||||
<div className="flex items-center mb-2">
|
||||
<span className="w-3 h-3 rounded-full mr-2" style={{ backgroundColor: 'var(--success)' }}></span>
|
||||
<p className="font-medium" style={{ color: 'var(--success)' }}>
|
||||
Connected ({walletState.walletType})
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full p-3 rounded-md mb-3" style={{ background: 'var(--card-bg)', border: '1px solid var(--card-border)' }}>
|
||||
<p className="text-sm font-mono break-all text-center">{walletState.publicKey}</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleDisconnect}
|
||||
className="px-4 py-2 rounded-md text-sm transition-colors"
|
||||
style={{
|
||||
backgroundColor: 'var(--muted)',
|
||||
color: 'var(--foreground)',
|
||||
border: '1px solid var(--input-border)'
|
||||
}}
|
||||
>
|
||||
Disconnect
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-full space-y-3">
|
||||
<button
|
||||
onClick={() => handleConnect('phantom')}
|
||||
disabled={connecting || !window.phantom?.solana}
|
||||
className="px-6 py-3 rounded-md w-full transition-colors disabled:opacity-50"
|
||||
style={{
|
||||
backgroundColor: connecting ? 'var(--muted)' : 'var(--primary)',
|
||||
color: 'var(--primary-foreground)',
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-center">
|
||||
{connecting && (
|
||||
<svg className="animate-spin -ml-1 mr-3 h-5 w-5" 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>
|
||||
)}
|
||||
{connecting ? 'Connecting...' : 'Connect Phantom Wallet'}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => handleConnect('solflare')}
|
||||
disabled={connecting || !window.solflare}
|
||||
className="px-6 py-3 rounded-md w-full transition-colors disabled:opacity-50"
|
||||
style={{
|
||||
backgroundColor: connecting ? 'var(--muted)' : 'var(--primary)',
|
||||
color: 'var(--primary-foreground)',
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-center">
|
||||
{connecting && (
|
||||
<svg className="animate-spin -ml-1 mr-3 h-5 w-5" 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>
|
||||
)}
|
||||
{connecting ? 'Connecting...' : 'Connect Solflare Wallet'}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{!window.phantom?.solana && !window.solflare && (
|
||||
<p className="text-sm text-center" style={{ color: 'var(--muted)' }}>
|
||||
Please install Phantom or Solflare wallet extension
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -43,4 +43,55 @@ export const getAppName = (): string => {
|
||||
return process.env.APP_NAME || 'atom-deploy';
|
||||
};
|
||||
|
||||
export const COSMOS_DENOM = 'uatom';
|
||||
export const COSMOS_DENOM = 'uatom';
|
||||
|
||||
// Solana/GOR Token Configuration
|
||||
export const GOR_PAYMENT_AMOUNT = 50; // 50 GOR tokens
|
||||
export const GOR_TOKEN_DECIMALS = 9; // Standard SPL token decimals
|
||||
|
||||
export const getSolanaConfig = () => {
|
||||
const requiredEnvVars = [
|
||||
'NEXT_PUBLIC_SOLANA_RPC_URL',
|
||||
'NEXT_PUBLIC_GOR_MINT_ADDRESS',
|
||||
'NEXT_PUBLIC_GOR_RECIPIENT_ADDRESS'
|
||||
];
|
||||
|
||||
for (const envVar of requiredEnvVars) {
|
||||
if (!process.env[envVar]) {
|
||||
throw new Error(`Missing environment variable: ${envVar}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
rpcUrl: process.env.NEXT_PUBLIC_SOLANA_RPC_URL!,
|
||||
websocketUrl: process.env.NEXT_PUBLIC_SOLANA_WEBSOCKET_URL,
|
||||
gorMintAddress: process.env.NEXT_PUBLIC_GOR_MINT_ADDRESS!,
|
||||
gorRecipientAddress: process.env.NEXT_PUBLIC_GOR_RECIPIENT_ADDRESS!,
|
||||
paymentAmount: GOR_PAYMENT_AMOUNT,
|
||||
tokenDecimals: GOR_TOKEN_DECIMALS
|
||||
};
|
||||
};
|
||||
|
||||
export const getLaconicTransferConfig = () => {
|
||||
const requiredEnvVars = [
|
||||
'REGISTRY_USER_KEY', // Same account as the registry user
|
||||
'LACONIC_SERVICE_PROVIDER_ADDRESS',
|
||||
'LACONIC_TRANSFER_AMOUNT'
|
||||
];
|
||||
|
||||
for (const envVar of requiredEnvVars) {
|
||||
if (!process.env[envVar]) {
|
||||
throw new Error(`Missing environment variable: ${envVar}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
prefilledPrivateKey: process.env.REGISTRY_USER_KEY!, // Use the same key as registry operations
|
||||
serviceProviderAddress: process.env.LACONIC_SERVICE_PROVIDER_ADDRESS!,
|
||||
transferAmount: process.env.LACONIC_TRANSFER_AMOUNT!
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
80
src/services/laconicTransfer.ts
Normal file
80
src/services/laconicTransfer.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import { Registry } from '@cerc-io/registry-sdk';
|
||||
import { GasPrice } from '@cosmjs/stargate';
|
||||
import { getRegistryConfig, getLaconicTransferConfig } from '../config';
|
||||
import { LaconicTransferResult } from '../types';
|
||||
|
||||
let registryInstance: Registry | null = null;
|
||||
|
||||
const getRegistry = (): Registry => {
|
||||
if (!registryInstance) {
|
||||
const config = getRegistryConfig();
|
||||
const gasPrice = GasPrice.fromString(config.fee.gasPrice + 'alnt');
|
||||
|
||||
registryInstance = new Registry(
|
||||
config.gqlEndpoint,
|
||||
config.rpcEndpoint,
|
||||
{ chainId: config.chainId, gasPrice }
|
||||
);
|
||||
}
|
||||
return registryInstance;
|
||||
};
|
||||
|
||||
export const transferLNTTokens = async (): Promise<LaconicTransferResult> => {
|
||||
try {
|
||||
const registryConfig = getRegistryConfig();
|
||||
const transferConfig = getLaconicTransferConfig();
|
||||
const registry = getRegistry();
|
||||
|
||||
console.log('Initiating LNT transfer from prefilled account to service provider...');
|
||||
|
||||
// Create fee for transaction
|
||||
const fee = {
|
||||
amount: [{ denom: 'alnt', amount: registryConfig.fee.fees.replace('alnt', '') || '900000' }],
|
||||
gas: registryConfig.fee.gas || '900000',
|
||||
};
|
||||
|
||||
// Send tokens from prefilled account to service provider
|
||||
const transferResult = await registry.sendCoins(
|
||||
{
|
||||
destinationAddress: transferConfig.serviceProviderAddress,
|
||||
amount: transferConfig.transferAmount,
|
||||
denom: 'alnt'
|
||||
},
|
||||
transferConfig.prefilledPrivateKey,
|
||||
fee
|
||||
);
|
||||
|
||||
console.log('LNT transfer result:', transferResult);
|
||||
|
||||
if (!transferResult || !(transferResult as any).transactionHash) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'LNT transfer failed - no transaction hash returned'
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
transactionHash: (transferResult as any).transactionHash
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to transfer LNT tokens:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error during LNT transfer'
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to validate transfer configuration
|
||||
export const validateLaconicTransferConfig = (): { valid: boolean; error?: string } => {
|
||||
try {
|
||||
getLaconicTransferConfig();
|
||||
return { valid: true };
|
||||
} catch (error) {
|
||||
return {
|
||||
valid: false,
|
||||
error: error instanceof Error ? error.message : 'Invalid Laconic transfer configuration'
|
||||
};
|
||||
}
|
||||
};
|
||||
@ -1,12 +1,13 @@
|
||||
import axios from 'axios';
|
||||
import { CreateRecordResponse } from '../types';
|
||||
import { CreateRecordResponse, PaymentType } from '../types';
|
||||
|
||||
export const createApplicationDeploymentRequest = async (
|
||||
url: string,
|
||||
txHash: string
|
||||
txHash: string,
|
||||
paymentType: PaymentType = 'ATOM'
|
||||
): Promise<CreateRecordResponse> => {
|
||||
try {
|
||||
console.log(`Creating deployment request for URL: ${url} with transaction: ${txHash}`);
|
||||
console.log(`Creating deployment request for URL: ${url} with transaction: ${txHash} using ${paymentType} payment`);
|
||||
|
||||
// Call our serverless API endpoint to handle the registry interaction
|
||||
const response = await fetch('/api/registry', {
|
||||
@ -14,7 +15,7 @@ export const createApplicationDeploymentRequest = async (
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ url, txHash }),
|
||||
body: JSON.stringify({ url, txHash, paymentType }),
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
224
src/services/solana.ts
Normal file
224
src/services/solana.ts
Normal file
@ -0,0 +1,224 @@
|
||||
import { Connection, PublicKey, Transaction } from '@solana/web3.js';
|
||||
import {
|
||||
TOKEN_PROGRAM_ID,
|
||||
createTransferInstruction,
|
||||
createAssociatedTokenAccountInstruction,
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
getAssociatedTokenAddress
|
||||
} from '@solana/spl-token';
|
||||
import BN from 'bn.js';
|
||||
import { getSolanaConfig } from '../config';
|
||||
import { SolanaPaymentResult, SolanaWalletType, SolanaWalletState } from '../types';
|
||||
|
||||
let connection: Connection | null = null;
|
||||
|
||||
const getConnection = (): Connection => {
|
||||
if (!connection) {
|
||||
const config = getSolanaConfig();
|
||||
connection = new Connection(
|
||||
config.rpcUrl,
|
||||
{
|
||||
commitment: 'confirmed',
|
||||
wsEndpoint: config.websocketUrl,
|
||||
confirmTransactionInitialTimeout: 60000,
|
||||
}
|
||||
);
|
||||
}
|
||||
return connection;
|
||||
};
|
||||
|
||||
export const connectSolanaWallet = async (walletType: SolanaWalletType): Promise<SolanaWalletState> => {
|
||||
try {
|
||||
let wallet: any = null;
|
||||
|
||||
if (walletType === 'phantom') {
|
||||
if (!window.phantom?.solana) {
|
||||
throw new Error('Phantom wallet not found. Please install Phantom browser extension.');
|
||||
}
|
||||
wallet = window.phantom.solana;
|
||||
} else if (walletType === 'solflare') {
|
||||
if (!window.solflare) {
|
||||
throw new Error('Solflare wallet not found. Please install Solflare browser extension.');
|
||||
}
|
||||
wallet = window.solflare;
|
||||
}
|
||||
|
||||
if (!wallet) {
|
||||
throw new Error(`${walletType} wallet not available`);
|
||||
}
|
||||
|
||||
const response = await wallet.connect();
|
||||
const publicKey = response.publicKey.toString();
|
||||
|
||||
return {
|
||||
connected: true,
|
||||
publicKey,
|
||||
walletType
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to connect to Solana wallet:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const disconnectSolanaWallet = async (walletType: SolanaWalletType): Promise<void> => {
|
||||
try {
|
||||
let wallet: any = null;
|
||||
|
||||
if (walletType === 'phantom') {
|
||||
wallet = window.phantom?.solana;
|
||||
} else if (walletType === 'solflare') {
|
||||
wallet = window.solflare;
|
||||
}
|
||||
|
||||
if (wallet && wallet.disconnect) {
|
||||
await wallet.disconnect();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to disconnect Solana wallet:', error);
|
||||
}
|
||||
};
|
||||
|
||||
export const sendGorPayment = async (
|
||||
walletPublicKey: string,
|
||||
walletType: SolanaWalletType
|
||||
): Promise<SolanaPaymentResult> => {
|
||||
try {
|
||||
const config = getSolanaConfig();
|
||||
let wallet: any = null;
|
||||
|
||||
if (walletType === 'phantom') {
|
||||
wallet = window.phantom?.solana;
|
||||
} else if (walletType === 'solflare') {
|
||||
wallet = window.solflare;
|
||||
}
|
||||
|
||||
if (!wallet) {
|
||||
return {
|
||||
success: false,
|
||||
error: `${walletType} wallet not found`
|
||||
};
|
||||
}
|
||||
|
||||
const connection = getConnection();
|
||||
const senderPublicKey = new PublicKey(walletPublicKey);
|
||||
const mintPublicKey = new PublicKey(config.gorMintAddress);
|
||||
const receiverPublicKey = new PublicKey(config.gorRecipientAddress);
|
||||
|
||||
console.log('Processing GOR payment with keys:', {
|
||||
sender: senderPublicKey.toBase58(),
|
||||
mint: mintPublicKey.toBase58(),
|
||||
receiver: receiverPublicKey.toBase58(),
|
||||
amount: config.paymentAmount
|
||||
});
|
||||
|
||||
// Get associated token addresses
|
||||
const senderATA = await getAssociatedTokenAddress(
|
||||
mintPublicKey,
|
||||
senderPublicKey
|
||||
);
|
||||
|
||||
const receiverATA = await getAssociatedTokenAddress(
|
||||
mintPublicKey,
|
||||
receiverPublicKey
|
||||
);
|
||||
|
||||
console.log('Token accounts:', {
|
||||
senderATA: senderATA.toBase58(),
|
||||
receiverATA: receiverATA.toBase58(),
|
||||
});
|
||||
|
||||
const transaction = new Transaction();
|
||||
|
||||
// Check if accounts exist
|
||||
const [senderATAInfo, receiverATAInfo] = await Promise.all([
|
||||
connection.getAccountInfo(senderATA),
|
||||
connection.getAccountInfo(receiverATA),
|
||||
]);
|
||||
|
||||
// Create receiver token account if it doesn't exist
|
||||
if (!receiverATAInfo) {
|
||||
console.log('Creating receiver token account');
|
||||
transaction.add(
|
||||
createAssociatedTokenAccountInstruction(
|
||||
senderPublicKey,
|
||||
receiverATA,
|
||||
receiverPublicKey,
|
||||
mintPublicKey
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Create sender token account if it doesn't exist
|
||||
if (!senderATAInfo) {
|
||||
console.log('Creating sender token account');
|
||||
transaction.add(
|
||||
createAssociatedTokenAccountInstruction(
|
||||
senderPublicKey,
|
||||
senderATA,
|
||||
senderPublicKey,
|
||||
mintPublicKey
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Calculate amount in smallest units (considering decimals)
|
||||
const amountToSend = BigInt(config.paymentAmount * Math.pow(10, config.tokenDecimals));
|
||||
|
||||
// Add transfer instruction
|
||||
transaction.add(
|
||||
createTransferInstruction(
|
||||
senderATA,
|
||||
receiverATA,
|
||||
senderPublicKey,
|
||||
amountToSend
|
||||
)
|
||||
);
|
||||
|
||||
// Set transaction details
|
||||
const latestBlockhash = await connection.getLatestBlockhash('confirmed');
|
||||
transaction.recentBlockhash = latestBlockhash.blockhash;
|
||||
transaction.feePayer = senderPublicKey;
|
||||
|
||||
console.log('Sending GOR payment transaction...');
|
||||
const { signature } = await wallet.signAndSendTransaction(transaction);
|
||||
console.log('Transaction sent:', signature);
|
||||
|
||||
// Confirm transaction
|
||||
const confirmation = await connection.confirmTransaction({
|
||||
signature,
|
||||
blockhash: latestBlockhash.blockhash,
|
||||
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
|
||||
}, 'confirmed');
|
||||
|
||||
if (confirmation.value.err) {
|
||||
console.error('Transaction error:', confirmation.value.err);
|
||||
throw new Error(`Transaction failed: ${JSON.stringify(confirmation.value.err)}`);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
transactionSignature: signature
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('GOR payment error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Payment failed'
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to check wallet connection status
|
||||
export const checkSolanaWalletConnection = (walletType: SolanaWalletType): boolean => {
|
||||
try {
|
||||
if (walletType === 'phantom') {
|
||||
return window.phantom?.solana?.isConnected || false;
|
||||
} else if (walletType === 'solflare') {
|
||||
return window.solflare?.isConnected || false;
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@ -1,8 +1,23 @@
|
||||
import { Window as KeplrWindow } from "@keplr-wallet/types";
|
||||
|
||||
// extend the global Window interface to include Keplr
|
||||
// extend the global Window interface to include Keplr and Solana wallets
|
||||
declare global {
|
||||
interface Window extends KeplrWindow {}
|
||||
interface Window extends KeplrWindow {
|
||||
phantom?: {
|
||||
solana?: {
|
||||
signAndSendTransaction(transaction: any): Promise<{ signature: string }>;
|
||||
connect(): Promise<{ publicKey: { toString(): string } }>;
|
||||
disconnect(): Promise<void>;
|
||||
isConnected: boolean;
|
||||
};
|
||||
};
|
||||
solflare?: {
|
||||
signAndSendTransaction(transaction: any): Promise<{ signature: string }>;
|
||||
connect(): Promise<{ publicKey: { toString(): string } }>;
|
||||
disconnect(): Promise<void>;
|
||||
isConnected: boolean;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export interface RegistryConfig {
|
||||
@ -51,4 +66,35 @@ export interface CreateRecordResponse {
|
||||
shortCommitHash?: string;
|
||||
status: 'success' | 'error';
|
||||
message?: string;
|
||||
}
|
||||
|
||||
// Payment types
|
||||
export type PaymentType = 'ATOM' | 'GOR';
|
||||
|
||||
export type SolanaWalletType = 'phantom' | 'solflare';
|
||||
|
||||
export interface SolanaPaymentResult {
|
||||
success: boolean;
|
||||
transactionSignature?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface PaymentModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
url: string;
|
||||
paymentType: PaymentType;
|
||||
onPaymentComplete: (txHash: string) => void;
|
||||
}
|
||||
|
||||
export interface SolanaWalletState {
|
||||
connected: boolean;
|
||||
publicKey: string | null;
|
||||
walletType: SolanaWalletType | null;
|
||||
}
|
||||
|
||||
export interface LaconicTransferResult {
|
||||
success: boolean;
|
||||
transactionHash?: string;
|
||||
error?: string;
|
||||
}
|
||||
185
src/utils/solanaVerify.ts
Normal file
185
src/utils/solanaVerify.ts
Normal file
@ -0,0 +1,185 @@
|
||||
import { Connection } from '@solana/web3.js';
|
||||
import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
|
||||
import { getSolanaConfig } from '../config';
|
||||
|
||||
let connection: Connection | null = null;
|
||||
|
||||
const getConnection = (): Connection => {
|
||||
if (!connection) {
|
||||
const config = getSolanaConfig();
|
||||
connection = new Connection(
|
||||
config.rpcUrl,
|
||||
{
|
||||
commitment: 'confirmed',
|
||||
confirmTransactionInitialTimeout: 60000,
|
||||
}
|
||||
);
|
||||
}
|
||||
return connection;
|
||||
};
|
||||
|
||||
interface SolanaTransactionInfo {
|
||||
authority: string;
|
||||
amount: string;
|
||||
}
|
||||
|
||||
const extractSolanaTransactionInfo = async (transactionSignature: string): Promise<SolanaTransactionInfo> => {
|
||||
const connection = getConnection();
|
||||
const result = await connection.getParsedTransaction(transactionSignature, 'confirmed');
|
||||
|
||||
if (!result) {
|
||||
throw new Error('Transaction not found');
|
||||
}
|
||||
|
||||
const transferInstruction = result.transaction.message.instructions.find(
|
||||
(instr) => 'parsed' in instr && instr.programId.equals(TOKEN_PROGRAM_ID)
|
||||
);
|
||||
|
||||
if (!transferInstruction || !('parsed' in transferInstruction)) {
|
||||
throw new Error('Transfer instruction not found');
|
||||
}
|
||||
|
||||
const { info: { amount, authority } } = transferInstruction.parsed;
|
||||
return { authority, amount };
|
||||
};
|
||||
|
||||
export const verifySolanaPayment = async (
|
||||
transactionSignature: string
|
||||
): Promise<{
|
||||
valid: boolean,
|
||||
reason?: string,
|
||||
amount?: string,
|
||||
sender?: string
|
||||
}> => {
|
||||
try {
|
||||
const config = getSolanaConfig();
|
||||
const requiredAmountInSmallestUnits = (config.paymentAmount * Math.pow(10, config.tokenDecimals)).toString();
|
||||
|
||||
const connection = getConnection();
|
||||
|
||||
// Fetch transaction details
|
||||
const transactionResult = await connection.getParsedTransaction(transactionSignature, 'confirmed');
|
||||
|
||||
if (!transactionResult) {
|
||||
return {
|
||||
valid: false,
|
||||
reason: 'Transaction not found on Solana blockchain'
|
||||
};
|
||||
}
|
||||
|
||||
// Check if transaction was successful
|
||||
if (transactionResult.meta?.err) {
|
||||
return {
|
||||
valid: false,
|
||||
reason: `Transaction failed: ${JSON.stringify(transactionResult.meta.err)}`
|
||||
};
|
||||
}
|
||||
|
||||
// Check transaction timestamp (5-minute window)
|
||||
const txTimestamp = transactionResult.blockTime ? new Date(transactionResult.blockTime * 1000) : null;
|
||||
if (!txTimestamp) {
|
||||
return {
|
||||
valid: false,
|
||||
reason: 'Transaction timestamp not available'
|
||||
};
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const timeDiffMs = now.getTime() - txTimestamp.getTime();
|
||||
const timeWindowMs = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
if (timeDiffMs > timeWindowMs) {
|
||||
return {
|
||||
valid: false,
|
||||
reason: `Transaction is older than 5 minutes (${Math.round(timeDiffMs / 60000)} minutes old)`
|
||||
};
|
||||
}
|
||||
|
||||
// Extract transaction info
|
||||
const { amount, authority } = await extractSolanaTransactionInfo(transactionSignature);
|
||||
|
||||
// Verify amount
|
||||
if (parseInt(amount) < parseInt(requiredAmountInSmallestUnits)) {
|
||||
return {
|
||||
valid: false,
|
||||
reason: `Payment amount (${amount}) is less than required (${requiredAmountInSmallestUnits})`
|
||||
};
|
||||
}
|
||||
|
||||
// Verify recipient address by checking the transaction instructions
|
||||
let foundValidTransfer = false;
|
||||
|
||||
for (const instruction of transactionResult.transaction.message.instructions) {
|
||||
if ('parsed' in instruction && instruction.programId.equals(TOKEN_PROGRAM_ID)) {
|
||||
const parsed = instruction.parsed;
|
||||
if (parsed.type === 'transferChecked' || parsed.type === 'transfer') {
|
||||
const destination = parsed.info.destination;
|
||||
|
||||
// We need to verify this transfer was to our expected recipient
|
||||
// For now, we'll check if the amount matches and trust the verification
|
||||
if (parsed.info.amount === amount) {
|
||||
foundValidTransfer = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundValidTransfer) {
|
||||
return {
|
||||
valid: false,
|
||||
reason: 'Valid GOR transfer not found in transaction'
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
amount,
|
||||
sender: authority
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error verifying Solana payment:', error);
|
||||
return {
|
||||
valid: false,
|
||||
reason: `Failed to verify transaction: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Helper to track used signatures (simple in-memory store for now)
|
||||
const usedSignatures = new Set<string>();
|
||||
|
||||
export const isSignatureUsed = (transactionSignature: string): boolean => {
|
||||
return usedSignatures.has(transactionSignature);
|
||||
};
|
||||
|
||||
export const markSignatureAsUsed = (transactionSignature: string): void => {
|
||||
usedSignatures.add(transactionSignature);
|
||||
};
|
||||
|
||||
export const verifyUnusedSolanaPayment = async (
|
||||
transactionSignature: string
|
||||
): Promise<{
|
||||
valid: boolean,
|
||||
reason?: string,
|
||||
amount?: string,
|
||||
sender?: string
|
||||
}> => {
|
||||
// Check if signature is already used
|
||||
if (isSignatureUsed(transactionSignature)) {
|
||||
return {
|
||||
valid: false,
|
||||
reason: 'Transaction signature has already been used'
|
||||
};
|
||||
}
|
||||
|
||||
// Verify the payment
|
||||
const result = await verifySolanaPayment(transactionSignature);
|
||||
|
||||
// If valid, mark as used
|
||||
if (result.valid) {
|
||||
markSignatureAsUsed(transactionSignature);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user