feat: add multi-network USDT deposit (TON, Polkadot, TRC20 HD wallet)

This commit is contained in:
2026-02-08 02:00:20 +03:00
parent 456bbf1dd2
commit 734a8111db
7 changed files with 1284 additions and 135 deletions
+519 -3
View File
@@ -1,12 +1,12 @@
{
"name": "pezkuwi-telegram-miniapp",
"version": "1.0.107",
"version": "1.0.169",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pezkuwi-telegram-miniapp",
"version": "1.0.107",
"version": "1.0.169",
"license": "MIT",
"dependencies": {
"@pezkuwi/api": "^16.5.36",
@@ -14,6 +14,8 @@
"@pezkuwi/util-crypto": "^14.0.25",
"@supabase/supabase-js": "^2.93.1",
"@tanstack/react-query": "^5.56.2",
"@ton/crypto": "^3.3.0",
"@ton/ton": "^16.2.2",
"@types/qrcode": "^1.5.6",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@@ -36,6 +38,7 @@
"@vitejs/plugin-react-swc": "^3.5.0",
"@vitest/coverage-v8": "^4.0.18",
"autoprefixer": "^10.4.20",
"bip39": "^3.1.0",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.5",
@@ -48,6 +51,7 @@
"postcss": "^8.4.47",
"prettier": "^3.8.1",
"tailwindcss": "^3.4.11",
"tronweb": "^6.2.0",
"typescript": "^5.5.3",
"vite": "^5.4.1",
"vitest": "^4.0.18"
@@ -70,6 +74,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/@adraffy/ens-normalize": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz",
"integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==",
"dev": true,
"license": "MIT"
},
"node_modules/@alloc/quick-lru": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
@@ -2331,6 +2342,94 @@
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@scure/bip32": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz",
"integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@noble/curves": "~1.4.0",
"@noble/hashes": "~1.4.0",
"@scure/base": "~1.1.6"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@scure/bip32/node_modules/@noble/curves": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz",
"integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@noble/hashes": "1.4.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@scure/bip32/node_modules/@noble/hashes": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz",
"integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@scure/bip32/node_modules/@scure/base": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz",
"integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@scure/bip39": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz",
"integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@noble/hashes": "~1.4.0",
"@scure/base": "~1.1.6"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@scure/bip39/node_modules/@noble/hashes": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz",
"integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@scure/bip39/node_modules/@scure/base": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz",
"integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@standard-schema/spec": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
@@ -2752,6 +2851,60 @@
}
}
},
"node_modules/@ton/core": {
"version": "0.63.0",
"resolved": "https://registry.npmjs.org/@ton/core/-/core-0.63.0.tgz",
"integrity": "sha512-uBc0WQNYVzjAwPvIazf0Ryhpv4nJd4dKIuHoj766gUdwe8sVzGM+TxKKKJETL70hh/mxACyUlR4tAwN0LWDNow==",
"license": "MIT",
"peer": true,
"peerDependencies": {
"@ton/crypto": ">=3.2.0"
}
},
"node_modules/@ton/crypto": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@ton/crypto/-/crypto-3.3.0.tgz",
"integrity": "sha512-/A6CYGgA/H36OZ9BbTaGerKtzWp50rg67ZCH2oIjV1NcrBaCK9Z343M+CxedvM7Haf3f/Ee9EhxyeTp0GKMUpA==",
"license": "MIT",
"dependencies": {
"@ton/crypto-primitives": "2.1.0",
"jssha": "3.2.0",
"tweetnacl": "1.0.3"
}
},
"node_modules/@ton/crypto-primitives": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@ton/crypto-primitives/-/crypto-primitives-2.1.0.tgz",
"integrity": "sha512-PQesoyPgqyI6vzYtCXw4/ZzevePc4VGcJtFwf08v10OevVJHVfW238KBdpj1kEDQkxWLeuNHEpTECNFKnP6tow==",
"license": "MIT",
"dependencies": {
"jssha": "3.2.0"
}
},
"node_modules/@ton/ton": {
"version": "16.2.2",
"resolved": "https://registry.npmjs.org/@ton/ton/-/ton-16.2.2.tgz",
"integrity": "sha512-yEOw4IW3gpRZxJAcILMI4dQ1d5/eAAbD2VU/Iwc6z7f2jt1mLDWVED8yn2vLNucQfZr+1eaqYHLztYVFZ7PKmw==",
"license": "MIT",
"dependencies": {
"axios": "^1.6.7",
"dataloader": "^2.0.0",
"zod": "^3.21.4"
},
"peerDependencies": {
"@ton/core": ">=0.63.0 <1.0.0",
"@ton/crypto": ">=3.2.0"
}
},
"node_modules/@ton/ton/node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/@types/aria-query": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
@@ -3246,6 +3399,13 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/aes-js": {
"version": "4.0.0-beta.5",
"resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz",
"integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==",
"dev": true,
"license": "MIT"
},
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
@@ -3562,6 +3722,12 @@
"node": ">= 0.4"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/autoprefixer": {
"version": "10.4.24",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.24.tgz",
@@ -3614,6 +3780,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/axios": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -3661,6 +3838,16 @@
"require-from-string": "^2.0.2"
}
},
"node_modules/bignumber.js": {
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz",
"integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==",
"dev": true,
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -3673,6 +3860,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/bip39": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bip39/-/bip39-3.1.0.tgz",
"integrity": "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==",
"dev": true,
"license": "ISC",
"dependencies": {
"@noble/hashes": "^1.2.0"
}
},
"node_modules/bn.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz",
@@ -4246,6 +4443,18 @@
"dev": true,
"license": "MIT"
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/commander": {
"version": "14.0.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz",
@@ -4530,6 +4739,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/dataloader": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.2.3.tgz",
"integrity": "sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==",
"license": "MIT"
},
"node_modules/date-fns": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
@@ -4614,6 +4829,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
@@ -4915,7 +5139,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@@ -5377,6 +5600,146 @@
"node": ">=0.10.0"
}
},
"node_modules/ethereum-cryptography": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz",
"integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@noble/curves": "1.4.2",
"@noble/hashes": "1.4.0",
"@scure/bip32": "1.4.0",
"@scure/bip39": "1.3.0"
}
},
"node_modules/ethereum-cryptography/node_modules/@noble/curves": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz",
"integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@noble/hashes": "1.4.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/ethereum-cryptography/node_modules/@noble/hashes": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz",
"integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/ethers": {
"version": "6.13.5",
"resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.5.tgz",
"integrity": "sha512-+knKNieu5EKRThQJWwqaJ10a6HE9sSehGeqWN65//wE7j47ZpFhKAnHB/JJFibwwg61I/koxaPsXbXpD/skNOQ==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/ethers-io/"
},
{
"type": "individual",
"url": "https://www.buymeacoffee.com/ricmoo"
}
],
"license": "MIT",
"dependencies": {
"@adraffy/ens-normalize": "1.10.1",
"@noble/curves": "1.2.0",
"@noble/hashes": "1.3.2",
"@types/node": "22.7.5",
"aes-js": "4.0.0-beta.5",
"tslib": "2.7.0",
"ws": "8.17.1"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/ethers/node_modules/@noble/curves": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz",
"integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@noble/hashes": "1.3.2"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/ethers/node_modules/@noble/hashes": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
"integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/ethers/node_modules/@types/node": {
"version": "22.7.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz",
"integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.19.2"
}
},
"node_modules/ethers/node_modules/tslib": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
"dev": true,
"license": "0BSD"
},
"node_modules/ethers/node_modules/undici-types": {
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"dev": true,
"license": "MIT"
},
"node_modules/ethers/node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"dev": true,
"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/eventemitter3": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
@@ -5562,6 +5925,26 @@
"dev": true,
"license": "ISC"
},
"node_modules/follow-redirects": {
"version": "1.15.11",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/for-each": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
@@ -5577,6 +5960,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/form-data": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
@@ -5795,6 +6194,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/google-protobuf": {
"version": "3.21.4",
"resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.4.tgz",
"integrity": "sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ==",
"dev": true,
"license": "(BSD-3-Clause AND Apache-2.0)"
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -6766,6 +7172,15 @@
"node": ">=6"
}
},
"node_modules/jssha": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jssha/-/jssha-3.2.0.tgz",
"integrity": "sha512-QuruyBENDWdN4tZwJbQq7/eAK85FqrI4oDbXjy5IBhYD+2pTJyBUWZe8ctWaCkrV0gy6AaelgOZZBMeswEa/6Q==",
"license": "BSD-3-Clause",
"engines": {
"node": "*"
}
},
"node_modules/jsx-ast-utils": {
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
@@ -7056,6 +7471,27 @@
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
"license": "MIT"
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mimic-function": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
@@ -8130,6 +8566,12 @@
"node": ">= 8"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/public-encrypt": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
@@ -8344,6 +8786,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
"dev": true,
"license": "MIT"
},
"node_modules/regexp.prototype.flags": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
@@ -9532,6 +9981,57 @@
"node": ">=20"
}
},
"node_modules/tronweb": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/tronweb/-/tronweb-6.2.0.tgz",
"integrity": "sha512-09kyW6mqiFuSYXkR35ndxCeNF5rW1O18hKAClCLtVHP2xBFPYSGx3lDYC2hRKcuLiq6iLPxOVCrhzoKNGlFuQQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/runtime": "7.26.10",
"axios": "1.12.2",
"bignumber.js": "9.1.2",
"ethereum-cryptography": "2.2.1",
"ethers": "6.13.5",
"eventemitter3": "5.0.1",
"google-protobuf": "3.21.4",
"semver": "7.7.1",
"validator": "13.15.23"
}
},
"node_modules/tronweb/node_modules/@babel/runtime": {
"version": "7.26.10",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz",
"integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==",
"dev": true,
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/tronweb/node_modules/eventemitter3": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
"dev": true,
"license": "MIT"
},
"node_modules/tronweb/node_modules/semver": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/ts-api-utils": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
@@ -9563,6 +10063,12 @@
"integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==",
"license": "MIT"
},
"node_modules/tweetnacl": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
"license": "Unlicense"
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -9771,6 +10277,16 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/validator": {
"version": "13.15.23",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.23.tgz",
"integrity": "sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/vite": {
"version": "5.4.21",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+4 -1
View File
@@ -1,6 +1,6 @@
{
"name": "pezkuwi-telegram-miniapp",
"version": "1.0.169",
"version": "1.0.170",
"type": "module",
"description": "Pezkuwichain Telegram Mini App - Forum, Announcements, Rewards",
"author": "Pezkuwichain Team",
@@ -42,6 +42,8 @@
"@pezkuwi/util-crypto": "^14.0.25",
"@supabase/supabase-js": "^2.93.1",
"@tanstack/react-query": "^5.56.2",
"@ton/crypto": "^3.3.0",
"@ton/ton": "^16.2.2",
"@types/qrcode": "^1.5.6",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@@ -64,6 +66,7 @@
"@vitejs/plugin-react-swc": "^3.5.0",
"@vitest/coverage-v8": "^4.0.18",
"autoprefixer": "^10.4.20",
"bip39": "^3.1.0",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.5",
+251 -128
View File
@@ -1,6 +1,6 @@
/**
* Deposit USDT Modal
* Allows users to deposit USDT (TRC20 or Polkadot) to get wUSDT on Asset Hub
* Supports TON, Polkadot (recommended) and TRC20 (with fee warning)
*/
import { useState, useEffect } from 'react';
@@ -13,43 +13,57 @@ import {
Loader2,
History,
Plus,
AlertTriangle,
} from 'lucide-react';
import { useTelegram } from '@/hooks/useTelegram';
import { supabase } from '@/lib/supabase';
type Network = 'trc20' | 'polkadot';
type Network = 'ton' | 'polkadot' | 'trc20';
interface NetworkInfo {
id: Network;
name: string;
description: string;
address: string;
explorer: string;
icon: string;
minAmount: number;
confirmations: number;
recommended: boolean;
fee: number;
feeWarning?: string;
explorer: string;
minDeposit: number;
}
const NETWORKS: NetworkInfo[] = [
{
id: 'trc20',
name: 'TRC20 (TRON)',
description: 'USDT li ser tora TRON',
address: import.meta.env.VITE_DEPOSIT_TRON_ADDRESS || '',
explorer: 'https://tronscan.org/#/transaction/',
icon: '🔴',
minAmount: 10,
confirmations: 20,
id: 'ton',
name: 'TON',
description: 'Telegram Wallet',
icon: '💎',
recommended: true,
fee: 0.05,
explorer: 'https://tonviewer.com/transaction/',
minDeposit: 10,
},
{
id: 'polkadot',
name: 'Polkadot Asset Hub',
description: 'USDT li ser Polkadot',
address: import.meta.env.VITE_DEPOSIT_POLKADOT_ADDRESS || '',
explorer: 'https://assethub-polkadot.subscan.io/extrinsic/',
name: 'Polkadot',
description: 'Asset Hub',
icon: '⚪',
minAmount: 10,
confirmations: 1,
recommended: true,
fee: 0.05,
explorer: 'https://assethub-polkadot.subscan.io/extrinsic/',
minDeposit: 10,
},
{
id: 'trc20',
name: 'TRC20',
description: 'TRON Network',
icon: '🔴',
recommended: false,
fee: 3,
feeWarning:
'TRC20 ağ masrafı yaklaşık $3 civarındadır. 1000 USDT altındaki gönderimlerde verimli değildir. TON veya Polkadot ağını öneriyoruz.',
explorer: 'https://tronscan.org/#/transaction/',
minDeposit: 10,
},
];
@@ -71,18 +85,35 @@ interface Props {
export function DepositUSDTModal({ isOpen, onClose }: Props) {
const { hapticImpact, showAlert } = useTelegram();
const [selectedNetwork, setSelectedNetwork] = useState<Network>('trc20');
const [selectedNetwork, setSelectedNetwork] = useState<Network>('ton');
const [depositCode, setDepositCode] = useState<string>('');
const [depositAddress, setDepositAddress] = useState<string>('');
const [copied, setCopied] = useState<'address' | 'memo' | null>(null);
const [deposits, setDeposits] = useState<Deposit[]>([]);
const [showHistory, setShowHistory] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [trc20Accepted, setTrc20Accepted] = useState(false);
const network = NETWORKS.find((n) => n.id === selectedNetwork) || NETWORKS[0];
// Fetch user's deposit code via edge function
// Get deposit address based on network
const getNetworkAddress = (networkId: Network): string => {
switch (networkId) {
case 'ton':
return import.meta.env.VITE_DEPOSIT_TON_ADDRESS || '';
case 'polkadot':
return import.meta.env.VITE_DEPOSIT_POLKADOT_ADDRESS || '';
case 'trc20':
// TRC20 uses HD wallet - address comes from backend
return depositAddress;
default:
return '';
}
};
// Fetch user's deposit code and TRC20 address
useEffect(() => {
const fetchDepositCode = async () => {
const fetchDepositInfo = async () => {
if (!isOpen) return;
const initData = window.Telegram?.WebApp?.initData;
@@ -93,25 +124,26 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
setIsLoading(true);
try {
const { data, error } = await supabase.functions.invoke('get-deposit-code', {
const { data, error } = await supabase.functions.invoke('get-deposit-info', {
body: { initData },
});
if (error) {
console.error('Error fetching deposit code:', error);
console.error('Error fetching deposit info:', error);
setDepositCode('---');
} else if (data?.code) {
setDepositCode(data.code);
} else {
if (data?.code) setDepositCode(data.code);
if (data?.trc20Address) setDepositAddress(data.trc20Address);
}
} catch (err) {
console.error('Error fetching deposit code:', err);
console.error('Error fetching deposit info:', err);
setDepositCode('---');
} finally {
setIsLoading(false);
}
};
fetchDepositCode();
fetchDepositInfo();
}, [isOpen]);
// Fetch deposits history
@@ -179,6 +211,9 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
}
};
const currentAddress = getNetworkAddress(selectedNetwork);
const showTrc20Warning = selectedNetwork === 'trc20' && !trc20Accepted;
if (!isOpen) return null;
return (
@@ -223,7 +258,13 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
<div className="flex justify-between items-start">
<div>
<div className="flex items-center gap-2">
<span>{deposit.network === 'trc20' ? '🔴' : '⚪'}</span>
<span>
{deposit.network === 'ton'
? '💎'
: deposit.network === 'polkadot'
? '⚪'
: '🔴'}
</span>
<span className="font-mono">{deposit.amount} USDT</span>
</div>
<p className="text-xs text-muted-foreground mt-1">
@@ -260,128 +301,210 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) {
{/* Network Selection */}
<div>
<label className="text-sm text-muted-foreground mb-2 block">Torê Hilbijêre</label>
<div className="flex gap-2">
<div className="grid grid-cols-3 gap-2">
{NETWORKS.map((net) => (
<button
key={net.id}
onClick={() => {
setSelectedNetwork(net.id);
setTrc20Accepted(false);
hapticImpact('light');
}}
className={`flex-1 p-3 rounded-xl border transition-all ${
className={`p-3 rounded-xl border transition-all relative ${
selectedNetwork === net.id
? 'border-green-500 bg-green-500/10'
? net.recommended
? 'border-green-500 bg-green-500/10'
: 'border-yellow-500 bg-yellow-500/10'
: 'border-border bg-muted/50'
}`}
>
{net.recommended && (
<span className="absolute -top-2 left-1/2 -translate-x-1/2 text-[10px] bg-green-500 text-white px-1.5 py-0.5 rounded">
Pêşniyar
</span>
)}
<div className="text-xl mb-1">{net.icon}</div>
<div className="text-sm font-medium">{net.name}</div>
<div className="text-xs text-muted-foreground">{net.description}</div>
<div className="text-[10px] text-muted-foreground">{net.description}</div>
<div
className={`text-[10px] mt-1 ${net.fee > 1 ? 'text-yellow-400' : 'text-green-400'}`}
>
~${net.fee} fee
</div>
</button>
))}
</div>
</div>
{/* Important Notice */}
<div className="p-3 bg-yellow-500/10 border border-yellow-500/30 rounded-lg">
<div className="flex gap-2">
<AlertCircle className="w-5 h-5 text-yellow-400 flex-shrink-0" />
<div className="text-sm text-yellow-400">
<p className="font-medium">Girîng!</p>
<ul className="list-disc list-inside text-xs mt-1 space-y-1">
<li>
Kêmtirîn: <strong>{network.minAmount} USDT</strong>
</li>
<li>Memo/Not qada de koda xwe binivîse</li>
<li>Tenê USDT bişîne, tokenên din winda dibin</li>
</ul>
{/* TRC20 Warning */}
{showTrc20Warning && (
<div className="p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-xl">
<div className="flex gap-3">
<AlertTriangle className="w-6 h-6 text-yellow-400 flex-shrink-0" />
<div>
<p className="text-sm text-yellow-400 font-medium mb-2">Dikkkat!</p>
<p className="text-xs text-yellow-400/80 mb-3">{network.feeWarning}</p>
<p className="text-xs text-yellow-400/80 mb-3">
Mînak: 10 USDT bişîne 7 wUSDT werbigire ($3 masraf)
</p>
<button
onClick={() => {
setTrc20Accepted(true);
hapticImpact('medium');
}}
className="w-full py-2 bg-yellow-500/20 border border-yellow-500/50 rounded-lg text-yellow-400 text-sm font-medium"
>
Qebûl dikim, bi TRC20 bişîne
</button>
</div>
</div>
</div>
</div>
)}
{/* Deposit Address */}
<div className="bg-muted/50 rounded-xl p-4 space-y-4">
{/* Address */}
<div>
<label className="text-xs text-muted-foreground">Navnîşana Depoyê</label>
<div className="flex items-center gap-2 mt-1">
<code className="flex-1 text-sm font-mono bg-background p-2 rounded-lg break-all">
{network.address || 'Amade nîne'}
</code>
<button
onClick={() => copyToClipboard(network.address, 'address')}
disabled={!network.address}
className="p-2 bg-background rounded-lg hover:bg-muted transition-colors"
>
{copied === 'address' ? (
<CheckCircle className="w-5 h-5 text-green-400" />
) : (
<Copy className="w-5 h-5" />
)}
</button>
</div>
</div>
{/* Memo/Reference */}
<div>
<label className="text-xs text-muted-foreground">Memo / Referans (PÊWÎST)</label>
<div className="flex items-center gap-2 mt-1">
{isLoading ? (
<div className="flex-1 p-2 bg-background rounded-lg flex justify-center">
<Loader2 className="w-5 h-5 animate-spin" />
{/* Deposit Info - Show only when TRC20 is accepted or other networks */}
{(selectedNetwork !== 'trc20' || trc20Accepted) && (
<>
{/* Important Notice */}
<div className="p-3 bg-blue-500/10 border border-blue-500/30 rounded-lg">
<div className="flex gap-2">
<AlertCircle className="w-5 h-5 text-blue-400 flex-shrink-0" />
<div className="text-sm text-blue-400">
<p className="font-medium">Girîng!</p>
<ul className="list-disc list-inside text-xs mt-1 space-y-1">
<li>
Kêmtirîn: <strong>{network.minDeposit} USDT</strong>
</li>
{selectedNetwork !== 'trc20' && (
<li>Memo/Comment qada de koda xwe binivîse</li>
)}
<li>Tenê USDT bişîne, tokenên din winda dibin</li>
{selectedNetwork === 'trc20' && (
<li className="text-yellow-400">
$3 masraf ji mîqdara we kêmkirin
</li>
)}
</ul>
</div>
) : (
<code className="flex-1 text-lg font-mono font-bold bg-background p-2 rounded-lg text-center text-green-400">
{depositCode || '---'}
</code>
)}
<button
onClick={() => copyToClipboard(depositCode, 'memo')}
disabled={!depositCode || depositCode === '---'}
className="p-2 bg-background rounded-lg hover:bg-muted transition-colors"
>
{copied === 'memo' ? (
<CheckCircle className="w-5 h-5 text-green-400" />
) : (
<Copy className="w-5 h-5" />
)}
</button>
</div>
</div>
<p className="text-xs text-red-400 mt-1">
kodê di memo/not de binivîse, wekî din depoya te nayê nas kirin!
{/* Deposit Address */}
<div className="bg-muted/50 rounded-xl p-4 space-y-4">
{/* Address */}
<div>
<label className="text-xs text-muted-foreground">Navnîşana Depoyê</label>
<div className="flex items-center gap-2 mt-1">
{isLoading && selectedNetwork === 'trc20' ? (
<div className="flex-1 p-2 bg-background rounded-lg flex justify-center">
<Loader2 className="w-5 h-5 animate-spin" />
</div>
) : (
<code className="flex-1 text-xs font-mono bg-background p-2 rounded-lg break-all">
{currentAddress || 'Amade nîne'}
</code>
)}
<button
onClick={() => copyToClipboard(currentAddress, 'address')}
disabled={!currentAddress}
className="p-2 bg-background rounded-lg hover:bg-muted transition-colors"
>
{copied === 'address' ? (
<CheckCircle className="w-5 h-5 text-green-400" />
) : (
<Copy className="w-5 h-5" />
)}
</button>
</div>
</div>
{/* Memo/Reference - Not needed for TRC20 (unique address) */}
{selectedNetwork !== 'trc20' && (
<div>
<label className="text-xs text-muted-foreground">
Memo / Comment (PÊWÎST)
</label>
<div className="flex items-center gap-2 mt-1">
{isLoading ? (
<div className="flex-1 p-2 bg-background rounded-lg flex justify-center">
<Loader2 className="w-5 h-5 animate-spin" />
</div>
) : (
<code className="flex-1 text-lg font-mono font-bold bg-background p-2 rounded-lg text-center text-green-400">
{depositCode || '---'}
</code>
)}
<button
onClick={() => copyToClipboard(depositCode, 'memo')}
disabled={!depositCode || depositCode === '---'}
className="p-2 bg-background rounded-lg hover:bg-muted transition-colors"
>
{copied === 'memo' ? (
<CheckCircle className="w-5 h-5 text-green-400" />
) : (
<Copy className="w-5 h-5" />
)}
</button>
</div>
<p className="text-xs text-red-400 mt-1">
kodê di memo de binivîse, wekî din depoya te nayê nas kirin!
</p>
</div>
)}
{selectedNetwork === 'trc20' && (
<p className="text-xs text-green-400">
Ev navnîşan tenê ya te ye. Memo ne pêwîst e.
</p>
)}
</div>
{/* Steps */}
<div className="bg-muted/30 rounded-xl p-4">
<h4 className="text-sm font-medium mb-3">Çawa Depo Bikim?</h4>
<ol className="text-xs text-muted-foreground space-y-2">
<li className="flex gap-2">
<span className="text-green-400 font-bold">1.</span>
<span>Navnîşan kopî bike</span>
</li>
{selectedNetwork !== 'trc20' && (
<li className="flex gap-2">
<span className="text-green-400 font-bold">2.</span>
<span>Memo kodê kopî bike</span>
</li>
)}
<li className="flex gap-2">
<span className="text-green-400 font-bold">
{selectedNetwork === 'trc20' ? '2' : '3'}.
</span>
<span>
{selectedNetwork === 'ton'
? 'Telegram Wallet an cîzdana xwe veke'
: selectedNetwork === 'polkadot'
? 'Polkadot cîzdana xwe veke'
: 'TronLink an cîzdana xwe veke'}
</span>
</li>
<li className="flex gap-2">
<span className="text-green-400 font-bold">
{selectedNetwork === 'trc20' ? '3' : '4'}.
</span>
<span>USDT bişîne navnîşana jorîn</span>
</li>
<li className="flex gap-2">
<span className="text-green-400 font-bold">
{selectedNetwork === 'trc20' ? '4' : '5'}.
</span>
<span>wUSDT di nav çend hûrdeman de li hesabê te be</span>
</li>
</ol>
</div>
{/* Processing Time */}
<p className="text-center text-xs text-muted-foreground">
Dema pêvajoyê: ~1-5 hûrdem
</p>
</div>
</div>
{/* Steps */}
<div className="bg-muted/30 rounded-xl p-4">
<h4 className="text-sm font-medium mb-3">Çawa Depo Bikim?</h4>
<ol className="text-xs text-muted-foreground space-y-2">
<li className="flex gap-2">
<span className="text-green-400 font-bold">1.</span>
<span>Navnîşan û memo kopî bike</span>
</li>
<li className="flex gap-2">
<span className="text-green-400 font-bold">2.</span>
<span>Di cîzdana xwe de ({network.name}) USDT bişîne navnîşana jorîn</span>
</li>
<li className="flex gap-2">
<span className="text-green-400 font-bold">3.</span>
<span>
<strong>MEMO qada de koda xwe binivîse!</strong>
</span>
</li>
<li className="flex gap-2">
<span className="text-green-400 font-bold">4.</span>
<span>Piştî {network.confirmations} pejirandinê, wUSDT li Asset Hub be</span>
</li>
</ol>
</div>
{/* Processing Time */}
<p className="text-center text-xs text-muted-foreground">
Dema pêvajoyê: ~5-30 hûrdem li gorî torê
</p>
</>
)}
</div>
)}
</div>
+3 -3
View File
@@ -1,5 +1,5 @@
{
"version": "1.0.169",
"buildTime": "2026-02-07T22:14:22.046Z",
"buildNumber": 1770502462047
"version": "1.0.170",
"buildTime": "2026-02-07T23:00:20.055Z",
"buildNumber": 1770505220056
}
@@ -0,0 +1,290 @@
/**
* Get Deposit Info - Returns user's deposit code and TRC20 HD wallet address
*/
import { serve } from 'https://deno.land/std@0.177.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
import { createHmac } from 'https://deno.land/std@0.177.0/node/crypto.ts';
import { HDKey } from 'https://esm.sh/@scure/bip32@1.3.2';
import * as bip39 from 'https://esm.sh/@scure/bip39@1.2.1';
import { wordlist } from 'https://esm.sh/@scure/bip39@1.2.1/wordlists/english';
import { keccak_256 } from 'https://esm.sh/@noble/hashes@1.3.2/sha3';
import { sha256 } from 'https://esm.sh/@noble/hashes@1.3.2/sha256';
import { bytesToHex, hexToBytes } from 'https://esm.sh/@noble/hashes@1.3.2/utils';
import { secp256k1 } from 'https://esm.sh/@noble/curves@1.2.0/secp256k1';
const ALLOWED_ORIGIN = 'https://telegram.pezkuwichain.io';
const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
function getCorsHeaders(): Record<string, string> {
return {
'Access-Control-Allow-Origin': ALLOWED_ORIGIN,
'Access-Control-Allow-Headers':
'authorization, x-client-info, apikey, content-type, x-supabase-client-platform',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
};
}
interface TelegramUser {
id: number;
first_name: string;
}
function validateInitData(initData: string, botToken: string): TelegramUser | null {
try {
const params = new URLSearchParams(initData);
const hash = params.get('hash');
if (!hash) return null;
params.delete('hash');
const sortedParams = Array.from(params.entries())
.sort(([a], [b]) => a.localeCompare(b))
.map(([key, value]) => `${key}=${value}`)
.join('\n');
const secretKey = createHmac('sha256', 'WebAppData').update(botToken).digest();
const calculatedHash = createHmac('sha256', secretKey).update(sortedParams).digest('hex');
if (calculatedHash !== hash) return null;
const authDate = parseInt(params.get('auth_date') || '0');
const now = Math.floor(Date.now() / 1000);
if (now - authDate > 86400) return null;
const userStr = params.get('user');
if (!userStr) return null;
return JSON.parse(userStr) as TelegramUser;
} catch {
return null;
}
}
function generateDepositCode(): string {
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
let result = 'PEZ-';
for (let i = 0; i < 8; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
// Base58Check encode
function base58CheckEncode(payload: Uint8Array): string {
// Double SHA256 for checksum
const hash1 = sha256(payload);
const hash2 = sha256(hash1);
const checksum = hash2.slice(0, 4);
// Combine payload and checksum
const data = new Uint8Array(payload.length + 4);
data.set(payload);
data.set(checksum, payload.length);
// Count leading zeros
let zeros = 0;
for (let i = 0; i < data.length && data[i] === 0; i++) {
zeros++;
}
// Convert to base58
let num = BigInt('0x' + bytesToHex(data));
let result = '';
while (num > 0n) {
const remainder = Number(num % 58n);
result = BASE58_ALPHABET[remainder] + result;
num = num / 58n;
}
// Add leading '1's for zeros
return '1'.repeat(zeros) + result;
}
// Derive TRC20 address from HD wallet
function deriveTronAddress(mnemonic: string, index: number): string {
try {
// Convert mnemonic to seed
const seed = bip39.mnemonicToSeedSync(mnemonic, '');
// Create HD key from seed
const hdKey = HDKey.fromMasterSeed(seed);
// Derive path: m/44'/195'/0'/0/{index}
// 195 is TRON's coin type
const derivedKey = hdKey.derive(`m/44'/195'/0'/0/${index}`);
if (!derivedKey.privateKey) {
throw new Error('Failed to derive private key');
}
// Get uncompressed public key
const publicKey = secp256k1.getPublicKey(derivedKey.privateKey, false);
// Skip the 0x04 prefix and hash with keccak256
const publicKeyWithoutPrefix = publicKey.slice(1);
const hash = keccak_256(publicKeyWithoutPrefix);
// Take last 20 bytes for address
const addressBytes = hash.slice(-20);
// Add TRON mainnet prefix (0x41)
const addressWithPrefix = new Uint8Array(21);
addressWithPrefix[0] = 0x41;
addressWithPrefix.set(addressBytes, 1);
// Base58Check encode
return base58CheckEncode(addressWithPrefix);
} catch (err) {
console.error('Error deriving TRON address:', err);
throw err;
}
}
serve(async (req) => {
const corsHeaders = getCorsHeaders();
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders });
}
try {
const body = await req.json();
const { initData } = body;
if (!initData) {
return new Response(JSON.stringify({ error: 'Missing initData' }), {
status: 400,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
});
}
const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
const botToken = Deno.env.get('TELEGRAM_BOT_TOKEN');
const tronHdMnemonic = Deno.env.get('DEPOSIT_TRON_HD_MNEMONIC');
if (!botToken) {
return new Response(JSON.stringify({ error: 'Server configuration error' }), {
status: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
});
}
const telegramUser = validateInitData(initData, botToken);
if (!telegramUser) {
return new Response(JSON.stringify({ error: 'Invalid Telegram data' }), {
status: 401,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
});
}
const supabase = createClient(supabaseUrl, supabaseServiceKey, {
auth: { autoRefreshToken: false, persistSession: false },
});
// Get or create user
let userId: string;
let depositIndex: number;
const { data: existingUser } = await supabase
.from('tg_users')
.select('id, deposit_index')
.eq('telegram_id', telegramUser.id)
.single();
if (existingUser) {
userId = existingUser.id;
depositIndex = existingUser.deposit_index ?? -1;
// Ensure deposit_index exists
if (depositIndex < 0) {
// Get next available index
const { data: maxIndexData } = await supabase
.from('tg_users')
.select('deposit_index')
.not('deposit_index', 'is', null)
.order('deposit_index', { ascending: false })
.limit(1)
.single();
depositIndex = (maxIndexData?.deposit_index ?? -1) + 1;
await supabase.from('tg_users').update({ deposit_index: depositIndex }).eq('id', userId);
}
} else {
// Get next available index
const { data: maxIndexData } = await supabase
.from('tg_users')
.select('deposit_index')
.not('deposit_index', 'is', null)
.order('deposit_index', { ascending: false })
.limit(1)
.single();
depositIndex = (maxIndexData?.deposit_index ?? -1) + 1;
const { data: newUser, error: createError } = await supabase
.from('tg_users')
.insert({
telegram_id: telegramUser.id,
first_name: telegramUser.first_name,
deposit_index: depositIndex,
})
.select('id')
.single();
if (createError || !newUser) {
return new Response(JSON.stringify({ error: 'Failed to create user' }), {
status: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
});
}
userId = newUser.id;
}
// Get or create deposit code
let depositCode: string;
const { data: existingCode } = await supabase
.from('tg_user_deposit_codes')
.select('code')
.eq('user_id', userId)
.single();
if (existingCode) {
depositCode = existingCode.code;
} else {
depositCode = generateDepositCode();
await supabase.from('tg_user_deposit_codes').insert({
user_id: userId,
code: depositCode,
});
}
// Generate TRC20 address from HD wallet
let trc20Address = '';
if (tronHdMnemonic && depositIndex >= 0) {
try {
trc20Address = deriveTronAddress(tronHdMnemonic, depositIndex);
} catch (err) {
console.error('Error deriving TRC20 address:', err);
}
}
return new Response(
JSON.stringify({
code: depositCode,
trc20Address,
depositIndex,
}),
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
} catch (err) {
console.error('Get deposit info error:', err);
return new Response(JSON.stringify({ error: 'Internal server error' }), {
status: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
});
}
});
@@ -0,0 +1,201 @@
/**
* Process Deposits - Cron job to check for incoming USDT deposits
* Checks TRC20 (TRON) and Polkadot Asset Hub for incoming transfers
* Then transfers wUSDT to user's Asset Hub address
*/
import { serve } from 'https://deno.land/std@0.177.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
const TRON_API = 'https://api.trongrid.io';
const USDT_TRC20_CONTRACT = 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t'; // USDT TRC20 contract
interface TRC20Transfer {
transaction_id: string;
from: string;
to: string;
value: string;
block_timestamp: number;
}
interface DepositCode {
code: string;
user_id: string;
user_address: string;
}
serve(async (req) => {
// Verify this is called by Supabase cron or with secret
const authHeader = req.headers.get('Authorization');
const cronSecret = Deno.env.get('CRON_SECRET');
if (cronSecret && authHeader !== `Bearer ${cronSecret}`) {
// Also allow service role
const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY');
if (authHeader !== `Bearer ${supabaseServiceKey}`) {
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
status: 401,
headers: { 'Content-Type': 'application/json' },
});
}
}
const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
const tronAddress = Deno.env.get('DEPOSIT_TRON_ADDRESS');
const polkadotAddress = Deno.env.get('DEPOSIT_POLKADOT_ADDRESS');
const supabase = createClient(supabaseUrl, supabaseServiceKey, {
auth: { autoRefreshToken: false, persistSession: false },
});
const results = {
trc20: { checked: 0, found: 0, processed: 0, errors: [] as string[] },
polkadot: { checked: 0, found: 0, processed: 0, errors: [] as string[] },
};
// Get all deposit codes with user addresses
const { data: depositCodes } = await supabase.from('tg_user_deposit_codes').select(`
code,
user_id,
tg_users!inner(wallet_address)
`);
const codeMap = new Map<string, { userId: string; walletAddress: string }>();
if (depositCodes) {
for (const dc of depositCodes) {
const userAddress = (dc as any).tg_users?.wallet_address;
if (userAddress) {
codeMap.set(dc.code, { userId: dc.user_id, walletAddress: userAddress });
}
}
}
// ==================== TRC20 DEPOSITS ====================
if (tronAddress) {
try {
// Get recent TRC20 transfers to our deposit address
const response = await fetch(
`${TRON_API}/v1/accounts/${tronAddress}/transactions/trc20?limit=50&contract_address=${USDT_TRC20_CONTRACT}`,
{
headers: { 'TRON-PRO-API-KEY': Deno.env.get('TRONGRID_API_KEY') || '' },
}
);
if (response.ok) {
const data = await response.json();
const transfers = (data.data || []) as TRC20Transfer[];
results.trc20.checked = transfers.length;
for (const tx of transfers) {
// Only incoming transfers
if (tx.to.toLowerCase() !== tronAddress.toLowerCase()) continue;
// Check if already processed
const { data: existing } = await supabase
.from('tg_deposits')
.select('id')
.eq('tx_hash', tx.transaction_id)
.single();
if (existing) continue;
results.trc20.found++;
// USDT has 6 decimals
const amount = parseInt(tx.value) / 1e6;
// For TRC20, we need to check the memo in the transaction
// Unfortunately, TRC20 transfers don't have memo field directly
// We'll need to match by amount or create pending deposits that admin confirms
// Create pending deposit for manual review or amount matching
const { error: insertError } = await supabase.from('tg_deposits').insert({
user_id: null, // Will be matched later
network: 'trc20',
amount,
tx_hash: tx.transaction_id,
status: 'confirming',
created_at: new Date(tx.block_timestamp).toISOString(),
});
if (insertError) {
results.trc20.errors.push(`Insert error: ${insertError.message}`);
} else {
results.trc20.processed++;
}
}
}
} catch (err) {
results.trc20.errors.push(`TRC20 error: ${err}`);
}
}
// ==================== POLKADOT DEPOSITS ====================
if (polkadotAddress) {
try {
// Use Subscan API for Polkadot Asset Hub
const subscanResponse = await fetch(
'https://assethub-polkadot.api.subscan.io/api/v2/scan/transfers',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': Deno.env.get('SUBSCAN_API_KEY') || '',
},
body: JSON.stringify({
address: polkadotAddress,
direction: 'received',
row: 50,
page: 0,
asset_symbol: 'USDT',
}),
}
);
if (subscanResponse.ok) {
const data = await subscanResponse.json();
const transfers = data.data?.transfers || [];
results.polkadot.checked = transfers.length;
for (const tx of transfers) {
// Check if already processed
const { data: existing } = await supabase
.from('tg_deposits')
.select('id')
.eq('tx_hash', tx.hash)
.single();
if (existing) continue;
results.polkadot.found++;
// Polkadot USDT has 6 decimals
const amount = parseFloat(tx.amount) / 1e6;
// Create pending deposit
const { error: insertError } = await supabase.from('tg_deposits').insert({
user_id: null,
network: 'polkadot',
amount,
tx_hash: tx.hash,
status: 'confirming',
created_at: new Date(tx.block_timestamp * 1000).toISOString(),
});
if (insertError) {
results.polkadot.errors.push(`Insert error: ${insertError.message}`);
} else {
results.polkadot.processed++;
}
}
}
} catch (err) {
results.polkadot.errors.push(`Polkadot error: ${err}`);
}
}
return new Response(JSON.stringify({ success: true, results }), {
headers: { 'Content-Type': 'application/json' },
});
});
@@ -0,0 +1,16 @@
-- Add deposit_index column for TRC20 HD wallet derivation
ALTER TABLE tg_users ADD COLUMN IF NOT EXISTS deposit_index INTEGER UNIQUE;
-- Create index for faster lookups
CREATE INDEX IF NOT EXISTS idx_users_deposit_index ON tg_users(deposit_index);
-- Assign deposit_index to existing users
WITH numbered_users AS (
SELECT id, ROW_NUMBER() OVER (ORDER BY created_at) - 1 as idx
FROM tg_users
WHERE deposit_index IS NULL
)
UPDATE tg_users
SET deposit_index = numbered_users.idx
FROM numbered_users
WHERE tg_users.id = numbered_users.id;