mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-01-02 12:53:10 +00:00
wip: multichain wallet support
This commit is contained in:
parent
a18498f681
commit
509faae6c9
4
.gitignore
vendored
4
.gitignore
vendored
@ -43,4 +43,6 @@ node_modules/
|
||||
# OS specific
|
||||
# Task files
|
||||
tasks.json
|
||||
tasks/
|
||||
tasks/
|
||||
|
||||
IMPLEMENTATION_PLAN.md
|
||||
341
package-lock.json
generated
341
package-lock.json
generated
@ -39,7 +39,9 @@
|
||||
"@radix-ui/react-toggle-group": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.1.4",
|
||||
"@reown/appkit": "^1.7.17",
|
||||
"@reown/appkit-adapter-bitcoin": "^1.7.17",
|
||||
"@reown/appkit-adapter-wagmi": "^1.7.17",
|
||||
"@reown/appkit-wallet-button": "^1.7.17",
|
||||
"@tanstack/react-query": "^5.84.1",
|
||||
"@waku/sdk": "^0.0.30",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
@ -1134,6 +1136,56 @@
|
||||
"@ethersproject/logger": "^5.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@exodus/bitcoin-wallet-standard": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@exodus/bitcoin-wallet-standard/-/bitcoin-wallet-standard-0.0.0.tgz",
|
||||
"integrity": "sha512-b/BE2VNLy7CEe3ziOTIjveSBzC4E71OxKIcAyguEQnPizNZ/cC27WQdJhfGeeubOy48+uxCFT1fZq2T53I2mpg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@exodus/bitcoin-wallet-standard-core": "^0.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@exodus/bitcoin-wallet-standard-chains": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@exodus/bitcoin-wallet-standard-chains/-/bitcoin-wallet-standard-chains-0.0.0.tgz",
|
||||
"integrity": "sha512-D9ddBdkZZCyX4lMLirqdRchrkDhRzlqhAwmKE1IC/N2mndY0Sz2PayJrhpxvS+vlVbU54ZlBGfSwOhPUQekP/w==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@wallet-standard/base": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@exodus/bitcoin-wallet-standard-core": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@exodus/bitcoin-wallet-standard-core/-/bitcoin-wallet-standard-core-0.0.0.tgz",
|
||||
"integrity": "sha512-E9sJ4taqZl72fFU8pUAe15dxf92XzgVTPpPLE5XDHcqz1TkaIUAZ2zbOjvns469vxUj9bDHbBxNAPLlIgohasA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@exodus/bitcoin-wallet-standard-chains": "^0.0.0",
|
||||
"@exodus/bitcoin-wallet-standard-features": "^0.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@exodus/bitcoin-wallet-standard-features": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@exodus/bitcoin-wallet-standard-features/-/bitcoin-wallet-standard-features-0.0.0.tgz",
|
||||
"integrity": "sha512-IXt07cl0A+7QuhfB/FiXtGdtTdLN7LyKZjE0ZvU/7msI3D39OsjQMj0qLxLiKj8n+jzAcO2/ULZcrOv5e/jT5A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@wallet-standard/base": "^1.0.1",
|
||||
"@wallet-standard/features": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "1.6.8",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz",
|
||||
@ -3576,6 +3628,25 @@
|
||||
"@reown/appkit-siwx": "1.7.17"
|
||||
}
|
||||
},
|
||||
"node_modules/@reown/appkit-adapter-bitcoin": {
|
||||
"version": "1.7.17",
|
||||
"resolved": "https://registry.npmjs.org/@reown/appkit-adapter-bitcoin/-/appkit-adapter-bitcoin-1.7.17.tgz",
|
||||
"integrity": "sha512-X/KCzGee4Y0w9FTn9qU7UGrKsldm1zVmJ4qKJz7xLQSkCD07UG6JoHGyySpJ7j5JzkRnqnQtXeH57IDnJpcN8g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@exodus/bitcoin-wallet-standard": "0.0.0",
|
||||
"@reown/appkit": "1.7.17",
|
||||
"@reown/appkit-common": "1.7.17",
|
||||
"@reown/appkit-controllers": "1.7.17",
|
||||
"@reown/appkit-polyfills": "1.7.17",
|
||||
"@reown/appkit-utils": "1.7.17",
|
||||
"@wallet-standard/app": "1.1.0",
|
||||
"@wallet-standard/base": "1.1.0",
|
||||
"@walletconnect/universal-provider": "2.21.5",
|
||||
"bitcoinjs-lib": "6.1.7",
|
||||
"sats-connect": "3.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@reown/appkit-adapter-wagmi": {
|
||||
"version": "1.7.17",
|
||||
"resolved": "https://registry.npmjs.org/@reown/appkit-adapter-wagmi/-/appkit-adapter-wagmi-1.7.17.tgz",
|
||||
@ -3861,6 +3932,23 @@
|
||||
"zod": "3.22.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@reown/appkit-wallet-button": {
|
||||
"version": "1.7.17",
|
||||
"resolved": "https://registry.npmjs.org/@reown/appkit-wallet-button/-/appkit-wallet-button-1.7.17.tgz",
|
||||
"integrity": "sha512-4W/Joqyqkwyj3VtGkDgyLvZXwcuwLQpcJ76KuKPIYqRwjv6VvvpN3FLr3C0KXu6hYQ/o9DXCOxe03/SckAu3cw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@reown/appkit-common": "1.7.17",
|
||||
"@reown/appkit-controllers": "1.7.17",
|
||||
"@reown/appkit-ui": "1.7.17",
|
||||
"@reown/appkit-utils": "1.7.17",
|
||||
"lit": "3.3.0",
|
||||
"valtio": "2.1.5"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@lit/react": "1.0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@reown/appkit-wallet/node_modules/zod": {
|
||||
"version": "3.22.4",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz",
|
||||
@ -4123,6 +4211,42 @@
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@sats-connect/core": {
|
||||
"version": "0.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@sats-connect/core/-/core-0.6.5.tgz",
|
||||
"integrity": "sha512-jltPdG3RzY6NPSp/ldoVTu7hOmMiuXr90osTHFPoQzprxcOs/1U4sZt/qAyGhfOM6B+ZXb7DF686BmFkUpPCJA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "1.8.4",
|
||||
"bitcoin-address-validation": "2.2.3",
|
||||
"buffer": "6.0.3",
|
||||
"jsontokens": "4.0.1",
|
||||
"valibot": "0.42.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@sats-connect/make-default-provider-config": {
|
||||
"version": "0.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@sats-connect/make-default-provider-config/-/make-default-provider-config-0.0.10.tgz",
|
||||
"integrity": "sha512-BBot3Ofa2J7OwXprgYPD4C8dppX4nnPxj4FXWq1H7fDsvwJmW4sAnfmnAIzwmyWZJOR2uZqtTkXAA08sVkoN5g==",
|
||||
"dependencies": {
|
||||
"@sats-connect/ui": "^0.0.6",
|
||||
"bowser": "^2.11.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@sats-connect/core": "*",
|
||||
"typescript": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sats-connect/make-default-provider-config/node_modules/@sats-connect/ui": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@sats-connect/ui/-/ui-0.0.6.tgz",
|
||||
"integrity": "sha512-H3bFFhr9CcY1oNosNi/QJszmMHSht4U19bUWfM3vzayAKgV4ebY6iUnRK5g3p2rVLLWVzlpaw1J9m+7JWwyBfA=="
|
||||
},
|
||||
"node_modules/@sats-connect/ui": {
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@sats-connect/ui/-/ui-0.0.7.tgz",
|
||||
"integrity": "sha512-dq02JxvTSAkfgFzEz4iWDSamm6Dte1omzxK0F1yytRZbIrbjjz1KmlMHM+uuxnFN9+EzHIHNsA4aS2dEDwh0xw=="
|
||||
},
|
||||
"node_modules/@scure/base": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz",
|
||||
@ -5519,6 +5643,18 @@
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/@wallet-standard/app": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@wallet-standard/app/-/app-1.1.0.tgz",
|
||||
"integrity": "sha512-3CijvrO9utx598kjr45hTbbeeykQrQfKmSnxeWOgU25TOEpvcipD/bYDQWIqUv1Oc6KK4YStokSMu/FBNecGUQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@wallet-standard/base": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@wallet-standard/base": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@wallet-standard/base/-/base-1.1.0.tgz",
|
||||
@ -5528,6 +5664,18 @@
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@wallet-standard/features": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@wallet-standard/features/-/features-1.1.0.tgz",
|
||||
"integrity": "sha512-hiEivWNztx73s+7iLxsuD1sOJ28xtRix58W7Xnz4XzzA/pF0+aicnWgjOdA10doVDEDZdUuZCIIqG96SFNlDUg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@wallet-standard/base": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@wallet-standard/wallet": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@wallet-standard/wallet/-/wallet-1.1.0.tgz",
|
||||
@ -8026,6 +8174,12 @@
|
||||
"tslib": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/atomic-sleep": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
|
||||
@ -8088,6 +8242,17 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.8.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
|
||||
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@ -8100,6 +8265,15 @@
|
||||
"integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/base58-js": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/base58-js/-/base58-js-1.0.5.tgz",
|
||||
"integrity": "sha512-LkkAPP8Zu+c0SVNRTRVDyMfKVORThX+rCViget00xdgLRrKkClCTz1T7cIrpr69ShwV5XJuuoZvMvJ43yURwkA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
@ -8124,8 +8298,7 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz",
|
||||
"integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/big.js": {
|
||||
"version": "6.2.2",
|
||||
@ -8167,7 +8340,6 @@
|
||||
"resolved": "https://registry.npmjs.org/bip174/-/bip174-2.1.1.tgz",
|
||||
"integrity": "sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
@ -8198,12 +8370,22 @@
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/bitcoin-address-validation": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/bitcoin-address-validation/-/bitcoin-address-validation-2.2.3.tgz",
|
||||
"integrity": "sha512-1uGCGl26Ye8JG5qcExtFLQfuib6qEZWNDo1ZlLlwp/z7ygUFby3IxolgEfgMGaC+LG9csbVASLcH8fRLv7DIOg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base58-js": "^1.0.0",
|
||||
"bech32": "^2.0.0",
|
||||
"sha256-uint8array": "^0.10.3"
|
||||
}
|
||||
},
|
||||
"node_modules/bitcoinjs-lib": {
|
||||
"version": "6.1.7",
|
||||
"resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.7.tgz",
|
||||
"integrity": "sha512-tlf/r2DGMbF7ky1MgUqXHzypYHakkEnm0SZP23CJKIqNY/5uNAnMbFhMJdhjrL/7anfb/U8+AlpdjPWjPnAalg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^1.2.0",
|
||||
"bech32": "^2.0.0",
|
||||
@ -8411,7 +8593,6 @@
|
||||
"resolved": "https://registry.npmjs.org/bs58check/-/bs58check-3.0.1.tgz",
|
||||
"integrity": "sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^1.2.0",
|
||||
"bs58": "^5.0.0"
|
||||
@ -8421,15 +8602,13 @@
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.1.tgz",
|
||||
"integrity": "sha512-uAZ8x6r6S3aUM9rbHGVOIsR15U/ZSc82b3ymnCPsT45Gk1DDvhDPdIgB5MrhirZWt+5K0EEPQH985kNqZgNPFw==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bs58check/node_modules/bs58": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz",
|
||||
"integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"base-x": "^4.0.0"
|
||||
}
|
||||
@ -9206,6 +9385,18 @@
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
||||
@ -9617,6 +9808,15 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dequal": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||
@ -10021,6 +10221,21 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-toolkit": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.33.0.tgz",
|
||||
@ -10640,6 +10855,26 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/for-each": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
||||
@ -10671,6 +10906,22 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/fraction.js": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
|
||||
@ -11808,6 +12059,17 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jsontokens": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/jsontokens/-/jsontokens-4.0.1.tgz",
|
||||
"integrity": "sha512-+MO415LEN6M+3FGsRz4wU20g7N2JA+2j9d9+pGaNJHviG4L8N0qzavGyENw6fJqsq9CcrHOIL6iWX5yeTZ86+Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^1.1.2",
|
||||
"@noble/secp256k1": "^1.6.3",
|
||||
"base64-js": "^1.5.1"
|
||||
}
|
||||
},
|
||||
"node_modules/keccak": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz",
|
||||
@ -12544,6 +12806,27 @@
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/min-indent": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
|
||||
@ -13562,6 +13845,12 @@
|
||||
"integrity": "sha512-V9plBAt3qjMlS1+nC8771KNf6oJ12gExvaxnNzN/9yVRLdTv/lc+oJlnSzrdYDAvBfTStPCoiaCOTmTs0adv7Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pump": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
|
||||
@ -14169,6 +14458,17 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sats-connect": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/sats-connect/-/sats-connect-3.5.0.tgz",
|
||||
"integrity": "sha512-/Czx9XcBV57ubAMrII3WaQG4kq7imdgGUOU9IRg0/8AWdjFczOq9wu1i7vCnxENm8MoRLuCfhMIHbekxUn23HA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sats-connect/core": "0.6.5",
|
||||
"@sats-connect/make-default-provider-config": "0.0.10",
|
||||
"@sats-connect/ui": "0.0.7"
|
||||
}
|
||||
},
|
||||
"node_modules/saxes": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
|
||||
@ -14269,6 +14569,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/sha256-uint8array": {
|
||||
"version": "0.10.7",
|
||||
"resolved": "https://registry.npmjs.org/sha256-uint8array/-/sha256-uint8array-0.10.7.tgz",
|
||||
"integrity": "sha512-1Q6JQU4tX9NqsDGodej6pkrUVQVNapLZnvkwIhddH/JqzBZF1fSaxSWNY6sziXBE8aEa2twtGkXUrwzGeZCMpQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
@ -15017,14 +15323,12 @@
|
||||
"version": "1.18.0",
|
||||
"resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz",
|
||||
"integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.6.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
|
||||
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
@ -15242,6 +15546,20 @@
|
||||
"uuid": "dist/esm/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/valibot": {
|
||||
"version": "0.42.1",
|
||||
"resolved": "https://registry.npmjs.org/valibot/-/valibot-0.42.1.tgz",
|
||||
"integrity": "sha512-3keXV29Ar5b//Hqi4MbSdV7lfVp6zuYLZuA9V1PvQUsXqogr+u5lvLPLk3A4f74VUXDnf/JfWMN6sB+koJ/FFw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/valtio": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/valtio/-/valtio-2.1.5.tgz",
|
||||
@ -15271,7 +15589,6 @@
|
||||
"resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz",
|
||||
"integrity": "sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.1.1"
|
||||
}
|
||||
|
||||
@ -44,7 +44,9 @@
|
||||
"@radix-ui/react-toggle-group": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.1.4",
|
||||
"@reown/appkit": "^1.7.17",
|
||||
"@reown/appkit-adapter-bitcoin": "^1.7.17",
|
||||
"@reown/appkit-adapter-wagmi": "^1.7.17",
|
||||
"@reown/appkit-wallet-button": "^1.7.17",
|
||||
"@tanstack/react-query": "^5.84.1",
|
||||
"@waku/sdk": "^0.0.30",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
|
||||
47
src/App.tsx
47
src/App.tsx
@ -22,29 +22,40 @@ import CellPage from "./pages/CellPage";
|
||||
import PostPage from "./pages/PostPage";
|
||||
import NotFound from "./pages/NotFound";
|
||||
import Dashboard from "./pages/Dashboard";
|
||||
import { appkitConfig } from "./lib/identity/wallets/appkit";
|
||||
import { createAppKit } from "@reown/appkit";
|
||||
import { WagmiProvider } from "wagmi";
|
||||
import { config } from "./lib/identity/wallets/appkit";
|
||||
import { AppKitProvider } from "@reown/appkit/react";
|
||||
|
||||
// Create a client
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
createAppKit(appkitConfig);
|
||||
|
||||
const App = () => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Router>
|
||||
<AuthProvider>
|
||||
<ForumProvider>
|
||||
<TooltipProvider>
|
||||
<Toaster />
|
||||
<Sonner />
|
||||
<Routes>
|
||||
<Route path="/" element={<Dashboard />} />
|
||||
<Route path="/cell/:cellId" element={<CellPage />} />
|
||||
<Route path="/post/:postId" element={<PostPage />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</TooltipProvider>
|
||||
</ForumProvider>
|
||||
</AuthProvider>
|
||||
</Router>
|
||||
</QueryClientProvider>
|
||||
<WagmiProvider config={config}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AppKitProvider {...appkitConfig}>
|
||||
<Router>
|
||||
<AuthProvider>
|
||||
<ForumProvider>
|
||||
<TooltipProvider>
|
||||
<Toaster />
|
||||
<Sonner />
|
||||
<Routes>
|
||||
<Route path="/" element={<Dashboard />} />
|
||||
<Route path="/cell/:cellId" element={<CellPage />} />
|
||||
<Route path="/post/:postId" element={<PostPage />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</TooltipProvider>
|
||||
</ForumProvider>
|
||||
</AuthProvider>
|
||||
</Router>
|
||||
</AppKitProvider>
|
||||
</QueryClientProvider>
|
||||
</WagmiProvider>
|
||||
);
|
||||
|
||||
export default App;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useAuth } from '@/contexts/useAuth';
|
||||
import { useForum } from '@/contexts/useForum';
|
||||
@ -7,14 +7,14 @@ import { Badge } from '@/components/ui/badge';
|
||||
import { LogOut, Terminal, Wifi, WifiOff, AlertTriangle, CheckCircle, Key, RefreshCw, CircleSlash } from 'lucide-react';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { useToast } from '@/components/ui/use-toast';
|
||||
import { useAppKitAccount, useDisconnect } from '@reown/appkit/react';
|
||||
import { WalletConnectionDialog } from '@/components/ui/wallet-dialog';
|
||||
|
||||
const Header = () => {
|
||||
const {
|
||||
currentUser,
|
||||
isAuthenticated,
|
||||
verificationStatus,
|
||||
connectWallet,
|
||||
disconnectWallet,
|
||||
verifyOrdinal,
|
||||
delegateKey,
|
||||
isDelegationValid,
|
||||
@ -23,13 +23,29 @@ const Header = () => {
|
||||
} = useAuth();
|
||||
const { isNetworkConnected, isRefreshing } = useForum();
|
||||
const { toast } = useToast();
|
||||
// Use AppKit hooks for multi-chain support
|
||||
const bitcoinAccount = useAppKitAccount({ namespace: "bip122" });
|
||||
const ethereumAccount = useAppKitAccount({ namespace: "eip155" });
|
||||
const { disconnect } = useDisconnect();
|
||||
|
||||
// Determine which account is connected
|
||||
const isBitcoinConnected = bitcoinAccount.isConnected;
|
||||
const isEthereumConnected = ethereumAccount.isConnected;
|
||||
const isConnected = isBitcoinConnected || isEthereumConnected;
|
||||
const address = isConnected ? (isBitcoinConnected ? bitcoinAccount.address : ethereumAccount.address) : undefined;
|
||||
|
||||
const [walletDialogOpen, setWalletDialogOpen] = useState(false);
|
||||
|
||||
const handleConnect = async () => {
|
||||
await connectWallet();
|
||||
setWalletDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleDisconnect = () => {
|
||||
disconnectWallet();
|
||||
const handleDisconnect = async () => {
|
||||
await disconnect();
|
||||
toast({
|
||||
title: "Wallet Disconnected",
|
||||
description: "Your wallet has been disconnected successfully.",
|
||||
});
|
||||
};
|
||||
|
||||
const handleVerify = async () => {
|
||||
@ -41,7 +57,7 @@ const Header = () => {
|
||||
if (!isWalletAvailable()) {
|
||||
toast({
|
||||
title: "Wallet Not Available",
|
||||
description: "Phantom wallet is not installed or not available. Please install Phantom wallet and try again.",
|
||||
description: "Wallet is not installed or not available. Please install a compatible wallet and try again.",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
@ -177,83 +193,91 @@ const Header = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<header className="border-b border-cyber-muted bg-cyber-dark fixed top-0 left-0 right-0 z-50 h-16">
|
||||
<div className="container mx-auto px-4 h-full flex justify-between items-center">
|
||||
<div className="flex items-center gap-2">
|
||||
<Terminal className="text-cyber-accent w-6 h-6" />
|
||||
<Link to="/" className="text-xl font-bold text-glow text-cyber-accent">
|
||||
OpChan
|
||||
</Link>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 items-center">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Badge
|
||||
variant={isNetworkConnected ? "default" : "destructive"}
|
||||
className="flex items-center gap-1 text-xs px-2 h-7 cursor-help"
|
||||
>
|
||||
{isNetworkConnected ? (
|
||||
<>
|
||||
<Wifi className="w-3 h-3" />
|
||||
<span>WAKU: Connected</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<WifiOff className="w-3 h-3" />
|
||||
<span>WAKU: Offline</span>
|
||||
</>
|
||||
)}
|
||||
</Badge>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="text-sm">
|
||||
<p>{isNetworkConnected ? "Waku network connection active." : "Waku network connection lost."}</p>
|
||||
{isRefreshing && <p>Refreshing data...</p>}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<>
|
||||
<header className="border-b border-cyber-muted bg-cyber-dark fixed top-0 left-0 right-0 z-50 h-16">
|
||||
<div className="container mx-auto px-4 h-full flex justify-between items-center">
|
||||
<div className="flex items-center gap-2">
|
||||
<Terminal className="text-cyber-accent w-6 h-6" />
|
||||
<Link to="/" className="text-xl font-bold text-glow text-cyber-accent">
|
||||
OpChan
|
||||
</Link>
|
||||
|
||||
</div>
|
||||
|
||||
{!currentUser ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleConnect}
|
||||
className="text-xs px-2 h-7"
|
||||
>
|
||||
Connect Wallet
|
||||
</Button>
|
||||
) : (
|
||||
<div className="flex gap-2 items-center">
|
||||
{renderAccessBadge()}
|
||||
{renderDelegationButton()}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="hidden md:flex items-center text-xs text-muted-foreground cursor-default px-2 h-7">
|
||||
{currentUser.address.slice(0, 5)}...{currentUser.address.slice(-4)}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="text-sm">
|
||||
<p>{currentUser.address}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={handleDisconnect}
|
||||
className="w-7 h-7"
|
||||
>
|
||||
<LogOut className="w-4 h-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="text-sm">Disconnect Wallet</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex gap-3 items-center">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Badge
|
||||
variant={isNetworkConnected ? "default" : "destructive"}
|
||||
className="flex items-center gap-1 text-xs px-2 h-7 cursor-help"
|
||||
>
|
||||
{isNetworkConnected ? (
|
||||
<>
|
||||
<Wifi className="w-3 h-3" />
|
||||
<span>WAKU: Connected</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<WifiOff className="w-3 h-3" />
|
||||
<span>WAKU: Offline</span>
|
||||
</>
|
||||
)}
|
||||
</Badge>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="text-sm">
|
||||
<p>{isNetworkConnected ? "Waku network connection active." : "Waku network connection lost."}</p>
|
||||
{isRefreshing && <p>Refreshing data...</p>}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
{!isConnected ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleConnect}
|
||||
className="text-xs px-2 h-7"
|
||||
>
|
||||
Connect Wallet
|
||||
</Button>
|
||||
) : (
|
||||
<div className="flex gap-2 items-center">
|
||||
{renderAccessBadge()}
|
||||
{renderDelegationButton()}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="hidden md:flex items-center text-xs text-muted-foreground cursor-default px-2 h-7">
|
||||
{address?.slice(0, 5)}...{address?.slice(-4)}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="text-sm">
|
||||
<p>{address}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={handleDisconnect}
|
||||
className="w-7 h-7"
|
||||
>
|
||||
<LogOut className="w-4 h-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="text-sm">Disconnect Wallet</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</header>
|
||||
|
||||
<WalletConnectionDialog
|
||||
open={walletDialogOpen}
|
||||
onOpenChange={setWalletDialogOpen}
|
||||
onConnect={handleConnect}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -8,60 +8,212 @@ import {
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { WalletConnectionStatus } from "@/lib/identity/wallets/phantom";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Bitcoin, Coins } from "lucide-react";
|
||||
import {
|
||||
useAppKit,
|
||||
useAppKitAccount,
|
||||
useDisconnect,
|
||||
useAppKitState
|
||||
} from "@reown/appkit/react";
|
||||
|
||||
interface WalletDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onConnectPhantom: () => void;
|
||||
onInstallPhantom: () => void;
|
||||
status: WalletConnectionStatus;
|
||||
isAuthenticating: boolean;
|
||||
onConnect: () => void;
|
||||
}
|
||||
|
||||
export function WalletConnectionDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
onConnectPhantom,
|
||||
onInstallPhantom,
|
||||
status,
|
||||
isAuthenticating,
|
||||
onConnect,
|
||||
}: WalletDialogProps) {
|
||||
// Always call hooks to follow React rules
|
||||
const { initialized } = useAppKitState();
|
||||
const appKit = useAppKit();
|
||||
const { disconnect } = useDisconnect();
|
||||
|
||||
// Get account info for different chains
|
||||
const bitcoinAccount = useAppKitAccount({ namespace: "bip122" });
|
||||
const ethereumAccount = useAppKitAccount({ namespace: "eip155" });
|
||||
|
||||
// Determine which account is connected
|
||||
const isBitcoinConnected = bitcoinAccount.isConnected;
|
||||
const isEthereumConnected = ethereumAccount.isConnected;
|
||||
const isConnected = isBitcoinConnected || isEthereumConnected;
|
||||
|
||||
// Get the active account info
|
||||
const activeAccount = isBitcoinConnected ? bitcoinAccount : ethereumAccount;
|
||||
const activeAddress = activeAccount.address;
|
||||
const activeChain = isBitcoinConnected ? "Bitcoin" : "Ethereum";
|
||||
|
||||
const handleDisconnect = async () => {
|
||||
await disconnect();
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
const handleBitcoinConnect = () => {
|
||||
if (!initialized || !appKit) {
|
||||
console.error('AppKit not initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
appKit.open({
|
||||
view: "Connect",
|
||||
namespace: "bip122"
|
||||
});
|
||||
onConnect();
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
const handleEthereumConnect = () => {
|
||||
if (!initialized || !appKit) {
|
||||
console.error('AppKit not initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
appKit.open({
|
||||
view: "Connect",
|
||||
namespace: "eip155"
|
||||
});
|
||||
onConnect();
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
// Show loading state if AppKit is not initialized
|
||||
if (!initialized) {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-md border-neutral-800 bg-black text-white">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-xl">Connect Wallet</DialogTitle>
|
||||
<DialogDescription className="text-neutral-400">
|
||||
Initializing wallet connection...
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-white"></div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-md border-neutral-800 bg-black text-white">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-xl">Connect Wallet</DialogTitle>
|
||||
<DialogDescription className="text-neutral-400">
|
||||
{status === WalletConnectionStatus.NotDetected
|
||||
? "Phantom wallet not detected. Please install it to continue."
|
||||
: "Choose a wallet connection method to continue"}
|
||||
{isConnected
|
||||
? `Connected to ${activeChain} with ${activeAddress?.slice(0, 6)}...${activeAddress?.slice(-4)}`
|
||||
: "Choose a network and wallet to connect to OpChan"
|
||||
}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="grid gap-4 py-4">
|
||||
{status !== WalletConnectionStatus.NotDetected ? (
|
||||
<Button
|
||||
onClick={onConnectPhantom}
|
||||
disabled={isAuthenticating}
|
||||
className="w-full bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-700 hover:to-blue-700"
|
||||
>
|
||||
{isAuthenticating ? "Connecting..." : "Connect Phantom Wallet"}
|
||||
</Button>
|
||||
{!isConnected ? (
|
||||
<div className="space-y-4">
|
||||
{/* Bitcoin Section */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Bitcoin className="h-5 w-5 text-orange-500" />
|
||||
<h3 className="font-semibold text-white">Bitcoin</h3>
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
Ordinal Verification Required
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Button
|
||||
onClick={handleBitcoinConnect}
|
||||
className="w-full bg-gradient-to-r from-orange-500 to-orange-600 hover:from-orange-600 hover:to-orange-700 text-white"
|
||||
style={{
|
||||
height: '44px',
|
||||
borderRadius: '8px',
|
||||
border: 'none',
|
||||
fontSize: '14px',
|
||||
fontWeight: '600',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '8px'
|
||||
}}
|
||||
>
|
||||
Connect Bitcoin Wallet
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<span className="w-full border-t border-neutral-700" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-xs uppercase">
|
||||
<span className="bg-black px-2 text-neutral-500">or</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Ethereum Section */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Coins className="h-5 w-5 text-blue-500" />
|
||||
<h3 className="font-semibold text-white">Ethereum</h3>
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
ENS Ownership Required
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Button
|
||||
onClick={handleEthereumConnect}
|
||||
className="w-full bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-700 hover:to-blue-700 text-white"
|
||||
style={{
|
||||
height: '44px',
|
||||
borderRadius: '8px',
|
||||
border: 'none',
|
||||
fontSize: '14px',
|
||||
fontWeight: '600',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '8px'
|
||||
}}
|
||||
>
|
||||
Connect Ethereum Wallet
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
onClick={onInstallPhantom}
|
||||
className="w-full bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-700 hover:to-blue-700"
|
||||
>
|
||||
Install Phantom Wallet
|
||||
</Button>
|
||||
<div className="space-y-3">
|
||||
<div className="p-3 bg-neutral-900 rounded-lg border border-neutral-700">
|
||||
<p className="text-sm text-neutral-300 mb-2">Connected Network:</p>
|
||||
<p className="text-sm font-semibold text-white mb-2">{activeChain}</p>
|
||||
<p className="text-sm text-neutral-300 mb-2">Address:</p>
|
||||
<p className="text-xs font-mono text-neutral-400 break-all">
|
||||
{activeAddress}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={handleDisconnect}
|
||||
variant="outline"
|
||||
className="w-full border-red-500 text-red-500 hover:bg-red-500/10"
|
||||
>
|
||||
Disconnect Wallet
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter className="flex flex-col sm:flex-row sm:justify-between text-xs text-neutral-500">
|
||||
<p>Phantom wallet is required to use OpChan's features</p>
|
||||
<p>Connect your wallet to use OpChan's features</p>
|
||||
{isConnected && (
|
||||
<p className="text-green-400">✓ Wallet connected to {activeChain}</p>
|
||||
)}
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,7 @@ import { useToast } from '@/components/ui/use-toast';
|
||||
import { User } from '@/types';
|
||||
import { AuthService, AuthResult } from '@/lib/identity/services/AuthService';
|
||||
import { OpchanMessage } from '@/types';
|
||||
import { useAppKitAccount, useDisconnect } from '@reown/appkit/react';
|
||||
|
||||
export type VerificationStatus = 'unverified' | 'verified-none' | 'verified-owner' | 'verifying';
|
||||
|
||||
@ -11,8 +12,6 @@ interface AuthContextType {
|
||||
isAuthenticated: boolean;
|
||||
isAuthenticating: boolean;
|
||||
verificationStatus: VerificationStatus;
|
||||
connectWallet: () => Promise<void>;
|
||||
disconnectWallet: () => void;
|
||||
verifyOrdinal: () => Promise<boolean>;
|
||||
delegateKey: () => Promise<boolean>;
|
||||
isDelegationValid: () => boolean;
|
||||
@ -34,76 +33,66 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const [verificationStatus, setVerificationStatus] = useState<VerificationStatus>('unverified');
|
||||
const { toast } = useToast();
|
||||
|
||||
// Use AppKit hooks for multi-chain support
|
||||
const bitcoinAccount = useAppKitAccount({ namespace: "bip122" });
|
||||
const ethereumAccount = useAppKitAccount({ namespace: "eip155" });
|
||||
|
||||
// Determine which account is connected
|
||||
const isBitcoinConnected = bitcoinAccount.isConnected;
|
||||
const isEthereumConnected = ethereumAccount.isConnected;
|
||||
const isConnected = isBitcoinConnected || isEthereumConnected;
|
||||
|
||||
// Get the active account info
|
||||
const activeAccount = isBitcoinConnected ? bitcoinAccount : ethereumAccount;
|
||||
const address = activeAccount.address;
|
||||
|
||||
// Create ref for AuthService so it persists between renders
|
||||
const authServiceRef = useRef(new AuthService());
|
||||
|
||||
// Sync with AppKit wallet state
|
||||
useEffect(() => {
|
||||
const storedUser = authServiceRef.current.loadStoredUser();
|
||||
if (storedUser) {
|
||||
setCurrentUser(storedUser);
|
||||
if (isConnected && address) {
|
||||
// Check if we have a stored user for this address
|
||||
const storedUser = authServiceRef.current.loadStoredUser();
|
||||
|
||||
if ('ordinalOwnership' in storedUser) {
|
||||
setVerificationStatus(storedUser.ordinalOwnership ? 'verified-owner' : 'verified-none');
|
||||
if (storedUser && storedUser.address === address) {
|
||||
// Use stored user data
|
||||
setCurrentUser(storedUser);
|
||||
if ('ordinalOwnership' in storedUser) {
|
||||
setVerificationStatus(storedUser.ordinalOwnership ? 'verified-owner' : 'verified-none');
|
||||
} else {
|
||||
setVerificationStatus('unverified');
|
||||
}
|
||||
} else {
|
||||
// Create new user from AppKit wallet
|
||||
const newUser: User = {
|
||||
address,
|
||||
walletType: isBitcoinConnected ? 'bitcoin' : 'ethereum',
|
||||
ordinalOwnership: false,
|
||||
delegationExpiry: null,
|
||||
verificationStatus: 'unverified',
|
||||
};
|
||||
setCurrentUser(newUser);
|
||||
setVerificationStatus('unverified');
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
const connectWallet = async () => {
|
||||
setIsAuthenticating(true);
|
||||
try {
|
||||
const result: AuthResult = await authServiceRef.current.connectWallet();
|
||||
|
||||
if (!result.success) {
|
||||
authServiceRef.current.saveUser(newUser);
|
||||
|
||||
const chainName = isBitcoinConnected ? 'Bitcoin' : 'Ethereum';
|
||||
toast({
|
||||
title: "Connection Failed",
|
||||
description: result.error || "Failed to connect to wallet. Please try again.",
|
||||
variant: "destructive",
|
||||
title: "Wallet Connected",
|
||||
description: `Connected to ${chainName} with address ${address.slice(0, 6)}...${address.slice(-4)}`,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: "Action Required",
|
||||
description: "Please verify your Ordinal ownership and delegate a signing key for better UX.",
|
||||
});
|
||||
throw new Error(result.error);
|
||||
}
|
||||
|
||||
const newUser = result.user!;
|
||||
setCurrentUser(newUser);
|
||||
authServiceRef.current.saveUser(newUser);
|
||||
} else {
|
||||
// Wallet disconnected
|
||||
setCurrentUser(null);
|
||||
setVerificationStatus('unverified');
|
||||
|
||||
toast({
|
||||
title: "Wallet Connected",
|
||||
description: `Connected with address ${newUser.address.slice(0, 6)}...${newUser.address.slice(-4)}`,
|
||||
});
|
||||
|
||||
toast({
|
||||
title: "Action Required",
|
||||
description: "Please verify your Ordinal ownership and delegate a signing key for better UX.",
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error connecting wallet:", error);
|
||||
toast({
|
||||
title: "Connection Failed",
|
||||
description: "Failed to connect to wallet. Please try again.",
|
||||
variant: "destructive",
|
||||
});
|
||||
throw error;
|
||||
} finally {
|
||||
setIsAuthenticating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const disconnectWallet = () => {
|
||||
authServiceRef.current.disconnectWallet();
|
||||
authServiceRef.current.clearStoredUser();
|
||||
|
||||
setCurrentUser(null);
|
||||
setVerificationStatus('unverified');
|
||||
|
||||
toast({
|
||||
title: "Disconnected",
|
||||
description: "Your wallet has been disconnected.",
|
||||
});
|
||||
};
|
||||
}, [isConnected, address, isBitcoinConnected, toast]);
|
||||
|
||||
const verifyOrdinal = async (): Promise<boolean> => {
|
||||
if (!currentUser || !currentUser.address) {
|
||||
@ -222,7 +211,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
} else if (error.message.includes("timeout")) {
|
||||
errorMessage = "Wallet request timed out. Please try again and approve the signature promptly.";
|
||||
} else if (error.message.includes("Failed to connect wallet")) {
|
||||
errorMessage = "Unable to connect to Phantom wallet. Please ensure it's installed and unlocked, then try again.";
|
||||
errorMessage = "Unable to connect to wallet. Please ensure it's installed and unlocked, then try again.";
|
||||
} else if (error.message.includes("Wallet is not connected")) {
|
||||
errorMessage = "Wallet connection was lost. Please reconnect your wallet and try again.";
|
||||
} else {
|
||||
@ -251,7 +240,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
};
|
||||
|
||||
const isWalletAvailable = (): boolean => {
|
||||
return authServiceRef.current.getWalletInfo()?.type === 'phantom';
|
||||
return isConnected && !!address;
|
||||
};
|
||||
|
||||
return (
|
||||
@ -261,8 +250,6 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
isAuthenticated: !!currentUser?.ordinalOwnership,
|
||||
isAuthenticating,
|
||||
verificationStatus,
|
||||
connectWallet,
|
||||
disconnectWallet,
|
||||
verifyOrdinal,
|
||||
delegateKey,
|
||||
isDelegationValid,
|
||||
|
||||
49
src/lib/identity/wallets/appkit.ts
Normal file
49
src/lib/identity/wallets/appkit.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { AppKitOptions } from '@reown/appkit'
|
||||
import { BitcoinAdapter } from '@reown/appkit-adapter-bitcoin'
|
||||
import { WagmiAdapter } from '@reown/appkit-adapter-wagmi'
|
||||
import { createStorage } from 'wagmi'
|
||||
import { mainnet, bitcoin, AppKitNetwork } from '@reown/appkit/networks'
|
||||
|
||||
const networks: [AppKitNetwork, ...AppKitNetwork[]] = [mainnet, bitcoin]
|
||||
|
||||
const projectId = process.env.VITE_REOWN_SECRET || '2ead96ea166a03e5ab50e5c190532e72'
|
||||
|
||||
if (!projectId) {
|
||||
throw new Error('VITE_REOWN_SECRET is not defined. Please set it in your .env file')
|
||||
}
|
||||
|
||||
export const wagmiAdapter = new WagmiAdapter({
|
||||
storage: createStorage({ storage: localStorage }),
|
||||
ssr: false, // Set to false for Vite/React apps
|
||||
projectId,
|
||||
networks
|
||||
})
|
||||
|
||||
// Export the Wagmi config for the provider
|
||||
export const config = wagmiAdapter.wagmiConfig
|
||||
|
||||
const bitcoinAdapter = new BitcoinAdapter({
|
||||
projectId
|
||||
|
||||
})
|
||||
|
||||
const metadata = {
|
||||
name: 'OpChan',
|
||||
description: 'Decentralized forum powered by Bitcoin Ordinals',
|
||||
url: process.env.NODE_ENV === 'production' ? 'https://opchan.app' : 'http://localhost:8080',
|
||||
icons: ['https://opchan.com/logo.png']
|
||||
}
|
||||
|
||||
export const appkitConfig: AppKitOptions = {
|
||||
adapters: [wagmiAdapter, bitcoinAdapter],
|
||||
networks,
|
||||
metadata,
|
||||
projectId,
|
||||
features: {
|
||||
analytics: false,
|
||||
socials: false,
|
||||
allWallets: false,
|
||||
},
|
||||
enableWalletConnect: false
|
||||
|
||||
}
|
||||
@ -1,216 +0,0 @@
|
||||
import { PhantomWalletAdapter } from './phantom';
|
||||
import { KeyDelegation } from '../signatures/key-delegation';
|
||||
import { DelegationInfo } from '../signatures/types';
|
||||
|
||||
export type WalletType = 'phantom';
|
||||
|
||||
export interface WalletInfo {
|
||||
address: string;
|
||||
type: WalletType;
|
||||
delegated: boolean;
|
||||
delegationExpiry?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service for managing wallet connections and key delegation
|
||||
*/
|
||||
export class WalletService {
|
||||
// Default delegation validity period: 24 hours
|
||||
private static readonly DEFAULT_DELEGATION_PERIOD = 24 * 60 * 60 * 1000;
|
||||
|
||||
private keyDelegation: KeyDelegation;
|
||||
private phantomAdapter: PhantomWalletAdapter;
|
||||
|
||||
constructor() {
|
||||
this.keyDelegation = new KeyDelegation();
|
||||
this.phantomAdapter = new PhantomWalletAdapter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a specific wallet type is available in the browser
|
||||
*/
|
||||
public isWalletAvailable(type: WalletType): boolean {
|
||||
return this.phantomAdapter.isInstalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if wallet is available and can be connected
|
||||
*/
|
||||
public async canConnectWallet(type: WalletType = 'phantom'): Promise<boolean> {
|
||||
if (!this.isWalletAvailable(type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const isConnected = await this.phantomAdapter.isConnected();
|
||||
return isConnected;
|
||||
} catch (error) {
|
||||
console.debug('WalletService: Cannot connect wallet:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to a specific wallet type
|
||||
* @param type The wallet type to connect to
|
||||
* @returns Promise resolving to the wallet's address
|
||||
*/
|
||||
public async connectWallet(type: WalletType = 'phantom'): Promise<string> {
|
||||
return await this.phantomAdapter.connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect the current wallet
|
||||
* @param type The wallet type to disconnect
|
||||
*/
|
||||
public async disconnectWallet(type: WalletType): Promise<void> {
|
||||
this.keyDelegation.clearDelegation(); // Clear any delegation
|
||||
await this.phantomAdapter.disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current wallet information from local storage
|
||||
* @returns The current wallet info or null if not connected
|
||||
*/
|
||||
public getWalletInfo(): WalletInfo | null {
|
||||
const userJson = localStorage.getItem('opchan-user');
|
||||
if (!userJson) return null;
|
||||
|
||||
try {
|
||||
const user = JSON.parse(userJson);
|
||||
const delegation = this.keyDelegation.retrieveDelegation();
|
||||
|
||||
return {
|
||||
address: user.address,
|
||||
type: 'phantom',
|
||||
delegated: !!delegation && this.keyDelegation.isDelegationValid(),
|
||||
delegationExpiry: delegation?.expiryTimestamp
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Failed to parse user data', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up key delegation for the connected wallet
|
||||
* @param bitcoinAddress The Bitcoin address to delegate from
|
||||
* @param walletType The wallet type
|
||||
* @param validityPeriod Milliseconds the delegation should be valid for
|
||||
* @returns Promise resolving to the delegation info
|
||||
*/
|
||||
public async setupKeyDelegation(
|
||||
bitcoinAddress: string,
|
||||
walletType: WalletType,
|
||||
validityPeriod: number = WalletService.DEFAULT_DELEGATION_PERIOD
|
||||
): Promise<Omit<DelegationInfo, 'browserPrivateKey'>> {
|
||||
console.debug('WalletService: Starting key delegation for address:', bitcoinAddress);
|
||||
|
||||
let isConnected = await this.phantomAdapter.isConnected();
|
||||
console.debug('WalletService: Initial wallet connection check result:', isConnected);
|
||||
|
||||
if (!isConnected) {
|
||||
console.debug('WalletService: Wallet not connected, attempting to connect automatically');
|
||||
try {
|
||||
await this.phantomAdapter.connect();
|
||||
isConnected = await this.phantomAdapter.isConnected();
|
||||
console.debug('WalletService: Auto-connection result:', isConnected);
|
||||
} catch (error) {
|
||||
console.error('WalletService: Failed to auto-connect wallet:', error);
|
||||
throw new Error('Failed to connect wallet. Please ensure Phantom wallet is installed and try again.');
|
||||
}
|
||||
}
|
||||
|
||||
if (!isConnected) {
|
||||
console.error('WalletService: Wallet is still not connected after auto-connection attempt');
|
||||
throw new Error('Wallet is not connected. Please connect your wallet first.');
|
||||
}
|
||||
|
||||
// Generate browser keypair
|
||||
const keypair = this.keyDelegation.generateKeypair();
|
||||
console.debug('WalletService: Generated browser keypair');
|
||||
|
||||
// Calculate expiry in hours
|
||||
const expiryHours = validityPeriod / (60 * 60 * 1000);
|
||||
|
||||
// Create delegation message
|
||||
const delegationMessage = this.keyDelegation.createDelegationMessage(
|
||||
keypair.publicKey,
|
||||
bitcoinAddress,
|
||||
Date.now() + validityPeriod
|
||||
);
|
||||
console.debug('WalletService: Created delegation message');
|
||||
|
||||
// Sign the delegation message with the Bitcoin wallet
|
||||
console.debug('WalletService: Requesting signature from wallet');
|
||||
const signature = await this.phantomAdapter.signMessage(delegationMessage);
|
||||
console.debug('WalletService: Received signature from wallet');
|
||||
|
||||
// Create and store the delegation
|
||||
const delegationInfo = this.keyDelegation.createDelegation(
|
||||
bitcoinAddress,
|
||||
signature,
|
||||
keypair.publicKey,
|
||||
keypair.privateKey,
|
||||
expiryHours
|
||||
);
|
||||
|
||||
this.keyDelegation.storeDelegation(delegationInfo);
|
||||
console.debug('WalletService: Stored delegation info');
|
||||
|
||||
// Return delegation info (excluding private key)
|
||||
return {
|
||||
signature,
|
||||
expiryTimestamp: delegationInfo.expiryTimestamp,
|
||||
browserPublicKey: keypair.publicKey,
|
||||
bitcoinAddress
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs a message using the delegated browser key
|
||||
* @param message The message to sign
|
||||
* @returns Promise resolving to the signature or null if no valid delegation
|
||||
*/
|
||||
public async signMessage(message: string): Promise<string | null> {
|
||||
return this.keyDelegation.signMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a message signature against a public key
|
||||
* @param message The original message
|
||||
* @param signature The signature to verify
|
||||
* @param publicKey The public key to verify against
|
||||
* @returns Promise resolving to a boolean indicating if the signature is valid
|
||||
*/
|
||||
public async verifySignature(
|
||||
message: string,
|
||||
signature: string,
|
||||
publicKey: string
|
||||
): Promise<boolean> {
|
||||
return this.keyDelegation.verifySignature(message, signature, publicKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current key delegation is valid
|
||||
* @returns boolean indicating if the delegation is valid
|
||||
*/
|
||||
public isDelegationValid(): boolean {
|
||||
return this.keyDelegation.isDelegationValid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the time remaining on the current delegation
|
||||
* @returns Time remaining in milliseconds, or 0 if expired/no delegation
|
||||
*/
|
||||
public getDelegationTimeRemaining(): number {
|
||||
return this.keyDelegation.getDelegationTimeRemaining();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the stored delegation
|
||||
*/
|
||||
public clearDelegation(): void {
|
||||
this.keyDelegation.clearDelegation();
|
||||
}
|
||||
}
|
||||
@ -1,216 +0,0 @@
|
||||
import { sha512 } from '@noble/hashes/sha2';
|
||||
import * as ed from '@noble/ed25519';
|
||||
import { PhantomBitcoinProvider, PhantomWallet, WalletConnectionStatus } from './types';
|
||||
|
||||
ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));
|
||||
|
||||
/**
|
||||
* PhantomWalletAdapter provides methods for connecting to and interacting with
|
||||
* the Phantom wallet for Bitcoin operations.
|
||||
*/
|
||||
export class PhantomWalletAdapter {
|
||||
private provider: PhantomWallet | null = null;
|
||||
private btcProvider: PhantomBitcoinProvider | null = null;
|
||||
private connectionStatus: WalletConnectionStatus = WalletConnectionStatus.Disconnected;
|
||||
private currentAccount: string | null = null;
|
||||
|
||||
constructor() {
|
||||
this.checkWalletAvailability();
|
||||
this.restoreConnectionState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore connection state from existing wallet connection
|
||||
*/
|
||||
private async restoreConnectionState(): Promise<void> {
|
||||
if (typeof window === 'undefined' || !window?.phantom?.bitcoin) {
|
||||
console.debug('PhantomWalletAdapter: No wallet available for connection restoration');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.debug('PhantomWalletAdapter: Attempting to restore connection state');
|
||||
this.provider = window.phantom;
|
||||
this.btcProvider = window.phantom.bitcoin;
|
||||
|
||||
// Check if wallet is already connected by trying to get accounts
|
||||
if (this.btcProvider?.requestAccounts) {
|
||||
const btcAccounts = await this.btcProvider.requestAccounts();
|
||||
|
||||
if (btcAccounts && btcAccounts.length > 0) {
|
||||
const ordinalAccount = btcAccounts.find(acc => acc.purpose === 'ordinals');
|
||||
const account = ordinalAccount || btcAccounts[0];
|
||||
|
||||
this.currentAccount = account.address;
|
||||
this.connectionStatus = WalletConnectionStatus.Connected;
|
||||
console.debug('PhantomWalletAdapter: Successfully restored connection for account:', account.address);
|
||||
} else {
|
||||
console.debug('PhantomWalletAdapter: No accounts found during connection restoration');
|
||||
}
|
||||
} else {
|
||||
console.debug('PhantomWalletAdapter: requestAccounts method not available');
|
||||
}
|
||||
} catch (error) {
|
||||
// If we can't restore the connection, that's okay - user will need to reconnect
|
||||
console.debug('PhantomWalletAdapter: Could not restore existing wallet connection:', error);
|
||||
this.connectionStatus = WalletConnectionStatus.Disconnected;
|
||||
}
|
||||
}
|
||||
|
||||
public getStatus(): WalletConnectionStatus {
|
||||
return this.connectionStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the wallet is actually connected by attempting to get accounts
|
||||
*/
|
||||
public async isConnected(): Promise<boolean> {
|
||||
if (!this.btcProvider) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.btcProvider.requestAccounts) {
|
||||
const accounts = await this.btcProvider.requestAccounts();
|
||||
return accounts && accounts.length > 0;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.debug('Error checking wallet connection:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public isInstalled(): boolean {
|
||||
if (typeof window === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
return !!(window?.phantom?.bitcoin?.isPhantom);
|
||||
}
|
||||
|
||||
async connect(): Promise<string> {
|
||||
this.connectionStatus = WalletConnectionStatus.Connecting;
|
||||
|
||||
try {
|
||||
if (!window?.phantom?.bitcoin) {
|
||||
this.connectionStatus = WalletConnectionStatus.NotDetected;
|
||||
return Promise.reject(new Error('Phantom wallet not detected. Please install Phantom wallet.'));
|
||||
}
|
||||
|
||||
this.provider = window.phantom;
|
||||
this.btcProvider = window.phantom.bitcoin;
|
||||
|
||||
if (this.btcProvider?.connect) {
|
||||
await this.btcProvider.connect();
|
||||
}
|
||||
|
||||
if (this.btcProvider?.requestAccounts) {
|
||||
const btcAccounts = await this.btcProvider.requestAccounts();
|
||||
|
||||
if (!btcAccounts || btcAccounts.length === 0) {
|
||||
this.connectionStatus = WalletConnectionStatus.Disconnected;
|
||||
throw new Error('No accounts found');
|
||||
}
|
||||
|
||||
const ordinalAccount = btcAccounts.find(acc => acc.purpose === 'ordinals');
|
||||
const account = ordinalAccount || btcAccounts[0];
|
||||
|
||||
this.currentAccount = account.address;
|
||||
this.connectionStatus = WalletConnectionStatus.Connected;
|
||||
return account.address;
|
||||
} else {
|
||||
throw new Error('requestAccounts method not available on wallet provider');
|
||||
}
|
||||
} catch (error) {
|
||||
this.connectionStatus = window?.phantom?.bitcoin
|
||||
? WalletConnectionStatus.Disconnected
|
||||
: WalletConnectionStatus.NotDetected;
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async disconnect(): Promise<void> {
|
||||
if (this.btcProvider && this.btcProvider.disconnect) {
|
||||
try {
|
||||
await this.btcProvider.disconnect();
|
||||
} catch (error) {
|
||||
console.error('Error disconnecting from Phantom wallet:', error);
|
||||
}
|
||||
}
|
||||
|
||||
this.provider = null;
|
||||
this.btcProvider = null;
|
||||
this.currentAccount = null;
|
||||
this.connectionStatus = WalletConnectionStatus.Disconnected;
|
||||
}
|
||||
|
||||
async signMessage(message: string): Promise<string> {
|
||||
console.debug('PhantomWalletAdapter: signMessage called, btcProvider:', !!this.btcProvider, 'currentAccount:', this.currentAccount);
|
||||
|
||||
if (!this.btcProvider && window?.phantom?.bitcoin) {
|
||||
console.debug('PhantomWalletAdapter: Attempting to restore connection before signing');
|
||||
await this.restoreConnectionState();
|
||||
}
|
||||
|
||||
if (!this.btcProvider || !this.currentAccount) {
|
||||
console.debug('PhantomWalletAdapter: Wallet not connected, attempting to connect automatically');
|
||||
try {
|
||||
await this.connect();
|
||||
} catch (error) {
|
||||
console.error('PhantomWalletAdapter: Failed to auto-connect wallet:', error);
|
||||
throw new Error('Failed to connect wallet. Please ensure Phantom wallet is installed and try again.');
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.btcProvider) {
|
||||
console.error('PhantomWalletAdapter: Wallet is not connected - no btcProvider');
|
||||
throw new Error('Wallet is not connected');
|
||||
}
|
||||
|
||||
if (!this.currentAccount) {
|
||||
console.error('PhantomWalletAdapter: No active account to sign with');
|
||||
throw new Error('No active account to sign with');
|
||||
}
|
||||
|
||||
try {
|
||||
if (!this.btcProvider.signMessage) {
|
||||
throw new Error('signMessage method not available on wallet provider');
|
||||
}
|
||||
|
||||
console.debug('PhantomWalletAdapter: Signing message for account:', this.currentAccount);
|
||||
const messageBytes = new TextEncoder().encode(message);
|
||||
|
||||
const { signature } = await this.btcProvider.signMessage(
|
||||
this.currentAccount,
|
||||
messageBytes
|
||||
);
|
||||
|
||||
if (signature instanceof Uint8Array) {
|
||||
const binString = String.fromCodePoint(...signature);
|
||||
return btoa(binString);
|
||||
}
|
||||
|
||||
return String(signature);
|
||||
} catch (error) {
|
||||
console.error('PhantomWalletAdapter: Error signing message:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private checkWalletAvailability(): void {
|
||||
if (typeof window === 'undefined') {
|
||||
this.connectionStatus = WalletConnectionStatus.NotDetected;
|
||||
return;
|
||||
}
|
||||
|
||||
const isPhantomInstalled = window?.phantom?.bitcoin || window?.phantom;
|
||||
|
||||
if (!isPhantomInstalled) {
|
||||
this.connectionStatus = WalletConnectionStatus.NotDetected;
|
||||
} else {
|
||||
this.connectionStatus = WalletConnectionStatus.Disconnected;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
4
src/types/global.d.ts
vendored
Normal file
4
src/types/global.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
import 'react';
|
||||
|
||||
// Ensures file is treated as a module
|
||||
export {};
|
||||
@ -3,12 +3,23 @@ import { CellMessage, CommentMessage, PostMessage, VoteMessage, ModerateMessage
|
||||
export type OpchanMessage = CellMessage | PostMessage | CommentMessage | VoteMessage | ModerateMessage;
|
||||
|
||||
export interface User {
|
||||
address: string;
|
||||
address: string;
|
||||
walletType: 'bitcoin' | 'ethereum';
|
||||
|
||||
// Bitcoin-specific
|
||||
ordinalOwnership?: boolean | { id: string; details: string };
|
||||
|
||||
// Ethereum-specific
|
||||
ensName?: string;
|
||||
ensAvatar?: string;
|
||||
ensOwnership?: boolean;
|
||||
|
||||
verificationStatus: 'verified' | 'unverified';
|
||||
|
||||
signature?: string;
|
||||
lastChecked?: number;
|
||||
browserPubKey?: string; // Browser-generated public key for key delegation
|
||||
delegationSignature?: string; // Signature from Bitcoin wallet for delegation
|
||||
delegationSignature?: string; // Signature from Bitcoin/Ethereum wallet for delegation
|
||||
delegationExpiry?: number; // When the delegation expires
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user