diff --git a/package.json b/package.json index eeb2beb4..260780e3 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,10 @@ "packages/client/core", "packages/client/cryptid", "packages/client/cli", - "packages/tests" + "packages/tests", + "wallet/cryptid-wallet-standard-library", + "wallet/cryptid-wallet-ui", + "wallet/sample-dapp" ], "devDependencies": { "@project-serum/anchor-cli": "^0.26.0", diff --git a/packages/client/cli/src/service/did.ts b/packages/client/cli/src/service/did.ts index aa0bce4f..033be389 100644 --- a/packages/client/cli/src/service/did.ts +++ b/packages/client/cli/src/service/did.ts @@ -23,7 +23,7 @@ export const addKeyToDID = async ( const newKeyVerificationMethod = { flags: [BitwiseVerificationMethodFlag.CapabilityInvocation], fragment: name, - keyData: key.toBytes(), + keyData: key.toBuffer(), methodType: VerificationMethodType.Ed25519VerificationKey2018, }; diff --git a/tsconfig.json b/tsconfig.json index d435c328..d6b36e74 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,5 +23,6 @@ ], "include": [ "packages/**/src/*", + "wallet/**/src/*" ] } diff --git a/wallet/.cert/cert.pem b/wallet-legacy/.cert/cert.pem similarity index 100% rename from wallet/.cert/cert.pem rename to wallet-legacy/.cert/cert.pem diff --git a/wallet/.cert/key.pem b/wallet-legacy/.cert/key.pem similarity index 100% rename from wallet/.cert/key.pem rename to wallet-legacy/.cert/key.pem diff --git a/wallet/.editorconfig b/wallet-legacy/.editorconfig similarity index 100% rename from wallet/.editorconfig rename to wallet-legacy/.editorconfig diff --git a/wallet/.env.development b/wallet-legacy/.env.development similarity index 100% rename from wallet/.env.development rename to wallet-legacy/.env.development diff --git a/wallet/.gitignore b/wallet-legacy/.gitignore similarity index 100% rename from wallet/.gitignore rename to wallet-legacy/.gitignore diff --git a/wallet/.travis.yml b/wallet-legacy/.travis.yml similarity index 100% rename from wallet/.travis.yml rename to wallet-legacy/.travis.yml diff --git a/wallet/LICENSE b/wallet-legacy/LICENSE similarity index 100% rename from wallet/LICENSE rename to wallet-legacy/LICENSE diff --git a/wallet-legacy/README.md b/wallet-legacy/README.md new file mode 100644 index 00000000..026c52da --- /dev/null +++ b/wallet-legacy/README.md @@ -0,0 +1,13 @@ +# Cryptid Wallet UI + +A browser-based UI for managing your Cryptid accounts. + +See [cryptid.identity.com](https://cryptid.identity.com) to try it out. + +Some features include: +* Connect using your existing Solana wallet +* Manage your keys and account control +* Send SOL & SPL tokens +* Interact with dApps + +See the Cryptid documentation [here](../README.md) for more information diff --git a/wallet/craco.config.js b/wallet-legacy/craco.config.js similarity index 100% rename from wallet/craco.config.js rename to wallet-legacy/craco.config.js diff --git a/wallet/etc/deploy/create-stack.sh b/wallet-legacy/etc/deploy/create-stack.sh similarity index 100% rename from wallet/etc/deploy/create-stack.sh rename to wallet-legacy/etc/deploy/create-stack.sh diff --git a/wallet/etc/deploy/deploy-site.sh b/wallet-legacy/etc/deploy/deploy-site.sh similarity index 100% rename from wallet/etc/deploy/deploy-site.sh rename to wallet-legacy/etc/deploy/deploy-site.sh diff --git a/wallet/etc/deploy/secure-cloudfront-s3-website.yml b/wallet-legacy/etc/deploy/secure-cloudfront-s3-website.yml similarity index 100% rename from wallet/etc/deploy/secure-cloudfront-s3-website.yml rename to wallet-legacy/etc/deploy/secure-cloudfront-s3-website.yml diff --git a/wallet/extension/src/background.js b/wallet-legacy/extension/src/background.js similarity index 100% rename from wallet/extension/src/background.js rename to wallet-legacy/extension/src/background.js diff --git a/wallet/extension/src/contentscript.js b/wallet-legacy/extension/src/contentscript.js similarity index 100% rename from wallet/extension/src/contentscript.js rename to wallet-legacy/extension/src/contentscript.js diff --git a/wallet/extension/src/manifest.json b/wallet-legacy/extension/src/manifest.json similarity index 100% rename from wallet/extension/src/manifest.json rename to wallet-legacy/extension/src/manifest.json diff --git a/wallet/extension/src/script.js b/wallet-legacy/extension/src/script.js similarity index 100% rename from wallet/extension/src/script.js rename to wallet-legacy/extension/src/script.js diff --git a/wallet/extension/src/sollet128.png b/wallet-legacy/extension/src/sollet128.png similarity index 100% rename from wallet/extension/src/sollet128.png rename to wallet-legacy/extension/src/sollet128.png diff --git a/wallet/extension/src/sollet_screenshot.png b/wallet-legacy/extension/src/sollet_screenshot.png similarity index 100% rename from wallet/extension/src/sollet_screenshot.png rename to wallet-legacy/extension/src/sollet_screenshot.png diff --git a/wallet/extension/src/sollet_screenshot_1280x800.png b/wallet-legacy/extension/src/sollet_screenshot_1280x800.png similarity index 100% rename from wallet/extension/src/sollet_screenshot_1280x800.png rename to wallet-legacy/extension/src/sollet_screenshot_1280x800.png diff --git a/wallet/package.json b/wallet-legacy/package.json similarity index 100% rename from wallet/package.json rename to wallet-legacy/package.json diff --git a/wallet/public/CNAME b/wallet-legacy/public/CNAME similarity index 100% rename from wallet/public/CNAME rename to wallet-legacy/public/CNAME diff --git a/wallet/public/civic_monogram.png b/wallet-legacy/public/civic_monogram.png similarity index 100% rename from wallet/public/civic_monogram.png rename to wallet-legacy/public/civic_monogram.png diff --git a/wallet/public/favicon.ico b/wallet-legacy/public/favicon.ico similarity index 100% rename from wallet/public/favicon.ico rename to wallet-legacy/public/favicon.ico diff --git a/wallet/public/index.html b/wallet-legacy/public/index.html similarity index 100% rename from wallet/public/index.html rename to wallet-legacy/public/index.html diff --git a/wallet/public/logo300.png b/wallet-legacy/public/logo300.png similarity index 100% rename from wallet/public/logo300.png rename to wallet-legacy/public/logo300.png diff --git a/wallet/public/logo600.png b/wallet-legacy/public/logo600.png similarity index 100% rename from wallet/public/logo600.png rename to wallet-legacy/public/logo600.png diff --git a/wallet/public/manifest.json b/wallet-legacy/public/manifest.json similarity index 100% rename from wallet/public/manifest.json rename to wallet-legacy/public/manifest.json diff --git a/wallet/public/robots.txt b/wallet-legacy/public/robots.txt similarity index 100% rename from wallet/public/robots.txt rename to wallet-legacy/public/robots.txt diff --git a/wallet/public/title.png b/wallet-legacy/public/title.png similarity index 100% rename from wallet/public/title.png rename to wallet-legacy/public/title.png diff --git a/wallet/public/title.webp b/wallet-legacy/public/title.webp similarity index 100% rename from wallet/public/title.webp rename to wallet-legacy/public/title.webp diff --git a/wallet/src/App.test.js b/wallet-legacy/src/App.test.js similarity index 100% rename from wallet/src/App.test.js rename to wallet-legacy/src/App.test.js diff --git a/wallet/src/App.tsx b/wallet-legacy/src/App.tsx similarity index 100% rename from wallet/src/App.tsx rename to wallet-legacy/src/App.tsx diff --git a/wallet/src/components/AddAccountDialog.js b/wallet-legacy/src/components/AddAccountDialog.js similarity index 100% rename from wallet/src/components/AddAccountDialog.js rename to wallet-legacy/src/components/AddAccountDialog.js diff --git a/wallet/src/components/AddCustomClusterDialog.js b/wallet-legacy/src/components/AddCustomClusterDialog.js similarity index 100% rename from wallet/src/components/AddCustomClusterDialog.js rename to wallet-legacy/src/components/AddCustomClusterDialog.js diff --git a/wallet/src/components/AddTokenDialog.tsx b/wallet-legacy/src/components/AddTokenDialog.tsx similarity index 100% rename from wallet/src/components/AddTokenDialog.tsx rename to wallet-legacy/src/components/AddTokenDialog.tsx diff --git a/wallet/src/components/AddressLink.tsx b/wallet-legacy/src/components/AddressLink.tsx similarity index 100% rename from wallet/src/components/AddressLink.tsx rename to wallet-legacy/src/components/AddressLink.tsx diff --git a/wallet/src/components/BalancesList.tsx b/wallet-legacy/src/components/BalancesList.tsx similarity index 100% rename from wallet/src/components/BalancesList.tsx rename to wallet-legacy/src/components/BalancesList.tsx diff --git a/wallet/src/components/CloseTokenAccountButton.js b/wallet-legacy/src/components/CloseTokenAccountButton.js similarity index 100% rename from wallet/src/components/CloseTokenAccountButton.js rename to wallet-legacy/src/components/CloseTokenAccountButton.js diff --git a/wallet/src/components/ConnectionIcon.js b/wallet-legacy/src/components/ConnectionIcon.js similarity index 100% rename from wallet/src/components/ConnectionIcon.js rename to wallet-legacy/src/components/ConnectionIcon.js diff --git a/wallet/src/components/ConnectionsList.js b/wallet-legacy/src/components/ConnectionsList.js similarity index 100% rename from wallet/src/components/ConnectionsList.js rename to wallet-legacy/src/components/ConnectionsList.js diff --git a/wallet/src/components/CopyableAddress.tsx b/wallet-legacy/src/components/CopyableAddress.tsx similarity index 100% rename from wallet/src/components/CopyableAddress.tsx rename to wallet-legacy/src/components/CopyableAddress.tsx diff --git a/wallet/src/components/CopyableDisplay.tsx b/wallet-legacy/src/components/CopyableDisplay.tsx similarity index 100% rename from wallet/src/components/CopyableDisplay.tsx rename to wallet-legacy/src/components/CopyableDisplay.tsx diff --git a/wallet/src/components/Cryptid/AddKeyOrCryptidAccountModal.tsx b/wallet-legacy/src/components/Cryptid/AddKeyOrCryptidAccountModal.tsx similarity index 100% rename from wallet/src/components/Cryptid/AddKeyOrCryptidAccountModal.tsx rename to wallet-legacy/src/components/Cryptid/AddKeyOrCryptidAccountModal.tsx diff --git a/wallet/src/components/Cryptid/AliasAvatar.tsx b/wallet-legacy/src/components/Cryptid/AliasAvatar.tsx similarity index 100% rename from wallet/src/components/Cryptid/AliasAvatar.tsx rename to wallet-legacy/src/components/Cryptid/AliasAvatar.tsx diff --git a/wallet/src/components/Cryptid/ControllerList.tsx b/wallet-legacy/src/components/Cryptid/ControllerList.tsx similarity index 100% rename from wallet/src/components/Cryptid/ControllerList.tsx rename to wallet-legacy/src/components/Cryptid/ControllerList.tsx diff --git a/wallet/src/components/Cryptid/CryptidDetails.tsx b/wallet-legacy/src/components/Cryptid/CryptidDetails.tsx similarity index 100% rename from wallet/src/components/Cryptid/CryptidDetails.tsx rename to wallet-legacy/src/components/Cryptid/CryptidDetails.tsx diff --git a/wallet/src/components/Cryptid/CryptidSummary.tsx b/wallet-legacy/src/components/Cryptid/CryptidSummary.tsx similarity index 100% rename from wallet/src/components/Cryptid/CryptidSummary.tsx rename to wallet-legacy/src/components/Cryptid/CryptidSummary.tsx diff --git a/wallet/src/components/Cryptid/CryptidTypeSelector.tsx b/wallet-legacy/src/components/Cryptid/CryptidTypeSelector.tsx similarity index 100% rename from wallet/src/components/Cryptid/CryptidTypeSelector.tsx rename to wallet-legacy/src/components/Cryptid/CryptidTypeSelector.tsx diff --git a/wallet/src/components/Cryptid/KeyList.tsx b/wallet-legacy/src/components/Cryptid/KeyList.tsx similarity index 100% rename from wallet/src/components/Cryptid/KeyList.tsx rename to wallet-legacy/src/components/Cryptid/KeyList.tsx diff --git a/wallet/src/components/DeleteMnemonicDialog.js b/wallet-legacy/src/components/DeleteMnemonicDialog.js similarity index 100% rename from wallet/src/components/DeleteMnemonicDialog.js rename to wallet-legacy/src/components/DeleteMnemonicDialog.js diff --git a/wallet/src/components/DepositDialog.tsx b/wallet-legacy/src/components/DepositDialog.tsx similarity index 100% rename from wallet/src/components/DepositDialog.tsx rename to wallet-legacy/src/components/DepositDialog.tsx diff --git a/wallet/src/components/DialogForm.tsx b/wallet-legacy/src/components/DialogForm.tsx similarity index 100% rename from wallet/src/components/DialogForm.tsx rename to wallet-legacy/src/components/DialogForm.tsx diff --git a/wallet/src/components/EditAccountNameDialog.js b/wallet-legacy/src/components/EditAccountNameDialog.js similarity index 100% rename from wallet/src/components/EditAccountNameDialog.js rename to wallet-legacy/src/components/EditAccountNameDialog.js diff --git a/wallet/src/components/ExportAccountDialog.js b/wallet-legacy/src/components/ExportAccountDialog.js similarity index 100% rename from wallet/src/components/ExportAccountDialog.js rename to wallet-legacy/src/components/ExportAccountDialog.js diff --git a/wallet/src/components/LoadingIndicator.js b/wallet-legacy/src/components/LoadingIndicator.js similarity index 100% rename from wallet/src/components/LoadingIndicator.js rename to wallet-legacy/src/components/LoadingIndicator.js diff --git a/wallet/src/components/NavigationFrame.tsx b/wallet-legacy/src/components/NavigationFrame.tsx similarity index 100% rename from wallet/src/components/NavigationFrame.tsx rename to wallet-legacy/src/components/NavigationFrame.tsx diff --git a/wallet/src/components/SendDialog.tsx b/wallet-legacy/src/components/SendDialog.tsx similarity index 100% rename from wallet/src/components/SendDialog.tsx rename to wallet-legacy/src/components/SendDialog.tsx diff --git a/wallet/src/components/SignFormContent.js b/wallet-legacy/src/components/SignFormContent.js similarity index 100% rename from wallet/src/components/SignFormContent.js rename to wallet-legacy/src/components/SignFormContent.js diff --git a/wallet/src/components/SignTransactionFormContent.js b/wallet-legacy/src/components/SignTransactionFormContent.js similarity index 100% rename from wallet/src/components/SignTransactionFormContent.js rename to wallet-legacy/src/components/SignTransactionFormContent.js diff --git a/wallet/src/components/SolanaIcon.tsx b/wallet-legacy/src/components/SolanaIcon.tsx similarity index 100% rename from wallet/src/components/SolanaIcon.tsx rename to wallet-legacy/src/components/SolanaIcon.tsx diff --git a/wallet/src/components/TokenIcon.tsx b/wallet-legacy/src/components/TokenIcon.tsx similarity index 100% rename from wallet/src/components/TokenIcon.tsx rename to wallet-legacy/src/components/TokenIcon.tsx diff --git a/wallet/src/components/TokenInfoDialog.tsx b/wallet-legacy/src/components/TokenInfoDialog.tsx similarity index 100% rename from wallet/src/components/TokenInfoDialog.tsx rename to wallet-legacy/src/components/TokenInfoDialog.tsx diff --git a/wallet/src/components/Wallet/WalletAvatar.tsx b/wallet-legacy/src/components/Wallet/WalletAvatar.tsx similarity index 100% rename from wallet/src/components/Wallet/WalletAvatar.tsx rename to wallet-legacy/src/components/Wallet/WalletAvatar.tsx diff --git a/wallet/src/components/Wallet/WalletList.tsx b/wallet-legacy/src/components/Wallet/WalletList.tsx similarity index 100% rename from wallet/src/components/Wallet/WalletList.tsx rename to wallet-legacy/src/components/Wallet/WalletList.tsx diff --git a/wallet/src/components/Wallet/WalletList2.tsx b/wallet-legacy/src/components/Wallet/WalletList2.tsx similarity index 100% rename from wallet/src/components/Wallet/WalletList2.tsx rename to wallet-legacy/src/components/Wallet/WalletList2.tsx diff --git a/wallet/src/components/balances/BalanceListItemDetails.tsx b/wallet-legacy/src/components/balances/BalanceListItemDetails.tsx similarity index 100% rename from wallet/src/components/balances/BalanceListItemDetails.tsx rename to wallet-legacy/src/components/balances/BalanceListItemDetails.tsx diff --git a/wallet/src/components/balances/BalanceListItemView.tsx b/wallet-legacy/src/components/balances/BalanceListItemView.tsx similarity index 100% rename from wallet/src/components/balances/BalanceListItemView.tsx rename to wallet-legacy/src/components/balances/BalanceListItemView.tsx diff --git a/wallet/src/components/balances/BalanceListView.tsx b/wallet-legacy/src/components/balances/BalanceListView.tsx similarity index 100% rename from wallet/src/components/balances/BalanceListView.tsx rename to wallet-legacy/src/components/balances/BalanceListView.tsx diff --git a/wallet/src/components/balances/CryptidButton.tsx b/wallet-legacy/src/components/balances/CryptidButton.tsx similarity index 100% rename from wallet/src/components/balances/CryptidButton.tsx rename to wallet-legacy/src/components/balances/CryptidButton.tsx diff --git a/wallet/src/components/balances/TokenButtons.tsx b/wallet-legacy/src/components/balances/TokenButtons.tsx similarity index 100% rename from wallet/src/components/balances/TokenButtons.tsx rename to wallet-legacy/src/components/balances/TokenButtons.tsx diff --git a/wallet/src/components/instructions/layout/InstructionView.tsx b/wallet-legacy/src/components/instructions/layout/InstructionView.tsx similarity index 100% rename from wallet/src/components/instructions/layout/InstructionView.tsx rename to wallet-legacy/src/components/instructions/layout/InstructionView.tsx diff --git a/wallet/src/components/instructions/layout/TransactionView.tsx b/wallet-legacy/src/components/instructions/layout/TransactionView.tsx similarity index 100% rename from wallet/src/components/instructions/layout/TransactionView.tsx rename to wallet-legacy/src/components/instructions/layout/TransactionView.tsx diff --git a/wallet/src/components/instructions/views/DexInstruction.js b/wallet-legacy/src/components/instructions/views/DexInstruction.js similarity index 100% rename from wallet/src/components/instructions/views/DexInstruction.js rename to wallet-legacy/src/components/instructions/views/DexInstruction.js diff --git a/wallet/src/components/instructions/views/LabelValue.js b/wallet-legacy/src/components/instructions/views/LabelValue.js similarity index 100% rename from wallet/src/components/instructions/views/LabelValue.js rename to wallet-legacy/src/components/instructions/views/LabelValue.js diff --git a/wallet/src/components/instructions/views/NewOrder.js b/wallet-legacy/src/components/instructions/views/NewOrder.js similarity index 100% rename from wallet/src/components/instructions/views/NewOrder.js rename to wallet-legacy/src/components/instructions/views/NewOrder.js diff --git a/wallet/src/components/instructions/views/StakeInstruction.js b/wallet-legacy/src/components/instructions/views/StakeInstruction.js similarity index 100% rename from wallet/src/components/instructions/views/StakeInstruction.js rename to wallet-legacy/src/components/instructions/views/StakeInstruction.js diff --git a/wallet/src/components/instructions/views/SystemInstruction.js b/wallet-legacy/src/components/instructions/views/SystemInstruction.js similarity index 100% rename from wallet/src/components/instructions/views/SystemInstruction.js rename to wallet-legacy/src/components/instructions/views/SystemInstruction.js diff --git a/wallet/src/components/instructions/views/TokenInstruction.js b/wallet-legacy/src/components/instructions/views/TokenInstruction.js similarity index 100% rename from wallet/src/components/instructions/views/TokenInstruction.js rename to wallet-legacy/src/components/instructions/views/TokenInstruction.js diff --git a/wallet/src/components/instructions/views/UnknownInstruction.js b/wallet-legacy/src/components/instructions/views/UnknownInstruction.js similarity index 100% rename from wallet/src/components/instructions/views/UnknownInstruction.js rename to wallet-legacy/src/components/instructions/views/UnknownInstruction.js diff --git a/wallet/src/components/modals/AddControllerModal.tsx b/wallet-legacy/src/components/modals/AddControllerModal.tsx similarity index 100% rename from wallet/src/components/modals/AddControllerModal.tsx rename to wallet-legacy/src/components/modals/AddControllerModal.tsx diff --git a/wallet/src/components/modals/AddMnenomicModal.tsx b/wallet-legacy/src/components/modals/AddMnenomicModal.tsx similarity index 100% rename from wallet/src/components/modals/AddMnenomicModal.tsx rename to wallet-legacy/src/components/modals/AddMnenomicModal.tsx diff --git a/wallet/src/components/modals/WalletConnectModal.tsx b/wallet-legacy/src/components/modals/WalletConnectModal.tsx similarity index 100% rename from wallet/src/components/modals/WalletConnectModal.tsx rename to wallet-legacy/src/components/modals/WalletConnectModal.tsx diff --git a/wallet/src/components/modals/modal.tsx b/wallet-legacy/src/components/modals/modal.tsx similarity index 100% rename from wallet/src/components/modals/modal.tsx rename to wallet-legacy/src/components/modals/modal.tsx diff --git a/wallet/src/components/selectors/IdentitySelector.tsx b/wallet-legacy/src/components/selectors/IdentitySelector.tsx similarity index 100% rename from wallet/src/components/selectors/IdentitySelector.tsx rename to wallet-legacy/src/components/selectors/IdentitySelector.tsx diff --git a/wallet/src/fonts/osaka-sans-serif.regular.ttf b/wallet-legacy/src/fonts/osaka-sans-serif.regular.ttf similarity index 100% rename from wallet/src/fonts/osaka-sans-serif.regular.ttf rename to wallet-legacy/src/fonts/osaka-sans-serif.regular.ttf diff --git a/wallet/src/index.css b/wallet-legacy/src/index.css similarity index 100% rename from wallet/src/index.css rename to wallet-legacy/src/index.css diff --git a/wallet/src/index.js b/wallet-legacy/src/index.js similarity index 100% rename from wallet/src/index.js rename to wallet-legacy/src/index.js diff --git a/wallet/src/pages/ConnectionsPage.js b/wallet-legacy/src/pages/ConnectionsPage.js similarity index 100% rename from wallet/src/pages/ConnectionsPage.js rename to wallet-legacy/src/pages/ConnectionsPage.js diff --git a/wallet/src/pages/IdentityPage.tsx b/wallet-legacy/src/pages/IdentityPage.tsx similarity index 100% rename from wallet/src/pages/IdentityPage.tsx rename to wallet-legacy/src/pages/IdentityPage.tsx diff --git a/wallet/src/pages/LoginPage.js b/wallet-legacy/src/pages/LoginPage.js similarity index 100% rename from wallet/src/pages/LoginPage.js rename to wallet-legacy/src/pages/LoginPage.js diff --git a/wallet/src/pages/PopupPage.tsx b/wallet-legacy/src/pages/PopupPage.tsx similarity index 100% rename from wallet/src/pages/PopupPage.tsx rename to wallet-legacy/src/pages/PopupPage.tsx diff --git a/wallet/src/pages/ProposedPage.tsx b/wallet-legacy/src/pages/ProposedPage.tsx similarity index 100% rename from wallet/src/pages/ProposedPage.tsx rename to wallet-legacy/src/pages/ProposedPage.tsx diff --git a/wallet/src/pages/WalletPage.tsx b/wallet-legacy/src/pages/WalletPage.tsx similarity index 100% rename from wallet/src/pages/WalletPage.tsx rename to wallet-legacy/src/pages/WalletPage.tsx diff --git a/wallet/src/pollyfill/buffer.js b/wallet-legacy/src/pollyfill/buffer.js similarity index 100% rename from wallet/src/pollyfill/buffer.js rename to wallet-legacy/src/pollyfill/buffer.js diff --git a/wallet/src/react-app-env.d.ts b/wallet-legacy/src/react-app-env.d.ts similarity index 100% rename from wallet/src/react-app-env.d.ts rename to wallet-legacy/src/react-app-env.d.ts diff --git a/wallet/src/serviceWorker.js b/wallet-legacy/src/serviceWorker.js similarity index 100% rename from wallet/src/serviceWorker.js rename to wallet-legacy/src/serviceWorker.js diff --git a/wallet/src/setupTests.js b/wallet-legacy/src/setupTests.js similarity index 100% rename from wallet/src/setupTests.js rename to wallet-legacy/src/setupTests.js diff --git a/wallet/src/utils/Cryptid/MetaWalletProvider.tsx b/wallet-legacy/src/utils/Cryptid/MetaWalletProvider.tsx similarity index 100% rename from wallet/src/utils/Cryptid/MetaWalletProvider.tsx rename to wallet-legacy/src/utils/Cryptid/MetaWalletProvider.tsx diff --git a/wallet/src/utils/Cryptid/cryptid-external-types.ts b/wallet-legacy/src/utils/Cryptid/cryptid-external-types.ts similarity index 100% rename from wallet/src/utils/Cryptid/cryptid-external-types.ts rename to wallet-legacy/src/utils/Cryptid/cryptid-external-types.ts diff --git a/wallet/src/utils/Cryptid/cryptid.tsx b/wallet-legacy/src/utils/Cryptid/cryptid.tsx similarity index 100% rename from wallet/src/utils/Cryptid/cryptid.tsx rename to wallet-legacy/src/utils/Cryptid/cryptid.tsx diff --git a/wallet/src/utils/Wallet/AccountWallet.ts b/wallet-legacy/src/utils/Wallet/AccountWallet.ts similarity index 100% rename from wallet/src/utils/Wallet/AccountWallet.ts rename to wallet-legacy/src/utils/Wallet/AccountWallet.ts diff --git a/wallet/src/utils/clusters.ts b/wallet-legacy/src/utils/clusters.ts similarity index 100% rename from wallet/src/utils/clusters.ts rename to wallet-legacy/src/utils/clusters.ts diff --git a/wallet/src/utils/config.ts b/wallet-legacy/src/utils/config.ts similarity index 100% rename from wallet/src/utils/config.ts rename to wallet-legacy/src/utils/config.ts diff --git a/wallet/src/utils/connected-wallets.tsx b/wallet-legacy/src/utils/connected-wallets.tsx similarity index 100% rename from wallet/src/utils/connected-wallets.tsx rename to wallet-legacy/src/utils/connected-wallets.tsx diff --git a/wallet/src/utils/connection.tsx b/wallet-legacy/src/utils/connection.tsx similarity index 100% rename from wallet/src/utils/connection.tsx rename to wallet-legacy/src/utils/connection.tsx diff --git a/wallet/src/utils/fetch-loop.ts b/wallet-legacy/src/utils/fetch-loop.ts similarity index 100% rename from wallet/src/utils/fetch-loop.ts rename to wallet-legacy/src/utils/fetch-loop.ts diff --git a/wallet/src/utils/markets.ts b/wallet-legacy/src/utils/markets.ts similarity index 100% rename from wallet/src/utils/markets.ts rename to wallet-legacy/src/utils/markets.ts diff --git a/wallet/src/utils/name-service/index.ts b/wallet-legacy/src/utils/name-service/index.ts similarity index 100% rename from wallet/src/utils/name-service/index.ts rename to wallet-legacy/src/utils/name-service/index.ts diff --git a/wallet/src/utils/notifications.tsx b/wallet-legacy/src/utils/notifications.tsx similarity index 100% rename from wallet/src/utils/notifications.tsx rename to wallet-legacy/src/utils/notifications.tsx diff --git a/wallet/src/utils/page.tsx b/wallet-legacy/src/utils/page.tsx similarity index 100% rename from wallet/src/utils/page.tsx rename to wallet-legacy/src/utils/page.tsx diff --git a/wallet/src/utils/tokens/data.ts b/wallet-legacy/src/utils/tokens/data.ts similarity index 100% rename from wallet/src/utils/tokens/data.ts rename to wallet-legacy/src/utils/tokens/data.ts diff --git a/wallet/src/utils/tokens/index.ts b/wallet-legacy/src/utils/tokens/index.ts similarity index 100% rename from wallet/src/utils/tokens/index.ts rename to wallet-legacy/src/utils/tokens/index.ts diff --git a/wallet/src/utils/tokens/instructions.ts b/wallet-legacy/src/utils/tokens/instructions.ts similarity index 100% rename from wallet/src/utils/tokens/instructions.ts rename to wallet-legacy/src/utils/tokens/instructions.ts diff --git a/wallet/src/utils/tokens/names.tsx b/wallet-legacy/src/utils/tokens/names.tsx similarity index 100% rename from wallet/src/utils/tokens/names.tsx rename to wallet-legacy/src/utils/tokens/names.tsx diff --git a/wallet/src/utils/transactions.ts b/wallet-legacy/src/utils/transactions.ts similarity index 100% rename from wallet/src/utils/transactions.ts rename to wallet-legacy/src/utils/transactions.ts diff --git a/wallet/src/utils/utils.ts b/wallet-legacy/src/utils/utils.ts similarity index 100% rename from wallet/src/utils/utils.ts rename to wallet-legacy/src/utils/utils.ts diff --git a/wallet/src/utils/wallet-seed.ts b/wallet-legacy/src/utils/wallet-seed.ts similarity index 100% rename from wallet/src/utils/wallet-seed.ts rename to wallet-legacy/src/utils/wallet-seed.ts diff --git a/wallet/src/utils/wallet.tsx b/wallet-legacy/src/utils/wallet.tsx similarity index 100% rename from wallet/src/utils/wallet.tsx rename to wallet-legacy/src/utils/wallet.tsx diff --git a/wallet/src/wdyr.ts b/wallet-legacy/src/wdyr.ts similarity index 100% rename from wallet/src/wdyr.ts rename to wallet-legacy/src/wdyr.ts diff --git a/wallet/tailwind.config.js b/wallet-legacy/tailwind.config.js similarity index 100% rename from wallet/tailwind.config.js rename to wallet-legacy/tailwind.config.js diff --git a/wallet/tsconfig.json b/wallet-legacy/tsconfig.json similarity index 100% rename from wallet/tsconfig.json rename to wallet-legacy/tsconfig.json diff --git a/wallet/yarn.lock b/wallet-legacy/yarn.lock similarity index 100% rename from wallet/yarn.lock rename to wallet-legacy/yarn.lock diff --git a/wallet/README.md b/wallet/README.md index 026c52da..7ed8bade 100644 --- a/wallet/README.md +++ b/wallet/README.md @@ -1,13 +1,5 @@ -# Cryptid Wallet UI +Dapp: The cryptid user interface that allows transactions to be generated. -A browser-based UI for managing your Cryptid accounts. +React: Test app that displays connected wallets -See [cryptid.identity.com](https://cryptid.identity.com) to try it out. - -Some features include: -* Connect using your existing Solana wallet -* Manage your keys and account control -* Send SOL & SPL tokens -* Interact with dApps - -See the Cryptid documentation [here](../README.md) for more information +Unique-Cryptid: The wallet standard interface diff --git a/wallet/cryptid-wallet-standard-library/.gitignore b/wallet/cryptid-wallet-standard-library/.gitignore new file mode 100644 index 00000000..a65b4177 --- /dev/null +++ b/wallet/cryptid-wallet-standard-library/.gitignore @@ -0,0 +1 @@ +lib diff --git a/wallet/cryptid-wallet-standard-library/.prettierrc b/wallet/cryptid-wallet-standard-library/.prettierrc new file mode 100644 index 00000000..b9ce4c19 --- /dev/null +++ b/wallet/cryptid-wallet-standard-library/.prettierrc @@ -0,0 +1,7 @@ +{ + "printWidth": 120, + "trailingComma": "es5", + "tabWidth": 4, + "semi": true, + "singleQuote": true +} \ No newline at end of file diff --git a/wallet/cryptid-wallet-standard-library/CHANGELOG.md b/wallet/cryptid-wallet-standard-library/CHANGELOG.md new file mode 100644 index 00000000..5ecf5054 --- /dev/null +++ b/wallet/cryptid-wallet-standard-library/CHANGELOG.md @@ -0,0 +1,8 @@ +# @solana/wallet-standard-ghost + +## null + +### Patch Changes + +- Updated dependencies [ba56499] + - @solana/wallet-standard-features@1.0.0 diff --git a/wallet/cryptid-wallet-standard-library/LICENSE b/wallet/cryptid-wallet-standard-library/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/wallet/cryptid-wallet-standard-library/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/wallet/cryptid-wallet-standard-library/README.md b/wallet/cryptid-wallet-standard-library/README.md new file mode 100644 index 00000000..e69de29b diff --git a/wallet/cryptid-wallet-standard-library/package.json b/wallet/cryptid-wallet-standard-library/package.json new file mode 100644 index 00000000..7f0a82d6 --- /dev/null +++ b/wallet/cryptid-wallet-standard-library/package.json @@ -0,0 +1,41 @@ +{ + "private": true, + "name": "unique-cryptid-standard-wallet", + "author": "Identity.com", + "repository": "https://github.com/identity-com/cryptid", + "license": "Apache-2.0", + "engines": { + "node": ">=16" + }, + "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": { + "fmt": "prettier --write '{*,**/*}.{ts,tsx,js,jsx,json}'", + "clean": "shx mkdir -p lib && shx rm -rf lib", + "tsc": "tsc --build --verbose tsconfig.all.json", + "package": "shx mkdir -p lib/cjs && shx echo '{ \"type\": \"commonjs\" }' > lib/cjs/package.json", + "build": "npm run clean && npm run tsc && npm run package" + }, + "dependencies": { + "@solana/wallet-standard-features": "^1.0.0", + "@solana/web3.js": "^1.58.0", + "@wallet-standard/base": "^1.0.0", + "@wallet-standard/features": "^1.0.0", + "bs58": "^4.0.1" + }, + "devDependencies": { + "@types/bs58": "^4.0.1", + "@types/node-fetch": "^2.6.2", + "prettier": "^2.7.1", + "shx": "^0.3.4", + "typescript": "^4.8.4" + } +} diff --git a/wallet/cryptid-wallet-standard-library/src/account.ts b/wallet/cryptid-wallet-standard-library/src/account.ts new file mode 100644 index 00000000..23972d24 --- /dev/null +++ b/wallet/cryptid-wallet-standard-library/src/account.ts @@ -0,0 +1,53 @@ +// This is copied with modification from @wallet-standard/wallet + +import type { WalletAccount } from '@wallet-standard/base'; +import { SOLANA_CHAINS } from './solana.js'; + +const chains = SOLANA_CHAINS; +const features = ['solana:signAndSendTransaction', 'solana:signMessage', 'solana:signTransaction'] as const; + +export class UniqueCryptidWalletAccount implements WalletAccount { + readonly #address: WalletAccount['address']; + readonly #publicKey: WalletAccount['publicKey']; + readonly #chains: WalletAccount['chains']; + readonly #features: WalletAccount['features']; + readonly #label: WalletAccount['label']; + readonly #icon: WalletAccount['icon']; + + get address() { + return this.#address; + } + + get publicKey() { + return this.#publicKey.slice(); + } + + get chains() { + return this.#chains.slice(); + } + + get features() { + return this.#features.slice(); + } + + get label() { + return this.#label; + } + + get icon() { + return this.#icon; + } + + constructor({ address, publicKey, label, icon }: Omit) { + if (new.target === UniqueCryptidWalletAccount) { + Object.freeze(this); + } + + this.#address = address; + this.#publicKey = publicKey; + this.#chains = chains; + this.#features = features; + this.#label = label; + this.#icon = icon; + } +} diff --git a/wallet/cryptid-wallet-standard-library/src/icon.ts b/wallet/cryptid-wallet-standard-library/src/icon.ts new file mode 100644 index 00000000..e94be710 --- /dev/null +++ b/wallet/cryptid-wallet-standard-library/src/icon.ts @@ -0,0 +1,4 @@ +import type { WalletIcon } from '@wallet-standard/base'; + +export const icon: WalletIcon = + 'data:image/svg+xml;base64,PHN2ZyBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAyODAuMTczIDI4MC4xNzMiIHZpZXdCb3g9IjAgMCAyODAuMTczIDI4MC4xNzMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTEzMy45NjEuMTQ1Yy03MC44ODIgMy41LTEyNS4xMzcgNjMuODgxLTEyNS4xMzcgMTM0Ljc2M3Y2Ni41MDYgNjUuNjMxYzAgNi4xMjYgNi4xMjYgOS42MjYgMTEuMzc2IDYuMTI2bDIwLjEyNy0xMi4yNTFjNy44NzYtNC4zNzUgMTcuNTAyLTQuMzc1IDI1LjM3NyAwbDE4LjM3NyAxMC41MDFjNy44NzYgNC4zNzUgMTcuNTAyIDQuMzc1IDI1LjM3NyAwbDE4LjM3Ny0xMC41MDFjNy44NzYtNC4zNzUgMTcuNTAyLTQuMzc1IDI1LjM3NyAwbDE4LjM3NyAxMC41MDFjNy44NzYgNC4zNzUgMTcuNTAyIDQuMzc1IDI1LjM3NyAwbDE4LjM3Ny0xMC41MDFjNy44NzYtNC4zNzUgMTcuNTAyLTQuMzc1IDI1LjM3NyAwbDE5LjI1MiAxMS4zNzZjNS4yNTEgMi42MjUgMTEuMzc2LS44NzUgMTEuMzc2LTYuMTI2IDAtMTguMzc3IDAtNTAuNzU1IDAtNjUuNjMxdi03MC4wMDdjLjAwMS03My41MDctNjIuMTMtMTMzLjg4Ny0xMzcuMzg3LTEzMC4zODd6IiBmaWxsPSIjZTI1NzRjIi8+PHBhdGggZD0ibTI2LjMyNSAxMzEuNDA4YzAtNjkuMTMyIDU0LjI1NS0xMjYuMDEyIDEyMi41MTItMTMxLjI2My0yLjYyNSAwLTYuMTI2IDAtOC43NTEgMC03Mi42MzIgMC0xMzEuMjYyIDU4LjYzMS0xMzEuMjYyIDEzMS4yNjN2MTQ4Ljc2NWM3Ljg3NiAwIDEzLjEyNi0zLjUgMTcuNTAyLTcuODc2LS4wMDEtMTUuNzUyLS4wMDEtMTQwLjg4OS0uMDAxLTE0MC44ODl6IiBmaWxsPSIjZDI1MTQ3Ii8+PHBhdGggZD0ibTE4OC4yMTYgMTEzLjkwNmMtMTYuNjI3IDAtMzAuNjI4IDE0LjAwMS0zMC42MjggMzAuNjI4czE0LjAwMSAzMC42MjggMzAuNjI4IDMwLjYyOCAzMC42MjgtMTQuMDAxIDMwLjYyOC0zMC42MjgtMTQuMDAxLTMwLjYyOC0zMC42MjgtMzAuNjI4em0tOTYuMjU5IDBjLTE2LjYyNyAwLTMwLjYyOCAxNC4wMDEtMzAuNjI4IDMwLjYyOHMxNC4wMDEgMzAuNjI4IDMwLjYyOCAzMC42MjggMzAuNjI4LTE0LjAwMSAzMC42MjgtMzAuNjI4LTE0LjAwMi0zMC42MjgtMzAuNjI4LTMwLjYyOHoiIGZpbGw9IiNlNGU3ZTciLz48cGF0aCBkPSJtMTg4LjIxNiAxMzEuNDA4Yy03LjAwMSAwLTEzLjEyNiA2LjEyNi0xMy4xMjYgMTMuMTI2IDAgNy4wMDEgNi4xMjYgMTMuMTI2IDEzLjEyNiAxMy4xMjZzMTMuMTI2LTYuMTI2IDEzLjEyNi0xMy4xMjZjMC03LjAwMS02LjEyNS0xMy4xMjYtMTMuMTI2LTEzLjEyNnptLTk2LjI1OSAwYy03LjAwMSAwLTEzLjEyNiA2LjEyNi0xMy4xMjYgMTMuMTI2IDAgNy4wMDEgNi4xMjYgMTMuMTI2IDEzLjEyNiAxMy4xMjYgNy4wMDEgMCAxMy4xMjYtNi4xMjYgMTMuMTI2LTEzLjEyNiAwLTcuMDAxLTYuMTI2LTEzLjEyNi0xMy4xMjYtMTMuMTI2eiIgZmlsbD0iIzMyNGQ1YiIvPjwvc3ZnPg==' as const; diff --git a/wallet/cryptid-wallet-standard-library/src/index.ts b/wallet/cryptid-wallet-standard-library/src/index.ts new file mode 100644 index 00000000..db326b60 --- /dev/null +++ b/wallet/cryptid-wallet-standard-library/src/index.ts @@ -0,0 +1 @@ +export * from './initialize.js'; diff --git a/wallet/cryptid-wallet-standard-library/src/initialize.ts b/wallet/cryptid-wallet-standard-library/src/initialize.ts new file mode 100644 index 00000000..e32903a5 --- /dev/null +++ b/wallet/cryptid-wallet-standard-library/src/initialize.ts @@ -0,0 +1,7 @@ +import { registerWallet } from './register.js'; +import { UniqueCryptidWallet } from './wallet.js'; +import type { UniqueCryptid } from './window.js'; + +export function initialize(uniqueCryptid: UniqueCryptid): void { + registerWallet(new UniqueCryptidWallet(uniqueCryptid)); +} diff --git a/wallet/cryptid-wallet-standard-library/src/register.ts b/wallet/cryptid-wallet-standard-library/src/register.ts new file mode 100644 index 00000000..b8544c59 --- /dev/null +++ b/wallet/cryptid-wallet-standard-library/src/register.ts @@ -0,0 +1,72 @@ +// This is copied from @wallet-standard/wallet + +import type { + DEPRECATED_WalletsWindow, + Wallet, + WalletEventsWindow, + WindowRegisterWalletEvent, + WindowRegisterWalletEventCallback, +} from '@wallet-standard/base'; + +export function registerWallet(wallet: Wallet): void { + const callback: WindowRegisterWalletEventCallback = ({ register }) => register(wallet); + try { + (window as WalletEventsWindow).dispatchEvent(new RegisterWalletEvent(callback)); + window.open('http://localhost:3000/', 'Cryptid', 'height=500,width=500'); + } catch (error) { + console.error('wallet-standard:register-wallet event could not be dispatched\n', error); + } + try { + (window as WalletEventsWindow).addEventListener('wallet-standard:app-ready', ({ detail: api }) => + callback(api) + ); + } catch (error) { + console.error('wallet-standard:app-ready event listener could not be added\n', error); + } +} + +class RegisterWalletEvent extends Event implements WindowRegisterWalletEvent { + readonly #detail: WindowRegisterWalletEventCallback; + + get detail() { + return this.#detail; + } + + get type() { + return 'wallet-standard:register-wallet' as const; + } + + constructor(callback: WindowRegisterWalletEventCallback) { + super('wallet-standard:register-wallet', { + bubbles: false, + cancelable: false, + composed: false, + }); + this.#detail = callback; + } + + /** @deprecated */ + preventDefault(): never { + throw new Error('preventDefault cannot be called'); + } + + /** @deprecated */ + stopImmediatePropagation(): never { + throw new Error('stopImmediatePropagation cannot be called'); + } + + /** @deprecated */ + stopPropagation(): never { + throw new Error('stopPropagation cannot be called'); + } +} + +/** @deprecated */ +export function DEPRECATED_registerWallet(wallet: Wallet): void { + registerWallet(wallet); + try { + ((window as DEPRECATED_WalletsWindow).navigator.wallets ||= []).push(({ register }) => register(wallet)); + } catch (error) { + console.error('window.navigator.wallets could not be pushed\n', error); + } +} diff --git a/wallet/cryptid-wallet-standard-library/src/solana.ts b/wallet/cryptid-wallet-standard-library/src/solana.ts new file mode 100644 index 00000000..0d327b84 --- /dev/null +++ b/wallet/cryptid-wallet-standard-library/src/solana.ts @@ -0,0 +1,33 @@ +// This is copied from @solana/wallet-standard-chains + +import type { IdentifierString } from '@wallet-standard/base'; + +/** Solana Mainnet (beta) cluster, e.g. https://api.mainnet-beta.solana.com */ +export const SOLANA_MAINNET_CHAIN = 'solana:mainnet'; + +/** Solana Devnet cluster, e.g. https://api.devnet.solana.com */ +export const SOLANA_DEVNET_CHAIN = 'solana:devnet'; + +/** Solana Testnet cluster, e.g. https://api.testnet.solana.com */ +export const SOLANA_TESTNET_CHAIN = 'solana:testnet'; + +/** Solana Localnet cluster, e.g. http://localhost:8899 */ +export const SOLANA_LOCALNET_CHAIN = 'solana:localnet'; + +/** Array of all Solana clusters */ +export const SOLANA_CHAINS = [ + SOLANA_MAINNET_CHAIN, + SOLANA_DEVNET_CHAIN, + SOLANA_TESTNET_CHAIN, + SOLANA_LOCALNET_CHAIN, +] as const; + +/** Type of all Solana clusters */ +export type SolanaChain = typeof SOLANA_CHAINS[number]; + +/** + * Check if a chain corresponds with one of the Solana clusters. + */ +export function isSolanaChain(chain: IdentifierString): chain is SolanaChain { + return SOLANA_CHAINS.includes(chain as SolanaChain); +} diff --git a/wallet/cryptid-wallet-standard-library/src/util.ts b/wallet/cryptid-wallet-standard-library/src/util.ts new file mode 100644 index 00000000..17816202 --- /dev/null +++ b/wallet/cryptid-wallet-standard-library/src/util.ts @@ -0,0 +1,23 @@ +// This is copied from @wallet-standard/wallet + +export function bytesEqual(a: Uint8Array, b: Uint8Array): boolean { + return arraysEqual(a, b); +} + +interface Indexed { + length: number; + [index: number]: T; +} + +export function arraysEqual(a: Indexed, b: Indexed): boolean { + if (a === b) return true; + + const length = a.length; + if (length !== b.length) return false; + + for (let i = 0; i < length; i++) { + if (a[i] !== b[i]) return false; + } + + return true; +} diff --git a/wallet/cryptid-wallet-standard-library/src/wallet.ts b/wallet/cryptid-wallet-standard-library/src/wallet.ts new file mode 100644 index 00000000..aeca2917 --- /dev/null +++ b/wallet/cryptid-wallet-standard-library/src/wallet.ts @@ -0,0 +1,272 @@ +import type { + SolanaSignAndSendTransactionFeature, + SolanaSignAndSendTransactionMethod, + SolanaSignAndSendTransactionOutput, + SolanaSignMessageFeature, + SolanaSignMessageMethod, + SolanaSignMessageOutput, + SolanaSignTransactionFeature, + SolanaSignTransactionMethod, + SolanaSignTransactionOutput, +} from '@solana/wallet-standard-features'; +import { Transaction, VersionedTransaction } from '@solana/web3.js'; +import type { Wallet } from '@wallet-standard/base'; +import type { + ConnectFeature, + ConnectMethod, + DisconnectFeature, + DisconnectMethod, + EventsFeature, + EventsListeners, + EventsNames, + EventsOnMethod, +} from '@wallet-standard/features'; +import bs58 from 'bs58'; +import { UniqueCryptidWalletAccount } from './account.js'; +import { icon } from './icon.js'; +import type { SolanaChain } from './solana.js'; +import { isSolanaChain, SOLANA_CHAINS } from './solana.js'; +import { bytesEqual } from './util.js'; +import type { UniqueCryptid } from './window.js'; + +export type UniqueCryptidFeature = { + 'uniqueCryptid:': { + uniqueCryptid: UniqueCryptid; + }; +}; + +export class UniqueCryptidWallet implements Wallet { + readonly #listeners: { [E in EventsNames]?: EventsListeners[E][] } = {}; + readonly #version = '1.0.0' as const; + readonly #name = 'Unique Cryptid' as const; + readonly #icon = icon; + #account: UniqueCryptidWalletAccount | null = null; + readonly #uniqueCryptid: UniqueCryptid; + + get version() { + return this.#version; + } + + get name() { + return this.#name; + } + + get icon() { + return this.#icon; + } + + get chains() { + return SOLANA_CHAINS.slice(); + } + + get features(): ConnectFeature & + DisconnectFeature & + EventsFeature & + SolanaSignAndSendTransactionFeature & + SolanaSignTransactionFeature & + SolanaSignMessageFeature & + UniqueCryptidFeature { + return { + 'standard:connect': { + version: '1.0.0', + connect: this.#connect, + }, + 'standard:disconnect': { + version: '1.0.0', + disconnect: this.#disconnect, + }, + 'standard:events': { + version: '1.0.0', + on: this.#on, + }, + 'solana:signAndSendTransaction': { + version: '1.0.0', + supportedTransactionVersions: ['legacy', 0], + signAndSendTransaction: this.#signAndSendTransaction, + }, + 'solana:signTransaction': { + version: '1.0.0', + supportedTransactionVersions: ['legacy', 0], + signTransaction: this.#signTransaction, + }, + 'solana:signMessage': { + version: '1.0.0', + signMessage: this.#signMessage, + }, + 'uniqueCryptid:': { + uniqueCryptid: this.#uniqueCryptid, + }, + }; + } + + get accounts() { + return this.#account ? [this.#account] : []; + } + + constructor(uniqueCryptid: UniqueCryptid) { + if (new.target === UniqueCryptidWallet) { + Object.freeze(this); + } + + this.#uniqueCryptid = uniqueCryptid; + + uniqueCryptid.on('connect', this.#connected, this); + uniqueCryptid.on('disconnect', this.#disconnected, this); + uniqueCryptid.on('accountChanged', this.#reconnected, this); + + this.#connected(); + } + + #on: EventsOnMethod = (event, listener) => { + this.#listeners[event]?.push(listener) || (this.#listeners[event] = [listener]); + return (): void => this.#off(event, listener); + }; + + #emit(event: E, ...args: Parameters): void { + // eslint-disable-next-line prefer-spread + this.#listeners[event]?.forEach((listener) => listener.apply(null, args)); + } + + #off(event: E, listener: EventsListeners[E]): void { + this.#listeners[event] = this.#listeners[event]?.filter((existingListener) => listener !== existingListener); + } + + #connected = () => { + const address = this.#uniqueCryptid.publicKey?.toBase58(); + if (address) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const publicKey = this.#uniqueCryptid.publicKey!.toBytes(); + + const account = this.#account; + if (!account || account.address !== address || !bytesEqual(account.publicKey, publicKey)) { + this.#account = new UniqueCryptidWalletAccount({ address, publicKey }); + this.#emit('change', { accounts: this.accounts }); + } + } + }; + + #disconnected = () => { + if (this.#account) { + this.#account = null; + this.#emit('change', { accounts: this.accounts }); + } + }; + + #reconnected = () => { + if (this.#uniqueCryptid.publicKey) { + this.#connected(); + } else { + this.#disconnected(); + } + }; + + #connect: ConnectMethod = async ({ silent } = {}) => { + if (!this.#account) { + await this.#uniqueCryptid.connect(silent ? { onlyIfTrusted: true } : undefined); + } + + this.#connected(); + + return { accounts: this.accounts }; + }; + + #disconnect: DisconnectMethod = async () => { + await this.#uniqueCryptid.disconnect(); + }; + + #signAndSendTransaction: SolanaSignAndSendTransactionMethod = async (...inputs) => { + if (!this.#account) throw new Error('not connected'); + + const outputs: SolanaSignAndSendTransactionOutput[] = []; + + if (inputs.length === 1) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const { transaction, account, chain, options } = inputs[0]!; + const { minContextSlot, preflightCommitment, skipPreflight, maxRetries } = options || {}; + if (account !== this.#account) throw new Error('invalid account'); + if (!isSolanaChain(chain)) throw new Error('invalid chain'); + + const { signature } = await this.#uniqueCryptid.signAndSendTransaction( + VersionedTransaction.deserialize(transaction), + { + preflightCommitment, + minContextSlot, + maxRetries, + skipPreflight, + } + ); + + outputs.push({ signature: bs58.decode(signature) }); + } else if (inputs.length > 1) { + for (const input of inputs) { + outputs.push(...(await this.#signAndSendTransaction(input))); + } + } + + return outputs; + }; + + #signTransaction: SolanaSignTransactionMethod = async (...inputs) => { + if (!this.#account) throw new Error('not connected'); + + const outputs: SolanaSignTransactionOutput[] = []; + + if (inputs.length === 1) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const { transaction, account, chain } = inputs[0]!; + if (account !== this.#account) throw new Error('invalid account'); + if (chain && !isSolanaChain(chain)) throw new Error('invalid chain'); + + const signedTransaction = await this.#uniqueCryptid.signTransaction( + VersionedTransaction.deserialize(transaction) + ); + + outputs.push({ signedTransaction: signedTransaction.serialize() }); + } else if (inputs.length > 1) { + let chain: SolanaChain | undefined = undefined; + for (const input of inputs) { + if (input.account !== this.#account) throw new Error('invalid account'); + if (input.chain) { + if (!isSolanaChain(input.chain)) throw new Error('invalid chain'); + if (chain) { + if (input.chain !== chain) throw new Error('conflicting chain'); + } else { + chain = input.chain; + } + } + } + + const transactions = inputs.map(({ transaction }) => Transaction.from(transaction)); + + const signedTransactions = await this.#uniqueCryptid.signAllTransactions(transactions); + + outputs.push( + ...signedTransactions.map((signedTransaction) => ({ signedTransaction: signedTransaction.serialize() })) + ); + } + + return outputs; + }; + + #signMessage: SolanaSignMessageMethod = async (...inputs) => { + if (!this.#account) throw new Error('not connected'); + + const outputs: SolanaSignMessageOutput[] = []; + + if (inputs.length === 1) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const { message, account } = inputs[0]!; + if (account !== this.#account) throw new Error('invalid account'); + + const { signature } = await this.#uniqueCryptid.signMessage(message); + + outputs.push({ signedMessage: message, signature }); + } else if (inputs.length > 1) { + for (const input of inputs) { + outputs.push(...(await this.#signMessage(input))); + } + } + + return outputs; + }; +} diff --git a/wallet/cryptid-wallet-standard-library/src/window.ts b/wallet/cryptid-wallet-standard-library/src/window.ts new file mode 100644 index 00000000..4c69ddb4 --- /dev/null +++ b/wallet/cryptid-wallet-standard-library/src/window.ts @@ -0,0 +1,25 @@ +import type { PublicKey, SendOptions, Transaction, TransactionSignature, VersionedTransaction } from '@solana/web3.js'; + +export interface UniqueCryptidEvent { + connect(...args: unknown[]): unknown; + disconnect(...args: unknown[]): unknown; + accountChanged(...args: unknown[]): unknown; +} + +export interface UniqueCryptidEventEmitter { + on(event: E, listener: UniqueCryptidEvent[E], context?: any): void; + off(event: E, listener: UniqueCryptidEvent[E], context?: any): void; +} + +export interface UniqueCryptid extends UniqueCryptidEventEmitter { + publicKey: PublicKey | null; + connect(options?: { onlyIfTrusted?: boolean }): Promise<{ publicKey: PublicKey }>; + disconnect(): Promise; + signAndSendTransaction( + transaction: T, + options?: SendOptions + ): Promise<{ signature: TransactionSignature }>; + signTransaction(transaction: T): Promise; + signAllTransactions(transactions: T[]): Promise; + signMessage(message: Uint8Array): Promise<{ signature: Uint8Array }>; +} diff --git a/wallet/cryptid-wallet-standard-library/tsconfig.all.json b/wallet/cryptid-wallet-standard-library/tsconfig.all.json new file mode 100644 index 00000000..e993bf7d --- /dev/null +++ b/wallet/cryptid-wallet-standard-library/tsconfig.all.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "composite": true + }, + "references": [ + { + "path": "./tsconfig.cjs.json" + }, + { + "path": "./tsconfig.esm.json" + } + ] +} diff --git a/wallet/cryptid-wallet-standard-library/tsconfig.base.json b/wallet/cryptid-wallet-standard-library/tsconfig.base.json new file mode 100644 index 00000000..28e7c151 --- /dev/null +++ b/wallet/cryptid-wallet-standard-library/tsconfig.base.json @@ -0,0 +1,15 @@ +{ + "include": [], + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Node", + "esModuleInterop": true, + "isolatedModules": true, + "noEmitOnError": true, + "resolveJsonModule": true, + "strict": true, + "stripInternal": true, + "noUncheckedIndexedAccess": true + } +} diff --git a/wallet/cryptid-wallet-standard-library/tsconfig.cjs.json b/wallet/cryptid-wallet-standard-library/tsconfig.cjs.json new file mode 100644 index 00000000..2db9b715 --- /dev/null +++ b/wallet/cryptid-wallet-standard-library/tsconfig.cjs.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.base.json", + "include": ["src"], + "compilerOptions": { + "outDir": "lib/cjs", + "target": "ES2016", + "module": "CommonJS", + "sourceMap": true + } +} diff --git a/wallet/cryptid-wallet-standard-library/tsconfig.esm.json b/wallet/cryptid-wallet-standard-library/tsconfig.esm.json new file mode 100644 index 00000000..25e7e25e --- /dev/null +++ b/wallet/cryptid-wallet-standard-library/tsconfig.esm.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.base.json", + "include": ["src"], + "compilerOptions": { + "outDir": "lib/esm", + "declarationDir": "lib/types", + "target": "ES2020", + "module": "ES2020", + "sourceMap": true, + "declaration": true, + "declarationMap": true + } +} diff --git a/wallet/cryptid-wallet-standard-library/yarn.lock b/wallet/cryptid-wallet-standard-library/yarn.lock new file mode 100644 index 00000000..653b82c7 --- /dev/null +++ b/wallet/cryptid-wallet-standard-library/yarn.lock @@ -0,0 +1,597 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/runtime@^7.12.5", "@babel/runtime@^7.17.2": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.7.tgz#fcb41a5a70550e04a7b708037c7c32f7f356d8fd" + integrity sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ== + dependencies: + regenerator-runtime "^0.13.11" + +"@noble/ed25519@^1.7.0": + version "1.7.1" + resolved "https://registry.yarnpkg.com/@noble/ed25519/-/ed25519-1.7.1.tgz#6899660f6fbb97798a6fbd227227c4589a454724" + integrity sha512-Rk4SkJFaXZiznFyC/t77Q0NKS4FL7TLJJsVG2V2oiEq3kJVeTdxysEe/yRWSpnWMe808XRDJ+VFh5pt/FN5plw== + +"@noble/hashes@^1.1.2": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.1.5.tgz#1a0377f3b9020efe2fae03290bd2a12140c95c11" + integrity sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ== + +"@noble/secp256k1@^1.6.3": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.0.tgz#d15357f7c227e751d90aa06b05a0e5cf993ba8c1" + integrity sha512-kbacwGSsH/CTout0ZnZWxnW1B+jH/7r/WAAKLBtrRJ/+CUH7lgmQzl3GTrQua3SGKWNSDsS6lmjnDpIJ5Dxyaw== + +"@solana/buffer-layout@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz#b996235eaec15b1e0b5092a8ed6028df77fa6c15" + integrity sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA== + dependencies: + buffer "~6.0.3" + +"@solana/wallet-standard-features@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@solana/wallet-standard-features/-/wallet-standard-features-1.0.0.tgz#fee71c47fa8c4bacbdc5c8750487e60a2e5e6746" + integrity sha512-cZKUm2w67MQOAzbfdZCGAbePWuqSwpvpbWA2K2D0UcHX30I8ry8YEeHlqwqULIOTeY8lRCHu8WMxZwC9iMEqHQ== + dependencies: + "@wallet-standard/base" "^1.0.0" + "@wallet-standard/features" "^1.0.0" + +"@solana/web3.js@^1.58.0": + version "1.73.0" + resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.73.0.tgz#c65f9f954ac80fca6952765c931dd72e57e1b572" + integrity sha512-YrgX3Py7ylh8NYkbanoINUPCj//bWUjYZ5/WPy9nQ9SK3Cl7QWCR+NmbDjmC/fTspZGR+VO9LTQslM++jr5PRw== + dependencies: + "@babel/runtime" "^7.12.5" + "@noble/ed25519" "^1.7.0" + "@noble/hashes" "^1.1.2" + "@noble/secp256k1" "^1.6.3" + "@solana/buffer-layout" "^4.0.0" + agentkeepalive "^4.2.1" + bigint-buffer "^1.1.5" + bn.js "^5.0.0" + borsh "^0.7.0" + bs58 "^4.0.1" + buffer "6.0.1" + fast-stable-stringify "^1.0.0" + jayson "^3.4.4" + node-fetch "2" + rpc-websockets "^7.5.0" + superstruct "^0.14.2" + +"@types/bs58@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/bs58/-/bs58-4.0.1.tgz#3d51222aab067786d3bc3740a84a7f5a0effaa37" + integrity sha512-yfAgiWgVLjFCmRv8zAcOIHywYATEwiTVccTLnRp6UxTNavT55M9d/uhK3T03St/+8/z/wW+CRjGKUNmEqoHHCA== + dependencies: + base-x "^3.0.6" + +"@types/connect@^3.4.33": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + +"@types/node-fetch@^2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da" + integrity sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A== + dependencies: + "@types/node" "*" + form-data "^3.0.0" + +"@types/node@*": + version "18.11.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f" + integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA== + +"@types/node@^12.12.54": + version "12.20.55" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" + integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== + +"@types/ws@^7.4.4": + version "7.4.7" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" + integrity sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww== + dependencies: + "@types/node" "*" + +"@wallet-standard/base@^1.0.0", "@wallet-standard/base@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@wallet-standard/base/-/base-1.0.1.tgz#860dd94d47c9e3c5c43b79d91c6afdbd7a36264e" + integrity sha512-1To3ekMfzhYxe0Yhkpri+Fedq0SYcfrOfJi3vbLjMwF2qiKPjTGLwZkf2C9ftdQmxES+hmxhBzTwF4KgcOwf8w== + +"@wallet-standard/features@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@wallet-standard/features/-/features-1.0.1.tgz#9aab662ebc5394ec2463e5c8f67b8bbf30fef1b2" + integrity sha512-B/WzRpoEiGQ+kwL/AuzqzNjrGvdL5J2OXiXHkDGONsNbKdtTpExU+ttZKQwSP/EvPl8+ZZ/4OO/Bz4YkigtQDg== + dependencies: + "@wallet-standard/base" "^1.0.1" + +JSONStream@^1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" + integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== + dependencies: + jsonparse "^1.2.0" + through ">=2.2.7 <3" + +agentkeepalive@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.2.1.tgz#a7975cbb9f83b367f06c90cc51ff28fe7d499717" + integrity sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA== + dependencies: + debug "^4.1.0" + depd "^1.1.2" + humanize-ms "^1.2.1" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base-x@^3.0.2, base-x@^3.0.6: + version "3.0.9" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" + integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== + dependencies: + safe-buffer "^5.0.1" + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bigint-buffer@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/bigint-buffer/-/bigint-buffer-1.1.5.tgz#d038f31c8e4534c1f8d0015209bf34b4fa6dd442" + integrity sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA== + dependencies: + bindings "^1.3.0" + +bindings@^1.3.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + +bn.js@^5.0.0, bn.js@^5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + +borsh@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/borsh/-/borsh-0.7.0.tgz#6e9560d719d86d90dc589bca60ffc8a6c51fec2a" + integrity sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA== + dependencies: + bn.js "^5.2.0" + bs58 "^4.0.0" + text-encoding-utf-8 "^1.0.2" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +bs58@^4.0.0, bs58@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" + integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw== + dependencies: + base-x "^3.0.2" + +buffer@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.1.tgz#3cbea8c1463e5a0779e30b66d4c88c6ffa182ac2" + integrity sha512-rVAXBwEcEoYtxnHSO5iWyhzV/O1WMtkUYWlfdLS7FjU4PnSJJHEfHXi/uHPI5EwltmOA794gN3bm3/pzuctWjQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + +buffer@~6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + +bufferutil@^4.0.1: + version "4.0.7" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.7.tgz#60c0d19ba2c992dd8273d3f73772ffc894c153ad" + integrity sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw== + dependencies: + node-gyp-build "^4.3.0" + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@^2.20.3: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +debug@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +delay@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" + integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +depd@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== + +es6-promise@^4.0.3: + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + integrity sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ== + dependencies: + es6-promise "^4.0.3" + +eventemitter3@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +eyes@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" + integrity sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ== + +fast-stable-stringify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz#5c5543462b22aeeefd36d05b34e51c78cb86d313" + integrity sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag== + +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +glob@^7.0.0: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== + dependencies: + ms "^2.0.0" + +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +interpret@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" + integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== + +is-core-module@^2.9.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" + integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== + dependencies: + has "^1.0.3" + +isomorphic-ws@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" + integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== + +jayson@^3.4.4: + version "3.7.0" + resolved "https://registry.yarnpkg.com/jayson/-/jayson-3.7.0.tgz#b735b12d06d348639ae8230d7a1e2916cb078f25" + integrity sha512-tfy39KJMrrXJ+mFcMpxwBvFDetS8LAID93+rycFglIQM4kl3uNR3W4lBLE/FFhsoUCEox5Dt2adVpDm/XtebbQ== + dependencies: + "@types/connect" "^3.4.33" + "@types/node" "^12.12.54" + "@types/ws" "^7.4.4" + JSONStream "^1.3.5" + 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" + lodash "^4.17.20" + uuid "^8.3.2" + ws "^7.4.5" + +json-stringify-safe@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + +jsonparse@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" + integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== + +lodash@^4.17.20: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.3: + version "1.2.7" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" + integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@^2.0.0: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +node-fetch@2: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + +node-gyp-build@^4.3.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.0.tgz#0c52e4cbf54bbd28b709820ef7b6a3c2d6209055" + integrity sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +prettier@^2.7.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.1.tgz#4e1fd11c34e2421bc1da9aea9bd8127cd0a35efc" + integrity sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg== + +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== + dependencies: + resolve "^1.1.6" + +regenerator-runtime@^0.13.11: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + +resolve@^1.1.6: + version "1.22.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +rpc-websockets@^7.5.0: + version "7.5.0" + resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-7.5.0.tgz#bbeb87572e66703ff151e50af1658f98098e2748" + integrity sha512-9tIRi1uZGy7YmDjErf1Ax3wtqdSSLIlnmL5OtOzgd5eqPKbsPpwDP5whUDO2LQay3Xp0CcHlcNSGzacNRluBaQ== + dependencies: + "@babel/runtime" "^7.17.2" + eventemitter3 "^4.0.7" + uuid "^8.3.2" + ws "^8.5.0" + optionalDependencies: + bufferutil "^4.0.1" + utf-8-validate "^5.0.2" + +safe-buffer@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +shelljs@^0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" + integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + +shx@^0.3.4: + version "0.3.4" + resolved "https://registry.yarnpkg.com/shx/-/shx-0.3.4.tgz#74289230b4b663979167f94e1935901406e40f02" + integrity sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g== + dependencies: + minimist "^1.2.3" + shelljs "^0.8.5" + +superstruct@^0.14.2: + version "0.14.2" + resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.14.2.tgz#0dbcdf3d83676588828f1cf5ed35cda02f59025b" + integrity sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ== + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +text-encoding-utf-8@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz#585b62197b0ae437e3c7b5d0af27ac1021e10d13" + integrity sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg== + +"through@>=2.2.7 <3": + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +typescript@^4.8.4: + version "4.9.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78" + integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg== + +utf-8-validate@^5.0.2: + version "5.0.10" + resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz#d7d10ea39318171ca982718b6b96a8d2442571a2" + integrity sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ== + dependencies: + node-gyp-build "^4.3.0" + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +ws@^7.4.5: + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + +ws@^8.5.0: + version "8.11.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" + integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== diff --git a/wallet/cryptid-wallet-ui/.babelrc b/wallet/cryptid-wallet-ui/.babelrc new file mode 100644 index 00000000..865c6b31 --- /dev/null +++ b/wallet/cryptid-wallet-ui/.babelrc @@ -0,0 +1 @@ +{ "presets": ["next/babel"] } diff --git a/wallet/cryptid-wallet-ui/.gitignore b/wallet/cryptid-wallet-ui/.gitignore new file mode 100644 index 00000000..0c57fcf9 --- /dev/null +++ b/wallet/cryptid-wallet-ui/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo + +# logs +*.log \ No newline at end of file diff --git a/wallet/cryptid-wallet-ui/Dockerfile b/wallet/cryptid-wallet-ui/Dockerfile new file mode 100644 index 00000000..c21e282e --- /dev/null +++ b/wallet/cryptid-wallet-ui/Dockerfile @@ -0,0 +1,15 @@ +FROM node:12-alpine + +WORKDIR /opt/app + +ENV NODE_ENV production + +COPY package*.json ./ + +RUN npm ci + +COPY . /opt/app + +RUN npm install --dev && npm run build + +CMD [ "npm", "start" ] \ No newline at end of file diff --git a/wallet/cryptid-wallet-ui/LICENSE b/wallet/cryptid-wallet-ui/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/wallet/cryptid-wallet-ui/LICENSE @@ -0,0 +1,201 @@ + 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/wallet/cryptid-wallet-ui/README.md b/wallet/cryptid-wallet-ui/README.md new file mode 100644 index 00000000..af5ccbd2 --- /dev/null +++ b/wallet/cryptid-wallet-ui/README.md @@ -0,0 +1,127 @@ + +# Solana dApp Scaffold Next + +The Solana dApp Scaffold repos are meant to house good starting scaffolds for ecosystem developers to get up and running quickly with a front end client UI that integrates several common features found in dApps with some basic usage examples. Wallet Integration. State management. Components examples. Notifications. Setup recommendations. + +Responsive | Desktop +:-------------------------:|:-------------------------: +![](scaffold-mobile.png) | ![](scaffold-desktop.png) + +## Getting Started + +This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). + +The responsive version for wallets and wallet adapter may not function or work as expected for mobile based on plugin and wallet compatibility. For more code examples and implementations please visit the [Solana Cookbook](https://solanacookbook.com/) + +## Installation + +```bash +npm install +# or +yarn install +``` + +## Build and Run + +Next, run the development server: + +```bash +npm run dev +# or +yarn dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. + +[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. + +The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. + +## Features + +Each Scaffold will contain at least the following features: + +``` +Wallet Integration with Auto Connec / Refresh + +State Management + +Components: One or more components demonstrating state management + +Web3 Js: Examples of one or more uses of web3 js including a transaction with a connection provider + +Sample navigation and page changing to demonstate state + +Clean Simple Styling + +Notifications (optional): Example of using a notification system + +``` + +A Solana Components Repo will be released in the near future to house a common components library. + + +### Structure + +The scaffold project structure may vary based on the front end framework being utilized. The below is an example structure for the Next js Scaffold. + +``` +├── public : publically hosted files +├── src : primary code folders and files +│ ├── components : should house anything considered a resuable UI component +│ ├── contexts` : any context considered reusable and useuful to many compoennts that can be passed down through a component tree +│ ├── hooks` : any functions that let you 'hook' into react state or lifecycle features from function components +│ ├── models` : any data structure that may be reused throughout the project +│ ├── pages` : the pages that host meta data and the intended `View` for the page +│ ├── stores` : stores used in state management +│ ├── styles` : contain any global and reusable styles +│ ├── utils` : any other functionality considered reusable code that can be referenced +│ ├── views` : contains the actual views of the project that include the main content and components within +style, package, configuration, and other project files + +``` + +## Contributing + +Anyone is welcome to create an issue to build, discuss or request a new feature or update to the existing code base. Please keep in mind the following when submitting an issue. We consider merging high value features that may be utilized by the majority of scaffold users. If this is not a common feature or fix, consider adding it to the component library or cookbook. Please refer to the project's architecture and style when contributing. + +If submitting a feature, please reference the project structure shown above and try to follow the overall architecture and style presented in the existing scaffold. + +### Committing + +To choose a task or make your own, do the following: + +1. [Add an issue](https://github.com/solana-dev-adv/solana-dapp-next/issues/new) for the task and assign it to yourself or comment on the issue +2. Make a draft PR referencing the issue. + +The general flow for making a contribution: + +1. Fork the repo on GitHub +2. Clone the project to your own machine +3. Commit changes to your own branch +4. Push your work back up to your fork +5. Submit a Pull request so that we can review your changes + +**NOTE**: Be sure to merge the latest from "upstream" before making a +pull request! + +You can find tasks on the [project board](https://github.com/solana-dev-adv/solana-dapp-next/projects/1) +or create an issue and assign it to yourself. + + +## Learn More Next Js + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/wallet/cryptid-wallet-ui/docker-compose.yml b/wallet/cryptid-wallet-ui/docker-compose.yml new file mode 100644 index 00000000..0af1a6c8 --- /dev/null +++ b/wallet/cryptid-wallet-ui/docker-compose.yml @@ -0,0 +1,24 @@ +version: "3" +services: + dapp: + container_name: dapp + build: . + restart: always + + nginx: + container_name: nginx + image: jonasal/nginx-certbot + restart: always + environment: + - CERTBOT_EMAIL=ronaldhoodjr@gmail.com + ports: + - 80:80 + - 443:443 + volumes: + - nginx_secrets:/etc/letsencrypt + - ./user_conf.d:/etc/nginx/user_conf.d + depends_on: + - dapp + +volumes: + nginx_secrets: \ No newline at end of file diff --git a/wallet/cryptid-wallet-ui/next-env.d.ts b/wallet/cryptid-wallet-ui/next-env.d.ts new file mode 100644 index 00000000..4f11a03d --- /dev/null +++ b/wallet/cryptid-wallet-ui/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/wallet/cryptid-wallet-ui/next.config.js b/wallet/cryptid-wallet-ui/next.config.js new file mode 100644 index 00000000..a843cbee --- /dev/null +++ b/wallet/cryptid-wallet-ui/next.config.js @@ -0,0 +1,6 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, +} + +module.exports = nextConfig diff --git a/wallet/cryptid-wallet-ui/package.json b/wallet/cryptid-wallet-ui/package.json new file mode 100644 index 00000000..bf629752 --- /dev/null +++ b/wallet/cryptid-wallet-ui/package.json @@ -0,0 +1,47 @@ +{ + "name": "dapp", + "version": "0.1.0", + "license": "MIT", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@heroicons/react": "^1.0.5", + "@identity.com/cryptid": "0.3.0-alpha.11", + "@identity.com/cryptid-idl": "^0.3.0-alpha.1", + "@identity.com/sol-did-client": "^3.1.4", + "@noble/ed25519": "^1.7.1", + "@project-serum/anchor": "^0.26.0", + "@solana/wallet-adapter-base": "^0.9.18", + "@solana/wallet-adapter-react": "^0.15.21-rc.4", + "@solana/wallet-adapter-react-ui": "^0.9.19-rc.4", + "@solana/wallet-adapter-wallets": "^0.19.4", + "@solana/web3.js": "^1.58.0", + "@tailwindcss/typography": "^0.5.0", + "@typescript-eslint/eslint-plugin": "^5.48.0", + "bs58": "^5.0.0", + "daisyui": "^1.24.3", + "immer": "^9.0.12", + "next": "12.0.8", + "next-compose-plugins": "^2.2.1", + "next-transpile-modules": "^9.0.0", + "ramda": "^0.28.0", + "react": "17.0.2", + "react-dom": "17.0.2", + "zustand": "^3.6.9" + }, + "devDependencies": { + "@types/node": "17.0.10", + "@types/react": "17.0.38", + "autoprefixer": "^10.4.2", + "eslint": "^8.31.0", + "eslint-config-next": "^13.1.1", + "postcss": "^8.4.5", + "tailwindcss": "^3.0.15", + "typescript": "^4.9.4" + } +} diff --git a/wallet/cryptid-wallet-ui/postcss.config.js b/wallet/cryptid-wallet-ui/postcss.config.js new file mode 100644 index 00000000..33ad091d --- /dev/null +++ b/wallet/cryptid-wallet-ui/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/wallet/cryptid-wallet-ui/public/favicon.ico b/wallet/cryptid-wallet-ui/public/favicon.ico new file mode 100644 index 00000000..509de8ea Binary files /dev/null and b/wallet/cryptid-wallet-ui/public/favicon.ico differ diff --git a/wallet/cryptid-wallet-ui/public/logo.svg b/wallet/cryptid-wallet-ui/public/logo.svg new file mode 100644 index 00000000..1b8e7a6b --- /dev/null +++ b/wallet/cryptid-wallet-ui/public/logo.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/wallet/cryptid-wallet-ui/public/logo300.png b/wallet/cryptid-wallet-ui/public/logo300.png new file mode 100644 index 00000000..23673c2e Binary files /dev/null and b/wallet/cryptid-wallet-ui/public/logo300.png differ diff --git a/wallet/cryptid-wallet-ui/public/title.png b/wallet/cryptid-wallet-ui/public/title.png new file mode 100644 index 00000000..48bf60bd Binary files /dev/null and b/wallet/cryptid-wallet-ui/public/title.png differ diff --git a/wallet/cryptid-wallet-ui/public/vercel.svg b/wallet/cryptid-wallet-ui/public/vercel.svg new file mode 100644 index 00000000..fbf0e25a --- /dev/null +++ b/wallet/cryptid-wallet-ui/public/vercel.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/wallet/cryptid-wallet-ui/src/components/AppBar.tsx b/wallet/cryptid-wallet-ui/src/components/AppBar.tsx new file mode 100644 index 00000000..06b9e574 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/components/AppBar.tsx @@ -0,0 +1,101 @@ +import { FC } from "react"; +import Link from "next/link"; + +import { WalletMultiButton } from "@solana/wallet-adapter-react-ui"; +import { useAutoConnect } from "../contexts/AutoConnectProvider"; +import NetworkSwitcher from "./NetworkSwitcher"; +import title from "../../public/title.png"; +export const AppBar: FC = (props) => { + const { autoConnect, setAutoConnect } = useAutoConnect(); + + return ( +
+ {/* NavBar / Header */} +
+
+ + { + // + } +
+ + {/* Nav Links */} +
+
+ + Home + + + Actions + +
+
+ + {/* Wallet & Settings */} +
+ + +
+
+ + + + +
+
    +
  • +
    + + + +
    +
  • +
+
+
+
+ {props.children} +
+ ); +}; diff --git a/wallet/cryptid-wallet-ui/src/components/BasicStatus.tsx b/wallet/cryptid-wallet-ui/src/components/BasicStatus.tsx new file mode 100644 index 00000000..850769bc --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/components/BasicStatus.tsx @@ -0,0 +1,54 @@ +import { Cryptid } from "@identity.com/cryptid-core"; +import { + useConnection, + useWallet, + useAnchorWallet, +} from "@solana/wallet-adapter-react"; +import { + ConfirmOptions, + Keypair, + SystemProgram, + Transaction, + TransactionSignature, +} from "@solana/web3.js"; +import { FC, useCallback } from "react"; +import { Wallet } from "../types"; +import { notify } from "../utils/notifications"; +const { connection } = useConnection(); +const { publicKey, sendTransaction, signTransaction, signAllTransactions } = + useWallet(); +const reformWallet = { + publicKey: publicKey!, + signTransaction: signTransaction!, + signAllTransactions: signAllTransactions!, +}; +const BasicStatus: FC = () => { + //const { networkConfiguration, setNetworkConfiguration } = + //useNetworkConfiguration(); + + ///console.log(networkConfiguration); + const did = `did:sol:${publicKey.toBase58()}`; + const cryptid = Cryptid.buildFromDID(did, reformWallet, { + connection: connection, + }); + + return ( +
+ {/**/} +

DID: {cryptid.did}

+

Cryptid Account: {cryptid.details.didAccount}

+

Middlewares: {cryptid.details.middlewares}

+
+ ); +}; diff --git a/wallet/cryptid-wallet-ui/src/components/ContentContainer.tsx b/wallet/cryptid-wallet-ui/src/components/ContentContainer.tsx new file mode 100644 index 00000000..6b03b49a --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/components/ContentContainer.tsx @@ -0,0 +1,34 @@ +import { FC } from 'react'; +import Link from "next/link"; +export const ContentContainer: FC = props => { + + return ( +
+ {/*
*/} + +
+ {props.children} +
+ + {/* SideBar / Drawer */} +
+ + +
+
+ ); +}; diff --git a/wallet/cryptid-wallet-ui/src/components/CryptidAccountSwitcher.tsx b/wallet/cryptid-wallet-ui/src/components/CryptidAccountSwitcher.tsx new file mode 100644 index 00000000..3d64a554 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/components/CryptidAccountSwitcher.tsx @@ -0,0 +1,64 @@ +import { Cryptid } from "@identity.com/cryptid-core"; +import { + useConnection, + useWallet, + useAnchorWallet, +} from "@solana/wallet-adapter-react"; +import { + ConfirmOptions, + Keypair, + SystemProgram, + Transaction, + TransactionSignature, +} from "@solana/web3.js"; +import { FC, useCallback } from "react"; +import { Wallet } from "../types"; +import { notify } from "../utils/notifications"; +// import { useCryptidAccount } from "../contexts/CryptidAccountProvider"; +const { connection } = useConnection(); +const { publicKey, sendTransaction, signTransaction, signAllTransactions } = + useWallet(); +const reformWallet = { + publicKey: publicKey!, + signTransaction: signTransaction!, + signAllTransactions: signAllTransactions!, +}; + +const prepareAccounts = async (did: string) => { + return await Cryptid.findAll(did, { + connection: connection, + }); +}; + +const BasicStatus: FC = () => { + //const { networkConfiguration, setNetworkConfiguration } = + //useNetworkConfiguration(); + + ///console.log(networkConfiguration); + const did = `did:sol:${publicKey.toBase58()}`; + const cryptid = Cryptid.buildFromDID(did, reformWallet, { + connection: connection, + }); + + // Load with useEffect hook + // let accounts = await prepareAccounts(did); + + // const { cryptidAccount, setCryptidAccount } = useCryptidAccount(); + + return ( +
+ +
+ ); +}; diff --git a/wallet/cryptid-wallet-ui/src/components/Footer.tsx b/wallet/cryptid-wallet-ui/src/components/Footer.tsx new file mode 100644 index 00000000..af824f9c --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/components/Footer.tsx @@ -0,0 +1,30 @@ +import { FC } from 'react'; +import logo from '../../public/logo300.png' +export const Footer: FC = () => { + return ( +
+ +
+ ); +}; diff --git a/wallet/cryptid-wallet-ui/src/components/NetworkSwitcher.tsx b/wallet/cryptid-wallet-ui/src/components/NetworkSwitcher.tsx new file mode 100644 index 00000000..bd2a8295 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/components/NetworkSwitcher.tsx @@ -0,0 +1,28 @@ +import { FC } from 'react'; +import dynamic from 'next/dynamic'; +import { useNetworkConfiguration } from '../contexts/NetworkConfigurationProvider'; + +const NetworkSwitcher: FC = () => { + const { networkConfiguration, setNetworkConfiguration } = useNetworkConfiguration(); + + console.log(networkConfiguration); + + return ( + + ); +}; + +export default dynamic(() => Promise.resolve(NetworkSwitcher), { + ssr: false +}) \ No newline at end of file diff --git a/wallet/cryptid-wallet-ui/src/components/Notification.tsx b/wallet/cryptid-wallet-ui/src/components/Notification.tsx new file mode 100644 index 00000000..a1827cf9 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/components/Notification.tsx @@ -0,0 +1,135 @@ +import { useEffect, useState } from "react"; +import { + CheckCircleIcon, + InformationCircleIcon, + XCircleIcon, +} from "@heroicons/react/outline"; +import { XIcon } from "@heroicons/react/solid"; +import useNotificationStore from "../stores/useNotificationStore"; +import { useConnection } from "@solana/wallet-adapter-react"; +import { getExplorerUrl } from "../utils/explorer"; +import { useNetworkConfiguration } from "../contexts/NetworkConfigurationProvider"; + +const NotificationList = () => { + const { notifications, set: setNotificationStore } = useNotificationStore( + (s) => s + ); + + const reversedNotifications = [...notifications].reverse(); + + return ( +
+
+ {reversedNotifications.map((n, idx) => ( + { + setNotificationStore((state: any) => { + const reversedIndex = reversedNotifications.length - 1 - idx; + state.notifications = [ + ...notifications.slice(0, reversedIndex), + ...notifications.slice(reversedIndex + 1), + ]; + }); + }} + /> + ))} +
+
+ ); +}; + +const Notification = ({ type, message, description, txid, onHide }) => { + const { connection } = useConnection(); + const { networkConfiguration } = useNetworkConfiguration(); + + // TODO: we dont have access to the network or endpoint here.. + // getExplorerUrl(connection., txid, 'tx') + // Either a provider, context, and or wallet adapter related pro/contx need updated + + useEffect(() => { + const id = setTimeout(() => { + onHide(); + }, 8000); + + return () => { + clearInterval(id); + }; + }, [onHide]); + + return ( +
+
+
+
+ {type === "success" ? ( + + ) : null} + {type === "info" && ( + + )} + {type === "error" && } +
+
+
{message}
+ {description ? ( +

{description}

+ ) : null} + {txid ? ( + + ) : null} +
+
+ +
+
+
+
+ ); +}; + +export default NotificationList; diff --git a/wallet/cryptid-wallet-ui/src/components/ProposeTransaction.tsx b/wallet/cryptid-wallet-ui/src/components/ProposeTransaction.tsx new file mode 100644 index 00000000..82c2b741 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/components/ProposeTransaction.tsx @@ -0,0 +1,91 @@ +import { Cryptid } from "@identity.com/cryptid-core"; +import { + useConnection, + useWallet, + useAnchorWallet, +} from "@solana/wallet-adapter-react"; +import { + ConfirmOptions, + Keypair, + SystemProgram, + Transaction, + TransactionSignature, +} from "@solana/web3.js"; +import { FC, useCallback } from "react"; +import { Wallet } from "../types"; +import { notify } from "../utils/notifications"; + +export const ProposeTransaction: FC = () => { + const { connection } = useConnection(); + const { publicKey, sendTransaction, signTransaction, signAllTransactions } = + useWallet(); + const reformWallet = { + publicKey: publicKey!, + signTransaction: signTransaction!, + signAllTransactions: signAllTransactions!, + }; + + const onClick = useCallback(async () => { + if (!publicKey) { + notify({ type: "error", message: `Wallet not connected!` }); + console.log("error", `Send Transaction: Wallet not connected!`); + return; + } + + let signature: TransactionSignature = ""; + try { + const did = `did:sol:${publicKey.toBase58()}`; + const cryptid = Cryptid.buildFromDID(did, reformWallet, { + connection: connection, + }); + + console.log("Cryptid account:", cryptid.address()); + + const transferTransaction = new Transaction().add( + SystemProgram.transfer({ + fromPubkey: publicKey, + toPubkey: Keypair.generate().publicKey, + lamports: 1_000_000, + }) + ); + + const { proposeTransaction, proposeSigners, transactionAccount } = + await cryptid.propose(transferTransaction); + await cryptid.send(proposeTransaction, proposeSigners); + + const { executeTransactions, executeSigners } = await cryptid.execute( + transactionAccount + ); + executeTransactions.forEach( + async (tx) => await cryptid.send(tx, executeSigners) + ); + + notify({ + type: "success", + message: "Transaction successful!", + txid: signature, + }); + } catch (error: any) { + notify({ + type: "error", + message: `Transaction failed!`, + description: error?.message, + txid: signature, + }); + return; + } + }, [publicKey, notify, connection, sendTransaction]); + + return ( +
+ +
+ ); +}; diff --git a/wallet/cryptid-wallet-ui/src/components/RequestAirdrop.tsx b/wallet/cryptid-wallet-ui/src/components/RequestAirdrop.tsx new file mode 100644 index 00000000..179be794 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/components/RequestAirdrop.tsx @@ -0,0 +1,44 @@ +import { useConnection, useWallet } from '@solana/wallet-adapter-react'; +import { LAMPORTS_PER_SOL, TransactionSignature } from '@solana/web3.js'; +import { FC, useCallback } from 'react'; +import { notify } from "../utils/notifications"; +import useUserSOLBalanceStore from '../stores/useUserSOLBalanceStore'; + +export const RequestAirdrop: FC = () => { + const { connection } = useConnection(); + const { publicKey } = useWallet(); + const { getUserSOLBalance } = useUserSOLBalanceStore(); + + const onClick = useCallback(async () => { + if (!publicKey) { + console.log('error', 'Wallet not connected!'); + notify({ type: 'error', message: 'error', description: 'Wallet not connected!' }); + return; + } + + let signature: TransactionSignature = ''; + + try { + signature = await connection.requestAirdrop(publicKey, LAMPORTS_PER_SOL); + await connection.confirmTransaction(signature, 'confirmed'); + notify({ type: 'success', message: 'Airdrop successful!', txid: signature }); + + getUserSOLBalance(publicKey, connection); + } catch (error: any) { + notify({ type: 'error', message: `Airdrop failed!`, description: error?.message, txid: signature }); + console.log('error', `Airdrop failed! ${error?.message}`, signature); + } + }, [publicKey, connection, getUserSOLBalance]); + + return ( +
+ +
+ ); +}; + diff --git a/wallet/cryptid-wallet-ui/src/components/SendTransaction.tsx b/wallet/cryptid-wallet-ui/src/components/SendTransaction.tsx new file mode 100644 index 00000000..7ebdf436 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/components/SendTransaction.tsx @@ -0,0 +1,54 @@ +import { useConnection, useWallet } from '@solana/wallet-adapter-react'; +import { Keypair, SystemProgram, Transaction, TransactionSignature } from '@solana/web3.js'; +import { FC, useCallback } from 'react'; +import { notify } from "../utils/notifications"; + +export const SendTransaction: FC = () => { + const { connection } = useConnection(); + const { publicKey, sendTransaction } = useWallet(); + + const onClick = useCallback(async () => { + if (!publicKey) { + notify({ type: 'error', message: `Wallet not connected!` }); + console.log('error', `Send Transaction: Wallet not connected!`); + return; + } + + let signature: TransactionSignature = ''; + try { + const transaction = new Transaction().add( + SystemProgram.transfer({ + fromPubkey: publicKey, + toPubkey: Keypair.generate().publicKey, + lamports: 1_000_000, + }) + ); + + signature = await sendTransaction(transaction, connection); + + await connection.confirmTransaction(signature, 'confirmed'); + console.log(signature); + notify({ type: 'success', message: 'Transaction successful!', txid: signature }); + } catch (error: any) { + notify({ type: 'error', message: `Transaction failed!`, description: error?.message, txid: signature }); + console.log('error', `Transaction failed! ${error?.message}`, signature); + return; + } + }, [publicKey, notify, connection, sendTransaction]); + + return ( +
+ +
+ ); +}; diff --git a/wallet/cryptid-wallet-ui/src/components/SignMessage.tsx b/wallet/cryptid-wallet-ui/src/components/SignMessage.tsx new file mode 100644 index 00000000..1018edf5 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/components/SignMessage.tsx @@ -0,0 +1,45 @@ +// TODO: SignMessage +import { verify } from '@noble/ed25519'; +import { useWallet } from '@solana/wallet-adapter-react'; +import bs58 from 'bs58'; +import { FC, useCallback } from 'react'; +import { notify } from "../utils/notifications"; + +export const SignMessage: FC = () => { + const { publicKey, signMessage } = useWallet(); + + const onClick = useCallback(async () => { + try { + // `publicKey` will be null if the wallet isn't connected + if (!publicKey) throw new Error('Wallet not connected!'); + // `signMessage` will be undefined if the wallet doesn't support it + if (!signMessage) throw new Error('Wallet does not support message signing!'); + // Encode anything as bytes + const message = new TextEncoder().encode('Hello, world!'); + // Sign the bytes using the wallet + const signature = await signMessage(message); + // Verify that the bytes were signed using the private key that matches the known public key + if (!verify(signature, message, publicKey.toBytes())) throw new Error('Invalid signature!'); + notify({ type: 'success', message: 'Sign message successful!', txid: bs58.encode(signature) }); + } catch (error: any) { + notify({ type: 'error', message: `Sign Message failed!`, description: error?.message }); + console.log('error', `Sign Message failed! ${error?.message}`); + } + }, [publicKey, notify, signMessage]); + + return ( +
+ +
+ ); +}; diff --git a/wallet/cryptid-wallet-ui/src/components/cryptid.json b/wallet/cryptid-wallet-ui/src/components/cryptid.json new file mode 100644 index 00000000..8c9367a1 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/components/cryptid.json @@ -0,0 +1,568 @@ +{ + "version": "0.1.0", + "name": "cryptid", + "instructions": [ + { + "name": "create", + "accounts": [ + { + "name": "cryptidAccount", + "isMut": true, + "isSigner": false + }, + { + "name": "didProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The program for the DID" + ] + }, + { + "name": "did", + "isMut": false, + "isSigner": false, + "docs": [ + "The DID on the Cryptid instance" + ] + }, + { + "name": "authority", + "isMut": true, + "isSigner": true, + "docs": [ + "The signer of the transaction. Must be a DID authority." + ] + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "middleware", + "type": { + "option": "publicKey" + } + }, + { + "name": "index", + "type": "u32" + }, + { + "name": "didAccountBump", + "type": "u8" + } + ] + }, + { + "name": "directExecute", + "accounts": [ + { + "name": "cryptidAccount", + "isMut": true, + "isSigner": false, + "docs": [ + "The Cryptid instance to execute with" + ] + }, + { + "name": "did", + "isMut": false, + "isSigner": false, + "docs": [ + "The DID on the Cryptid instance" + ] + }, + { + "name": "didProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The program for the DID" + ] + }, + { + "name": "signer", + "isMut": false, + "isSigner": true, + "docs": [ + "The signer of the transaction" + ] + } + ], + "args": [ + { + "name": "controllerChain", + "type": "bytes" + }, + { + "name": "instructions", + "type": { + "vec": { + "defined": "AbbreviatedInstructionData" + } + } + }, + { + "name": "cryptidAccountBump", + "type": "u8" + }, + { + "name": "cryptidAccountIndex", + "type": "u32" + }, + { + "name": "didAccountBump", + "type": "u8" + }, + { + "name": "flags", + "type": "u8" + } + ] + }, + { + "name": "proposeTransaction", + "accounts": [ + { + "name": "cryptidAccount", + "isMut": false, + "isSigner": false, + "docs": [ + "The Cryptid instance that can execute the transaction." + ] + }, + { + "name": "did", + "isMut": false, + "isSigner": false, + "docs": [ + "The did account owner of the Cryptid instance" + ] + }, + { + "name": "didProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The program for the DID" + ] + }, + { + "name": "authority", + "isMut": true, + "isSigner": true + }, + { + "name": "transactionAccount", + "isMut": true, + "isSigner": true + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "controllerChain", + "type": "bytes" + }, + { + "name": "cryptidAccountBump", + "type": "u8" + }, + { + "name": "cryptidAccountIndex", + "type": "u32" + }, + { + "name": "didAccountBump", + "type": "u8" + }, + { + "name": "instructions", + "type": { + "vec": { + "defined": "AbbreviatedInstructionData" + } + } + }, + { + "name": "numAccounts", + "type": "u8" + } + ] + }, + { + "name": "executeTransaction", + "accounts": [ + { + "name": "cryptidAccount", + "isMut": true, + "isSigner": false, + "docs": [ + "The Cryptid instance to execute with" + ] + }, + { + "name": "did", + "isMut": false, + "isSigner": false, + "docs": [ + "The DID on the Cryptid instance" + ] + }, + { + "name": "didProgram", + "isMut": false, + "isSigner": false, + "docs": [ + "The program for the DID" + ] + }, + { + "name": "signer", + "isMut": false, + "isSigner": true, + "docs": [ + "The signer of the transaction" + ] + }, + { + "name": "destination", + "isMut": true, + "isSigner": false + }, + { + "name": "transactionAccount", + "isMut": true, + "isSigner": false, + "docs": [ + "The instruction to execute" + ] + } + ], + "args": [ + { + "name": "controllerChain", + "type": "bytes" + }, + { + "name": "cryptidAccountBump", + "type": "u8" + }, + { + "name": "cryptidAccountIndex", + "type": "u32" + }, + { + "name": "didAccountBump", + "type": "u8" + }, + { + "name": "flags", + "type": "u8" + } + ] + }, + { + "name": "approveExecution", + "accounts": [ + { + "name": "middlewareAccount", + "isMut": false, + "isSigner": true + }, + { + "name": "transactionAccount", + "isMut": true, + "isSigner": false + } + ], + "args": [] + } + ], + "accounts": [ + { + "name": "CryptidAccount", + "docs": [ + "The data for an on-chain Cryptid Account" + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "middleware", + "docs": [ + "The middleware, if any, used by this cryptid account" + ], + "type": { + "option": "publicKey" + } + }, + { + "name": "index", + "docs": [ + "The index of this cryptid account - allows multiple cryptid accounts per DID" + ], + "type": "u32" + } + ] + } + }, + { + "name": "TransactionAccount", + "docs": [ + "A proposed transaction stored on-chain, in preparation to be executed" + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "cryptidAccount", + "docs": [ + "The cryptid account for the transaction" + ], + "type": "publicKey" + }, + { + "name": "did", + "docs": [ + "The owner of the cryptid account (Typically a DID account)" + ], + "type": "publicKey" + }, + { + "name": "accounts", + "docs": [ + "The accounts `instructions` references (excluding the cryptid account" + ], + "type": { + "vec": "publicKey" + } + }, + { + "name": "instructions", + "docs": [ + "The instructions that will be executed" + ], + "type": { + "vec": { + "defined": "AbbreviatedInstructionData" + } + } + }, + { + "name": "approvedMiddleware", + "docs": [ + "The most recent middleware PDA that approved the transaction" + ], + "type": { + "option": "publicKey" + } + }, + { + "name": "slot", + "docs": [ + "The slot in which the transaction was proposed", + "This is used to prevent replay attacks TODO: Do we need it?" + ], + "type": "u8" + }, + { + "name": "state", + "docs": [ + "The transaction state, to prevent replay attacks", + "in case an executed transaction account is not immediately", + "garbage-collected by the runtime" + ], + "type": { + "defined": "TransactionState" + } + } + ] + } + } + ], + "types": [ + { + "name": "AbbreviatedAccountMeta", + "docs": [ + "An account for an instruction, similar to Solana's [`AccountMeta`](AccountMeta)" + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "key", + "docs": [ + "The key of the account" + ], + "type": "u8" + }, + { + "name": "meta", + "docs": [ + "Information about the account" + ], + "type": "u8" + } + ] + } + }, + { + "name": "AbbreviatedInstructionData", + "docs": [ + "The data about an instruction to be executed. Similar to Solana's [`Instruction`](SolanaInstruction).", + "Accounts are stored as indices in AbbreviatedAccountMeta to save space" + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "programId", + "docs": [ + "The program to execute" + ], + "type": "u8" + }, + { + "name": "accounts", + "docs": [ + "The accounts to send to the program" + ], + "type": { + "vec": { + "defined": "AbbreviatedAccountMeta" + } + } + }, + { + "name": "data", + "docs": [ + "The data for the instruction" + ], + "type": "bytes" + } + ] + } + }, + { + "name": "InstructionSize", + "docs": [ + "A helper struct for calculating [`InstructionData`] size" + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "accounts", + "docs": [ + "The number of accounts in the instruction" + ], + "type": "u8" + }, + { + "name": "dataLen", + "docs": [ + "The size of the instruction data" + ], + "type": "u16" + } + ] + } + }, + { + "name": "TransactionState", + "docs": [ + "A [`TransactionAccount`]'s state" + ], + "type": { + "kind": "enum", + "variants": [ + { + "name": "NotReady" + }, + { + "name": "Ready" + }, + { + "name": "Executed" + } + ] + } + } + ], + "errors": [ + { + "code": 6000, + "name": "NotEnoughSigners", + "msg": "Not enough signers" + }, + { + "code": 6001, + "name": "WrongDID", + "msg": "Wrong DID" + }, + { + "code": 6002, + "name": "WrongDIDProgram", + "msg": "Wrong DID program" + }, + { + "code": 6003, + "name": "WrongCryptidAccount", + "msg": "A wrong Cryptid account was passed" + }, + { + "code": 6004, + "name": "SubInstructionError", + "msg": "Error in sub-instruction" + }, + { + "code": 6005, + "name": "InvalidTransactionState", + "msg": "Invalid transaction state" + }, + { + "code": 6006, + "name": "AccountMismatch", + "msg": "An account in the transaction accounts did not match what was expected" + }, + { + "code": 6007, + "name": "KeyMustBeSigner", + "msg": "Signer is not authorised to sign for this Cryptid account" + }, + { + "code": 6008, + "name": "CreatingWithZeroIndex", + "msg": "Attempt to create a Cryptid account with index zero, reserved for the default account." + }, + { + "code": 6009, + "name": "IndexOutOfRange", + "msg": "Index out of range." + }, + { + "code": 6010, + "name": "NoAccountFromSeeds", + "msg": "No account from seeds." + }, + { + "code": 6011, + "name": "AccountNotFromSeeds", + "msg": "Account not from seeds." + }, + { + "code": 6012, + "name": "IncorrectMiddleware", + "msg": "The expected middleware did not approve the transaction." + } + ] +} \ No newline at end of file diff --git a/wallet/cryptid-wallet-ui/src/components/middlewareRegistry.ts b/wallet/cryptid-wallet-ui/src/components/middlewareRegistry.ts new file mode 100644 index 00000000..fce3668e --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/components/middlewareRegistry.ts @@ -0,0 +1,51 @@ +import { GenericMiddlewareParams, MiddlewareClient } from "../types/middleware"; +import { PublicKey } from "@solana/web3.js"; +import { CryptidAccountDetails } from "../lib/CryptidAccountDetails"; +import { Middleware } from "../lib/Middleware"; + +type MiddlewareContext = { + client: MiddlewareClient; + accounts: Middleware; +}; +export class MiddlewareRegistry { + // Map program IDs to middleware clients + // Use string instead of PublicKey due to inconsistencies when comparing keys + private middleware: Map>; + + constructor() { + this.middleware = new Map(); + } + + public register( + programId: PublicKey, + middleware: MiddlewareClient + ): void { + this.middleware.set(programId.toBase58(), middleware); + } + + public getMiddlewareContexts( + details: CryptidAccountDetails + ): MiddlewareContext[] { + return details.middlewares.map((middleware) => { + const middlewareClient = this.middleware.get( + middleware.programId.toBase58() + ); + if (!middlewareClient) { + throw new Error( + `No middleware registered for program ID ${middleware.programId.toBase58()}` + ); + } + return { client: middlewareClient, accounts: middleware }; + }); + } + + // Encourage a singleton pattern for the middleware registry + private static instance: MiddlewareRegistry; + static get(): MiddlewareRegistry { + if (!MiddlewareRegistry.instance) { + MiddlewareRegistry.instance = new MiddlewareRegistry(); + } + + return MiddlewareRegistry.instance; + } +} diff --git a/wallet/cryptid-wallet-ui/src/constants.ts b/wallet/cryptid-wallet-ui/src/constants.ts new file mode 100644 index 00000000..dda59469 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/constants.ts @@ -0,0 +1,6 @@ +import { PublicKey } from "@solana/web3.js"; + +//devnet +export const CRYPTID_PROGRAM = new PublicKey( + "cryptJTh61jY5kbUmBEXyc86tBUyueBDrLuNSZWmUcs" +); diff --git a/wallet/cryptid-wallet-ui/src/contexts/AccountIndexProvider.tsx b/wallet/cryptid-wallet-ui/src/contexts/AccountIndexProvider.tsx new file mode 100644 index 00000000..bb0172e4 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/contexts/AccountIndexProvider.tsx @@ -0,0 +1,29 @@ +import { useLocalStorage } from "@solana/wallet-adapter-react"; + +import { createContext, FC, ReactNode, useContext } from "react"; + +export interface AccountIndexState { + accountIndex: Number; + + setAccountIndex(accountIndex: number): void; +} + +export const AccountIndexContext = createContext( + {} as AccountIndexState +); + +export function useAccountIndex(): AccountIndexState { + return useContext(AccountIndexContext); +} + +export const AccountIndexProvider: FC<{ children: ReactNode }> = ({ + children, +}) => { + const [accountIndex, setAccountIndex] = useLocalStorage("index", 0); + + return ( + + {children} + + ); +}; diff --git a/wallet/cryptid-wallet-ui/src/contexts/AutoConnectProvider.tsx b/wallet/cryptid-wallet-ui/src/contexts/AutoConnectProvider.tsx new file mode 100644 index 00000000..7b129eaf --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/contexts/AutoConnectProvider.tsx @@ -0,0 +1,30 @@ +import { useLocalStorage } from "@solana/wallet-adapter-react"; +import { createContext, FC, ReactNode, useContext } from "react"; + +export interface AutoConnectContextState { + autoConnect: boolean; + setAutoConnect(autoConnect: boolean): void; +} + +export const AutoConnectContext = createContext( + {} as AutoConnectContextState +); + +export function useAutoConnect(): AutoConnectContextState { + return useContext(AutoConnectContext); +} + +export const AutoConnectProvider: FC<{ children: ReactNode }> = ({ + children, +}) => { + // TODO: fix auto connect to actual reconnect on refresh/other. + // TODO: make switch/slider settings + // const [autoConnect, setAutoConnect] = useLocalStorage('autoConnect', false); + const [autoConnect, setAutoConnect] = useLocalStorage("autoConnect", true); + + return ( + + {children} + + ); +}; diff --git a/wallet/cryptid-wallet-ui/src/contexts/ContextProvider.tsx b/wallet/cryptid-wallet-ui/src/contexts/ContextProvider.tsx new file mode 100644 index 00000000..1faa5833 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/contexts/ContextProvider.tsx @@ -0,0 +1,87 @@ +import { WalletAdapterNetwork, WalletError } from "@solana/wallet-adapter-base"; +import { + ConnectionProvider, + WalletProvider, +} from "@solana/wallet-adapter-react"; + +// import { +// PhantomWalletAdapter, +// SolflareWalletAdapter, +// SolletExtensionWalletAdapter, +// SolletWalletAdapter, +// TorusWalletAdapter, +// // LedgerWalletAdapter, +// // SlopeWalletAdapter, +// } from "@solana/wallet-adapter-wallets"; +import { Cluster, clusterApiUrl } from "@solana/web3.js"; +import { FC, ReactNode, useCallback, useMemo } from "react"; +import { AutoConnectProvider, useAutoConnect } from "./AutoConnectProvider"; +import { notify } from "../utils/notifications"; +import { + NetworkConfigurationProvider, + useNetworkConfiguration, +} from "./NetworkConfigurationProvider"; +import dynamic from "next/dynamic"; + +const ReactUIWalletModalProviderDynamic = dynamic( + async () => + (await import("@solana/wallet-adapter-react-ui")).WalletModalProvider, + { ssr: false } +); + +const WalletContextProvider: FC<{ children: ReactNode }> = ({ children }) => { + const { autoConnect } = useAutoConnect(); + const { networkConfiguration } = useNetworkConfiguration(); + const network = networkConfiguration as WalletAdapterNetwork; + const endpoint = useMemo(() => clusterApiUrl(network), [network]); + + console.log(network); + + const wallets = useMemo( + () => [ + // new PhantomWalletAdapter(), + // new SolflareWalletAdapter(), + // new SolletWalletAdapter({ network }), + // new SolletExtensionWalletAdapter({ network }), + // new TorusWalletAdapter(), + // new LedgerWalletAdapter(), + // new SlopeWalletAdapter(), + ], + [network] + ); + + const onError = useCallback((error: WalletError) => { + notify({ + type: "error", + message: error.message ? `${error.name}: ${error.message}` : error.name, + }); + console.error(error); + }, []); + + return ( + // TODO: updates needed for updating and referencing endpoint: wallet adapter rework + + + + {children} + + + + ); +}; + +export const ContextProvider: FC<{ children: ReactNode }> = ({ children }) => { + return ( + <> + + + {children} + + + + ); +}; diff --git a/wallet/cryptid-wallet-ui/src/contexts/NetworkConfigurationProvider.tsx b/wallet/cryptid-wallet-ui/src/contexts/NetworkConfigurationProvider.tsx new file mode 100644 index 00000000..a05870b4 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/contexts/NetworkConfigurationProvider.tsx @@ -0,0 +1,31 @@ +import { useLocalStorage } from "@solana/wallet-adapter-react"; +import { createContext, FC, ReactNode, useContext } from "react"; + +export interface NetworkConfigurationState { + networkConfiguration: string; + setNetworkConfiguration(networkConfiguration: string): void; +} + +export const NetworkConfigurationContext = + createContext({} as NetworkConfigurationState); + +export function useNetworkConfiguration(): NetworkConfigurationState { + return useContext(NetworkConfigurationContext); +} + +export const NetworkConfigurationProvider: FC<{ children: ReactNode }> = ({ + children, +}) => { + const [networkConfiguration, setNetworkConfiguration] = useLocalStorage( + "network", + "devnet" + ); + + return ( + + {children} + + ); +}; diff --git a/wallet/cryptid-wallet-ui/src/hooks/useDID.tsx b/wallet/cryptid-wallet-ui/src/hooks/useDID.tsx new file mode 100644 index 00000000..7b290b42 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/hooks/useDID.tsx @@ -0,0 +1,167 @@ +import {createContext, FC, ReactNode, useCallback, useContext, useEffect, useState} from "react"; +import {useConnection, useWallet} from "@solana/wallet-adapter-react"; +import { + addServiceToDID, addVerificationMethodToDID, isMigratable, isInitialized, + keyToDid, migrate, + removeServiceFromDID, + removeVerificationMethodFromDID, + resolveDID, getDIDAddress, getVerificationMethodFlags, registerDID, setOwned +} from "../lib/didUtils"; +import {WalletAdapterNetwork} from "@solana/wallet-adapter-base"; +import {DIDDocument} from "did-resolver"; +import {PublicKey} from "@solana/web3.js"; +import { + AddVerificationMethodParams, DidSolIdentifier, ExtendedCluster, + Service, + VerificationMethodFlags, VerificationMethodType +} from "@identity.com/sol-did-client"; +import { Web3Provider } from '@ethersproject/providers'; +import { useWeb3React } from "@web3-react/core"; +import {useRegistry} from "./useRegistry"; +import { fromSolanaCluster } from "../lib/solanaUtils"; + +type DIDContextProps = { + did: string; + document: DIDDocument; + linkedDIDs: string[]; + cluster: ExtendedCluster; + registerDIDOnKey: () => Promise; + addKey: (key: AddVerificationMethodParams) => Promise; + removeKey: (fragment: string) => Promise; + getKeyFlags: (fragment: string) => Promise; + setKeyOwned: (fragment: string, type: VerificationMethodType) => Promise; + addService: (service: Service) => Promise; + removeService: (fragment: string) => Promise; + migrateDID: () => Promise; + isDIDInitialized: () => Promise; + isLegacyDID: boolean | undefined; // true if the DID refers to a "legacy" (v1 did:sol method) DID and needs migrating + accountAddress: PublicKey | undefined; // the address of the DID account if it is not a generative DID +} + +const defaultDocument = { + "@context": "https://w3id.org/did/v1", + id: "", + verificationMethod: [], + service: [], +} + +const defaultDIDContextProps: DIDContextProps = { + did: "", + document: defaultDocument, + linkedDIDs: [], + cluster: "mainnet-beta", + registerDIDOnKey: async () => {}, + addKey: async (key: AddVerificationMethodParams) => {}, + removeKey: async (fragment: string) => {}, + getKeyFlags: async (fragment: string) => undefined, + setKeyOwned: async (fragment: string, type: VerificationMethodType) => {}, + addService: async (service: Service) => {}, + removeService: async (fragment: string) => {}, + migrateDID: async () => {}, + isDIDInitialized: async () => false, + isLegacyDID: undefined, + accountAddress: undefined, +} +export const DIDContext = createContext(defaultDIDContextProps) + +export const DIDProvider: FC<{ children: ReactNode, network: WalletAdapterNetwork, setNetwork: (network : WalletAdapterNetwork) => void +}> = ({ children, network, setNetwork }) => { + const wallet = useWallet(); + const { library } = useWeb3React(); + const {connection} = useConnection(); + const [document, setDocument] = useState(); + const [did, setDID] = useState(""); + const [cluster, setCluster] = useState("mainnet-beta"); + const { registeredSolanaDIDs, registeredEthereumDIDs, reload} = useRegistry(); + const [linkedDIDs, setLinkedDIDs] = useState([]); + const [isLegacyDID, setIsLegacyDID] = useState(); + const [accountAddress, setAccountAddress] = useState(); + + + useEffect(() => { + setCluster(network) // TODO: Maybe add some translation layer here. + }, [network]); + + useEffect(() => { + const mergedDIDs = [...registeredSolanaDIDs, ...registeredEthereumDIDs]; + // TODO this is a hack - clean up + const didsOnNetwork = mergedDIDs.map(did => did.replace("did:sol:", `did:sol:${network}:`)); + setLinkedDIDs(didsOnNetwork); + }, [registeredSolanaDIDs, registeredEthereumDIDs]); + + const loadDID = useCallback(() => { + console.log("Loading DID", did); + console.log("network is", network); + if (did) { + resolveDID(did, connection).then(setDocument).catch(console.error); + isMigratable(did, connection).then(setIsLegacyDID) + getDIDAddress(did, connection).then(setAccountAddress); + } + }, [did, wallet, network, connection]) + + useEffect(() => { + const matches = window.location.href.match(/\/(did:sol:.*)#?$/); + if (matches) { + console.log(`Found DID in URL: ${matches.map(m => m)}`); + const identifier = DidSolIdentifier.parse(matches[1]); + console.log(`Setting to ${identifier.toString(false)}`); + const network = fromSolanaCluster(identifier.clusterType); + console.log(`Setting network to ${network}`); + if (network) { + console.log(`Calling Setting network to ${network}`); + setNetwork(network); + } + setDID(identifier.toString(false)); + } else if (wallet && wallet.publicKey) { + const did = keyToDid(wallet.publicKey, network); + setDID(did); + } + }, [wallet, network]); + + useEffect(() => { + const location = window.location.href.match(/\/(did:sol:.*)#?$/); + if (location) setDID(location[1]); + }, []) + + useEffect(loadDID, [did, loadDID]); + + const getKeyFlags = (fragment: string) => getVerificationMethodFlags(did, wallet, fragment, connection) + + const addService = (service: Service) => addServiceToDID(did, wallet, service, connection).then(() => loadDID()) + + const removeService = (fragment: string) => removeServiceFromDID(did, wallet, fragment, connection).then(() => loadDID()) + + const addKey = (key: AddVerificationMethodParams) => addVerificationMethodToDID(did, wallet, key, connection).then(() => loadDID()) + + const removeKey = (fragment: string) => removeVerificationMethodFromDID(did, wallet, fragment, connection).then(() => loadDID()) + + const migrateDID = () => migrate(did, wallet, connection) + const isDIDInitialized = () => isInitialized(did, connection); + + const registerDIDOnKey = () => registerDID(wallet, connection, did).then(reload) + + const setKeyOwned = (fragment: string, type: VerificationMethodType) => setOwned(fragment, type, did, wallet, + connection, library?.getSigner()).then(loadDID) + + return ( + {children} + ) +} + +export const useDID = (): DIDContextProps => useContext(DIDContext); diff --git a/wallet/cryptid-wallet-ui/src/hooks/useMetaWallet.tsx b/wallet/cryptid-wallet-ui/src/hooks/useMetaWallet.tsx new file mode 100644 index 00000000..628ae9e7 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/hooks/useMetaWallet.tsx @@ -0,0 +1,19 @@ +import { useWeb3React } from "@web3-react/core"; +import { useWallet } from "@solana/wallet-adapter-react"; +import { useCallback } from "react"; + +export type WalletContext = { + isConnected: (address: string) => boolean; +}; +export const useMetaWallet = (): WalletContext => { + const {account} = useWeb3React(); + const wallet = useWallet(); + + const isConnected = useCallback((address: string) => { + return account?.toLowerCase() === address.toLowerCase() || wallet.publicKey?.toBase58() === address; + }, [account, wallet]) + + return { + isConnected, + } +} diff --git a/wallet/cryptid-wallet-ui/src/hooks/useProfile.tsx b/wallet/cryptid-wallet-ui/src/hooks/useProfile.tsx new file mode 100644 index 00000000..4a8ac7fd --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/hooks/useProfile.tsx @@ -0,0 +1,18 @@ +import {CivicProfile, Profile} from "@civic/profile"; +import {useWeb3React} from "@web3-react/core"; +import {useEffect, useState} from "react"; +import {useConnection} from "@solana/wallet-adapter-react"; + +export const useProfile = ():Profile|undefined => { + const { account } = useWeb3React(); + const { connection } = useConnection(); + const [ profile, setProfile ] = useState(); + + useEffect(() => { + if (account) { + CivicProfile.get(account, { solana: { connection }}).then(setProfile); + } + }, [account, connection]); + + return profile; +} \ No newline at end of file diff --git a/wallet/cryptid-wallet-ui/src/hooks/useQueryContext.tsx b/wallet/cryptid-wallet-ui/src/hooks/useQueryContext.tsx new file mode 100644 index 00000000..9eb1422d --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/hooks/useQueryContext.tsx @@ -0,0 +1,21 @@ +import { useRouter } from 'next/router' +import { EndpointTypes } from '../models/types' + +export default function useQueryContext() { + const router = useRouter() + const { cluster } = router.query + + const endpoint = cluster ? (cluster as EndpointTypes) : 'mainnet' + const hasClusterOption = endpoint !== 'mainnet' + const fmtUrlWithCluster = (url) => { + if (hasClusterOption) { + const mark = url.includes('?') ? '&' : '?' + return decodeURIComponent(`${url}${mark}cluster=${endpoint}`) + } + return url + } + + return { + fmtUrlWithCluster, + } +} diff --git a/wallet/cryptid-wallet-ui/src/hooks/useRegistry.ts b/wallet/cryptid-wallet-ui/src/hooks/useRegistry.ts new file mode 100644 index 00000000..38fc266f --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/hooks/useRegistry.ts @@ -0,0 +1,99 @@ +import { useWeb3React } from "@web3-react/core"; +import { useConnection, useWallet } from "@solana/wallet-adapter-react"; +import { useCallback, useEffect, useState } from "react"; +import { + EthRegistry, + ReadOnlyRegistry, + Registry, + Wallet, +} from "@civic/did-registry"; +import { PublicKey } from "@solana/web3.js"; +import { useDID } from "./useDID"; + +export type RegistryContext = { + registeredSolanaDIDs: string[]; + registeredEthereumDIDs: string[]; + solanaKeyRegistry?: Registry; + ethereumKeyRegistry?: EthRegistry; + getRegisteredDIDsForEthAddress: (ethAddress: string) => Promise; + getRegisteredDIDsForAccount: (account: PublicKey) => Promise; + reload: () => void; +}; +export const useRegistry = (): RegistryContext => { + const { cluster } = useDID(); + const { account } = useWeb3React(); + const wallet = useWallet(); + const { connection } = useConnection(); + const [registeredSolanaDIDs, setRegisteredSolanaDIDs] = useState( + [] + ); + const [registeredEthereumDIDs, setRegisteredEthereumDIDs] = useState< + string[] + >([]); + const [solanaKeyRegistry, setSolanaKeyRegistry] = useState(); + const [ethereumKeyRegistry, setEthereumKeyRegistry] = useState(); + + const getRegisteredDIDsForEthAddress = useCallback( + (ethAddress: string): Promise => + ReadOnlyRegistry.forEthAddress( + ethAddress, + connection, + cluster + ).listDIDs(), + [connection] + ); + + const getRegisteredDIDsForAccount = useCallback( + (account: PublicKey): Promise => + ReadOnlyRegistry.for(account, connection, cluster).listDIDs(), + [connection] + ); + + const loadDIDs = useCallback(() => { + console.log("Loading DIDs"); + if (wallet.publicKey) { + console.log("Found wallet public key " + wallet.publicKey.toBase58()); + getRegisteredDIDsForAccount(wallet.publicKey).then( + setRegisteredSolanaDIDs + ); + setSolanaKeyRegistry(Registry.for(wallet as Wallet, connection, cluster)); + } else { + setRegisteredSolanaDIDs([]); + } + + if (account) { + console.log("Found Ethereum account " + account); + getRegisteredDIDsForEthAddress(account).then(setRegisteredEthereumDIDs); + setEthereumKeyRegistry( + EthRegistry.forEthAddress( + account, + wallet as Wallet, + connection, + cluster + ) + ); + } else { + setRegisteredEthereumDIDs([]); + } + }, [ + account, + wallet, + connection, + setSolanaKeyRegistry, + setEthereumKeyRegistry, + setRegisteredSolanaDIDs, + setRegisteredEthereumDIDs, + ]); + + useEffect(loadDIDs, [account, wallet, connection]); + + return { + registeredSolanaDIDs, + registeredEthereumDIDs, + solanaKeyRegistry, + ethereumKeyRegistry, + getRegisteredDIDsForEthAddress, + getRegisteredDIDsForAccount, + reload: loadDIDs, + }; +}; diff --git a/wallet/cryptid-wallet-ui/src/lib/CryptidAccountDetails.ts b/wallet/cryptid-wallet-ui/src/lib/CryptidAccountDetails.ts new file mode 100644 index 00000000..f4689e20 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/lib/CryptidAccountDetails.ts @@ -0,0 +1,73 @@ +import { PublicKey, AccountInfo } from "@solana/web3.js"; +import { didToPDA } from "./did"; +import { getCryptidAccountAddress } from "./cryptid"; +import { Middleware } from "./Middleware"; +import { CryptidAccount } from "../types"; + +export class CryptidAccountDetails { + constructor( + // The address of the cryptid account PDA on chain + readonly address: PublicKey, + // The bump seed used to derive the cryptid account PDA + readonly bump: number, + // The index of the DID's cryptid account + readonly index: number, + // The DID of the cryptid account + readonly ownerDID: string, + // The on-chain PDA of the DID (may be generative) + readonly didAccount: PublicKey, + // The correct bump for the given didAccount + readonly didAccountBump: number, + // Middlewares attached to the account + readonly middlewares: Middleware[] + ) {} + + static defaultAccount(did: string): CryptidAccountDetails { + return CryptidAccountDetails.from(did); + } + + static from( + did: string, + index = 0, + middlewares: Middleware[] = [] + ): CryptidAccountDetails { + const didAccount = didToPDA(did); + const [address, bump] = getCryptidAccountAddress(didAccount[0], index); + return new CryptidAccountDetails( + address, + bump, + index, + did, + didAccount[0], + didAccount[1], + middlewares + ); + } + + static fromAccounts( + did: string, + address: PublicKey, + cryptidAccount: CryptidAccount, + middlewareAccounts: [PublicKey, AccountInfo][] + ): CryptidAccountDetails { + const didAccount = didToPDA(did); + // TODO optimise? this is literally just to recalculate the bump + // NOTE: the bump is needed when making transactions. + // It is not stored on the cryptid account, as that does not work with the generative case. + const [, bump] = getCryptidAccountAddress( + didAccount[0], + cryptidAccount.index + ); + return new CryptidAccountDetails( + address, + bump, + cryptidAccount.index, + did, + didAccount[0], + didAccount[1], + middlewareAccounts.map( + ([key, accountInfo]) => new Middleware(accountInfo.owner, key) + ) + ); + } +} diff --git a/wallet/cryptid-wallet-ui/src/lib/CryptidTransaction.ts b/wallet/cryptid-wallet-ui/src/lib/CryptidTransaction.ts new file mode 100644 index 00000000..cf543633 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/lib/CryptidTransaction.ts @@ -0,0 +1,194 @@ +import { + InstructionData, + TransactionAccount, + TransactionAccountMeta, +} from "../types"; +import { + AccountMeta, + Keypair, + PublicKey, + TransactionInstruction, +} from "@solana/web3.js"; +import { + extractAccountMetas, + toInstructionData, + transactionAccountMetasToAccountMetas, + uniqueKeys, +} from "./cryptid"; +import { Program } from "@project-serum/anchor"; +import { Cryptid } from "@identity.com/cryptid-idl"; +import { DID_SOL_PROGRAM } from "@identity.com/sol-did-client"; +import { CryptidAccountDetails } from "./CryptidAccountDetails"; + +// Used to replace the current signer +// so that the InstructionData are required to reference the signer in a separate position in the array, +// and not index 3. This is because the signer is not known at creation time. +// TODO - PublicKey.default uses the default key 11111111111111111111111111111111 which is the same as the system program. +// This is unsuitable as it may be referenced in the instructions somewhere. So we generate a "single use" key here. +// It feels hacky - is there a better alternative? +const NULL_KEY = Keypair.generate().publicKey; // PublicKey.default; + +export class CryptidTransaction { + constructor( + readonly cryptidAccount: CryptidAccountDetails, + readonly authority: PublicKey, + readonly instructions: InstructionData[], + readonly accountMetas: AccountMeta[] + ) {} + + static fromSolanaInstructions( + cryptidAccount: CryptidAccountDetails, + authority: PublicKey, + solanaInstructions: TransactionInstruction[] + ): CryptidTransaction { + const accountMetas = extractAccountMetas( + solanaInstructions, + cryptidAccount + ); + // The accounts available to the instructions are in the following order: + // Execute Transaction Accounts: + // 0 - cryptid account + // 1 - did* + // 2 - did program* + // 3 - signer* + // ... remaining accounts + // + // * These accounts are omitted from the Propose Transaction Accounts but included in the execute instructions + const namedAccounts = [ + cryptidAccount.address, + cryptidAccount.didAccount, + DID_SOL_PROGRAM, + // This key is added in place of the signer (aka authority), + // so that the InstructionData are required to reference the signer in a separate position in the array, + // and not index 3. This is because the signer at execute time is not known at creation time, + // and may be different to the authority here. + // TODO: This is not the case for DirectExecute, so we could optimise here. + NULL_KEY, + ]; + const availableInstructionAccounts = uniqueKeys([ + ...namedAccounts, + ...accountMetas.map((a) => a.pubkey), + ]); + const instructions = solanaInstructions.map( + toInstructionData(availableInstructionAccounts) + ); + const filteredAccountMetas = accountMetas.filter( + (a) => + !namedAccounts + .map((account) => account.toBase58()) + .includes(a.pubkey.toBase58()) + ); + + return new CryptidTransaction( + cryptidAccount, + authority, + instructions, + filteredAccountMetas + ); + } + + static fromTransactionAccount( + cryptidAccount: CryptidAccountDetails, + authority: PublicKey, + transactionAccount: TransactionAccount + ): CryptidTransaction { + // TODO remove typecasting in this function + const instructions = transactionAccount.instructions as InstructionData[]; + const namedAccounts = [ + cryptidAccount.address, + cryptidAccount.didAccount, + DID_SOL_PROGRAM, + authority, + ]; + const remainingAccounts = transactionAccount.accounts; + const allAccounts = [...namedAccounts, ...remainingAccounts]; + const accountMetas = transactionAccountMetasToAccountMetas( + instructions.flatMap((i) => [ + ...(i.accounts as TransactionAccountMeta[]), + { key: i.programId, meta: 0 }, + ]), + allAccounts, + cryptidAccount + ); + const filteredAccountMetas = accountMetas.filter( + (a) => + !namedAccounts + .map((account) => account.toBase58()) + .includes(a.pubkey.toBase58()) + ); + + return new CryptidTransaction( + cryptidAccount, + authority, + instructions, + filteredAccountMetas + ); + } + + // TODO move transactionAccountAddress into constructor? + // The anchor MethodsBuilder type is not exposed + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + propose(program: Program, transactionAccountAddress: PublicKey) { + return program.methods + .proposeTransaction( + Buffer.from([]), // TODO, support controller chain, + this.cryptidAccount.bump, + this.cryptidAccount.index, + this.cryptidAccount.didAccountBump, + this.instructions, + this.accountMetas.length + ) + .accounts({ + cryptidAccount: this.cryptidAccount.address, + didProgram: DID_SOL_PROGRAM, + did: this.cryptidAccount.didAccount, + authority: this.authority, + transactionAccount: transactionAccountAddress, + }) + .remainingAccounts(this.accountMetas); + } + + // TODO move transactionAccountAddress into constructor? + // The anchor MethodsBuilder type is not exposed + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + execute(program: Program, transactionAccountAddress: PublicKey) { + return program.methods + .executeTransaction( + Buffer.from([]), // TODO, support controller chain, + this.cryptidAccount.bump, + this.cryptidAccount.index, + this.cryptidAccount.didAccountBump, + 0 + ) + .accounts({ + cryptidAccount: this.cryptidAccount.address, + didProgram: DID_SOL_PROGRAM, + did: this.cryptidAccount.didAccount, + signer: this.authority, + destination: this.authority, + transactionAccount: transactionAccountAddress, + }) + .remainingAccounts(this.accountMetas); + } + + // The anchor MethodsBuilder type is not exposed + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + directExecute(program: Program) { + return program.methods + .directExecute( + Buffer.from([]), // TODO, support controller chain, + this.instructions, + this.cryptidAccount.bump, + this.cryptidAccount.index, + this.cryptidAccount.didAccountBump, + 0 + ) + .accounts({ + cryptidAccount: this.cryptidAccount.address, + did: this.cryptidAccount.didAccount, + didProgram: DID_SOL_PROGRAM, + signer: this.authority, + }) + .remainingAccounts(this.accountMetas); + } +} diff --git a/wallet/cryptid-wallet-ui/src/lib/Middleware.ts b/wallet/cryptid-wallet-ui/src/lib/Middleware.ts new file mode 100644 index 00000000..941b341a --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/lib/Middleware.ts @@ -0,0 +1,5 @@ +import { PublicKey } from "@solana/web3.js"; + +export class Middleware { + constructor(readonly programId: PublicKey, readonly address: PublicKey) {} +} diff --git a/wallet/cryptid-wallet-ui/src/lib/cryptid.ts b/wallet/cryptid-wallet-ui/src/lib/cryptid.ts new file mode 100644 index 00000000..06244f2b --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/lib/cryptid.ts @@ -0,0 +1,187 @@ +import { + AccountMeta, + PublicKey, + TransactionInstruction, +} from "@solana/web3.js"; +import * as anchor from "@project-serum/anchor"; +import { DID_SOL_PROGRAM } from "@identity.com/sol-did-client"; +import { CRYPTID_PROGRAM } from "../constants"; +import { InstructionData, TransactionAccountMeta } from "../types"; +import BN from "bn.js"; +import { Program } from "@project-serum/anchor"; +import { Cryptid } from "@identity.com/cryptid-idl"; +import { uniqBy } from "ramda"; +import { didToPDA } from "./did"; +import { CryptidAccountDetails } from "./CryptidAccountDetails"; + +// Creates a reference to an account, that is passed as part of cryptid instruction data for each account. +const toTransactionAccountMeta = ( + publicKeyIndex: number, + isWritable = false, + isSigner = false +): TransactionAccountMeta => ({ + key: publicKeyIndex, + meta: (+isWritable << 1) | +isSigner, +}); + +// Creates a reference to an account, that is passed as part of cryptid instruction data for each account. +const fromTransactionAccountMeta = ( + tam: TransactionAccountMeta, + account: PublicKey +): AccountMeta => ({ + pubkey: account, + isWritable: !!(tam.meta & 2), + isSigner: !!(tam.meta & 1), +}); + +// Given an instruction, and an array of accounts, convert it to a sparse `InstructionData` object, for passing +// into a cryptid meta-instruction +export const toInstructionData = + (accounts: PublicKey[]) => + (instruction: TransactionInstruction): InstructionData => { + const { programId, data } = instruction; + const programIndex = accounts.findIndex((a) => a.equals(programId)); + const accountMetas = instruction.keys.map( + ({ pubkey, isSigner, isWritable }) => + toTransactionAccountMeta( + accounts.findIndex((a) => a.equals(pubkey)), + isWritable, + isSigner + ) + ); + return { + programId: programIndex, + data, + accounts: accountMetas, + }; + }; + +// Given a set of accountMetas from a set of instructions, combine them, setting the signer and writable flags to true, +// if any of the instructions require them. +// Note, the cryptid account is not a signer of any instructions, since the program "signs". +const accountMetaReducer = + (cryptidAccount: CryptidAccountDetails) => + ( + accumulator: { [k: string]: AccountMeta }, + { pubkey, isSigner, isWritable }: AccountMeta + ): { [k: string]: AccountMeta } => { + const isCryptidAccountMeta = (accountMeta: AccountMeta) => + accountMeta.pubkey.equals(cryptidAccount.address); + + const mergeAccountMetas = ( + existingAccountMeta: AccountMeta | undefined, + newAccountMeta: AccountMeta + ): AccountMeta => { + const mergedAccountMeta = existingAccountMeta + ? { + pubkey: newAccountMeta.pubkey, + isSigner: existingAccountMeta.isSigner || newAccountMeta.isSigner, + isWritable: + existingAccountMeta.isWritable || newAccountMeta.isWritable, + } + : newAccountMeta; + + // if the key is the cryptid account, do not set it as a signer, as it will be "signed" by the + // cryptid program + if (isCryptidAccountMeta(mergedAccountMeta)) { + mergedAccountMeta.isSigner = false; + } + return mergedAccountMeta; + }; + + return { + ...accumulator, + [pubkey.toBase58()]: mergeAccountMetas(accumulator[pubkey.toBase58()], { + pubkey, + isSigner, + isWritable, + }), + }; + }; + +// Extract all account public keys from the instructions, remove duplicates, and return them as an array +export const extractAccountMetas = ( + instructions: TransactionInstruction[], + cryptidAccount: CryptidAccountDetails +): AccountMeta[] => + Object.values( + instructions + .flatMap((instruction) => [ + ...instruction.keys, + toAccountMeta(instruction.programId, false, false), + ]) + .reduce(accountMetaReducer(cryptidAccount), {}) + ); + +export const toAccountMeta = ( + publicKey: PublicKey, + isWritable = false, + isSigner = false +): AccountMeta => ({ + pubkey: publicKey, + isWritable, + isSigner, +}); + +// Convert TransactionAccountMetas, with an array of accounts, into Solana Account Metas +// TODO encapsulate in CryptidTransaction perhaps? +export const transactionAccountMetasToAccountMetas = ( + transactionAccountMetas: TransactionAccountMeta[], + accounts: PublicKey[], + cryptidAccount: CryptidAccountDetails +): AccountMeta[] => + Object.values( + transactionAccountMetas + .map((tam) => fromTransactionAccountMeta(tam, accounts[tam.key])) + .reduce(accountMetaReducer(cryptidAccount), {}) + ); + +export const uniqueKeys = uniqBy((k) => k.toBase58()); + +export const getCryptidAccountAddress = ( + didAccount: PublicKey, + index = 0 +): [PublicKey, number] => + PublicKey.findProgramAddressSync( + [ + anchor.utils.bytes.utf8.encode("cryptid_account"), + DID_SOL_PROGRAM.toBuffer(), + didAccount.toBuffer(), + new BN(index).toArrayLike(Buffer, "le", 4), + ], + CRYPTID_PROGRAM + ); + +export const getCryptidAccountAddressFromDID = ( + did: string, + index = 0 +): [PublicKey, number] => getCryptidAccountAddress(didToPDA(did)[0], index); + +export const createCryptidAccount = async ( + program: Program, + didAccount: PublicKey, + didAccountBump: number, + middlewareAccount?: PublicKey, + index = 0 +): Promise<[PublicKey, number]> => { + const [cryptidAccount, cryptidBump] = getCryptidAccountAddress( + didAccount, + index + ); + + await program.methods + .create( + middlewareAccount || null, // anchor requires null instead of undefined + index, + didAccountBump + ) + .accounts({ + cryptidAccount, + didProgram: DID_SOL_PROGRAM, + did: didAccount, + authority: program.provider.publicKey, + }) + .rpc({ skipPreflight: true }); + + return [cryptidAccount, cryptidBump]; +}; diff --git a/wallet/cryptid-wallet-ui/src/lib/crypto.ts b/wallet/cryptid-wallet-ui/src/lib/crypto.ts new file mode 100644 index 00000000..bccadb7b --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/lib/crypto.ts @@ -0,0 +1,41 @@ +import { SignAllCallback, SignCallback, Wallet } from "../types/crypto"; +import { Keypair, Transaction } from "@solana/web3.js"; + +const defaultSignAllCallback = + (keypair: Keypair): SignAllCallback => + (transactions: Transaction[]) => + Promise.all( + transactions.map((transaction) => { + transaction.partialSign(keypair); + return transaction; + }) + ); + +const defaultSignCallback = + (keypair: Keypair): SignCallback => + (transaction) => + defaultSignAllCallback(keypair)([transaction]).then( + (transactions) => transactions[0] + ); + +export const isKeypair = ( + keypairOrWallet: Keypair | Wallet +): keypairOrWallet is Keypair => + keypairOrWallet instanceof Keypair || + // IDCOM-1340 this clause is added to handle type erasure on compiled TS + keypairOrWallet.constructor.name === "Keypair"; + +export const toWallet = (keypair: Keypair): Wallet => ({ + publicKey: keypair.publicKey, + signTransaction: defaultSignCallback(keypair), + signAllTransactions: defaultSignAllCallback(keypair), +}); + +export const noSignWallet = (): Wallet => ({ + publicKey: Keypair.generate().publicKey, + signTransaction: () => Promise.reject(new Error("Not defined")), + signAllTransactions: () => Promise.reject(new Error("Not defined")), +}); + +export const normalizeSigner = (keypairOrWallet: Keypair | Wallet): Wallet => + isKeypair(keypairOrWallet) ? toWallet(keypairOrWallet) : keypairOrWallet; diff --git a/wallet/cryptid-wallet-ui/src/lib/did.ts b/wallet/cryptid-wallet-ui/src/lib/did.ts new file mode 100644 index 00000000..f24a0c0f --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/lib/did.ts @@ -0,0 +1,21 @@ +// Functions exposing the DID API +import { DidSolIdentifier, DidSolService } from "@identity.com/sol-did-client"; +import { Wallet } from "../types/crypto"; +import { Connection, PublicKey } from "@solana/web3.js"; + +export const didService = ( + did: DidSolIdentifier, + connection: Connection, + wallet: Wallet +) => { + return DidSolService.build(did, { + connection, + wallet, + }); +}; + +export const didToPublicKey = (did: string): PublicKey => + DidSolIdentifier.parse(did).authority; + +export const didToPDA = (did: string): [PublicKey, number] => + DidSolIdentifier.parse(did).dataAccount(); diff --git a/wallet/cryptid-wallet-ui/src/models/types.ts b/wallet/cryptid-wallet-ui/src/models/types.ts new file mode 100644 index 00000000..0d56f253 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/models/types.ts @@ -0,0 +1 @@ +export type EndpointTypes = "mainnet" | "devnet" | "localnet"; diff --git a/wallet/cryptid-wallet-ui/src/pages/_app.tsx b/wallet/cryptid-wallet-ui/src/pages/_app.tsx new file mode 100644 index 00000000..e5b72843 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/pages/_app.tsx @@ -0,0 +1,34 @@ +import { AppProps } from 'next/app'; +import Head from 'next/head'; +import { FC } from 'react'; +import { ContextProvider } from '../contexts/ContextProvider'; +import { AppBar } from '../components/AppBar'; +import { ContentContainer } from '../components/ContentContainer'; +import { Footer } from '../components/Footer'; +import Notifications from '../components/Notification' + +require('@solana/wallet-adapter-react-ui/styles.css'); +require('../styles/globals.css'); + +const App: FC = ({ Component, pageProps }) => { + return ( + <> + + Cryptid Wallet + + + +
+ + + + + +
+
+
+ + ); +}; + +export default App; diff --git a/wallet/cryptid-wallet-ui/src/pages/_document.tsx b/wallet/cryptid-wallet-ui/src/pages/_document.tsx new file mode 100644 index 00000000..0969e5a7 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/pages/_document.tsx @@ -0,0 +1,25 @@ +import Document, { DocumentContext, Head, Html, Main, NextScript } from 'next/document' + +class MyDocument extends Document { + static async getInitialProps(ctx: DocumentContext) { + const initialProps = await Document.getInitialProps(ctx) + + return initialProps + } + + render() { + return ( + + + + + +
+ + + + ); + } +} + +export default MyDocument; diff --git a/wallet/cryptid-wallet-ui/src/pages/actions.tsx b/wallet/cryptid-wallet-ui/src/pages/actions.tsx new file mode 100644 index 00000000..31648395 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/pages/actions.tsx @@ -0,0 +1,20 @@ +import type { NextPage } from "next"; +import Head from "next/head"; +import { ActionsView } from "../views"; + +const Actions: NextPage = (props) => { + return ( +
+ + Cryptid Wallet + + + +
+ ); +}; + +export default Actions; diff --git a/wallet/cryptid-wallet-ui/src/pages/api/hello.ts b/wallet/cryptid-wallet-ui/src/pages/api/hello.ts new file mode 100644 index 00000000..74a3605d --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/pages/api/hello.ts @@ -0,0 +1,13 @@ +// Next.js API route support: https://nextjs.org/docs/api-routes/introduction +import type { NextApiRequest, NextApiResponse } from "next"; + +type Data = { + name: string; +}; + +export default function handler( + req: NextApiRequest, + res: NextApiResponse +) { + res.status(200).json({ name: "John Doe" }); +} diff --git a/wallet/cryptid-wallet-ui/src/pages/index.tsx b/wallet/cryptid-wallet-ui/src/pages/index.tsx new file mode 100644 index 00000000..5e1c9da4 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/pages/index.tsx @@ -0,0 +1,20 @@ +import type { NextPage } from "next"; +import Head from "next/head"; +import { HomeView } from "../views"; + +const Home: NextPage = (props) => { + return ( +
+ + Cryptid Wallet + + + +
+ ); +}; + +export default Home; diff --git a/wallet/cryptid-wallet-ui/src/stores/useNotificationStore.tsx b/wallet/cryptid-wallet-ui/src/stores/useNotificationStore.tsx new file mode 100644 index 00000000..fec56e00 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/stores/useNotificationStore.tsx @@ -0,0 +1,19 @@ +import create, { State } from "zustand"; +import produce from "immer"; + +interface NotificationStore extends State { + notifications: Array<{ + type: string + message: string + description?: string + txid?: string + }> + set: (x: any) => void +} + +const useNotificationStore = create((set, _get) => ({ + notifications: [], + set: (fn) => set(produce(fn)), +})) + +export default useNotificationStore diff --git a/wallet/cryptid-wallet-ui/src/stores/useUserSOLBalanceStore.tsx b/wallet/cryptid-wallet-ui/src/stores/useUserSOLBalanceStore.tsx new file mode 100644 index 00000000..a4fe7b1c --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/stores/useUserSOLBalanceStore.tsx @@ -0,0 +1,29 @@ +import create, { State } from 'zustand' +import { Connection, PublicKey, LAMPORTS_PER_SOL } from '@solana/web3.js' + +interface UserSOLBalanceStore extends State { + balance: number; + getUserSOLBalance: (publicKey: PublicKey, connection: Connection) => void +} + +const useUserSOLBalanceStore = create((set, _get) => ({ + balance: 0, + getUserSOLBalance: async (publicKey, connection) => { + let balance = 0; + try { + balance = await connection.getBalance( + publicKey, + 'confirmed' + ); + balance = balance / LAMPORTS_PER_SOL; + } catch (e) { + console.log(`error getting balance: `, e); + } + set((s) => { + s.balance = balance; + console.log(`balance updated, `, balance); + }) + }, +})); + +export default useUserSOLBalanceStore; \ No newline at end of file diff --git a/wallet/cryptid-wallet-ui/src/styles/globals.css b/wallet/cryptid-wallet-ui/src/styles/globals.css new file mode 100644 index 00000000..80449650 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/styles/globals.css @@ -0,0 +1,26 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +html, +body { + padding: 0; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, + Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; +} + +a { + color: inherit; + text-decoration: none; +} + +* { + box-sizing: border-box; +} + +/* example: override wallet button style */ +.wallet-adapter-button:not([disabled]):hover { + background-color: #9e2c2c; +} + diff --git a/wallet/cryptid-wallet-ui/src/types.ts b/wallet/cryptid-wallet-ui/src/types.ts new file mode 100644 index 00000000..7e950708 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/types.ts @@ -0,0 +1,24 @@ +import { IdlAccounts, IdlTypes } from "@project-serum/anchor"; +import { Cryptid } from "@identity.com/cryptid-idl"; + +export type { ExtendedCluster } from "./types/solana"; +export type { Wallet } from "./types/crypto"; +export type { + ProposalResult, + ExecuteResult, + ExecuteArrayResult, +} from "./types/cryptid"; +export type { + MiddlewareClient, + GenericMiddlewareParams, + ExecuteMiddlewareParams, + MiddlewareResult, +} from "./types/middleware"; + +export type TransactionAccountMeta = + IdlTypes["AbbreviatedAccountMeta"]; +export type InstructionData = IdlTypes["AbbreviatedInstructionData"]; +export type TransactionAccount = IdlAccounts["transactionAccount"]; +export type CryptidAccount = IdlAccounts["cryptidAccount"]; +// ReturnType["transactionAccount"]["fetch"]> +//TypeDef> diff --git a/wallet/cryptid-wallet-ui/src/types/cryptid.ts b/wallet/cryptid-wallet-ui/src/types/cryptid.ts new file mode 100644 index 00000000..bb22a393 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/types/cryptid.ts @@ -0,0 +1,19 @@ +import { PublicKey, Signer, Transaction } from "@solana/web3.js"; +import { CryptidTransaction } from "../lib/CryptidTransaction"; + +export type ProposalResult = { + proposeTransaction: Transaction; + transactionAccount: PublicKey; + proposeSigners: Signer[]; + cryptidTransactionRepresentation: CryptidTransaction; +}; + +export type ExecuteResult = { + executeTransaction: Transaction; + executeSigners: Signer[]; +}; + +export type ExecuteArrayResult = { + executeTransactions: Transaction[]; + executeSigners: Signer[]; +}; diff --git a/wallet/cryptid-wallet-ui/src/types/crypto.ts b/wallet/cryptid-wallet-ui/src/types/crypto.ts new file mode 100644 index 00000000..595d56c8 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/types/crypto.ts @@ -0,0 +1,11 @@ +import { PublicKey, Transaction } from "@solana/web3.js"; + +export type SignCallback = (transaction: Transaction) => Promise; +export type SignAllCallback = ( + transactions: Transaction[] +) => Promise; +export type Wallet = { + publicKey: PublicKey; + signTransaction: SignCallback; + signAllTransactions: SignAllCallback; +}; diff --git a/wallet/cryptid-wallet-ui/src/types/lang.ts b/wallet/cryptid-wallet-ui/src/types/lang.ts new file mode 100644 index 00000000..42fb93d5 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/types/lang.ts @@ -0,0 +1,7 @@ +/** + * Copied from [fp-ts](https://gcanti.github.io/fp-ts/modules/NonEmptyArray.ts.html) + * If we use more constructs from fp-ts we should import it and remove this. + */ +export type NonEmptyArray = Array & { + 0: A; +}; diff --git a/wallet/cryptid-wallet-ui/src/types/middleware.ts b/wallet/cryptid-wallet-ui/src/types/middleware.ts new file mode 100644 index 00000000..0dc7ac86 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/types/middleware.ts @@ -0,0 +1,37 @@ +import { + ConfirmOptions, + Connection, + PublicKey, + Signer, + Transaction, + TransactionInstruction, +} from "@solana/web3.js"; +import { Wallet } from "./crypto"; +import { CryptidAccountDetails } from "../lib/CryptidAccountDetails"; + +export type GenericMiddlewareParams = { + authority: Wallet; + connection: Connection; + opts: ConfirmOptions; + previousMiddleware?: PublicKey; +}; + +export type ExecuteMiddlewareParams = GenericMiddlewareParams & { + // The account representing the middleware state + middlewareAccount: PublicKey; + // The account representing the proposed transaction state + transactionAccount: PublicKey; + // The cryptid account performing the transaction + cryptidAccountDetails: CryptidAccountDetails; +}; + +export type MiddlewareResult = { + instructions: TransactionInstruction[]; + signers: Signer[]; +}; + +export interface MiddlewareClient { + createMiddleware(params: C): Promise; + onPropose(params: ExecuteMiddlewareParams): Promise; + onExecute(params: ExecuteMiddlewareParams): Promise; +} diff --git a/wallet/cryptid-wallet-ui/src/types/solana.ts b/wallet/cryptid-wallet-ui/src/types/solana.ts new file mode 100644 index 00000000..71c71478 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/types/solana.ts @@ -0,0 +1,3 @@ +import { Cluster } from "@solana/web3.js"; + +export type ExtendedCluster = Cluster | "localnet"; diff --git a/wallet/cryptid-wallet-ui/src/utils/alephUtils.test.ts b/wallet/cryptid-wallet-ui/src/utils/alephUtils.test.ts new file mode 100644 index 00000000..f5518815 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/utils/alephUtils.test.ts @@ -0,0 +1,32 @@ +import { WalletContextState } from "@solana/wallet-adapter-react"; +import { Keypair } from "@solana/web3.js"; +import nacl from "tweetnacl"; +import { addToAleph, retrieveFromAleph } from "./alephUtils"; +import { MessageSignerWalletAdapter } from "@solana/wallet-adapter-base"; + +describe("alephUtils", () => { + const createWallet = () => { + const keypair = Keypair.generate(); + return { + publicKey: keypair.publicKey, + signMessage: async (message: Uint8Array): Promise => + nacl.sign.detached(message, keypair.secretKey), + } as MessageSignerWalletAdapter; + }; + + const content = "Hello World"; + + it("should store a file", async () => { + const hash = await addToAleph(createWallet(), content); + const file = await retrieveFromAleph(hash); + + expect(file).toBe(content); + }, 20_000); + + it("should retrieve an old stored file", async () => { + const file = await retrieveFromAleph( + "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e" + ); + expect(file).toBe(content); + }); +}); diff --git a/wallet/cryptid-wallet-ui/src/utils/alephUtils.ts b/wallet/cryptid-wallet-ui/src/utils/alephUtils.ts new file mode 100644 index 00000000..48716c5e --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/utils/alephUtils.ts @@ -0,0 +1,73 @@ +import { solana } from "@civic/aleph-sdk-ts/accounts"; +import { SolanaWallet } from "@civic/aleph-sdk-ts/accounts/solana"; +import { Account } from "@civic/aleph-sdk-ts/accounts/account"; +import { Publish } from "@civic/aleph-sdk-ts/messages/store"; +import { DEFAULT_API_V2 } from "@civic/aleph-sdk-ts/global"; +import { ItemType } from "@civic/aleph-sdk-ts/messages/message"; +import { store } from "@civic/aleph-sdk-ts"; +import { Web3Storage } from "./types"; +import { MessageSignerWalletAdapter } from "@solana/wallet-adapter-base"; + +const uriToHash = (uri: string) => uri.replace(/^.*:\/\//, ""); +const hashToUri = (hash: string) => `aleph://${hash}`; + +const arraybufferToString = (ab: ArrayBuffer): string => + new TextDecoder().decode(ab); + +const createAccount = async ( + wallet: MessageSignerWalletAdapter +): Promise => { + if (!wallet.publicKey) throw new Error("Wallet is not ready"); + + const walletObj: SolanaWallet = { + publicKey: wallet.publicKey, + signMessage: wallet.signMessage, + }; + return new solana.SOLAccount(walletObj); +}; + +export const addToAleph = async ( + wallet: MessageSignerWalletAdapter, + content: string +): Promise => { + const alephAccount = await createAccount(wallet); + + // account from the aleph integration tests + // const mnemonic = "twenty enough win warrior then fiction smoke tenant juice lift palace inherit"; + // const alephAccount = ethereum.ImportAccountFromMnemonic(mnemonic); + + const fileContent = Buffer.from(content); + const storeMessage = await Publish({ + channel: "TEST", + APIServer: DEFAULT_API_V2, + account: alephAccount, + storageEngine: ItemType.storage, + fileObject: fileContent, + }); + + return storeMessage.content.item_hash; +}; + +export const retrieveFromAleph = async (uri: string): Promise => { + const response = await store.Get({ + fileHash: uriToHash(uri), + APIServer: DEFAULT_API_V2, + }); + return arraybufferToString(response); +}; + +export class AlephStorage implements Web3Storage { + add( + content: string, + name: string, + did: string, + wallet: MessageSignerWalletAdapter, + progressCallback: (percent: number) => void + ): Promise { + return addToAleph(wallet, content).then(hashToUri); + } + + retrieve(hash: string): Promise { + return retrieveFromAleph(hash); + } +} diff --git a/wallet/cryptid-wallet-ui/src/utils/cryptoUtils.ts b/wallet/cryptid-wallet-ui/src/utils/cryptoUtils.ts new file mode 100644 index 00000000..4cb00b68 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/utils/cryptoUtils.ts @@ -0,0 +1,60 @@ +import { LexiWallet } from "@civic/lexi"; +import { MessageSignerWalletAdapter } from "@solana/wallet-adapter-base"; + +const generateSeed = () => window.crypto.getRandomValues(new Uint8Array(32)); + +export type EncryptedPayload = { + jwe: string; + seed: string; + name: string; + mimeType: string; +}; + +export type UnencryptedPayload = { + data: Uint8Array; + name: string; + mimeType: string; +}; + +export const encrypt = async ( + unencryptedPayload: UnencryptedPayload, + did: string, + wallet: MessageSignerWalletAdapter +): Promise => { + const seed = Buffer.from(generateSeed()).toString("hex"); + const lexi = new LexiWallet(wallet, did, { publicSigningString: seed }); + + const jwe = await lexi.encryptForMe({ + data: new Buffer(unencryptedPayload.data).toString("base64"), + name: unencryptedPayload.name, + mimeType: unencryptedPayload.mimeType, + }); + + return { + jwe, + seed, + name: unencryptedPayload.name, + mimeType: unencryptedPayload.mimeType, + }; +}; + +export const decrypt = async ( + payload: EncryptedPayload, + did: string, + wallet: MessageSignerWalletAdapter +): Promise => { + const lexi = new LexiWallet(wallet, did, { + publicSigningString: payload.seed, + }); + + const decryptedPayload = (await lexi.decrypt(payload.jwe)) as Omit< + UnencryptedPayload, + "data" + > & { data: string }; + + return { + data: Buffer.from(decryptedPayload.data, "base64"), + name: payload.name, + mimeType: payload.mimeType, + }; +}; diff --git a/wallet/cryptid-wallet-ui/src/utils/didUtils.ts b/wallet/cryptid-wallet-ui/src/utils/didUtils.ts new file mode 100644 index 00000000..af1a2ea5 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/utils/didUtils.ts @@ -0,0 +1,291 @@ +import { + Connection, + PublicKey, + Transaction, + TransactionInstruction, +} from "@solana/web3.js"; +import { + DidSolDataAccount, + DidSolIdentifier, + DidSolService, + ExtendedCluster, + Service, + VerificationMethodFlags, + Wallet, + BitwiseVerificationMethodFlag, + AddVerificationMethodParams, + VerificationMethodType, + EthSigner, +} from "@identity.com/sol-did-client"; +import { WalletContextState } from "@solana/wallet-adapter-react/src/useWallet"; +import { sendTransaction, toSolanaCluster } from "./solanaUtils"; +import { WalletAdapterNetwork } from "@solana/wallet-adapter-base"; +import { + DIDDocument, + ServiceEndpoint, + VerificationMethod as DIDVerificationMethod, +} from "did-resolver"; +import { Registry } from "@civic/did-registry"; + +export const registerDID = ( + wallet: WalletContextState, + connection: Connection, + did: string +): Promise => { + const registry = Registry.for(toWallet(wallet), connection); + + return registry.register(did); +}; + +const toWallet = (walletContextState: WalletContextState): Wallet => { + if ( + !walletContextState.publicKey || + !walletContextState.signTransaction || + !walletContextState.signAllTransactions + ) { + throw Error("Unsupported wallet type"); + } + + return { + publicKey: walletContextState.publicKey, + signTransaction(tx: Transaction): Promise { + return walletContextState.signTransaction!(tx); + }, + signAllTransactions(txs: Transaction[]): Promise { + return walletContextState.signAllTransactions!(txs); + }, + }; +}; + +const getService = ( + authority: PublicKey, + clusterType: ExtendedCluster, + wallet?: WalletContextState, + connection?: Connection +) => + DidSolService.build(new DidSolIdentifier({ authority, clusterType }), { + wallet: wallet ? toWallet(wallet) : undefined, + connection, + }); + +const getServiceFromDID = ( + did: string, + wallet?: WalletContextState, + connection?: Connection +) => + DidSolService.build(DidSolIdentifier.parse(did), { + wallet: wallet ? toWallet(wallet) : undefined, + connection, + }); + +export const keyToDid = ( + key: PublicKey, + network: WalletAdapterNetwork +): string => { + const cluster = toSolanaCluster(network); + return DidSolIdentifier.create(key, cluster).toString(); +}; +export const findPFP = (document: DIDDocument): string | undefined => + document.service?.find((s) => s.type === "PFP")?.serviceEndpoint; + +export const isVerificationMethod = ( + entry: DIDVerificationMethod | ServiceEndpoint +): entry is DIDVerificationMethod => entry.hasOwnProperty("controller"); + +export const resolveDID = ( + did: string, + connection: Connection +): Promise => + getServiceFromDID(did, undefined, connection).resolve(); + +const sendInstruction = async ( + instruction: TransactionInstruction, + wallet: WalletContextState, + connection: Connection +): Promise => { + const latestBlockhash = await connection.getLatestBlockhash(); + const signature = await sendTransaction( + [instruction], + wallet, + connection, + latestBlockhash + ); + await connection.confirmTransaction({ + signature, + blockhash: "latest", + lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, + }); + return signature; +}; + +export const addServiceToDID = async ( + did: string, + wallet: WalletContextState, + service: Service, + connection: Connection +): Promise => { + if (!wallet.publicKey) throw new Error("Wallet is not connected"); + + const didSolService = await getServiceFromDID(did, wallet, connection); + + await didSolService + .addService(service) + .withAutomaticAlloc(wallet.publicKey) + .rpc(); +}; +export const removeServiceFromDID = async ( + did: string, + wallet: WalletContextState, + identifier: string, + connection: Connection +): Promise => { + if (!wallet.publicKey) throw new Error("Wallet is not connected"); + + const didSolService = await getServiceFromDID(did, wallet, connection); + + const fragment = identifier.match(/^did:sol:.*#(.*)$/)?.[1]; + if (!fragment) throw new Error(`Invalid identifier: ${identifier}`); + + await didSolService.removeService(fragment).rpc(); +}; + +export const getVerificationMethodFlags = async ( + did: string, + wallet: WalletContextState, + fragment: string, + connection: Connection +): Promise => { + if (!wallet.publicKey) throw new Error("Wallet is not connected"); + + const didSolService = await getServiceFromDID(did, wallet, connection); + + const account = await didSolService.getDidAccount(); + + if (!account) return undefined; + + return account.verificationMethods.find((vm) => vm.fragment === fragment) + ?.flags; +}; + +export const addVerificationMethodToDID = async ( + did: string, + wallet: WalletContextState, + key: AddVerificationMethodParams, + connection: Connection +): Promise => { + if (!wallet.publicKey) throw new Error("Wallet is not connected"); + + const didSolService = await getServiceFromDID(did, wallet, connection); + + await didSolService + .addVerificationMethod(key) + .withAutomaticAlloc(wallet.publicKey) + .rpc(); +}; + +export const removeVerificationMethodFromDID = async ( + did: string, + wallet: WalletContextState, + identifier: string, + connection: Connection +): Promise => { + if (!wallet.publicKey) throw new Error("Wallet is not connected"); + + const fragment = identifier.match(/^did:sol:.*#(.*)$/)?.[1]; + if (!fragment) throw new Error(`Invalid identifier: ${identifier}`); + + const didSolService = await getServiceFromDID(did, wallet, connection); + + await didSolService.removeVerificationMethod(fragment).rpc(); +}; + +export const setOwned = async ( + fragment: string, + type: VerificationMethodType, + did: string, + wallet: WalletContextState, + connection: Connection, + ethSigner?: EthSigner +): Promise => { + if (!wallet.publicKey) throw new Error("Wallet is not connected"); + + // Why is this required? Did we not read the state when initializing the service? + const didSolService = await getServiceFromDID(did, wallet, connection); + // get current flags + const flags = + (await getVerificationMethodFlags(did, wallet, fragment, connection)) || + VerificationMethodFlags.none(); + // set Ownership flag + flags.set(BitwiseVerificationMethodFlag.OwnershipProof); + + // get Nonce + // const nonce = await didSolService.getNonce(); + // console.log(`Nonce: ${nonce}`); + // console.log(`Flags: ${nonce.toBuffer('le', 8)}`); + + // Prepare prepare setVM Instruction + didSolService.setVerificationMethodFlags( + fragment, + flags.array, + wallet.publicKey + ); + + if (type !== VerificationMethodType.Ed25519VerificationKey2018 && ethSigner) { + // Assume Eth Signature required + didSolService.withEthSigner(ethSigner); + } + + await didSolService.rpc(); +}; + +export const isMigratable = async ( + did: string, + connection: Connection +): Promise => { + const didSolService = await getServiceFromDID(did, undefined, connection); + + return didSolService.isMigratable(); +}; + +export const migrate = async ( + did: string, + wallet: WalletContextState, + connection: Connection +): Promise => { + console.log({ + did, + wallet, + }); + if (!wallet.publicKey) throw new Error("Wallet is not connected"); + + const didSolService = await getServiceFromDID(did, wallet, connection); + + const isMigratable = await didSolService.isMigratable(); + if (!isMigratable) throw new Error("DID is not migratable"); + + await didSolService.migrate().rpc(); +}; + +export const getDIDAccount = async ( + did: string, + connection: Connection +): Promise => { + const didSolService = await getServiceFromDID(did, undefined, connection); + return didSolService.getDidAccount(); +}; + +export const getDIDAddress = async ( + did: string, + connection: Connection +): Promise => { + if (!(await isInitialized(did, connection))) return undefined; + + const didSolService = await getServiceFromDID(did, undefined, connection); + return didSolService.didDataAccount; +}; + +export const isInitialized = ( + did: string, + connection: Connection +): Promise => + getDIDAccount(did, connection).then((account) => !!account); diff --git a/wallet/cryptid-wallet-ui/src/utils/explorer.ts b/wallet/cryptid-wallet-ui/src/utils/explorer.ts new file mode 100644 index 00000000..70011f15 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/utils/explorer.ts @@ -0,0 +1,23 @@ +import { PublicKey, Transaction } from "@solana/web3.js"; +import base58 from "bs58"; + +export function getExplorerUrl( + endpoint: string, + viewTypeOrItemAddress: "inspector" | PublicKey | string, + itemType = "address" // | 'tx' | 'block' +) { + const getClusterUrlParam = () => { + let cluster = ""; + if (endpoint === "localnet") { + cluster = `custom&customUrl=${encodeURIComponent( + "http://127.0.0.1:8899" + )}`; + } else if (endpoint === "https://api.devnet.solana.com") { + cluster = "devnet"; + } + + return cluster ? `?cluster=${cluster}` : ""; + }; + + return `https://explorer.solana.com/${itemType}/${viewTypeOrItemAddress}${getClusterUrlParam()}`; +} diff --git a/wallet/cryptid-wallet-ui/src/utils/ipfsUtils.ts b/wallet/cryptid-wallet-ui/src/utils/ipfsUtils.ts new file mode 100644 index 00000000..1e1e3090 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/utils/ipfsUtils.ts @@ -0,0 +1,52 @@ +import { CID, create as ipfsHttpClient } from "ipfs-http-client"; +import { Web3Storage } from "./types"; +import { MessageSignerWalletAdapter } from "@solana/wallet-adapter-base"; + +export const cidPathToUrl = (cidPath: string) => + cidToHttpUrl(cidPath.replace(/^ipfs:\/\//, "")); +const cidToHttpUrl = (cid: string) => `https://ipfs.infura.io/ipfs/${cid}`; +const cidToUri = (cid: string) => `ipfs://${cid}`; + +const ipfs = ipfsHttpClient({ + url: "https://ipfs.infura.io:5001/api/v0", +}); + +export const addToIPFS = async ( + content: string, + path: string, + progressCallback: (percent: number) => void +): Promise => { + const result = await ipfs.add( + { + path, + content, + }, + { + progress: (bytes) => progressCallback((bytes * 100) / content.length), + } + ); + return result.cid; +}; + +export const retrieveFromIPFS = async (cid: string): Promise => { + const url = cidPathToUrl(cid); + return fetch(url).then((res) => res.text()); +}; + +export class IPFSStorage implements Web3Storage { + add( + content: string, + name: string, + did: string, + wallet: MessageSignerWalletAdapter, + progressCallback: (percent: number) => void + ): Promise { + return addToIPFS(content, name, progressCallback) + .then((cid) => cid.toString()) + .then(cidToUri); + } + + retrieve(hash: string): Promise { + return retrieveFromIPFS(hash); + } +} diff --git a/wallet/cryptid-wallet-ui/src/utils/notifications.tsx b/wallet/cryptid-wallet-ui/src/utils/notifications.tsx new file mode 100644 index 00000000..0f51348d --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/utils/notifications.tsx @@ -0,0 +1,20 @@ +import useNotificationStore from "../stores/useNotificationStore"; + +export function notify(newNotification: { + type?: string + message: string + description?: string + txid?: string +}) { + const { + notifications, + set: setNotificationStore, + } = useNotificationStore.getState() + + setNotificationStore((state: { notifications: any[] }) => { + state.notifications = [ + ...notifications, + { type: 'success', ...newNotification }, + ] + }) +} diff --git a/wallet/cryptid-wallet-ui/src/utils/solanaUtils.ts b/wallet/cryptid-wallet-ui/src/utils/solanaUtils.ts new file mode 100644 index 00000000..c7194e2a --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/utils/solanaUtils.ts @@ -0,0 +1,47 @@ +import { + BlockhashWithExpiryBlockHeight, + Connection, + Transaction, + TransactionInstruction, +} from "@solana/web3.js"; +import { WalletContextState } from "@solana/wallet-adapter-react"; +import { ExtendedCluster } from "@identity.com/sol-did-client"; +import { WalletAdapterNetwork } from "@solana/wallet-adapter-base"; + +export const sendTransaction = ( + instructions: TransactionInstruction[], + wallet: WalletContextState, + connection: Connection, + latestBlockhash: BlockhashWithExpiryBlockHeight +): Promise => { + if (!wallet.publicKey) throw new Error("Wallet is not connected"); + const transaction = new Transaction(latestBlockhash).add(...instructions); + return wallet.sendTransaction(transaction, connection); +}; + +export const fromSolanaCluster = ( + cluster: ExtendedCluster | undefined +): WalletAdapterNetwork | undefined => { + switch (cluster) { + case "mainnet-beta": + return WalletAdapterNetwork.Mainnet; + case "testnet": + return WalletAdapterNetwork.Testnet; + case "devnet": + return WalletAdapterNetwork.Devnet; + default: + } +}; + +export const toSolanaCluster = ( + cluster: WalletAdapterNetwork | undefined +): ExtendedCluster | undefined => { + switch (cluster) { + case WalletAdapterNetwork.Mainnet: + return "mainnet-beta"; + case WalletAdapterNetwork.Testnet: + return "testnet"; + case WalletAdapterNetwork.Devnet: + return "devnet"; + } +}; diff --git a/wallet/cryptid-wallet-ui/src/utils/storageUtils.ts b/wallet/cryptid-wallet-ui/src/utils/storageUtils.ts new file mode 100644 index 00000000..65ffb3f6 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/utils/storageUtils.ts @@ -0,0 +1,71 @@ +import { decrypt, encrypt, UnencryptedPayload } from "./cryptoUtils"; +import { MessageSignerWalletAdapter } from "@solana/wallet-adapter-base"; +import { AlephStorage } from "./alephUtils"; +import { Web3Storage } from "./types"; + +const storage: Web3Storage = new AlephStorage(); + +export const store = async ( + file: File, + did: string, + wallet: MessageSignerWalletAdapter, + progressCallback: (percent: number) => void +) => { + // TODO this is crazy inefficient - turning the file to a base64 string, encrypting it, then encoding the result as base64 again + // we should change lexi (& did-jwt) to accept a buffer if possible + const data = await file + .arrayBuffer() + .then((arrayBuffer) => new Buffer(arrayBuffer)); + const unencryptedPayload = { + data, + name: file.name, + mimeType: file.type, + }; + const payload = await encrypt(unencryptedPayload, did, wallet); + return storage.add( + JSON.stringify(payload), + file.name, + did, + wallet, + progressCallback + ); +}; + +export const retrieve = async ( + hash: string, + did: string, + wallet: MessageSignerWalletAdapter +): Promise => { + const encryptedContent = await storage.retrieve(hash).then(JSON.parse); + return decrypt(encryptedContent, did, wallet); +}; + +export const download = (file: UnencryptedPayload) => { + const blob = new Blob([file.data], { type: file.mimeType }); + + // Convert your blob into a Blob URL (a special url that points to an object in the browser's memory) + const blobUrl = URL.createObjectURL(blob); + + // Create a link element + const link = document.createElement("a"); + + // Set link's href to point to the Blob URL + link.href = blobUrl; + link.download = file.name; + + // Append link to the body + document.body.appendChild(link); + + // Dispatch click event on the link + // This is necessary as link.click() does not work on the latest firefox + link.dispatchEvent( + new MouseEvent("click", { + bubbles: true, + cancelable: true, + view: window, + }) + ); + + // Remove link from body + document.body.removeChild(link); +}; diff --git a/wallet/cryptid-wallet-ui/src/utils/types.ts b/wallet/cryptid-wallet-ui/src/utils/types.ts new file mode 100644 index 00000000..d8a9204d --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/utils/types.ts @@ -0,0 +1,14 @@ +import { MessageSignerWalletAdapter } from "@solana/wallet-adapter-base"; + +export type SelectedPage = "DID" | "Storage" | "Keys" | "Ethereum"; + +export interface Web3Storage { + add( + content: string, + name: string, + did: string, + wallet: MessageSignerWalletAdapter, + progressCallback: (percent: number) => void + ): Promise; + retrieve(hash: string): Promise; +} diff --git a/wallet/cryptid-wallet-ui/src/views/actions/index.tsx b/wallet/cryptid-wallet-ui/src/views/actions/index.tsx new file mode 100644 index 00000000..24b6a3ba --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/views/actions/index.tsx @@ -0,0 +1,26 @@ + +import { FC } from "react"; +import { SignMessage } from '../../components/SignMessage'; +import { SendTransaction } from '../../components/SendTransaction'; +import {ProposeTransaction} from '../../components/ProposeTransaction' +//import { ExecuteTransaction } from "components/ExecuteTransaction"; + +export const ActionsView: FC = ({ }) => { + + return ( +
+
+

+ Actions +

+ {/* CONTENT GOES HERE */} +
+ + + + { /**/} +
+
+
+ ); +}; diff --git a/wallet/cryptid-wallet-ui/src/views/home/index.tsx b/wallet/cryptid-wallet-ui/src/views/home/index.tsx new file mode 100644 index 00000000..1c4ba7e9 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/views/home/index.tsx @@ -0,0 +1,102 @@ +// Next, React +import { FC, useEffect, useState } from 'react'; +import Link from 'next/link'; + +// Wallet +import { useWallet, useConnection } from '@solana/wallet-adapter-react'; + +// Components +import { RequestAirdrop } from '../../components/RequestAirdrop'; +import pkg from '../../../package.json'; + +import bs58 from 'bs58' +// Store +import useUserSOLBalanceStore from '../../stores/useUserSOLBalanceStore'; +import {Connection, Keypair, LAMPORTS_PER_SOL, SystemProgram, Transaction} from '@solana/web3.js'; +import { build, Signer, util } from '@identity.com/cryptid'; +export const HomeView: FC = ({ }) => { + const wallet = useWallet(); + const { connection } = useConnection(); + + const balance = useUserSOLBalanceStore((s) => s.balance) + const { getUserSOLBalance } = useUserSOLBalanceStore() + + useEffect(() => { + if (wallet.publicKey) { + console.log(wallet.publicKey.toBase58()) + getUserSOLBalance(wallet.publicKey, connection) + } + }, [wallet.publicKey, connection, getUserSOLBalance]) + + + +// Create (or provide) a Solana keypair +/*const did = util.publicKeyToDid(wallet.publicKey, 'devnet'); +const mappedWallet = { + sign: wallet.signTransaction + //other methods +} +const keypair = Keypair.fromSecretKey(bs58.decode(wallet.p)) +// Create the Cryptid instance +const cryptid = build(did, wallet, { + connection, + waitForConfirmation: true, +}); +*/ + return ( + +
+
+

+ Cryptid Wallet v{pkg.version} +

+

+

Solana DID-aware on-chain signer and wallet integrations

+ All existing Solana wallets are automatically compatible with Cryptid. +

+
+
+            Go To Actions To Start using Cryptid  
+          
+
+
+ {wallet &&

+ +
+
SOL Balance:
+
{(balance || 0).toLocaleString()}
+
+ {/**/} +
+
+ + + +

} +
+
+
{/* + + + + + + + + + + + + + + + + +
Cryptid AccountAuthorityAccount MetasAddressBumpIndex
*/} +
+ +
+
+
+ ); +}; diff --git a/wallet/cryptid-wallet-ui/src/views/index.tsx b/wallet/cryptid-wallet-ui/src/views/index.tsx new file mode 100644 index 00000000..37be20d4 --- /dev/null +++ b/wallet/cryptid-wallet-ui/src/views/index.tsx @@ -0,0 +1,2 @@ +export { HomeView } from "./home"; +export { ActionsView } from "./actions"; diff --git a/wallet/cryptid-wallet-ui/tailwind.config.js b/wallet/cryptid-wallet-ui/tailwind.config.js new file mode 100644 index 00000000..a7b95ddb --- /dev/null +++ b/wallet/cryptid-wallet-ui/tailwind.config.js @@ -0,0 +1,17 @@ +module.exports = { + mode: "jit", + content: ["./src/**/*.{js,jsx,ts,tsx}"], + darkMode: "media", + theme: { + extend: {}, + }, + plugins: [ + require('daisyui'), + require("@tailwindcss/typography") + ], + daisyui: { + themes: [ + + ], + }, +} \ No newline at end of file diff --git a/wallet/cryptid-wallet-ui/tsconfig.json b/wallet/cryptid-wallet-ui/tsconfig.json new file mode 100644 index 00000000..ad70a923 --- /dev/null +++ b/wallet/cryptid-wallet-ui/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "baseUrl": "./src", + "target": "es6", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "strictNullChecks": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules", ".next",], + "ts-node": { + "require": ["tsconfig-paths/register"], + "compilerOptions": { + "module": "commonjs" + } + } +} diff --git a/wallet/cryptid-wallet-ui/user_conf.d/dapp.conf b/wallet/cryptid-wallet-ui/user_conf.d/dapp.conf new file mode 100644 index 00000000..8ded569f --- /dev/null +++ b/wallet/cryptid-wallet-ui/user_conf.d/dapp.conf @@ -0,0 +1,19 @@ +server { + listen 80; + server_name cryptid-dev.duckdns.org; + if ($host = cryptid-dev.duckdns.org){ + return 301 https://$host$request_uri; + } +} +server { + listen 443 ssl; + server_name cryptid-dev.duckdns.org; + location / { + proxy_pass http://dapp:3000/; + } + + # Load the certificate files + ssl_certificate /etc/letsencrypt/live/myportfolio/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/myportfolio/privkey.pem; + ssl_trusted_certificate /etc/letsencrypt/live/myportfolio/chain.pem; +} \ No newline at end of file diff --git a/wallet/sample-dapp/.gitignore b/wallet/sample-dapp/.gitignore new file mode 100644 index 00000000..92b6b972 --- /dev/null +++ b/wallet/sample-dapp/.gitignore @@ -0,0 +1,3 @@ +.parcel-cache +dist +lib diff --git a/wallet/sample-dapp/CHANGELOG.md b/wallet/sample-dapp/CHANGELOG.md new file mode 100644 index 00000000..45a17775 --- /dev/null +++ b/wallet/sample-dapp/CHANGELOG.md @@ -0,0 +1,9 @@ +# @wallet-standard/example-react + +## null + +### Patch Changes + +- Updated dependencies [59d90b2] + - @wallet-standard/core@1.0.0 + - @wallet-standard/react@0.1.0 diff --git a/wallet/sample-dapp/README.md b/wallet/sample-dapp/README.md new file mode 100644 index 00000000..e69de29b diff --git a/wallet/sample-dapp/package.json b/wallet/sample-dapp/package.json new file mode 100644 index 00000000..9f07d2ba --- /dev/null +++ b/wallet/sample-dapp/package.json @@ -0,0 +1,32 @@ +{ + "private": true, + "name": "@wallet-standard/example-react", + "author": "Solana Maintainers ", + "repository": "https://github.com/wallet-standard/wallet-standard", + "license": "Apache-2.0", + "scripts": { + "clean": "shx mkdir -p .parcel-cache dist lib && shx rm -rf .parcel-cache dist lib", + "start": "parcel src/index.html", + "build": "parcel build src/index.html" + }, + "dependencies": { + "@solana/wallet-adapter-base": "^0.9.18", + "@solana/wallet-adapter-glow": "^0.1.13", + "@solana/wallet-adapter-phantom": "^0.9.17", + "@solana/wallet-standard": "rc", + "@solana/web3.js": "^1.66.2", + "@wallet-standard/core": "workspace:^", + "@wallet-standard/react": "workspace:^", + "bs58": "^4.0.1", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.0.21", + "@types/react-dom": "^18.0.6", + "parcel": "^2.7.0", + "process": "^0.11.10", + "shx": "^0.3.4" + }, + "version": null +} diff --git a/wallet/sample-dapp/src/App.tsx b/wallet/sample-dapp/src/App.tsx new file mode 100644 index 00000000..e3f2b919 --- /dev/null +++ b/wallet/sample-dapp/src/App.tsx @@ -0,0 +1,44 @@ +import { GlowWalletAdapter } from '@solana/wallet-adapter-glow'; +import { PhantomWalletAdapter } from '@solana/wallet-adapter-phantom'; +import { registerWalletAdapter, SOLANA_MAINNET_CHAIN } from '@solana/wallet-standard'; +import { useWallets, WalletProvider, WalletsProvider } from '@wallet-standard/react'; +import type { FC, ReactNode } from 'react'; +import React, { useEffect } from 'react'; +import { getWallets, registerWallet } from '@wallet-standard/core'; +import {UniqueCryptidWallet} from '../../unique-cryptid/src/wallet' +export const App: FC = () => { + return ( + + + + ); +}; + +const Context: FC<{ children: NonNullable }> = ({ children }) => { + useEffect(() => { + const adapters = [new PhantomWalletAdapter(), new GlowWalletAdapter()]; + const destructors = adapters.map((adapter) => registerWalletAdapter(adapter, SOLANA_MAINNET_CHAIN)); + return () => destructors.forEach((destroy) => destroy()); + }, []); + registerWallet(new UniqueCryptidWallet(useWallets()[0])) + + return ( + + {children} + + ); +}; + +const Content: FC = () => { + const { wallets } = useWallets(); + return ( +
+
    + {wallets.map((wallet, index) => ( +
  • {'Cryptid'}
  • + ))} +
+