diff --git a/.env.example b/.env.example index 96e1ff2..5092938 100644 --- a/.env.example +++ b/.env.example @@ -11,4 +11,3 @@ APP_URL="MY_APP_URL" # Supabase Credentials VITE_SUPABASE_URL="https://klaompnbmjufvhjkeeno.supabase.co" VITE_SUPABASE_ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtsYW9tcG5ibWp1ZnZoamtlZW5vIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODE1NjY5ODEsImV4cCI6MjA5NzE0Mjk4MX0.udKgeFZLsVzXvSU0oqR0F3_J7EDCA1g7MxF00l8LEEc" -SUPABASE_STORAGE_BUCKET="imagenes" diff --git a/api/index.ts b/api/index.ts index 6896760..0c14791 100644 --- a/api/index.ts +++ b/api/index.ts @@ -1,3 +1,3 @@ -import { app } from "../server.js"; +import { app } from "../server"; export default app; diff --git a/package-lock.json b/package-lock.json index 34e1cdb..0a9e9e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "@google/genai": "^2.4.0", + "@solana/wallet-adapter-backpack": "^0.1.14", "@supabase/supabase-js": "^2.108.1", "@tailwindcss/vite": "^4.1.14", "@vitejs/plugin-react": "^5.0.4", @@ -249,6 +250,16 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", @@ -779,6 +790,35 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "peer": true, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -1167,6 +1207,164 @@ "win32" ] }, + "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", + "peer": true, + "dependencies": { + "buffer": "~6.0.3" + }, + "engines": { + "node": ">=5.10" + } + }, + "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", + "peer": true, + "dependencies": { + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "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", + "peer": true, + "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/errors": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.3.0.tgz", + "integrity": "sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ==", + "license": "MIT", + "peer": true, + "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/wallet-adapter-backpack": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/@solana/wallet-adapter-backpack/-/wallet-adapter-backpack-0.1.14.tgz", + "integrity": "sha512-DfNLd5S1P7rmrgqMp+jRd21ryuXUxia1mu4qmZ+cau1NGFO2v5ep14LhzYXmqPde6kgbzPLPkLdRnkffLdI4TA==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "Apache-2.0", + "dependencies": { + "@solana/wallet-adapter-base": "^0.9.23" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@solana/web3.js": "^1.77.3" + } + }, + "node_modules/@solana/wallet-adapter-base": { + "version": "0.9.27", + "resolved": "https://registry.npmjs.org/@solana/wallet-adapter-base/-/wallet-adapter-base-0.9.27.tgz", + "integrity": "sha512-kXjeNfNFVs/NE9GPmysBRKQ/nf+foSaq3kfVSeMcO/iVgigyRmB551OjU3WyAolLG/1jeEfKLqF9fKwMCRkUqg==", + "license": "Apache-2.0", + "dependencies": { + "@solana/wallet-standard-features": "^1.3.0", + "@wallet-standard/base": "^1.1.0", + "@wallet-standard/features": "^1.1.0", + "eventemitter3": "^5.0.1" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@solana/web3.js": "^1.98.0" + } + }, + "node_modules/@solana/wallet-standard-features": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@solana/wallet-standard-features/-/wallet-standard-features-1.3.0.tgz", + "integrity": "sha512-ZhpZtD+4VArf6RPitsVExvgkF+nGghd1rzPjd97GmBximpnt1rsUxMOEyoIEuH3XBxPyNB6Us7ha7RHWQR+abg==", + "license": "Apache-2.0", + "dependencies": { + "@wallet-standard/base": "^1.1.0", + "@wallet-standard/features": "^1.1.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@solana/web3.js": { + "version": "1.98.4", + "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.98.4.tgz", + "integrity": "sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw==", + "license": "MIT", + "peer": true, + "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/node-fetch": { + "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==", + "license": "MIT", + "peer": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/@supabase/auth-js": { "version": "2.108.1", "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.108.1.tgz", @@ -1251,6 +1449,16 @@ "node": ">=20.0.0" } }, + "node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/@tailwindcss/node": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.1.tgz", @@ -1564,7 +1772,6 @@ "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -1678,6 +1885,23 @@ "@types/node": "*" } }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "license": "MIT", + "peer": true + }, + "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", + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@vitejs/plugin-react": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", @@ -1698,6 +1922,27 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/@wallet-standard/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@wallet-standard/base/-/base-1.1.1.tgz", + "integrity": "sha512-gggIHTtxicF9XFMQ12DkfS6NAG92Ak795JeSA7f2whAQ6Y3AkMWWuCMxSZXG2NIPN42kEaZSNVjqMsJRaJRxMQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=22" + } + }, + "node_modules/@wallet-standard/features": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@wallet-standard/features/-/features-1.1.1.tgz", + "integrity": "sha512-aCWYmVeSCGViyEU5k7GMoW8zxE4Gs+C1s1Pp2XLesvSNlnZ4PMES9HUnTB3hl0b3RVj7C61yze3IWyrncqg4MA==", + "license": "Apache-2.0", + "dependencies": { + "@wallet-standard/base": "^1.1.1" + }, + "engines": { + "node": ">=22" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -1720,6 +1965,19 @@ "node": ">= 14" } }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -1763,6 +2021,16 @@ "postcss": "^8.1.0" } }, + "node_modules/base-x": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", + "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -1804,6 +2072,13 @@ "node": "*" } }, + "node_modules/bn.js": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.3.tgz", + "integrity": "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==", + "license": "MIT", + "peer": true + }, "node_modules/body-parser": { "version": "1.20.5", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", @@ -1843,6 +2118,18 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "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", + "peer": true, + "dependencies": { + "bn.js": "^5.2.0", + "bs58": "^4.0.0", + "text-encoding-utf-8": "^1.0.2" + } + }, "node_modules/browserslist": { "version": "4.28.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", @@ -1876,12 +2163,62 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "license": "MIT", + "peer": true, + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, + "node_modules/bufferutil": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz", + "integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1940,6 +2277,29 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -2008,6 +2368,19 @@ } } }, + "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", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -2135,6 +2508,23 @@ "node": ">= 0.4" } }, + "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", + "peer": true + }, + "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", + "peer": true, + "dependencies": { + "es6-promise": "^4.0.3" + } + }, "node_modules/esbuild": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", @@ -2200,6 +2590,12 @@ "node": ">= 0.6" } }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, "node_modules/express": { "version": "4.22.2", "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", @@ -2267,6 +2663,22 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, + "node_modules/eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", + "peer": true, + "engines": { + "node": "> 0.1.90" + } + }, + "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", + "peer": true + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -2610,6 +3022,16 @@ "node": ">= 14" } }, + "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", + "peer": true, + "dependencies": { + "ms": "^2.0.0" + } + }, "node_modules/iceberg-js": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", @@ -2631,6 +3053,27 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause", + "peer": true + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -2646,6 +3089,94 @@ "node": ">= 0.10" } }, + "node_modules/isomorphic-ws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", + "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/jayson": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/jayson/-/jayson-4.3.0.tgz", + "integrity": "sha512-AauzHcUcqs8OBnCHOkJY280VaTiCm57AbuO7lqzcw7JapGj50BisE3xhksye4zlTSR1+1tAz67wLTl8tEH1obQ==", + "license": "MIT", + "peer": true, + "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", + "peer": true + }, + "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", + "peer": true + }, + "node_modules/jayson/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, + "peer": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/jayson/node_modules/ws": { + "version": "7.5.11", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", + "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/jiti": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", @@ -2682,6 +3213,13 @@ "bignumber.js": "^9.0.0" } }, + "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", + "peer": true + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -3162,6 +3700,19 @@ "url": "https://opencollective.com/node-fetch" } }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-releases": { "version": "2.0.47", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", @@ -3434,6 +3985,54 @@ "fsevents": "~2.3.2" } }, + "node_modules/rpc-websockets": { + "version": "9.3.9", + "resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-9.3.9.tgz", + "integrity": "sha512-2iQDaTB4g5fDB2ihrTFSJSibCEuxaRi1q7qTW7ZO9/M5/TC+ToHA4D9/ffNLEbAoHNNrcdeP05oATNk44SKZXA==", + "license": "LGPL-3.0-only", + "peer": true, + "dependencies": { + "@swc/helpers": "^0.5.11", + "@types/uuid": "^10.0.0", + "@types/ws": "^8.2.2", + "buffer": "^6.0.3", + "eventemitter3": "^5.0.1", + "uuid": "^14.0.0", + "ws": "^8.5.0" + }, + "funding": { + "type": "paypal", + "url": "https://paypal.me/kozjak" + }, + "optionalDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^6.0.0" + } + }, + "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", + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/rpc-websockets/node_modules/uuid": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", + "integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "peer": true, + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -3625,6 +4224,33 @@ "node": ">= 0.8" } }, + "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", + "peer": true + }, + "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", + "peer": true, + "dependencies": { + "stream-chain": "^2.2.5" + } + }, + "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", + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tailwindcss": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.1.tgz", @@ -3644,6 +4270,12 @@ "url": "https://opencollective.com/webpack" } }, + "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==", + "peer": true + }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", @@ -3669,6 +4301,13 @@ "node": ">=0.6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT", + "peer": true + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -4169,7 +4808,6 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -4224,6 +4862,21 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/utf-8-validate": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.6.tgz", + "integrity": "sha512-q3l3P9UtEEiAHcsgsqTgf9PPjctrDWoIXW3NpOHFdRDbLvu4DLIcxHangJ4RLrWkBcKjmcs/6NkerI8T/rE4LA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -4233,6 +4886,17 @@ "node": ">= 0.4.0" } }, + "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==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "license": "MIT", + "peer": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -4325,6 +4989,24 @@ "node": ">= 8" } }, + "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==", + "license": "BSD-2-Clause", + "peer": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "peer": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/ws": { "version": "8.21.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", diff --git a/package.json b/package.json index 2847dcc..4ae316e 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ }, "dependencies": { "@google/genai": "^2.4.0", + "@solana/wallet-adapter-backpack": "^0.1.14", "@supabase/supabase-js": "^2.108.1", "@tailwindcss/vite": "^4.1.14", "@vitejs/plugin-react": "^5.0.4", diff --git a/schema.sql b/schema.sql index 434ef04..3ca4f5c 100644 --- a/schema.sql +++ b/schema.sql @@ -51,15 +51,40 @@ ALTER TABLE public.ledger ENABLE ROW LEVEL SECURITY; ALTER TABLE public.store_metrics ENABLE ROW LEVEL SECURITY; -- 5. Create policies to allow all actions for anonymous users (for development) +DROP POLICY IF EXISTS "Allow anonymous read crops" ON public.crops; CREATE POLICY "Allow anonymous read crops" ON public.crops FOR SELECT USING (true); +DROP POLICY IF EXISTS "Allow anonymous insert crops" ON public.crops; CREATE POLICY "Allow anonymous insert crops" ON public.crops FOR INSERT WITH CHECK (true); +DROP POLICY IF EXISTS "Allow anonymous update crops" ON public.crops; CREATE POLICY "Allow anonymous update crops" ON public.crops FOR UPDATE USING (true); +DROP POLICY IF EXISTS "Allow anonymous delete crops" ON public.crops; CREATE POLICY "Allow anonymous delete crops" ON public.crops FOR DELETE USING (true); +DROP POLICY IF EXISTS "Allow anonymous read ledger" ON public.ledger; CREATE POLICY "Allow anonymous read ledger" ON public.ledger FOR SELECT USING (true); +DROP POLICY IF EXISTS "Allow anonymous insert ledger" ON public.ledger; CREATE POLICY "Allow anonymous insert ledger" ON public.ledger FOR INSERT WITH CHECK (true); +DROP POLICY IF EXISTS "Allow anonymous delete ledger" ON public.ledger; CREATE POLICY "Allow anonymous delete ledger" ON public.ledger FOR DELETE USING (true); +DROP POLICY IF EXISTS "Allow anonymous read metrics" ON public.store_metrics; CREATE POLICY "Allow anonymous read metrics" ON public.store_metrics FOR SELECT USING (true); +DROP POLICY IF EXISTS "Allow anonymous insert metrics" ON public.store_metrics; CREATE POLICY "Allow anonymous insert metrics" ON public.store_metrics FOR INSERT WITH CHECK (true); +DROP POLICY IF EXISTS "Allow anonymous update metrics" ON public.store_metrics; CREATE POLICY "Allow anonymous update metrics" ON public.store_metrics FOR UPDATE USING (true); + +-- 6. Storage Bucket setup for 'imagenes' +-- Important: You must run this in the Supabase SQL Editor +INSERT INTO storage.buckets (id, name, public) VALUES ('imagenes', 'imagenes', true) ON CONFLICT DO NOTHING; + +-- Storage RLS Policies +DROP POLICY IF EXISTS "Allow public read imagenes" ON storage.objects; +CREATE POLICY "Allow public read imagenes" ON storage.objects FOR SELECT USING (bucket_id = 'imagenes'); +DROP POLICY IF EXISTS "Allow public insert imagenes" ON storage.objects; +CREATE POLICY "Allow public insert imagenes" ON storage.objects FOR INSERT WITH CHECK (bucket_id = 'imagenes'); +DROP POLICY IF EXISTS "Allow public update imagenes" ON storage.objects; +CREATE POLICY "Allow public update imagenes" ON storage.objects FOR UPDATE USING (bucket_id = 'imagenes'); +DROP POLICY IF EXISTS "Allow public delete imagenes" ON storage.objects; +CREATE POLICY "Allow public delete imagenes" ON storage.objects FOR DELETE USING (bucket_id = 'imagenes'); + diff --git a/server.ts b/server.ts index 910c29d..7347d17 100644 --- a/server.ts +++ b/server.ts @@ -13,63 +13,6 @@ const rawSupabaseUrl = "https://klaompnbmjufvhjkeeno.supabase.co"; const supabaseUrl = rawSupabaseUrl.replace(/\/rest\/v1\/?$/, ''); const supabaseAnonKey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtsYW9tcG5ibWp1ZnZoamtlZW5vIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODE1NjY5ODEsImV4cCI6MjA5NzE0Mjk4MX0.udKgeFZLsVzXvSU0oqR0F3_J7EDCA1g7MxF00l8LEEc"; const supabase = createClient(supabaseUrl, supabaseAnonKey); -const SUPABASE_QUERY_TIMEOUT_MS = 3000; -const PLANT_IMAGES_BUCKET = process.env.SUPABASE_STORAGE_BUCKET || "imagenes"; - -const normalizeImageExtension = (mimeType?: string) => { - const rawExt = mimeType?.split("/")?.[1]?.toLowerCase() || "jpg"; - if (rawExt === "jpeg") return "jpg"; - if (rawExt === "svg+xml") return "svg"; - return rawExt.replace(/[^a-z0-9]/g, "") || "jpg"; -}; - -const uploadScanImageToSupabase = async ( - base64Image: string, - mimeType?: string -): Promise => { - const parts = base64Image.split(";base64,"); - const cleanBase = parts.length > 1 ? parts[1] : base64Image; - const buffer = Buffer.from(cleanBase, "base64"); - const contentType = mimeType || base64Image.match(/^data:([^;]+);base64,/i)?.[1] || "image/jpeg"; - const ext = normalizeImageExtension(contentType); - const objectPath = `scans/scan-${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`; - - const { error } = await supabase.storage - .from(PLANT_IMAGES_BUCKET) - .upload(objectPath, buffer, { - contentType, - upsert: false, - }); - - if (error) { - throw error; - } - - const { data } = supabase.storage - .from(PLANT_IMAGES_BUCKET) - .getPublicUrl(objectPath); - - return data.publicUrl; -}; - -const withTimeout = async ( - promise: Promise, - timeoutMs: number, - errorMessage: string -): Promise => { - let timeout: NodeJS.Timeout | undefined; - const timeoutPromise = new Promise((_, reject) => { - timeout = setTimeout(() => reject(new Error(errorMessage)), timeoutMs); - }); - - try { - return await Promise.race([promise, timeoutPromise]); - } finally { - if (timeout) { - clearTimeout(timeout); - } - } -}; // Initialize Gemini Client Lazily/Safely let aiClient: GoogleGenAI | null = null; @@ -558,9 +501,10 @@ function saveVolume(volume: number) { } } -let activeCrops: ServerCrop[] = loadCrops(); -let paymentLedger: ServerLedgerLog[] = loadLedger(); -let mockVolumenSalesUsd = loadVolume(); +// Removing local variables +let activeCrops: ServerCrop[] = []; +let paymentLedger: ServerLedgerLog[] = []; +let mockVolumenSalesUsd = 0; export const app = express(); @@ -569,36 +513,17 @@ async function startServer() { app.use(express.json({ limit: "15mb" })); app.use(express.urlencoded({ extended: true, limit: "15mb" })); - app.use((err: any, _req: any, res: any, next: any) => { - if (!err) { - return next(); - } - - console.error("Error procesando el cuerpo de la petición:", err); - const status = err.type === "entity.too.large" ? 413 : 400; - return res.status(status).json({ - success: false, - error: status === 413 - ? "La imagen es demasiado grande para procesarla. Intenta con una foto más liviana." - : "No se pudo interpretar la petición de análisis.", - }); - }); - app.use("/src/Imagenes", express.static(path.join(process.cwd(), "src", "Imagenes"))); - app.use("/src/imagenes", express.static(path.join(process.cwd(), "src", "imagenes"))); - + // Remove express.static for imagenes since we use Supabase now + // API Endpoints app.get("/api/crops", async (req, res) => { try { - const { data, error } = await withTimeout( - Promise.resolve(supabase.from('crops').select('*')), - SUPABASE_QUERY_TIMEOUT_MS, - 'Timeout consultando cultivos en Supabase.' - ); + const { data, error } = await supabase.from('crops').select('*'); if (error) throw error; res.json(data || []); } catch (error: any) { console.error("Supabase /api/crops GET error:", error.message || error); - res.json(activeCrops); + res.status(500).json({ error: "Fallo al conectar con la base de datos (Supabase)." }); } }); @@ -638,107 +563,67 @@ async function startServer() { try { // Check if exists - const { data: existing } = await withTimeout( - Promise.resolve(supabase.from('crops').select('id').eq('id', newCrop.id).maybeSingle()), - SUPABASE_QUERY_TIMEOUT_MS, - 'Timeout verificando cultivo existente en Supabase.' - ); + const { data: existing } = await supabase.from('crops').select('id').eq('id', newCrop.id).single(); if (existing) { return res.json(newCrop); } - const { data, error } = await withTimeout( - Promise.resolve(supabase.from('crops').insert([newCrop]).select()), - SUPABASE_QUERY_TIMEOUT_MS, - 'Timeout guardando cultivo en Supabase.' - ); + const { data, error } = await supabase.from('crops').insert([newCrop]).select(); if (error) throw error; res.status(201).json(data?.[0] || newCrop); } catch (error: any) { console.error("Supabase /api/crops POST error:", JSON.stringify(error, null, 2), error.message); - if (!activeCrops.find(c => c.id === newCrop.id)) { - activeCrops.push(newCrop); - saveCrops(activeCrops); - } - res.status(201).json(newCrop); + res.status(500).json({ error: "Error al guardar el cultivo en base de datos. Verifica RLS y tablas." }); } }); app.put("/api/crops/:id", async (req, res) => { const { id } = req.params; try { - const { data, error } = await withTimeout( - Promise.resolve(supabase.from('crops').update(req.body).eq('id', id).select()), - SUPABASE_QUERY_TIMEOUT_MS, - 'Timeout actualizando cultivo en Supabase.' - ); + const { data, error } = await supabase.from('crops').update(req.body).eq('id', id).select(); if (error) throw error; if (data && data.length > 0) { res.json(data[0]); } else { - res.status(404).json({ error: "Cultivo no encontrado" }); + res.status(404).json({ error: "Cultivo no encontrado en DB" }); } } catch (error: any) { console.error("Supabase /api/crops PUT error:", error); - const idx = activeCrops.findIndex(c => c.id === id); - if (idx !== -1) { - activeCrops[idx] = { ...activeCrops[idx], ...req.body }; - saveCrops(activeCrops); - res.json(activeCrops[idx]); - } else { - res.status(404).json({ error: "Cultivo no encontrado localmente" }); - } + res.status(500).json({ error: "Error actualizando el cultivo en DB." }); } }); app.delete("/api/crops/:id", async (req, res) => { const { id } = req.params; try { - const { error } = await withTimeout( - Promise.resolve(supabase.from('crops').delete().eq('id', id)), - SUPABASE_QUERY_TIMEOUT_MS, - 'Timeout eliminando cultivo en Supabase.' - ); + const { error } = await supabase.from('crops').delete().eq('id', id); if (error) throw error; res.json({ success: true }); } catch (error: any) { console.error("Supabase /api/crops DELETE error:", error); - activeCrops = activeCrops.filter(c => c.id !== id); - saveCrops(activeCrops); - res.json({ success: true }); + res.status(500).json({ error: "Error borrando el cultivo en DB." }); } }); // Payment Ledger Endpoints app.get("/api/ledger", async (req, res) => { try { - const { data: ledgerData, error: ledgerError } = await withTimeout( - Promise.resolve(supabase.from('ledger').select('*').order('timestamp', { ascending: false })), - SUPABASE_QUERY_TIMEOUT_MS, - 'Timeout consultando ledger en Supabase.' - ); + const { data: ledgerData, error: ledgerError } = await supabase.from('ledger').select('*').order('timestamp', { ascending: false }); if (ledgerError) throw ledgerError; - const { data: volumeData } = await withTimeout( - Promise.resolve(supabase.from('store_metrics').select('totalSalesUsd').eq('id', 'main').maybeSingle()), - SUPABASE_QUERY_TIMEOUT_MS, - 'Timeout consultando métricas en Supabase.' - ); + const { data: volumeData } = await supabase.from('store_metrics').select('totalSalesUsd').eq('id', 'main').single(); res.json({ ledger: ledgerData || [], - totalSalesUsd: volumeData?.totalSalesUsd || mockVolumenSalesUsd || 0, + totalSalesUsd: volumeData?.totalSalesUsd || 0, }); } catch (error: any) { console.error("Supabase /api/ledger GET error:", error); - res.json({ - ledger: paymentLedger, - totalSalesUsd: mockVolumenSalesUsd - }); + res.status(500).json({ error: "Error obteniendo el ledger." }); } }); @@ -762,71 +647,41 @@ async function startServer() { } try { - const { error } = await withTimeout( - Promise.resolve(supabase.from('ledger').insert([newLog as any])), - SUPABASE_QUERY_TIMEOUT_MS, - 'Timeout guardando ledger en Supabase.' - ); + const { error } = await supabase.from('ledger').insert([newLog as any]); if (error) throw error; - const newVol = Number((mockVolumenSalesUsd + usdValue).toFixed(2)); - mockVolumenSalesUsd = newVol; - await withTimeout( - Promise.resolve(supabase.from('store_metrics').upsert([{ id: 'main', totalSalesUsd: newVol }])), - SUPABASE_QUERY_TIMEOUT_MS, - 'Timeout actualizando métricas en Supabase.' - ); + const { data: volumeData } = await supabase.from('store_metrics').select('totalSalesUsd').eq('id', 'main').single(); + const currentVol = volumeData?.totalSalesUsd || 0; + const newVol = Number((currentVol + usdValue).toFixed(2)); + + await supabase.from('store_metrics').upsert([{ id: 'main', totalSalesUsd: newVol }]); res.status(201).json({ log: newLog, totalSalesUsd: newVol }); } catch (error: any) { console.error("Supabase /api/ledger POST error:", error); - - if (!paymentLedger.find(l => l.id === newLog.id)) { - paymentLedger.unshift(newLog as ServerLedgerLog); - saveLedger(paymentLedger); - - const newVol = Number((mockVolumenSalesUsd + usdValue).toFixed(2)); - mockVolumenSalesUsd = newVol; - saveVolume(newVol); - } - - res.status(201).json({ log: newLog, totalSalesUsd: mockVolumenSalesUsd }); + res.status(500).json({ error: "Error guardando el pago." }); } }); app.delete("/api/ledger", async (req, res) => { try { - await withTimeout( - Promise.resolve(supabase.from('ledger').delete().neq('id', 'clear_all')), - SUPABASE_QUERY_TIMEOUT_MS, - 'Timeout limpiando ledger en Supabase.' - ); - await withTimeout( - Promise.resolve(supabase.from('store_metrics').upsert([{ id: 'main', totalSalesUsd: 0 }])), - SUPABASE_QUERY_TIMEOUT_MS, - 'Timeout reiniciando métricas en Supabase.' - ); - mockVolumenSalesUsd = 0; + await supabase.from('ledger').delete().neq('id', 'clear_all'); + await supabase.from('store_metrics').upsert([{ id: 'main', totalSalesUsd: 0 }]); res.json({ success: true, totalSalesUsd: 0.00 }); } catch (error: any) { console.error("Supabase /api/ledger DELETE error:", error); - paymentLedger = []; - saveLedger(paymentLedger); - mockVolumenSalesUsd = 0; - saveVolume(0); - res.json({ success: true, totalSalesUsd: 0.00 }); + res.status(500).json({ error: "Error borrando el ledger." }); } }); // Gemini Scan Helper with exponential retries and fallback model async function generateBotanicalContentWithRetry(client: any, imgPart: any, textPart: any, schema: any) { - const maxAttempts = 3; + const maxAttempts = 2; // Reduced to avoid vercel timeout let lastError: any = null; for (let attempt = 1; attempt <= maxAttempts; attempt++) { - // Alternate models: Attempt 1 uses gemini-3.5-flash. If busy, try gemini-3.1-flash-lite immediately - const model = attempt === 1 ? "gemini-3.5-flash" : "gemini-3.1-flash-lite"; + const model = attempt === 1 ? "gemini-2.5-flash" : "gemini-1.5-flash"; console.log(`🤖 [Botanical Analysis] Attempt ${attempt}/${maxAttempts} using model: ${model}`); try { const response = await client.models.generateContent({ @@ -844,34 +699,59 @@ async function startServer() { throw new Error("Respuesta vacia"); } catch (err: any) { lastError = err; - console.log(`[Botanical Analysis] Model ${model} returned status: API_BUSY_OR_UNAVAILABLE`); - // If we still have attempts, sleep with exponential backoff + console.log(`[Botanical Analysis] Model ${model} returned error:`, err?.message || err); + // If we still have attempts, sleep short to avoid vercel limits if (attempt < maxAttempts) { - const sleepMs = attempt * 1500; - console.log(`[Botanical Analysis] Sleeping ${sleepMs}ms before next model attempt...`); - await new Promise((resolve) => setTimeout(resolve, sleepMs)); + await new Promise((resolve) => setTimeout(resolve, 500)); } } } - throw lastError || new Error("Se rebasaron todos los reintentos"); + console.error("Gemini failed after retries:", lastError); + return null; } // Scan Plant Endpoint app.post("/api/scan-plant", async (req, res) => { - const { base64Image, mimeType, isPresetSeed, presetIndex, targetElement } = req.body || {}; + const { base64Image, mimeType, isPresetSeed, presetIndex, targetElement } = req.body; - // Guardar imagen en el bucket publico de Supabase Storage si se provee. + // Guardar imagen en Supabase Storage de inmediato si se provee let savedImagePath = ""; - if (base64Image && /^data:image\/[a-zA-Z0-9+.-]+;base64,/.test(base64Image)) { + if (base64Image && /^data:image\/\w+;base64,/.test(base64Image)) { try { - savedImagePath = await withTimeout( - uploadScanImageToSupabase(base64Image, mimeType), - 3500, - "La subida a Supabase Storage tardó demasiado." - ); - console.log(`📸 Imagen de escaneo guardada exitosamente en Supabase Storage (${PLANT_IMAGES_BUCKET}): ${savedImagePath}`); + const parts = base64Image.split(";base64,"); + const cleanBase = parts.length > 1 ? parts[1] : base64Image; + const buffer = Buffer.from(cleanBase, "base64"); + + let ext = "jpg"; + if (mimeType) { + const mParts = mimeType.split("/"); + if (mParts.length > 1) ext = mParts[1]; + } else { + const match = base64Image.match(/^data:image\/([a-zA-Z0-9+.-]+);base64,/i); + if (match) ext = match[1]; + } + + // Normalizar extensiones comunes + ext = ext.toLowerCase(); + if (ext === "jpeg") ext = "jpg"; + else if (ext === "svg+xml") ext = "svg"; + + const fileName = `scan-${Date.now()}.${ext}`; + + // Subir a Supabase Storage: bucket 'imagenes' + const { data, error } = await supabase.storage.from("imagenes").upload(fileName, buffer, { + contentType: mimeType || `image/${ext}` + }); + + if (error) { + console.error("Error al subir imagen a Supabase:", error); + } else { + const { data: publicUrlData } = supabase.storage.from("imagenes").getPublicUrl(fileName); + savedImagePath = publicUrlData.publicUrl; + console.log(`📸 Imagen de escaneo guardada exitosamente en Supabase: ${savedImagePath}`); + } } catch (err) { - console.error(`Error al guardar la imagen en Supabase Storage (${PLANT_IMAGES_BUCKET}):`, err); + console.error("Error procesando o subiendo la imagen:", err); } } @@ -950,7 +830,7 @@ async function startServer() { } try { - const cleanBase64 = base64Image.replace(/^data:image\/[a-zA-Z0-9+.-]+;base64,/, ""); + const cleanBase64 = base64Image.replace(/^data:image\/\w+;base64,/, ""); const imgPart = { inlineData: { mimeType: mimeType || "image/jpeg", @@ -1009,11 +889,7 @@ async function startServer() { ], }; - const response = await withTimeout( - generateBotanicalContentWithRetry(client, imgPart, textPart, responseSchema), - 8000, - "El análisis de Gemini tardó demasiado." - ); + const response = await generateBotanicalContentWithRetry(client, imgPart, textPart, responseSchema); if (response && response.text) { const parsedData = JSON.parse(response.text.trim()); diff --git a/src/components/PlantScanner.tsx b/src/components/PlantScanner.tsx index c38acb2..2976a37 100644 --- a/src/components/PlantScanner.tsx +++ b/src/components/PlantScanner.tsx @@ -52,9 +52,6 @@ export const PlantScanner: React.FC = ({ onCropIdentified }) }), }); const resultData = await res.json(); - if (!res.ok) { - throw new Error(resultData.error || `Error HTTP ${res.status} analizando la imagen`); - } if (resultData.success && resultData.data) { const cropRes: Crop = { @@ -98,14 +95,14 @@ export const PlantScanner: React.FC = ({ onCropIdentified }) } }; - const resizeImageBase64 = (base64Str: string): Promise => { - return new Promise((resolve, reject) => { + const resizeImageBase64 = (base64Str: string, mimeType: string = "image/jpeg"): Promise => { + return new Promise((resolve) => { const img = new Image(); img.src = base64Str; img.onload = () => { const canvas = document.createElement("canvas"); - const maxWidth = 256; - const maxHeight = 256; + const maxWidth = 320; + const maxHeight = 320; let width = img.width; let height = img.height; @@ -126,13 +123,13 @@ export const PlantScanner: React.FC = ({ onCropIdentified }) const ctx = canvas.getContext("2d"); if (ctx) { ctx.drawImage(img, 0, 0, width, height); - resolve(canvas.toDataURL("image/jpeg", 0.68)); + resolve(canvas.toDataURL(mimeType, 0.75)); } else { - reject(new Error("No se pudo preparar la imagen para el análisis.")); + resolve(base64Str); } }; img.onerror = () => { - reject(new Error("Formato de imagen no soportado por el navegador.")); + resolve(base64Str); }; }); }; @@ -147,17 +144,13 @@ export const PlantScanner: React.FC = ({ onCropIdentified }) const reader = new FileReader(); reader.onload = async () => { const parentBase64 = reader.result as string; - try { - const compressedBase64 = await resizeImageBase64(parentBase64); - triggerScanApi({ - base64Image: compressedBase64, - mimeType: "image/jpeg", - isPresetSeed: false, - }); - } catch (err: any) { - console.error(err); - alert(err.message || "No se pudo preparar la imagen para el análisis."); - } + const targetMimeType = file.type || "image/jpeg"; + const compressedBase64 = await resizeImageBase64(parentBase64, targetMimeType); + triggerScanApi({ + base64Image: compressedBase64, + mimeType: targetMimeType, + isPresetSeed: false, + }); }; reader.readAsDataURL(file); }; diff --git a/src/components/WalletHub.tsx b/src/components/WalletHub.tsx index 935fa40..a1d3822 100644 --- a/src/components/WalletHub.tsx +++ b/src/components/WalletHub.tsx @@ -10,6 +10,9 @@ interface WalletHubProps { onAirdrop: (token: 'SOL' | 'USDC' | 'USDT', amount: number) => void; } +const BACKPACK_ICON = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAbvSURBVHgB7Z1dUtxGEMf/LZH3fU0V4PUJQg4QVj5BnBOAT2BzAsMJAicwPoHJCRDrAxifgLVxVV73ObDqdEtsjKn4C8+0NDv9e7AxprRC85uvnp4RYYW5qKpxCVTcYKsgfiDfGjMwIsZIvh7d/lkmzAiYy5fzhultyZhdlagf1vU5VhjCiiGFXq01zYSJdqWgx/hB5AHN5I/6iuilyFBjxVgZAdqCZ34ORoVIqAzSOhxsvq6PsSIkL4A281LwL2IW/F1UhLKgRz/X9QyJUyBhuuae31gWviLjiPF1wxeX29vPkTjJtgAftrd3GHSMnmHw4eZ0uodESVKAoRT+kpQlSE6Ats/XZv/ONK5vZHC49+B1fYjESG4MUDKfYmCFr0ic4fmHqtpCYiQlgA66QsztIzFi5j+RGMl0AXebfgn0aOTuvGG8owIarZsXOj3ronlRuEYnn84CJLo4Lgi/QL/H/LHmy/RwI6GA0RoS4acFHi8kGieFXS/QhmijFfQXmH3uPy5lSkoLbIkYlfyzhuM4juM4juM4juMMj6TzATQ4JH9tlRqFk8BM2aV9RWHB9K5kzK/KLui0KqliSQmgBa4BIS54cpMD0OeawFye3jk19JdKkWq62OAFkEIfrTXNUxBV1okf38Ot3MGjlFqHwQrQZvQ22Cfw7xjg6t8XkZaBGzpKIXdwcAJojZeCP5SC30HipJBEOigBZLn3qdzSPlKr8V9hyEmkgxCgj8zefuD9jen0AAOidwE0i6ZhfjXgRI+gDK016DUjqE3ubPhNLoWvaDLJouHToaSP9SbA0DJ7LekyiviNPgP0TC9dQM6FfxeZ7eyuT6cv0RPmAmjTx11uXx/MiegEDd425cfcwWV+H4O3+uiO+pTAVIA2uMN8av6QiWr5TQ++JVlTc/tEiF3jOMScZGC43kME0VSA95PJhWXhM+Gt1Phn98nStZa1r9mB2SDQPqefjhayfnDfFG2J5882z84eynVM5u3thlONhRhj0gLc5PRfwAw62JjW+wjE5Xa1L0VkshO4kXt/EPDev4ZJCyBRvlcwggjHG4EfYHc9OoIBBWy3mEUX4H1V7Ur7ZvILaT8qy7FRduleF9jXc4RggOUWs/gtANs0nYquvMXaMaTXlQHlE1ggayLvf5OKY0DUMYDWfmpsBjZa+9enOmiLy+VkcmqxaNW2ZgX9GnsLXNQWoGj4KYzQ2g8LyG5WUDR4hshEE6CN+AFmg5lFiRMYcI0uKRQGyIAwegWKJkBjYO8tzq12C7efQ7CK2I00MomIxOsCiCcwQhaW3sEQ6W7sPi/yIDqKAHp8m2nIF7COoc9ghQw4NU8SkYgiQCmLKXCCUSziPc84XYBh83/DSiWR3qUo2tT4ONdGYDTub73cSzD/PNt0rojdQHAByoXxw0E7XfoFhsjnRduD+DnWIkkXXACJl1cwRoMmf3cbRaOjLRzDXnKZVj9GBIILUJBtbVzyj9HAU19AgR6I9VzDtwCgMXpAo2Yxp0v/Ybi49ennJtIFEPMY/TCKHTvv+aTSUQzBgwrQ92YHbQVi3UN3GAVZhrf/jzECE1SAq/7n4yOJ074KPSBcJoii598vxgwrqAByg70HZJZbr0JJ0G5XZz5Z1e1rYccA5TAicqEk0O5ECl/3LvYys7mLTLHHCEzS7wz6Esv3+nyYTF58rwha63XAl8PG1aCnhesWq6EdOcKM3WvmXRHh+Gvv/tNVTJlJPC4a3RVEK72+sCSZ4+J/FBVhTUS43J7gJqFjrnl33A3sxtCa3nAWhX6bbAT4hJugCsNZ2TGA8224AJnjAmSOC5A5LkDmuACZ4wJkjguQOS5A5rgAmeMCZI4LkDkuQOa4AJnjAmSOC5A5LkDmuACZ4wJkjguQOWEFYJvz85xwBBWgKM1P68oKKsI/36ACdC9nsDlWPTsIJ5t1Hfw01OBjgI1p/YwLegIibw0CwESz9gUYZ2d/wHEcx3Ecx3Ecx3Ecx3HuS5QjfdrXxTHv3JzEkd2xKwHR9xPNuKGjzdf1MSIQXAA9XUsuuw8nKPpK3PWzs+AvrgwqgP1LojOjoEf3fRv6Zy+JgBSLOGfaOx1NE/6o+rCrgeT9fWp4SljmuACZ4wJkjguQOS5A5rgAmeMCZI4LkDkuQOa4AJnjAmSOC5A5LkDmuACZ4wJkjguQOS5A5rgAmeMCZI4LkDkuQOa4AJnj5wRmTlABqHQBohKhggUVYAEEP8fO+UiMgziDCvCwrnU3aw0nOATMQu8LVIIPAq+JdAerdwWBaQ/fjEBwAaQVmMnN7sEJCB3EqP3tlRGJy6qqmPkFMcZw7sucmfZiHQ6hRBNgSXdaCHbA7KeFfBvz9pxlxtl1gcN2XBWRfwHK959XFRG6AgAAAABJRU5ErkJggg=="; +const PHANTOM_ICON = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDgiIGhlaWdodD0iMTA4IiB2aWV3Qm94PSIwIDAgMTA4IDEwOCIgZmlsbD0ibm9uZSI+CjxyZWN0IHdpZHRoPSIxMDgiIGhlaWdodD0iMTA4IiByeD0iMjYiIGZpbGw9IiNBQjlGRjIiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik00Ni41MjY3IDY5LjkyMjlDNDIuMDA1NCA3Ni44NTA5IDM0LjQyOTIgODUuNjE4MiAyNC4zNDggODUuNjE4MkMxOS41ODI0IDg1LjYxODIgMTUgODMuNjU2MyAxNSA3NS4xMzQyQzE1IDUzLjQzMDUgNDQuNjMyNiAxOS44MzI3IDcyLjEyNjggMTkuODMyN0M4Ny43NjggMTkuODMyNyA5NCAzMC42ODQ2IDk0IDQzLjAwNzlDOTQgNTguODI1OCA4My43MzU1IDc2LjkxMjIgNzMuNTMyMSA3Ni45MTIyQzcwLjI5MzkgNzYuOTEyMiA2OC43MDUzIDc1LjEzNDIgNjguNzA1MyA3Mi4zMTRDNjguNzA1MyA3MS41NzgzIDY4LjgyNzUgNzAuNzgxMiA2OS4wNzE5IDY5LjkyMjlDNjUuNTg5MyA3NS44Njk5IDU4Ljg2ODUgODEuMzg3OCA1Mi41NzU0IDgxLjM4NzhDNDcuOTkzIDgxLjM4NzggNDUuNjcxMyA3OC41MDYzIDQ1LjY3MTMgNzQuNDU5OEM0NS42NzEzIDcyLjk4ODQgNDUuOTc2OCA3MS40NTU2IDQ2LjUyNjcgNjkuOTIyOVpNODMuNjc2MSA0Mi41Nzk0QzgzLjY3NjEgNDYuMTcwNCA4MS41NTc1IDQ3Ljk2NTggNzkuMTg3NSAzNy4xOTMxQzgxLjU1NzUgMzcuMTkzMSA4My42NzYxIDM4Ljk4ODUgODMuNjc2MSA0Mi41Nzk0Wk03MC4yMTAzIDQyLjU3OTVDNzAuMjEwMyA0Ni4xNzA0IDY4LjA5MTYgNDcuOTY1OCA2NS43MjE2IDQ3Ljk2NThDNjMuMzE1NyA0Ny45NjU4IDYxLjIzMyA0Ni4xNzA0IDYxLjIzMyA0Mi41Nzk1QzYxLjIzMyAzOC45ODg1IDYzLjMxNTcgMzcuMTkzMSA2NS43MjE2IDM3LjE5MzFDNjguMDkxNiAzNy4xOTMxIDcwLjIxMDMgMzguOTg4NSA3MC4yMTAzIDQyLjU3OTVaIiBmaWxsPSIjRkZGREY4Ii8+Cjwvc3ZnPg=="; + export const WalletHub: React.FC = ({ walletState, onConnectWallet, @@ -56,18 +59,6 @@ export const WalletHub: React.FC = ({ exit={{ opacity: 0, y: -10 }} className="space-y-4" > -
- -
-

¿Eres nuevo en Solana?

-

- Las transacciones en Solana tardan menos de 1 segundo y cuestan apenas{" "} - ~0.000005 SOL ($0.00025 USD). - Conecta una cartera simulada para ver la magia de Solana Pay en acción. -

-
-
-

SELECCIONA TU CARTERA SIMULADA: @@ -79,7 +70,7 @@ export const WalletHub: React.FC = ({ className="flex flex-col items-center justify-center p-3 rounded-xl border border-purple-500/35 bg-purple-950/40 hover:bg-purple-900/40 active:scale-95 transition text-purple-200 gap-2 hover:border-purple-400" >

- 👻 + Phantom
Phantom @@ -89,8 +80,8 @@ export const WalletHub: React.FC = ({ onClick={() => onConnectWallet("Solflare")} className="flex flex-col items-center justify-center p-3 rounded-xl border border-amber-500/35 bg-amber-950/40 hover:bg-amber-900/40 active:scale-95 transition text-amber-200 gap-2 hover:border-amber-400" > -
- 🔥 +
+ Solflare
Solflare @@ -101,7 +92,7 @@ export const WalletHub: React.FC = ({ className="flex flex-col items-center justify-center p-3 rounded-xl border border-rose-500/35 bg-rose-950/40 hover:bg-rose-900/40 active:scale-95 transition text-rose-200 gap-2 hover:border-rose-400" >
- 🎒 + Backpack
Backpack @@ -120,8 +111,8 @@ export const WalletHub: React.FC = ({
- - {walletState.walletName === "Phantom" ? "👻" : walletState.walletName === "Solflare" ? "🔥" : "🎒"} + + {walletState.walletName === "Phantom" ? Phantom : walletState.walletName === "Solflare" ? Solflare : Backpack} {walletState.walletName} Conectado diff --git a/temp_phantom/adapter.ts b/temp_phantom/adapter.ts new file mode 100644 index 0000000..326c1cc --- /dev/null +++ b/temp_phantom/adapter.ts @@ -0,0 +1,307 @@ +import type { EventEmitter, SendTransactionOptions, WalletName } from '@solana/wallet-adapter-base'; +import { + BaseMessageSignerWalletAdapter, + isIosAndRedirectable, + isVersionedTransaction, + scopePollingDetectionStrategy, + WalletAccountError, + WalletConnectionError, + WalletDisconnectedError, + WalletDisconnectionError, + WalletError, + WalletNotConnectedError, + WalletNotReadyError, + WalletPublicKeyError, + WalletReadyState, + WalletSendTransactionError, + WalletSignMessageError, + WalletSignTransactionError, +} from '@solana/wallet-adapter-base'; +import type { + Connection, + SendOptions, + Transaction, + TransactionSignature, + TransactionVersion, + VersionedTransaction, +} from '@solana/web3.js'; +import { PublicKey } from '@solana/web3.js'; + +interface PhantomWalletEvents { + connect(...args: unknown[]): unknown; + disconnect(...args: unknown[]): unknown; + accountChanged(newPublicKey: PublicKey): unknown; +} + +interface PhantomWallet extends EventEmitter { + isPhantom?: boolean; + publicKey?: { toBytes(): Uint8Array }; + isConnected: boolean; + signTransaction(transaction: T): Promise; + signAllTransactions(transactions: T[]): Promise; + signAndSendTransaction( + transaction: T, + options?: SendOptions + ): Promise<{ signature: TransactionSignature }>; + signMessage(message: Uint8Array): Promise<{ signature: Uint8Array }>; + connect(): Promise; + disconnect(): Promise; +} + +interface PhantomWindow extends Window { + phantom?: { + solana?: PhantomWallet; + }; + solana?: PhantomWallet; + isPhantomInstalled?: boolean; +} + +declare const window: PhantomWindow; + +export interface PhantomWalletAdapterConfig {} + +export const PhantomWalletName = 'Phantom' as WalletName<'Phantom'>; + +export class PhantomWalletAdapter extends BaseMessageSignerWalletAdapter { + name = PhantomWalletName; + url = 'https://phantom.app'; + icon = + 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDgiIGhlaWdodD0iMTA4IiB2aWV3Qm94PSIwIDAgMTA4IDEwOCIgZmlsbD0ibm9uZSI+CjxyZWN0IHdpZHRoPSIxMDgiIGhlaWdodD0iMTA4IiByeD0iMjYiIGZpbGw9IiNBQjlGRjIiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik00Ni41MjY3IDY5LjkyMjlDNDIuMDA1NCA3Ni44NTA5IDM0LjQyOTIgODUuNjE4MiAyNC4zNDggODUuNjE4MkMxOS41ODI0IDg1LjYxODIgMTUgODMuNjU2MyAxNSA3NS4xMzQyQzE1IDUzLjQzMDUgNDQuNjMyNiAxOS44MzI3IDcyLjEyNjggMTkuODMyN0M4Ny43NjggMTkuODMyNyA5NCAzMC42ODQ2IDk0IDQzLjAwNzlDOTQgNTguODI1OCA4My43MzU1IDc2LjkxMjIgNzMuNTMyMSA3Ni45MTIyQzcwLjI5MzkgNzYuOTEyMiA2OC43MDUzIDc1LjEzNDIgNjguNzA1MyA3Mi4zMTRDNjguNzA1MyA3MS41NzgzIDY4LjgyNzUgNzAuNzgxMiA2OS4wNzE5IDY5LjkyMjlDNjUuNTg5MyA3NS44Njk5IDU4Ljg2ODUgODEuMzg3OCA1Mi41NzU0IDgxLjM4NzhDNDcuOTkzIDgxLjM4NzggNDUuNjcxMyA3OC41MDYzIDQ1LjY3MTMgNzQuNDU5OEM0NS42NzEzIDcyLjk4ODQgNDUuOTc2OCA3MS40NTU2IDQ2LjUyNjcgNjkuOTIyOVpNODMuNjc2MSA0Mi41Nzk0QzgzLjY3NjEgNDYuMTcwNCA4MS41NTc1IDQ3Ljk2NTggNzkuMTg3NSA0Ny45NjU4Qzc2Ljc4MTYgNDcuOTY1OCA3NC42OTg5IDQ2LjE3MDQgNzQuNjk4OSA0Mi41Nzk0Qzc0LjY5ODkgMzguOTg4NSA3Ni43ODE2IDM3LjE5MzEgNzkuMTg3NSAzNy4xOTMxQzgxLjU1NzUgMzcuMTkzMSA4My42NzYxIDM4Ljk4ODUgODMuNjc2MSA0Mi41Nzk0Wk03MC4yMTAzIDQyLjU3OTVDNzAuMjEwMyA0Ni4xNzA0IDY4LjA5MTYgNDcuOTY1OCA2NS43MjE2IDQ3Ljk2NThDNjMuMzE1NyA0Ny45NjU4IDYxLjIzMyA0Ni4xNzA0IDYxLjIzMyA0Mi41Nzk1QzYxLjIzMyAzOC45ODg1IDYzLjMxNTcgMzcuMTkzMSA2NS43MjE2IDM3LjE5MzFDNjguMDkxNiAzNy4xOTMxIDcwLjIxMDMgMzguOTg4NSA3MC4yMTAzIDQyLjU3OTVaIiBmaWxsPSIjRkZGREY4Ii8+Cjwvc3ZnPg=='; + supportedTransactionVersions: ReadonlySet = new Set(['legacy', 0]); + + private _connecting: boolean; + private _wallet: PhantomWallet | null; + private _publicKey: PublicKey | null; + private _readyState: WalletReadyState = + typeof window === 'undefined' || typeof document === 'undefined' + ? WalletReadyState.Unsupported + : WalletReadyState.NotDetected; + + constructor(config: PhantomWalletAdapterConfig = {}) { + super(); + this._connecting = false; + this._wallet = null; + this._publicKey = null; + + if (this._readyState !== WalletReadyState.Unsupported) { + if (isIosAndRedirectable()) { + // when in iOS (not webview), set Phantom as loadable instead of checking for install + this._readyState = WalletReadyState.Loadable; + this.emit('readyStateChange', this._readyState); + } else { + scopePollingDetectionStrategy(() => { + if (window?.isPhantomInstalled && (window.phantom?.solana?.isPhantom || window.solana?.isPhantom)) { + this._readyState = WalletReadyState.Installed; + this.emit('readyStateChange', this._readyState); + return true; + } + return false; + }); + } + } + } + + get publicKey() { + return this._publicKey; + } + + get connecting() { + return this._connecting; + } + + get readyState() { + return this._readyState; + } + + async autoConnect(): Promise { + // Skip autoconnect in the Loadable state + // We can't redirect to a universal link without user input + if (this.readyState === WalletReadyState.Installed) { + await this.connect(); + } + } + + async connect(): Promise { + try { + if (this.connected || this.connecting) return; + + if (this.readyState === WalletReadyState.Loadable) { + // redirect to the Phantom /browse universal link + // this will open the current URL in the Phantom in-wallet browser + const url = encodeURIComponent(window.location.href); + const ref = encodeURIComponent(window.location.origin); + window.location.href = `https://phantom.app/ul/browse/${url}?ref=${ref}`; + return; + } + + if (this.readyState !== WalletReadyState.Installed) throw new WalletNotReadyError(); + + this._connecting = true; + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const wallet = window.phantom?.solana || window.solana!; + + if (!wallet.isConnected) { + try { + await wallet.connect(); + } catch (error: any) { + throw new WalletConnectionError(error?.message, error); + } + } + + if (!wallet.publicKey) throw new WalletAccountError(); + + let publicKey: PublicKey; + try { + publicKey = new PublicKey(wallet.publicKey.toBytes()); + } catch (error: any) { + throw new WalletPublicKeyError(error?.message, error); + } + + wallet.on('disconnect', this._disconnected); + wallet.on('accountChanged', this._accountChanged); + + this._wallet = wallet; + this._publicKey = publicKey; + + this.emit('connect', publicKey); + } catch (error: any) { + this.emit('error', error); + throw error; + } finally { + this._connecting = false; + } + } + + async disconnect(): Promise { + const wallet = this._wallet; + if (wallet) { + wallet.off('disconnect', this._disconnected); + wallet.off('accountChanged', this._accountChanged); + + this._wallet = null; + this._publicKey = null; + + try { + await wallet.disconnect(); + } catch (error: any) { + this.emit('error', new WalletDisconnectionError(error?.message, error)); + } + } + + this.emit('disconnect'); + } + + async sendTransaction( + transaction: T, + connection: Connection, + options: SendTransactionOptions = {} + ): Promise { + try { + const wallet = this._wallet; + if (!wallet) throw new WalletNotConnectedError(); + + try { + const { signers, ...sendOptions } = options; + + if (isVersionedTransaction(transaction)) { + signers?.length && transaction.sign(signers); + } else { + transaction = (await this.prepareTransaction(transaction, connection, sendOptions)) as T; + signers?.length && (transaction as Transaction).partialSign(...signers); + } + + sendOptions.preflightCommitment = sendOptions.preflightCommitment || connection.commitment; + + const { signature } = await wallet.signAndSendTransaction(transaction, sendOptions); + return signature; + } catch (error: any) { + if (error instanceof WalletError) throw error; + throw new WalletSendTransactionError(error?.message, error); + } + } catch (error: any) { + this.emit('error', error); + throw error; + } + } + + async signTransaction(transaction: T): Promise { + try { + const wallet = this._wallet; + if (!wallet) throw new WalletNotConnectedError(); + + try { + return (await wallet.signTransaction(transaction)) || transaction; + } catch (error: any) { + throw new WalletSignTransactionError(error?.message, error); + } + } catch (error: any) { + this.emit('error', error); + throw error; + } + } + + async signAllTransactions(transactions: T[]): Promise { + try { + const wallet = this._wallet; + if (!wallet) throw new WalletNotConnectedError(); + + try { + return (await wallet.signAllTransactions(transactions)) || transactions; + } catch (error: any) { + throw new WalletSignTransactionError(error?.message, error); + } + } catch (error: any) { + this.emit('error', error); + throw error; + } + } + + async signMessage(message: Uint8Array): Promise { + try { + const wallet = this._wallet; + if (!wallet) throw new WalletNotConnectedError(); + + try { + const { signature } = await wallet.signMessage(message); + return signature; + } catch (error: any) { + throw new WalletSignMessageError(error?.message, error); + } + } catch (error: any) { + this.emit('error', error); + throw error; + } + } + + private _disconnected = () => { + const wallet = this._wallet; + if (wallet) { + wallet.off('disconnect', this._disconnected); + wallet.off('accountChanged', this._accountChanged); + + this._wallet = null; + this._publicKey = null; + + this.emit('error', new WalletDisconnectedError()); + this.emit('disconnect'); + } + }; + + private _accountChanged = (newPublicKey: PublicKey) => { + const publicKey = this._publicKey; + if (!publicKey) return; + + try { + newPublicKey = new PublicKey(newPublicKey.toBytes()); + } catch (error: any) { + this.emit('error', new WalletPublicKeyError(error?.message, error)); + return; + } + + if (publicKey.equals(newPublicKey)) return; + + this._publicKey = newPublicKey; + this.emit('connect', newPublicKey); + }; +} diff --git a/temp_phantom/index.ts b/temp_phantom/index.ts new file mode 100644 index 0000000..ddec7b5 --- /dev/null +++ b/temp_phantom/index.ts @@ -0,0 +1 @@ +export * from './adapter.js'; diff --git a/temp_phantom_pkg/CHANGELOG.md b/temp_phantom_pkg/CHANGELOG.md new file mode 100644 index 0000000..3291447 --- /dev/null +++ b/temp_phantom_pkg/CHANGELOG.md @@ -0,0 +1,90 @@ +# @solana/wallet-adapter-phantom + +## 0.9.29 + +### Patch Changes + +- f30323d: Improve Phantom detection logic to avoid false positives + +## 0.9.28 + +### Patch Changes + +- 75bf350: Update dependencies +- Updated dependencies [75bf350] + - @solana/wallet-adapter-base@0.9.27 + +## 0.9.27 + +### Patch Changes + +- db923f1: Use Node 20+ rather than 22 +- Updated dependencies [db923f1] + - @solana/wallet-adapter-base@0.9.26 + +## 0.9.26 + +### Patch Changes + +- 27e408d: Update dependencies +- Updated dependencies [27e408d] + - @solana/wallet-adapter-base@0.9.25 + +## 0.9.25 + +### Patch Changes + +- c96cae47: The base version of Node has been raised to v20 +- Updated dependencies [c96cae47] + - @solana/wallet-adapter-base@0.9.24 + +## 0.9.24 + +### Patch Changes + +- Updated dependencies [a3d35a1] + - @solana/wallet-adapter-base@0.9.23 + +## 0.9.23 + +### Patch Changes + +- 4de663e: Phantom logo updated with rebrand (https://twitter.com/phantom/status/1674513272115175424) + +## 0.9.22 + +### Patch Changes + +- 8a8fdc72: Update dependencies +- Updated dependencies [8a8fdc72] + - @solana/wallet-adapter-base@0.9.22 + +## 0.9.21 + +### Patch Changes + +- f99c2154: Fix for Phantom adapter's `connected` state +- Updated dependencies [f99c2154] + - @solana/wallet-adapter-base@0.9.21 + +## 0.9.20 + +### Patch Changes + +- b4558126: Add support for redirecting to Solflare browser on iOS + +## 0.9.19 + +### Patch Changes + +- 912cc0e: Allow wallets to customize autoConnect handling, adding support for Phantom deep links on iOS +- Updated dependencies [912cc0e] + - @solana/wallet-adapter-base@0.9.20 + +## 0.9.18 + +### Patch Changes + +- fed93f5: Add support for VersionedTransaction to Phantom adapter +- Updated dependencies [353f2a5] + - @solana/wallet-adapter-base@0.9.19 diff --git a/temp_phantom_pkg/LICENSE b/temp_phantom_pkg/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/temp_phantom_pkg/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/temp_phantom_pkg/README.md b/temp_phantom_pkg/README.md new file mode 100644 index 0000000..decd3f9 --- /dev/null +++ b/temp_phantom_pkg/README.md @@ -0,0 +1,5 @@ +# `@solana/wallet-adapter-phantom` + + + +Coming soon. \ No newline at end of file diff --git a/temp_phantom_pkg/package.json b/temp_phantom_pkg/package.json new file mode 100644 index 0000000..5fec88f --- /dev/null +++ b/temp_phantom_pkg/package.json @@ -0,0 +1,44 @@ +{ + "name": "@solana/wallet-adapter-phantom", + "version": "0.9.29", + "author": "Solana Maintainers ", + "repository": "https://github.com/anza-xyz/wallet-adapter", + "license": "Apache-2.0", + "publishConfig": { + "access": "public" + }, + "files": [ + "lib", + "src", + "LICENSE" + ], + "engines": { + "node": ">=20" + }, + "type": "module", + "sideEffects": false, + "main": "./lib/cjs/index.js", + "module": "./lib/esm/index.js", + "types": "./lib/types/index.d.ts", + "exports": { + "require": "./lib/cjs/index.js", + "import": "./lib/esm/index.js", + "types": "./lib/types/index.d.ts" + }, + "scripts": { + "build": "tsc --build --verbose && pnpm run package", + "clean": "shx mkdir -p lib && shx rm -rf lib", + "lint": "prettier --check 'src/{*,**/*}.{ts,tsx,js,jsx,json}' && eslint", + "package": "shx mkdir -p lib/cjs && shx echo '{ \"type\": \"commonjs\" }' > lib/cjs/package.json" + }, + "peerDependencies": { + "@solana/web3.js": "^1.98.0" + }, + "dependencies": { + "@solana/wallet-adapter-base": "workspace:^" + }, + "devDependencies": { + "@solana/web3.js": "^1.98.2", + "shx": "^0.4.0" + } +} diff --git a/temp_phantom_pkg/src/adapter.ts b/temp_phantom_pkg/src/adapter.ts new file mode 100644 index 0000000..326c1cc --- /dev/null +++ b/temp_phantom_pkg/src/adapter.ts @@ -0,0 +1,307 @@ +import type { EventEmitter, SendTransactionOptions, WalletName } from '@solana/wallet-adapter-base'; +import { + BaseMessageSignerWalletAdapter, + isIosAndRedirectable, + isVersionedTransaction, + scopePollingDetectionStrategy, + WalletAccountError, + WalletConnectionError, + WalletDisconnectedError, + WalletDisconnectionError, + WalletError, + WalletNotConnectedError, + WalletNotReadyError, + WalletPublicKeyError, + WalletReadyState, + WalletSendTransactionError, + WalletSignMessageError, + WalletSignTransactionError, +} from '@solana/wallet-adapter-base'; +import type { + Connection, + SendOptions, + Transaction, + TransactionSignature, + TransactionVersion, + VersionedTransaction, +} from '@solana/web3.js'; +import { PublicKey } from '@solana/web3.js'; + +interface PhantomWalletEvents { + connect(...args: unknown[]): unknown; + disconnect(...args: unknown[]): unknown; + accountChanged(newPublicKey: PublicKey): unknown; +} + +interface PhantomWallet extends EventEmitter { + isPhantom?: boolean; + publicKey?: { toBytes(): Uint8Array }; + isConnected: boolean; + signTransaction(transaction: T): Promise; + signAllTransactions(transactions: T[]): Promise; + signAndSendTransaction( + transaction: T, + options?: SendOptions + ): Promise<{ signature: TransactionSignature }>; + signMessage(message: Uint8Array): Promise<{ signature: Uint8Array }>; + connect(): Promise; + disconnect(): Promise; +} + +interface PhantomWindow extends Window { + phantom?: { + solana?: PhantomWallet; + }; + solana?: PhantomWallet; + isPhantomInstalled?: boolean; +} + +declare const window: PhantomWindow; + +export interface PhantomWalletAdapterConfig {} + +export const PhantomWalletName = 'Phantom' as WalletName<'Phantom'>; + +export class PhantomWalletAdapter extends BaseMessageSignerWalletAdapter { + name = PhantomWalletName; + url = 'https://phantom.app'; + icon = + 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDgiIGhlaWdodD0iMTA4IiB2aWV3Qm94PSIwIDAgMTA4IDEwOCIgZmlsbD0ibm9uZSI+CjxyZWN0IHdpZHRoPSIxMDgiIGhlaWdodD0iMTA4IiByeD0iMjYiIGZpbGw9IiNBQjlGRjIiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik00Ni41MjY3IDY5LjkyMjlDNDIuMDA1NCA3Ni44NTA5IDM0LjQyOTIgODUuNjE4MiAyNC4zNDggODUuNjE4MkMxOS41ODI0IDg1LjYxODIgMTUgODMuNjU2MyAxNSA3NS4xMzQyQzE1IDUzLjQzMDUgNDQuNjMyNiAxOS44MzI3IDcyLjEyNjggMTkuODMyN0M4Ny43NjggMTkuODMyNyA5NCAzMC42ODQ2IDk0IDQzLjAwNzlDOTQgNTguODI1OCA4My43MzU1IDc2LjkxMjIgNzMuNTMyMSA3Ni45MTIyQzcwLjI5MzkgNzYuOTEyMiA2OC43MDUzIDc1LjEzNDIgNjguNzA1MyA3Mi4zMTRDNjguNzA1MyA3MS41NzgzIDY4LjgyNzUgNzAuNzgxMiA2OS4wNzE5IDY5LjkyMjlDNjUuNTg5MyA3NS44Njk5IDU4Ljg2ODUgODEuMzg3OCA1Mi41NzU0IDgxLjM4NzhDNDcuOTkzIDgxLjM4NzggNDUuNjcxMyA3OC41MDYzIDQ1LjY3MTMgNzQuNDU5OEM0NS42NzEzIDcyLjk4ODQgNDUuOTc2OCA3MS40NTU2IDQ2LjUyNjcgNjkuOTIyOVpNODMuNjc2MSA0Mi41Nzk0QzgzLjY3NjEgNDYuMTcwNCA4MS41NTc1IDQ3Ljk2NTggNzkuMTg3NSA0Ny45NjU4Qzc2Ljc4MTYgNDcuOTY1OCA3NC42OTg5IDQ2LjE3MDQgNzQuNjk4OSA0Mi41Nzk0Qzc0LjY5ODkgMzguOTg4NSA3Ni43ODE2IDM3LjE5MzEgNzkuMTg3NSAzNy4xOTMxQzgxLjU1NzUgMzcuMTkzMSA4My42NzYxIDM4Ljk4ODUgODMuNjc2MSA0Mi41Nzk0Wk03MC4yMTAzIDQyLjU3OTVDNzAuMjEwMyA0Ni4xNzA0IDY4LjA5MTYgNDcuOTY1OCA2NS43MjE2IDQ3Ljk2NThDNjMuMzE1NyA0Ny45NjU4IDYxLjIzMyA0Ni4xNzA0IDYxLjIzMyA0Mi41Nzk1QzYxLjIzMyAzOC45ODg1IDYzLjMxNTcgMzcuMTkzMSA2NS43MjE2IDM3LjE5MzFDNjguMDkxNiAzNy4xOTMxIDcwLjIxMDMgMzguOTg4NSA3MC4yMTAzIDQyLjU3OTVaIiBmaWxsPSIjRkZGREY4Ii8+Cjwvc3ZnPg=='; + supportedTransactionVersions: ReadonlySet = new Set(['legacy', 0]); + + private _connecting: boolean; + private _wallet: PhantomWallet | null; + private _publicKey: PublicKey | null; + private _readyState: WalletReadyState = + typeof window === 'undefined' || typeof document === 'undefined' + ? WalletReadyState.Unsupported + : WalletReadyState.NotDetected; + + constructor(config: PhantomWalletAdapterConfig = {}) { + super(); + this._connecting = false; + this._wallet = null; + this._publicKey = null; + + if (this._readyState !== WalletReadyState.Unsupported) { + if (isIosAndRedirectable()) { + // when in iOS (not webview), set Phantom as loadable instead of checking for install + this._readyState = WalletReadyState.Loadable; + this.emit('readyStateChange', this._readyState); + } else { + scopePollingDetectionStrategy(() => { + if (window?.isPhantomInstalled && (window.phantom?.solana?.isPhantom || window.solana?.isPhantom)) { + this._readyState = WalletReadyState.Installed; + this.emit('readyStateChange', this._readyState); + return true; + } + return false; + }); + } + } + } + + get publicKey() { + return this._publicKey; + } + + get connecting() { + return this._connecting; + } + + get readyState() { + return this._readyState; + } + + async autoConnect(): Promise { + // Skip autoconnect in the Loadable state + // We can't redirect to a universal link without user input + if (this.readyState === WalletReadyState.Installed) { + await this.connect(); + } + } + + async connect(): Promise { + try { + if (this.connected || this.connecting) return; + + if (this.readyState === WalletReadyState.Loadable) { + // redirect to the Phantom /browse universal link + // this will open the current URL in the Phantom in-wallet browser + const url = encodeURIComponent(window.location.href); + const ref = encodeURIComponent(window.location.origin); + window.location.href = `https://phantom.app/ul/browse/${url}?ref=${ref}`; + return; + } + + if (this.readyState !== WalletReadyState.Installed) throw new WalletNotReadyError(); + + this._connecting = true; + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const wallet = window.phantom?.solana || window.solana!; + + if (!wallet.isConnected) { + try { + await wallet.connect(); + } catch (error: any) { + throw new WalletConnectionError(error?.message, error); + } + } + + if (!wallet.publicKey) throw new WalletAccountError(); + + let publicKey: PublicKey; + try { + publicKey = new PublicKey(wallet.publicKey.toBytes()); + } catch (error: any) { + throw new WalletPublicKeyError(error?.message, error); + } + + wallet.on('disconnect', this._disconnected); + wallet.on('accountChanged', this._accountChanged); + + this._wallet = wallet; + this._publicKey = publicKey; + + this.emit('connect', publicKey); + } catch (error: any) { + this.emit('error', error); + throw error; + } finally { + this._connecting = false; + } + } + + async disconnect(): Promise { + const wallet = this._wallet; + if (wallet) { + wallet.off('disconnect', this._disconnected); + wallet.off('accountChanged', this._accountChanged); + + this._wallet = null; + this._publicKey = null; + + try { + await wallet.disconnect(); + } catch (error: any) { + this.emit('error', new WalletDisconnectionError(error?.message, error)); + } + } + + this.emit('disconnect'); + } + + async sendTransaction( + transaction: T, + connection: Connection, + options: SendTransactionOptions = {} + ): Promise { + try { + const wallet = this._wallet; + if (!wallet) throw new WalletNotConnectedError(); + + try { + const { signers, ...sendOptions } = options; + + if (isVersionedTransaction(transaction)) { + signers?.length && transaction.sign(signers); + } else { + transaction = (await this.prepareTransaction(transaction, connection, sendOptions)) as T; + signers?.length && (transaction as Transaction).partialSign(...signers); + } + + sendOptions.preflightCommitment = sendOptions.preflightCommitment || connection.commitment; + + const { signature } = await wallet.signAndSendTransaction(transaction, sendOptions); + return signature; + } catch (error: any) { + if (error instanceof WalletError) throw error; + throw new WalletSendTransactionError(error?.message, error); + } + } catch (error: any) { + this.emit('error', error); + throw error; + } + } + + async signTransaction(transaction: T): Promise { + try { + const wallet = this._wallet; + if (!wallet) throw new WalletNotConnectedError(); + + try { + return (await wallet.signTransaction(transaction)) || transaction; + } catch (error: any) { + throw new WalletSignTransactionError(error?.message, error); + } + } catch (error: any) { + this.emit('error', error); + throw error; + } + } + + async signAllTransactions(transactions: T[]): Promise { + try { + const wallet = this._wallet; + if (!wallet) throw new WalletNotConnectedError(); + + try { + return (await wallet.signAllTransactions(transactions)) || transactions; + } catch (error: any) { + throw new WalletSignTransactionError(error?.message, error); + } + } catch (error: any) { + this.emit('error', error); + throw error; + } + } + + async signMessage(message: Uint8Array): Promise { + try { + const wallet = this._wallet; + if (!wallet) throw new WalletNotConnectedError(); + + try { + const { signature } = await wallet.signMessage(message); + return signature; + } catch (error: any) { + throw new WalletSignMessageError(error?.message, error); + } + } catch (error: any) { + this.emit('error', error); + throw error; + } + } + + private _disconnected = () => { + const wallet = this._wallet; + if (wallet) { + wallet.off('disconnect', this._disconnected); + wallet.off('accountChanged', this._accountChanged); + + this._wallet = null; + this._publicKey = null; + + this.emit('error', new WalletDisconnectedError()); + this.emit('disconnect'); + } + }; + + private _accountChanged = (newPublicKey: PublicKey) => { + const publicKey = this._publicKey; + if (!publicKey) return; + + try { + newPublicKey = new PublicKey(newPublicKey.toBytes()); + } catch (error: any) { + this.emit('error', new WalletPublicKeyError(error?.message, error)); + return; + } + + if (publicKey.equals(newPublicKey)) return; + + this._publicKey = newPublicKey; + this.emit('connect', newPublicKey); + }; +} diff --git a/temp_phantom_pkg/src/index.ts b/temp_phantom_pkg/src/index.ts new file mode 100644 index 0000000..ddec7b5 --- /dev/null +++ b/temp_phantom_pkg/src/index.ts @@ -0,0 +1 @@ +export * from './adapter.js'; diff --git a/temp_phantom_pkg/tsconfig.cjs.json b/temp_phantom_pkg/tsconfig.cjs.json new file mode 100644 index 0000000..099b9aa --- /dev/null +++ b/temp_phantom_pkg/tsconfig.cjs.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../tsconfig.cjs.json", + "include": ["src"], + "compilerOptions": { + "outDir": "lib/cjs" + } +} diff --git a/temp_phantom_pkg/tsconfig.esm.json b/temp_phantom_pkg/tsconfig.esm.json new file mode 100644 index 0000000..4900d2f --- /dev/null +++ b/temp_phantom_pkg/tsconfig.esm.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.esm.json", + "include": ["src"], + "compilerOptions": { + "outDir": "lib/esm", + "declarationDir": "lib/types" + } +} diff --git a/temp_phantom_pkg/tsconfig.json b/temp_phantom_pkg/tsconfig.json new file mode 100644 index 0000000..c2bd381 --- /dev/null +++ b/temp_phantom_pkg/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../tsconfig.root.json", + "references": [ + { + "path": "./tsconfig.cjs.json" + }, + { + "path": "./tsconfig.esm.json" + } + ] +} diff --git a/test-bucket.js b/test-bucket.js new file mode 100644 index 0000000..9160fde --- /dev/null +++ b/test-bucket.js @@ -0,0 +1,16 @@ +import { createClient } from '@supabase/supabase-js'; + +const rawSupabaseUrl = "https://klaompnbmjufvhjkeeno.supabase.co"; +const supabaseUrl = rawSupabaseUrl.replace(/\/rest\/v1\/?$/, ''); +const supabaseAnonKey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtsYW9tcG5ibWp1ZnZoamtlZW5vIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODE1NjY5ODEsImV4cCI6MjA5NzE0Mjk4MX0.udKgeFZLsVzXvSU0oqR0F3_J7EDCA1g7MxF00l8LEEc"; + +const supabase = createClient(supabaseUrl, supabaseAnonKey); + +async function check() { + const bucketsToTest = ['imagenes', 'Imagenes', 'images', 'crops']; + for (const name of bucketsToTest) { + const { data, error } = await supabase.storage.getBucket(name); + console.log(`Bucket ${name}:`, data ? "EXISTS" : error?.message); + } +} +check(); diff --git a/test-buckets.js b/test-buckets.js new file mode 100644 index 0000000..769d6e7 --- /dev/null +++ b/test-buckets.js @@ -0,0 +1,12 @@ +import { createClient } from '@supabase/supabase-js'; + +const supabase = createClient( + "https://klaompnbmjufvhjkeeno.supabase.co", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtsYW9tcG5ibWp1ZnZoamtlZW5vIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODE1NjY5ODEsImV4cCI6MjA5NzE0Mjk4MX0.udKgeFZLsVzXvSU0oqR0F3_J7EDCA1g7MxF00l8LEEc" +); + +async function check() { + const { data, error } = await supabase.storage.listBuckets(); + console.log("Buckets:", data?.map(b => b.name), error); +} +check(); diff --git a/test-upload.js b/test-upload.js new file mode 100644 index 0000000..a9c6ef8 --- /dev/null +++ b/test-upload.js @@ -0,0 +1,13 @@ +import { createClient } from '@supabase/supabase-js'; + +const rawSupabaseUrl = "https://klaompnbmjufvhjkeeno.supabase.co"; +const supabaseUrl = rawSupabaseUrl.replace(/\/rest\/v1\/?$/, ''); +const supabaseAnonKey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtsYW9tcG5ibWp1ZnZoamtlZW5vIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODE1NjY5ODEsImV4cCI6MjA5NzE0Mjk4MX0.udKgeFZLsVzXvSU0oqR0F3_J7EDCA1g7MxF00l8LEEc"; + +const supabase = createClient(supabaseUrl, supabaseAnonKey); + +async function check() { + const { data, error } = await supabase.storage.from('imagenes').upload('test.txt', 'hello world'); + console.log(`Upload to imagenes:`, data, error); +} +check();