diff --git a/examples/start-tailwind/src/app.tsx b/examples/start-tailwind/src/app.tsx index c90d41f..0d4cad8 100644 --- a/examples/start-tailwind/src/app.tsx +++ b/examples/start-tailwind/src/app.tsx @@ -5,9 +5,21 @@ import { Router } from "@solidjs/router" import { FileRoutes } from "@solidjs/start/router" import { Suspense } from "solid-js" import { WalletProvider } from "@solana-wallets-solid/solid" +import { UnifiedWalletProviderProps, UnifiedWalletButtonProps } from "@solana-wallets-solid/unified" import Nav from "~/components/Nav" +declare module "solid-js" { + namespace JSX { + interface IntrinsicElements { + "unified-wallet-modal": UnifiedWalletProviderProps + } + interface IntrinsicElements { + "unified-wallet-modal-button": UnifiedWalletButtonProps + } + } +} + export default function App() { // const adapters = [ // new CoinbaseWalletAdapter(), @@ -39,23 +51,7 @@ export default function App() { localStorageKey="unified:wallet-stoarge-key" env="devnet" > - + {props.children} diff --git a/examples/start-tailwind/src/routes/index.tsx b/examples/start-tailwind/src/routes/index.tsx index 9c66ac6..b5b4ee7 100644 --- a/examples/start-tailwind/src/routes/index.tsx +++ b/examples/start-tailwind/src/routes/index.tsx @@ -70,36 +70,6 @@ export default function Home() { console.log({ res }) } - // async function sendTxV1() { - // const APPEAL_WALLET_PUBKEY = new PublicKey("Hm9YjuVadcekDPbLeCSFE83r1QLpS2ksmKk7Sn5BCpfL") - // const rawPubKey = publicKey() - // if (!rawPubKey) { - // console.error("cannot sign tx, no pub key: ", { rawPubKey }) - // return - // } - // const pubKey = new PublicKey(rawPubKey) - // const lamportsToSend = 0.1 * LAMPORTS_PER_SOL - // const transaction = new Transaction().add( - // SystemProgram.transfer({ - // fromPubkey: pubKey, - // toPubkey: APPEAL_WALLET_PUBKEY, - // lamports: lamportsToSend, - // }), - // ) - // - // const connection = new Connection(DEVNET_RPC_ENDPOINT, "confirmed") - // const latestHash = await connection.getLatestBlockhash("finalized") - // if (transaction instanceof Transaction) { - // transaction.recentBlockhash = latestHash.blockhash - // transaction.feePayer = pubKey - // } - // const tx = new Uint8Array( - // transaction.serialize({ verifySignatures: false, requireAllSignatures: false }), - // ) - // const res = await sendTransaction(tx) - // console.log("successful tx: ", { res }) - // } - return ( Hello world! diff --git a/lefthook.yml b/lefthook.yml new file mode 100644 index 0000000..f6e5dfe --- /dev/null +++ b/lefthook.yml @@ -0,0 +1,35 @@ +# EXAMPLE USAGE: +# +# Refer for explanation to following link: +# https://github.com/evilmartians/lefthook/blob/master/docs/configuration.md +# +# pre-push: +# commands: +# packages-audit: +# tags: frontend security +# run: yarn audit +# gems-audit: +# tags: backend security +# run: bundle audit +# +# pre-commit: +# parallel: true +# commands: +# eslint: +# glob: "*.{js,ts,jsx,tsx}" +# run: yarn eslint {staged_files} +# rubocop: +# tags: backend style +# glob: "*.rb" +# exclude: '(^|/)(application|routes)\.rb$' +# run: bundle exec rubocop --force-exclusion {all_files} +# govet: +# tags: backend style +# files: git ls-files -m +# glob: "*.go" +# run: go vet {files} +# scripts: +# "hello.js": +# runner: node +# "any.go": +# runner: go run diff --git a/packages/core/src/constants.ts b/packages/core/src/constants.ts index 6fcaf98..a6f5a82 100644 --- a/packages/core/src/constants.ts +++ b/packages/core/src/constants.ts @@ -1 +1,55 @@ +import { WalletName } from "@solana/wallet-adapter-base" + +import { THardcodedWalletStandardAdapter } from "./hardcoded-wallet-adapter" export const SolanaMobileWalletAdapterWalletName = "Mobile Wallet Adapter v2" + +export const HARDCODED_WALLET_STANDARDS: THardcodedWalletStandardAdapter[] = [ + { + id: "Phantom", + name: "Phantom" as WalletName, + url: "https://phantom.app/", + icon: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDgiIGhlaWdodD0iMTA4IiB2aWV3Qm94PSIwIDAgMTA4IDEwOCIgZmlsbD0ibm9uZSI+CjxyZWN0IHdpZHRoPSIxMDgiIGhlaWdodD0iMTA4IiByeD0iMjYiIGZpbGw9IiNBQjlGRjIiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik00Ni41MjY3IDY5LjkyMjlDNDIuMDA1NCA3Ni44NTA5IDM0LjQyOTIgODUuNjE4MiAyNC4zNDggODUuNjE4MkMxOS41ODI0IDg1LjYxODIgMTUgODMuNjU2MyAxNSA3NS4xMzQyQzE1IDUzLjQzMDUgNDQuNjMyNiAxOS44MzI3IDcyLjEyNjggMTkuODMyN0M4Ny43NjggMTkuODMyNyA5NCAzMC42ODQ2IDk0IDQzLjAwNzlDOTQgNTguODI1OCA4My43MzU1IDc2LjkxMjIgNzMuNTMyMSA3Ni45MTIyQzcwLjI5MzkgNzYuOTEyMiA2OC43MDUzIDc1LjEzNDIgNjguNzA1MyA3Mi4zMTRDNjguNzA1MyA3MS41NzgzIDY4LjgyNzUgNzAuNzgxMiA2OS4wNzE5IDY5LjkyMjlDNjUuNTg5MyA3NS44Njk5IDU4Ljg2ODUgODEuMzg3OCA1Mi41NzU0IDgxLjM4NzhDNDcuOTkzIDgxLjM4NzggNDUuNjcxMyA3OC41MDYzIDQ1LjY3MTMgNzQuNDU5OEM0NS42NzEzIDcyLjk4ODQgNDUuOTc2OCA3MS40NTU2IDQ2LjUyNjcgNjkuOTIyOVpNODMuNjc2MSA0Mi41Nzk0QzgzLjY3NjEgNDYuMTcwNCA4MS41NTc1IDQ3Ljk2NTggNzkuMTg3NSA0Ny45NjU4Qzc2Ljc4MTYgNDcuOTY1OCA3NC42OTg5IDQ2LjE3MDQgNzQuNjk4OSA0Mi41Nzk0Qzc0LjY5ODkgMzguOTg4NSA3Ni43ODE2IDM3LjE5MzEgNzkuMTg3NSAzNy4xOTMxQzgxLjU1NzUgMzcuMTkzMSA4My42NzYxIDM4Ljk4ODUgODMuNjc2MSA0Mi41Nzk0Wk03MC4yMTAzIDQyLjU3OTVDNzAuMjEwMyA0Ni4xNzA0IDY4LjA5MTYgNDcuOTY1OCA2NS43MjE2IDQ3Ljk2NThDNjMuMzE1NyA0Ny45NjU4IDYxLjIzMyA0Ni4xNzA0IDYxLjIzMyA0Mi41Nzk1QzYxLjIzMyAzOC45ODg1IDYzLjMxNTcgMzcuMTkzMSA2NS43MjE2IDM3LjE5MzFDNjguMDkxNiAzNy4xOTMxIDcwLjIxMDMgMzguOTg4NSA3MC4yMTAzIDQyLjU3OTVaIiBmaWxsPSIjRkZGREY4Ii8+Cjwvc3ZnPg==", + deepUrl: loc => { + // redirect to the Phantom /browse universal link + // this will open the current URL in the Phantom in-wallet browser + const url = encodeURIComponent(loc.href) + const ref = encodeURIComponent(loc.origin) + return `https://phantom.app/ul/browse/${url}?ref=${ref}` + }, + }, + { + id: "Solflare", + name: "Solflare" as WalletName, + url: "https://solflare.com/", + icon: "data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjUwIiB2aWV3Qm94PSIwIDAgNTAgNTAiIHdpZHRoPSI1MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGxpbmVhckdyYWRpZW50IGlkPSJhIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNmZmMxMGIiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNmYjNmMmUiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iYiIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHgxPSI2LjQ3ODM1IiB4Mj0iMzQuOTEwNyIgeGxpbms6aHJlZj0iI2EiIHkxPSI3LjkyIiB5Mj0iMzMuNjU5MyIvPjxyYWRpYWxHcmFkaWVudCBpZD0iYyIgY3g9IjAiIGN5PSIwIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDQuOTkyMTg4MzIgMTIuMDYzODc5NjMgLTEyLjE4MTEzNjU1IDUuMDQwNzEwNzQgMjIuNTIwMiAyMC42MTgzKSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHI9IjEiIHhsaW5rOmhyZWY9IiNhIi8+PHBhdGggZD0ibTI1LjE3MDggNDcuOTEwNGMuNTI1IDAgLjk1MDcuNDIxLjk1MDcuOTQwM3MtLjQyNTcuOTQwMi0uOTUwNy45NDAyLS45NTA3LS40MjA5LS45NTA3LS45NDAyLjQyNTctLjk0MDMuOTUwNy0uOTQwM3ptLTEuMDMyOC00NC45MTU2NWMuNDY0Ni4wMzgzNi44Mzk4LjM5MDQuOTAyNy44NDY4MWwxLjEzMDcgOC4yMTU3NGMuMzc5OCAyLjcxNDMgMy42NTM1IDMuODkwNCA1LjY3NDMgMi4wNDU5bDExLjMyOTEtMTAuMzExNThjLjI3MzMtLjI0ODczLjY5ODktLjIzMTQ5Ljk1MDcuMDM4NTEuMjMwOS4yNDc3Mi4yMzc5LjYyNjk3LjAxNjEuODgyNzdsLTkuODc5MSAxMS4zOTU4Yy0xLjgxODcgMi4wOTQyLS40NzY4IDUuMzY0MyAyLjI5NTYgNS41OTc4bDguNzE2OC44NDAzYy40MzQxLjA0MTguNzUxNy40MjM0LjcwOTMuODUyNC0uMDM0OS4zNTM3LS4zMDc0LjYzOTUtLjY2MjguNjk0OWwtOS4xNTk0IDEuNDMwMmMtMi42NTkzLjM2MjUtMy44NjM2IDMuNTExNy0yLjEzMzkgNS41NTc2bDMuMjIgMy43OTYxYy4yNTk0LjMwNTguMjE4OC43NjE1LS4wOTA4IDEuMDE3OC0uMjYyMi4yMTcyLS42NDE5LjIyNTYtLjkxMzguMDIwM2wtMy45Njk0LTIuOTk3OGMtMi4xNDIxLTEuNjEwOS01LjIyOTctLjI0MTctNS40NTYxIDIuNDI0M2wtLjg3NDcgMTAuMzk3NmMtLjAzNjIuNDI5NS0uNDE3OC43NDg3LS44NTI1LjcxMy0uMzY5LS4wMzAzLS42NjcxLS4zMDk3LS43MTcxLS42NzIxbC0xLjM4NzEtMTAuMDQzN2MtLjM3MTctMi43MTQ0LTMuNjQ1NC0zLjg5MDQtNS42NzQzLTIuMDQ1OWwtMTIuMDUxOTUgMTAuOTc0Yy0uMjQ5NDcuMjI3MS0uNjM4MDkuMjExNC0uODY4LS4wMzUtLjIxMDk0LS4yMjYyLS4yMTczNS0uNTcyNC0uMDE0OTMtLjgwNmwxMC41MTgxOC0xMi4xMzg1YzEuODE4Ny0yLjA5NDIuNDg0OS01LjM2NDQtMi4yODc2LTUuNTk3OGwtOC43MTg3Mi0uODQwNWMtLjQzNDEzLS4wNDE4LS43NTE3Mi0uNDIzNS0uNzA5MzYtLjg1MjQuMDM0OTMtLjM1MzcuMzA3MzktLjYzOTQuNjYyNy0uNjk1bDkuMTUzMzgtMS40Mjk5YzIuNjU5NC0uMzYyNSAzLjg3MTgtMy41MTE3IDIuMTQyMS01LjU1NzZsLTIuMTkyLTIuNTg0MWMtLjMyMTctLjM3OTItLjI3MTMtLjk0NDMuMTEyNi0xLjI2MjEuMzI1My0uMjY5NC43OTYzLS4yNzk3IDEuMTMzNC0uMDI0OWwyLjY5MTggMi4wMzQ3YzIuMTQyMSAxLjYxMDkgNS4yMjk3LjI0MTcgNS40NTYxLTIuNDI0M2wuNzI0MS04LjU1OTk4Yy4wNDU3LS41NDA4LjUyNjUtLjk0MjU3IDEuMDczOS0uODk3Mzd6bS0yMy4xODczMyAyMC40Mzk2NWMuNTI1MDQgMCAuOTUwNjcuNDIxLjk1MDY3Ljk0MDNzLS40MjU2My45NDAzLS45NTA2Ny45NDAzYy0uNTI1MDQxIDAtLjk1MDY3LS40MjEtLjk1MDY3LS45NDAzcy40MjU2MjktLjk0MDMuOTUwNjctLjk0MDN6bTQ3LjY3OTczLS45NTQ3Yy41MjUgMCAuOTUwNy40MjEuOTUwNy45NDAzcy0uNDI1Ny45NDAyLS45NTA3Ljk0MDItLjk1MDctLjQyMDktLjk1MDctLjk0MDIuNDI1Ny0uOTQwMy45NTA3LS45NDAzem0tMjQuNjI5Ni0yMi40Nzk3Yy41MjUgMCAuOTUwNi40MjA5NzMuOTUwNi45NDAyNyAwIC41MTkzLS40MjU2Ljk0MDI3LS45NTA2Ljk0MDI3LS41MjUxIDAtLjk1MDctLjQyMDk3LS45NTA3LS45NDAyNyAwLS41MTkyOTcuNDI1Ni0uOTQwMjcuOTUwNy0uOTQwMjd6IiBmaWxsPSJ1cmwoI2IpIi8+PHBhdGggZD0ibTI0LjU3MSAzMi43NzkyYzQuOTU5NiAwIDguOTgwMi0zLjk3NjUgOC45ODAyLTguODgxOSAwLTQuOTA1My00LjAyMDYtOC44ODE5LTguOTgwMi04Ljg4MTlzLTguOTgwMiAzLjk3NjYtOC45ODAyIDguODgxOWMwIDQuOTA1NCA0LjAyMDYgOC44ODE5IDguOTgwMiA4Ljg4MTl6IiBmaWxsPSJ1cmwoI2MpIi8+PC9zdmc+", + deepUrl: loc => { + // redirect to the Solflare /browse universal link + // this will open the current URL in the Solflare in-wallet browser + const url = encodeURIComponent(loc.href) + const ref = encodeURIComponent(loc.origin) + return `https://solflare.com/ul/v1/browse/${url}?ref=${ref}` + }, + }, + { + id: "Backpack", + name: "Backpack" as WalletName, + url: "https://www.backpack.app/", + icon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAbvSURBVHgB7Z1dUtxGEMf/LZH3fU0V4PUJQg4QVj5BnBOAT2BzAsMJAicwPoHJCRDrAxifgLVxVV73ObDqdEtsjKn4C8+0NDv9e7AxprRC85uvnp4RYYW5qKpxCVTcYKsgfiDfGjMwIsZIvh7d/lkmzAiYy5fzhultyZhdlagf1vU5VhjCiiGFXq01zYSJdqWgx/hB5AHN5I/6iuilyFBjxVgZAdqCZ34ORoVIqAzSOhxsvq6PsSIkL4A281LwL2IW/F1UhLKgRz/X9QyJUyBhuuae31gWviLjiPF1wxeX29vPkTjJtgAftrd3GHSMnmHw4eZ0uodESVKAoRT+kpQlSE6Ats/XZv/ONK5vZHC49+B1fYjESG4MUDKfYmCFr0ic4fmHqtpCYiQlgA66QsztIzFi5j+RGMl0AXebfgn0aOTuvGG8owIarZsXOj3ronlRuEYnn84CJLo4Lgi/QL/H/LHmy/RwI6GA0RoS4acFHi8kGieFXS/QhmijFfQXmH3uPy5lSkoLbIkYlfyzhuM4juM4juM4juMMj6TzATQ4JH9tlRqFk8BM2aV9RWHB9K5kzK/KLui0KqliSQmgBa4BIS54cpMD0OeawFye3jk19JdKkWq62OAFkEIfrTXNUxBV1okf38Ot3MGjlFqHwQrQZvQ22Cfw7xjg6t8XkZaBGzpKIXdwcAJojZeCP5SC30HipJBEOigBZLn3qdzSPlKr8V9hyEmkgxCgj8zefuD9jen0AAOidwE0i6ZhfjXgRI+gDK016DUjqE3ubPhNLoWvaDLJouHToaSP9SbA0DJ7LekyiviNPgP0TC9dQM6FfxeZ7eyuT6cv0RPmAmjTx11uXx/MiegEDd425cfcwWV+H4O3+uiO+pTAVIA2uMN8av6QiWr5TQ++JVlTc/tEiF3jOMScZGC43kME0VSA95PJhWXhM+Gt1Phn98nStZa1r9mB2SDQPqefjhayfnDfFG2J5882z84eynVM5u3thlONhRhj0gLc5PRfwAw62JjW+wjE5Xa1L0VkshO4kXt/EPDev4ZJCyBRvlcwggjHG4EfYHc9OoIBBWy3mEUX4H1V7Ur7ZvILaT8qy7FRduleF9jXc4RggOUWs/gtANs0nYquvMXaMaTXlQHlE1ggayLvf5OKY0DUMYDWfmpsBjZa+9enOmiLy+VkcmqxaNW2ZgX9GnsLXNQWoGj4KYzQ2g8LyG5WUDR4hshEE6CN+AFmg5lFiRMYcI0uKRQGyIAwegWKJkBjYO8tzq12C7efQ7CK2I00MomIxOsCiCcwQhaW3sEQ6W7sPi/yIDqKAHp8m2nIF7COoc9ghQw4NU8SkYgiQCmLKXCCUSziPc84XYBh83/DSiWR3qUo2tT4ONdGYDTub73cSzD/PNt0rojdQHAByoXxw0E7XfoFhsjnRduD+DnWIkkXXACJl1cwRoMmf3cbRaOjLRzDXnKZVj9GBIILUJBtbVzyj9HAU19AgR6I9VzDtwCgMXpAo2Yxp0v/Ybi49ennJtIFEPMY/TCKHTvv+aTSUQzBgwrQ92YHbQVi3UN3GAVZhrf/jzECE1SAq/7n4yOJ074KPSBcJoii598vxgwrqAByg70HZJZbr0JJ0G5XZz5Z1e1rYccA5TAicqEk0O5ECl/3LvYys7mLTLHHCEzS7wz6Esv3+nyYTF58rwha63XAl8PG1aCnhesWq6EdOcKM3WvmXRHh+Gvv/tNVTJlJPC4a3RVEK72+sCSZ4+J/FBVhTUS43J7gJqFjrnl33A3sxtCa3nAWhX6bbAT4hJugCsNZ2TGA8224AJnjAmSOC5A5LkDmuACZ4wJkjguQOS5A5rgAmeMCZI4LkDkuQOa4AJnjAmSOC5A5LkDmuACZ4wJkjguQOWEFYJvz85xwBBWgKM1P68oKKsI/36ACdC9nsDlWPTsIJ5t1Hfw01OBjgI1p/YwLegIibw0CwESz9gUYZ2d/wHEcx3Ecx3Ecx3Ecx3HuS5QjfdrXxTHv3JzEkd2xKwHR9xPNuKGjzdf1MSIQXAA9XUsuuw8nKPpK3PWzs+AvrgwqgP1LojOjoEf3fRv6Zy+JgBSLOGfaOx1NE/6o+rCrgeT9fWp4SljmuACZ4wJkjguQOS5A5rgAmeMCZI4LkDkuQOa4AJnjAmSOC5A5LkDmuACZ4wJkjguQOS5A5rgAmeMCZI4LkDkuQOa4AJnj5wRmTlABqHQBohKhggUVYAEEP8fO+UiMgziDCvCwrnU3aw0nOATMQu8LVIIPAq+JdAerdwWBaQ/fjEBwAaQVmMnN7sEJCB3EqP3tlRGJy6qqmPkFMcZw7sucmfZiHQ6hRBNgSXdaCHbA7KeFfBvz9pxlxtl1gcN2XBWRfwHK959XFRG6AgAAAABJRU5ErkJggg==", + }, + { + id: "Coinbase Wallet", + name: "Coinbase Wallet" as WalletName, + url: "https://www.coinbase.com/wallet", + icon: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAyNCIgaGVpZ2h0PSIxMDI0IiB2aWV3Qm94PSIwIDAgMTAyNCAxMDI0IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8Y2lyY2xlIGN4PSI1MTIiIGN5PSI1MTIiIHI9IjUxMiIgZmlsbD0iIzAwNTJGRiIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTE1MiA1MTJDMTUyIDcxMC44MjMgMzEzLjE3NyA4NzIgNTEyIDg3MkM3MTAuODIzIDg3MiA4NzIgNzEwLjgyMyA4NzIgNTEyQzg3MiAzMTMuMTc3IDcxMC44MjMgMTUyIDUxMiAxNTJDMzEzLjE3NyAxNTIgMTUyIDMxMy4xNzcgMTUyIDUxMlpNNDIwIDM5NkM0MDYuNzQ1IDM5NiAzOTYgNDA2Ljc0NSAzOTYgNDIwVjYwNEMzOTYgNjE3LjI1NSA0MDYuNzQ1IDYyOCA0MjAgNjI4SDYwNEM2MTcuMjU1IDYyOCA2MjggNjE3LjI1NSA2MjggNjA0VjQyMEM2MjggNDA2Ljc0NSA2MTcuMjU1IDM5NiA2MDQgMzk2SDQyMFoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=", + deepUrl: loc => { + const url = encodeURIComponent(loc.href) + return `cbwallet://dapp?url=${url}` + }, + }, + { + id: "OKX Wallet", + name: "OKX Wallet" as WalletName, + url: "https://www.okx.com/web3", + icon: "https://station.jup.ag/img/wallet/glow.png", + }, +] diff --git a/packages/core/src/environment.ts b/packages/core/src/environment.ts index 30f9fed..02b623c 100644 --- a/packages/core/src/environment.ts +++ b/packages/core/src/environment.ts @@ -62,14 +62,6 @@ export function isSafari(ua: string) { return ua.includes("safari") } -export function isIosAndWebView() { - if (typeof window === "undefined" || !navigator) { - return false - } - const ua = navigator.userAgent.toLowerCase() - return isIos(ua) && !isSafari(ua) -} - /** * Users on iOS can be redirected into a wallet's in-app browser automatically, * if that wallet has a universal link configured to do so diff --git a/packages/core/src/events.ts b/packages/core/src/events.ts index 0e8b5d9..7049cff 100644 --- a/packages/core/src/events.ts +++ b/packages/core/src/events.ts @@ -1,5 +1,5 @@ -import { WalletAdapterCompatibleStandardWallet } from "@solana/wallet-adapter-base" import { Wallet, WalletAccount } from "@wallet-standard/base" +import { WalletInfo } from "./store" export type StandardEventChangeProperties = { readonly chains?: Wallet["chains"] @@ -72,9 +72,9 @@ export function dispatchWalletChanged(wallet?: StandardWalletConnectResult | und } export type AvailableWalletsChangedEvent = CustomEvent<{ - wallets: WalletAdapterCompatibleStandardWallet[] + wallets: WalletInfo[] }> -export function dispatchAvailableWalletsChanged(wallets: WalletAdapterCompatibleStandardWallet[]) { +export function dispatchAvailableWalletsChanged(wallets: WalletInfo[]) { const getAvailableWalletsChangedEvent = new CustomEvent(WalletEvent.AVAILABLE_WALLETS_CHANGED, { detail: { wallets }, }) diff --git a/packages/core/src/hardcoded-wallet-adapter.ts b/packages/core/src/hardcoded-wallet-adapter.ts new file mode 100644 index 0000000..f83849b --- /dev/null +++ b/packages/core/src/hardcoded-wallet-adapter.ts @@ -0,0 +1,79 @@ +import { + BaseSignerWalletAdapter, + isIosAndRedirectable, + WalletName, + WalletNotConnectedError, + WalletReadyState, +} from "@solana/wallet-adapter-base" +import { Transaction, TransactionVersion } from "@solana/web3.js" + +export type THardcodedWalletStandardAdapter = { + id: string + name: WalletName + url: string + icon: string + deepUrl?: (loc: Location) => string +} + +export default class HardcodedWalletStandardAdapter extends BaseSignerWalletAdapter { + name = "" as WalletName + url = "" + icon = "" + deepUrl?: (loc: Location) => string + supportedTransactionVersions: ReadonlySet = new Set(["legacy", 0]) + + /** + * Storing a keypair locally like this is not safe because any application using this adapter could retrieve the + * secret key, and because the keypair will be lost any time the wallet is disconnected or the window is refreshed. + */ + private _keypair: CryptoKeyPair | null = null + public readyState: WalletReadyState = WalletReadyState.NotDetected + + constructor({ name, url, icon, deepUrl }: Omit) { + super() + this.name = name + this.url = url + this.icon = icon + this.deepUrl = deepUrl + + if (this.deepUrl && isIosAndRedirectable()) { + this.readyState = WalletReadyState.Loadable + } + } + + get connecting() { + return false + } + + get publicKey() { + return this._keypair && this._keypair.publicKey + } + + async connect(): Promise { + if (this.readyState === WalletReadyState.Loadable && this.deepUrl) { + const url = this.deepUrl(window.location) + window.location.href = url + return + } + throw new WalletNotConnectedError() + } + + async disconnect(): Promise { + this._keypair = null + this.emit("disconnect") + } + + async signTransaction(transaction: T): Promise { + if (!this._keypair) throw new WalletNotConnectedError() + + console.log({ transaction }) + + // if (isVersionedTransaction(transaction)) { + // transaction.sign([this._keypair]) + // } else { + // transaction.partialSign(this._keypair) + // } + // + // return transaction + } +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 8fe4a6d..79a457a 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -3,3 +3,4 @@ export * from "./environment" export * from "./events" export * from "./store" export * from "./constants" +export * from "./hardcoded-wallet-adapter" diff --git a/packages/core/src/store.ts b/packages/core/src/store.ts index f8baf68..68ed757 100644 --- a/packages/core/src/store.ts +++ b/packages/core/src/store.ts @@ -4,6 +4,7 @@ import { isWalletAdapterCompatibleStandardWallet, WalletName, WalletAdapterCompatibleStandardWallet, + isIosAndRedirectable, } from "@solana/wallet-adapter-base" import { SolanaSignAndSendTransaction, @@ -33,6 +34,9 @@ import { WalletEvent, } from "./events" import { getLocalStorage, KEYS, setLocalStorage } from "./localstorage" +import { THardcodedWalletStandardAdapter } from "./hardcoded-wallet-adapter" +import { isIos } from "./environment" +import { HARDCODED_WALLET_STANDARDS } from "./constants" export type Cluster = "devnet" | "testnet" | "mainnet-beta" @@ -42,26 +46,31 @@ type StoreProps = { disconnectOnAccountChange: boolean // localStorageKey: string } + +export type WalletInfo = + | { type: "ios-webview"; wallet: THardcodedWalletStandardAdapter } + | { type: "standard"; wallet: WalletAdapterCompatibleStandardWallet } + export function initStore({ env, disconnectOnAccountChange }: StoreProps) { - const $wallets = atom([]) + const $wallets = atom([]) onSet($wallets, ({ newValue }) => { dispatchAvailableWalletsChanged(newValue) }) const $walletsMap = computed($wallets, wallets => { - return wallets.reduce>( - (_walletsByName, newWallet) => { - const name = newWallet.name - const existing = _walletsByName[name] - // Add wallet adapter if not yet added - if (!existing) { - _walletsByName[name] = newWallet - return _walletsByName - } + if (!wallets) { + return + } + return wallets.reduce>((_walletsByName, newWallet) => { + const name = newWallet.wallet.name + const existing = _walletsByName[name] + // Add wallet adapter if not yet added + if (!existing) { + _walletsByName[name] = newWallet return _walletsByName - }, - {}, - ) + } + return _walletsByName + }, {}) }) const $connectedAccount = atom() @@ -74,7 +83,7 @@ export function initStore({ env, disconnectOnAccountChange }: StoreProps) { if (!acc) { return } - return walletsMap[acc.name] + return walletsMap?.[acc.name] }) const $env = atom(env ?? "mainnet-beta") @@ -118,12 +127,12 @@ export function initStore({ env, disconnectOnAccountChange }: StoreProps) { updateWallet() } - function removeAdapterEventListeners( - _wallet?: WalletAdapterCompatibleStandardWallet | undefined, - ): void { - _wallet = _wallet ? _wallet : $wallet.get() - if (_wallet) { - _wallet.features["standard:events"].on("change", e => onChangeEvent(e, _wallet)) + function removeAdapterEventListeners(walletInfo?: WalletInfo): void { + walletInfo = walletInfo ? walletInfo : $wallet.get() + if (walletInfo && walletInfo.type === "standard") { + walletInfo.wallet.features["standard:events"].on("change", e => + onChangeEvent(e, walletInfo.wallet), + ) } } @@ -131,16 +140,19 @@ export function initStore({ env, disconnectOnAccountChange }: StoreProps) { * @throws Error */ async function connectStandardWallet(name: string): Promise { - const wallet = $walletsMap.get()[name] - if (!wallet) { + const walletInfo = $walletsMap.get()?.[name] + if (!walletInfo) { throw new Error(`connectStandardWallet: wallet with name: ${name} does not exist!`) } - if (!("standard:connect" in wallet.features)) { + if (walletInfo.type !== "standard") { + throw new Error("`connectStandardWallet: cannot connect to non-standard wallet") + } + if (!("standard:connect" in walletInfo.wallet.features)) { throw new Error( `connectStandardWallet: standard wallet does NOT have standard:connect feature enabled!`, ) } - const res = await wallet.features["standard:connect"].connect() + const res = await walletInfo.wallet.features["standard:connect"].connect() if (res.accounts.length === 0) { throw new Error(`connectStandardWallet: no accounts available!`) } @@ -150,7 +162,7 @@ export function initStore({ env, disconnectOnAccountChange }: StoreProps) { } return { name, - icon: wallet.icon, + icon: walletInfo.wallet.icon, type: "standard", pubKey: base58.encode(acc.publicKey), account: acc, @@ -165,6 +177,31 @@ export function initStore({ env, disconnectOnAccountChange }: StoreProps) { throw new Error("connect: failed to connect, already connecting/disconnecting") } + const wallets = $wallets.get() + + if (wallets.some(w => w.type === "ios-webview")) { + const walletInfo = wallets.find(w => w.wallet.name === name) + + if (!walletInfo || walletInfo.type !== "ios-webview") { + throw new Error( + `connect: failed to connect, requested wallet ${name} has not been hardcoded!`, + ) + } + + const wallet = walletInfo.wallet + if (!wallet.deepUrl) { + window.open(wallet.url, "_blank") + return + // throw new Error( + // `connect: failed to connect, requested wallet ${name} does NOT have deeplink enabled!`, + // ) + } + + const url = wallet.deepUrl(window.location) + window.location.href = url + return + } + // if (!(_ready === WalletReadyState.Installed || _ready === WalletReadyState.Loadable)) { // console.error("failed to connect, invalid ready state (not installed/loadable)") // updateWallet() @@ -214,19 +251,20 @@ export function initStore({ env, disconnectOnAccountChange }: StoreProps) { if ($disconnecting.get()) { return } - const _adapter = $wallet.get() - if (!_adapter) { + const walletInfo = $wallet.get() + if (!walletInfo || walletInfo.type === "ios-webview") { console.error( "disconnect: resetting wallet since cannot disconnect from nonexistent adapter: ", - _adapter, + walletInfo, ) updateWallet() return } try { $disconnecting.set(true) - if ("standard:disconnect" in _adapter.features) { - await _adapter.features["standard:disconnect"].disconnect() + const wallet = walletInfo.wallet + if ("standard:disconnect" in wallet.features) { + await wallet.features["standard:disconnect"].disconnect() } } finally { $disconnecting.set(false) @@ -243,7 +281,7 @@ export function initStore({ env, disconnectOnAccountChange }: StoreProps) { name: w.name, accounts: changes.accounts, }) - const connectedWalletName = $wallet.get()?.name + const connectedWalletName = $wallet.get()?.wallet.name if (connectedWalletName && connectedWalletName === w.name) { /** * w.accounts is empty ONLY IF @@ -275,7 +313,11 @@ export function initStore({ env, disconnectOnAccountChange }: StoreProps) { // } } - $wallets.set(getStandardWallets()) + const walletInfos: WalletInfo[] = getStandardWallets().map(w => ({ + type: "standard", + wallet: w, + })) + $wallets.set(walletInfos) } function onChangeEvent( @@ -337,16 +379,16 @@ export function initStore({ env, disconnectOnAccountChange }: StoreProps) { return get().filter(w => isWalletAdapterCompatibleStandardWallet(w)) } - function onMountLoadWallets() { + function onMountLoadStandardWallets() { console.log("onMountLoadWallets") const { on } = getWallets() - const initializedWallets = getStandardWallets().map(w => { + const initializedWallets: WalletInfo[] = getStandardWallets().map(w => { w.features["standard:events"].on("change", async x => { console.log({ x }) await onChange(w, x) }) - return w + return { type: "standard", wallet: w } }) console.log({ initializedWallets }) @@ -356,7 +398,11 @@ export function initStore({ env, disconnectOnAccountChange }: StoreProps) { */ $wallets.set(initializedWallets) const addWallet = () => { - $wallets.set(getStandardWallets()) + const walletInfos: WalletInfo[] = getStandardWallets().map(w => ({ + type: "standard", + wallet: w, + })) + $wallets.set(walletInfos) } /** @@ -371,8 +417,16 @@ export function initStore({ env, disconnectOnAccountChange }: StoreProps) { } } + function onMountLoadHardcodedMobileWallets() { + const walletInfos: WalletInfo[] = HARDCODED_WALLET_STANDARDS.map(w => ({ + type: "ios-webview", + wallet: w, + })) + $wallets.set(walletInfos) + } + async function select(name: string): Promise { - const existingName = $wallet.get()?.name + const existingName = $wallet.get()?.wallet.name if (existingName === name) { console.error("adapter already connected") @@ -401,10 +455,23 @@ export function initStore({ env, disconnectOnAccountChange }: StoreProps) { await select(walletName) } - function initOnMount(): () => void { + function initOnMount(): (() => void) | undefined { + if (isIosAndRedirectable()) { + alert("is ios!") + onMountLoadHardcodedMobileWallets() + return + } + + // if (isIosAndWebView()) { + // alert("ios and web view found!") + // return + // } + + // alert("init on mount!") + const cleanup = onMountClearAdapterEventListeners() loadConnectHandlers() - const cleanup2 = onMountLoadWallets() + const cleanup2 = onMountLoadStandardWallets() onMountAutoConnect() return () => { cleanup() @@ -420,11 +487,16 @@ export function initStore({ env, disconnectOnAccountChange }: StoreProps) { */ async function signTransactionV1(txBytes: Uint8Array) { const connectedAccount = $connectedAccount.get() - const wallet = $wallet.get() - if (!connectedAccount || !wallet) { + const walletInfo = $wallet.get() + if (!connectedAccount || !walletInfo) { throw new Error("signTransaction failed, missing adapter/public key!") } + if (walletInfo.type !== "standard") { + throw new Error("solana:signTransaction NOT found since wallet is not standard wallet") + } + + const wallet = walletInfo.wallet if (!(SolanaSignTransaction in wallet.features)) { throw new Error("solana:signTransaction NOT found in standard wallet features") } @@ -450,11 +522,16 @@ export function initStore({ env, disconnectOnAccountChange }: StoreProps) { */ async function signAllTransactionsV1(txs: Uint8Array[]) { const connectedAccount = $connectedAccount.get() - const wallet = $wallet.get() - if (!connectedAccount || !wallet) { + const walletInfo = $wallet.get() + if (!connectedAccount || !walletInfo) { throw new Error("signAllTransactions failed, missing adapter/public key!") } + if (walletInfo.type !== "standard") { + throw new Error("solana:signTransaction NOT found since wallet is not standard wallet") + } + + const wallet = walletInfo.wallet if (!(SolanaSignTransaction in wallet.features)) { throw new Error("solana:signTransaction NOT found in standard wallet features") } @@ -479,11 +556,16 @@ export function initStore({ env, disconnectOnAccountChange }: StoreProps) { */ async function signMessage(tx: Uint8Array): Promise { const connectedAccount = $connectedAccount.get() - const wallet = $wallet.get() - if (!connectedAccount || !wallet) { + const walletInfo = $wallet.get() + if (!connectedAccount || !walletInfo) { throw new Error("signMessage failed, missing adapter/public key!") } + if (walletInfo.type !== "standard") { + throw new Error("solana:signTransaction NOT found since wallet is not standard wallet") + } + + const wallet = walletInfo.wallet if (!(SolanaSignMessage in wallet.features)) { throw new Error("solana:signMessage NOT found in standard wallet features") } @@ -506,11 +588,16 @@ export function initStore({ env, disconnectOnAccountChange }: StoreProps) { */ async function sendTransactionV1(txBytes: Uint8Array) { const connectedAccount = $connectedAccount.get() - const wallet = $wallet.get() - if (!connectedAccount || !wallet) { + const walletInfo = $wallet.get() + if (!connectedAccount || !walletInfo) { throw new Error("sendTransaction failed: missing adapter / public key!") } + if (walletInfo.type !== "standard") { + throw new Error("solana:signAndSendTransaction NOT found since wallet is not standard wallet") + } + + const wallet = walletInfo.wallet if (!(SolanaSignAndSendTransaction in wallet.features)) { throw new Error("solana:signAndSendTransaction NOT found in standard wallet features") } @@ -550,12 +637,20 @@ export function initStore({ env, disconnectOnAccountChange }: StoreProps) { const { abortSignal, minContextSlot } = config abortSignal?.throwIfAborted() - const wallet = $wallet.get() - if (!wallet) { + const walletInfo = $wallet.get() + if (!walletInfo) { throw new Error("solana:signAndSendTransaction wallet not connected!") } + + if (walletInfo.type !== "standard") { + throw new Error( + "getTransactionSendingSigner NOT found since wallet is not standard wallet", + ) + } + + const wallet = walletInfo.wallet if (!(SolanaSignAndSendTransaction in wallet.features)) { - throw new Error("solana:signAndSendTransaction NOT found in standard wallet features") + throw new Error("getTransactionSendingSigner NOT found in standard wallet features") } const [tx] = txs diff --git a/packages/solid/src/useWallet.ts b/packages/solid/src/useWallet.ts index 3540448..9408e57 100644 --- a/packages/solid/src/useWallet.ts +++ b/packages/solid/src/useWallet.ts @@ -45,9 +45,11 @@ const [WalletProvider, _useWallet] = createContextProvider((props: WalletProvide onMount(() => { const cleanup = initOnMount() - onCleanup(() => { - cleanup() - }) + if (cleanup) { + onCleanup(() => { + cleanup() + }) + } }) return { diff --git a/packages/unified/src/components/UnifiedWalletModal/Onboarding.tsx b/packages/unified/src/components/UnifiedWalletModal/Onboarding.tsx index c1d34ac..e939db8 100644 --- a/packages/unified/src/components/UnifiedWalletModal/Onboarding.tsx +++ b/packages/unified/src/components/UnifiedWalletModal/Onboarding.tsx @@ -1,17 +1,6 @@ -import { - batch, - Component, - createEffect, - createSignal, - For, - Match, - on, - Show, - Switch, -} from "solid-js" +import { batch, Component, createEffect, createSignal, Match, on, Show, Switch } from "solid-js" import { useUnifiedWallet } from "../../contexts" -import { HARDCODED_WALLET_STANDARDS } from "../../constants" import ExternalIcon from "../../icons/ExternalIcon" export type OnboardingFlow = "Onboarding" | "Get Wallet" diff --git a/packages/unified/src/components/UnifiedWalletModal/WalletListItem.tsx b/packages/unified/src/components/UnifiedWalletModal/WalletListItem.tsx index 360c43f..de11660 100644 --- a/packages/unified/src/components/UnifiedWalletModal/WalletListItem.tsx +++ b/packages/unified/src/components/UnifiedWalletModal/WalletListItem.tsx @@ -1,11 +1,13 @@ import { Component, ComponentProps, createMemo, createSignal, mergeProps, Show } from "solid-js" -// import { SolanaMobileWalletAdapterWalletName } from "@solana-mobile/wallet-adapter-mobile" -import { isMobile } from "@solana-wallets-solid/core" +import { + isMobile, + SolanaMobileWalletAdapterWalletName, + WalletInfo, +} from "@solana-wallets-solid/core" import { Dynamic } from "solid-js/web" import UnknownIconSVG from "../../icons/UnknownWalletSVG" import { useTranslation } from "../../contexts/translation/useTranslation" -import { WalletInfo } from "./types" type WalletIconProps = { name: string @@ -47,30 +49,31 @@ export type WalletListItemProps = { export const WalletListItem: Component = props => { // const { theme } = useUnifiedWallet() - useTranslation() + const { t } = useTranslation() const adapterName = createMemo(() => { - if (props.info.type === "standard-wallet") { - // if (props.info.adapter.name === SolanaMobileWalletAdapterWalletName) { - // return t(`Mobile`) - // } - return props.info.wallet.name + if (props.info.wallet.name === SolanaMobileWalletAdapterWalletName) { + return t(`Mobile`) } - return props.info.name + return props.info.wallet.name }) return ( diff --git a/packages/unified/src/components/UnifiedWalletModal/index.tsx b/packages/unified/src/components/UnifiedWalletModal/index.tsx index d8ef450..867e27d 100644 --- a/packages/unified/src/components/UnifiedWalletModal/index.tsx +++ b/packages/unified/src/components/UnifiedWalletModal/index.tsx @@ -20,7 +20,12 @@ import { WalletReadyState, } from "@solana/wallet-adapter-base" // import { SolanaMobileWalletAdapterWalletName } from "@solana-mobile/wallet-adapter-mobile" -import { dispatchConnect, isMobile } from "@solana-wallets-solid/core" +import { + dispatchConnect, + isMobile, + SolanaMobileWalletAdapterWalletName, + WalletInfo, +} from "@solana-wallets-solid/core" import { Dynamic } from "solid-js/web" import { UnifiedWalletModalProps, useUnifiedWallet } from "../../contexts" @@ -30,13 +35,7 @@ import { WalletIcon, WalletListItem } from "./WalletListItem" import ChevronUpIcon from "../../icons/ChevronUpIcon" import ChevronDownIcon from "../../icons/ChevronDownIcon" import { Collapse } from "../Collapse" -import { WalletInfo } from "./types" import { NotInstalled } from "./NotInstalled" -// import { HARDCODED_WALLET_STANDARDS } from "../../constants" - -function createStandardWalletInfo(wallet: WalletAdapterCompatibleStandardWallet): WalletInfo { - return { type: "standard-wallet", wallet } -} export const Header: VoidComponent = () => { const { setIsModalOpen, t } = useUnifiedWallet() @@ -160,32 +159,31 @@ export const ListOfWallets: Component = props => { {info => { const walletName = createMemo(() => { - if (info.type === "standard-wallet") { - // if (info.adapter.name === SolanaMobileWalletAdapterWalletName) { - // return t(`Mobile`) as string - // } - return info.wallet.name + if (info.wallet.name === SolanaMobileWalletAdapterWalletName) { + return t(`Mobile`) as string } - return info.name + return info.wallet.name }) const attachment = walletAttachments ? walletAttachments[walletName()]?.attachment : null return ( onWalletClick(info.wallet.name) + info.type === "standard" ? () => onWalletClick(info.wallet.name) : undefined + } + href={ + info.type === "ios-webview" + ? info.wallet.deepUrl?.(window.location) : undefined } - href={info.type === "mobile-deeplink" ? info.deeplink : undefined} > @@ -237,7 +235,7 @@ export const ListOfWallets: Component = props => { - info.type === "standard-wallet" + info.type === "standard" ? onWalletClick(info.wallet.name) : undefined } @@ -330,12 +328,11 @@ export const TOP_WALLETS: WalletName[] = [ ] export const sortByPrecedence = - (walletPrecedence: WalletName[]) => - (a: WalletAdapterCompatibleStandardWallet, b: WalletAdapterCompatibleStandardWallet) => { + (walletPrecedence: WalletName[]) => (a: WalletInfo, b: WalletInfo) => { if (!walletPrecedence) return 0 - const aIndex = walletPrecedence.indexOf(a.name as WalletName) - const bIndex = walletPrecedence.indexOf(b.name as WalletName) + const aIndex = walletPrecedence.indexOf(a.wallet.name as WalletName) + const bIndex = walletPrecedence.indexOf(b.wallet.name as WalletName) if (aIndex === -1 && bIndex === -1) return 0 if (aIndex >= 0) { @@ -358,11 +355,11 @@ export function clickOutside(el: Element, handler: () => void) { } type FilteredAdapters = { - previouslyConnected: WalletAdapterCompatibleStandardWallet[] - installed: WalletAdapterCompatibleStandardWallet[] - top3: WalletAdapterCompatibleStandardWallet[] - loadable: WalletAdapterCompatibleStandardWallet[] - notDetected: WalletAdapterCompatibleStandardWallet[] + previouslyConnected: WalletInfo[] + installed: WalletInfo[] + top3: WalletInfo[] + loadable: WalletInfo[] + notDetected: WalletInfo[] } type WalletList = { @@ -377,33 +374,34 @@ const UnifiedWalletModal: Component = props => { const [isExpanded, setIsExpanded] = createSignal(props.isExpanded ?? true) const filteredAdapters = createMemo(() => { - console.log("filtered adapter props.wallets: ", { wallets: wallets() }) - - return wallets().reduce( - (acc, wallet) => { + const _wallets = wallets() + if (!_wallets) { + return { + previouslyConnected: [], + installed: [], + top3: [], + loadable: [], + notDetected: [], + } + } + return _wallets.reduce( + (acc, walletInfo) => { + const wallet = walletInfo.wallet const walletName = wallet.name - console.log({ walletName }) // Previously connected takes highest const previouslyConnectedIndex = getPreviouslyConnected().indexOf(walletName) if (previouslyConnectedIndex >= 0) { - acc.previouslyConnected[previouslyConnectedIndex] = wallet + acc.previouslyConnected.push(walletInfo) return acc } - // Then Installed - // if (wallet.readyState === WalletReadyState.Installed) { - // acc.installed.push(wallet) - // return acc - // } // Top 3 const topWalletsIndex = TOP_WALLETS.indexOf(walletName as WalletName) if (topWalletsIndex >= 0) { - console.log(`adding ${walletName} to top 3 wallets`) - acc.top3[topWalletsIndex] = wallet + acc.top3.push(walletInfo) return acc } - // Loadable - console.log(`adding ${walletName} to loadable wallets`) - acc.loadable.push(wallet) + + acc.loadable.push(walletInfo) return acc }, { @@ -418,22 +416,28 @@ const UnifiedWalletModal: Component = props => { const list = createMemo(() => { // Then, Installed, Top 3, Loadable, NotDetected - const filtered = filteredAdapters() + if (filtered.previouslyConnected.length > 0) { const { previouslyConnected, ...rest } = filtered - const previouslyConnectedInfos: WalletInfo[] = previouslyConnected.map(a => - createStandardWalletInfo(a), - ) + // const previouslyConnectedInfos: WalletInfo[] = previouslyConnected.wallets.map(a => + // isWalletAdapterCompatibleStandardWallet(a) + // ? createStandardWalletInfo(a) + // : { + // type: "mobile-deeplink", + // icon: a.icon, + // name: a.name, + // deeplink: a.deepUrl?.(window.location) ?? "", + // }, + // ) - const highlight: WalletInfo[] = previouslyConnectedInfos.slice(0, 3) + const highlight = previouslyConnected.slice(0, 3) - let others: WalletInfo[] = Object.values(rest) + let others = Object.values(rest) .flat() .sort(sortByPrecedence(walletPrecedence || [])) - .map(a => createStandardWalletInfo(a)) - others.unshift(...previouslyConnectedInfos.slice(3, previouslyConnectedInfos.length)) + others.unshift(...previouslyConnected.slice(3, previouslyConnected.length)) others = others.filter(Boolean) // if (isIosAndRedirectable()) { @@ -458,12 +462,6 @@ const UnifiedWalletModal: Component = props => { if (filtered.installed.length > 0) { const { installed, top3, ...rest } = filtered - const installedWalletInfos: WalletInfo[] = installed.map(i => ({ - type: "standard-wallet", - wallet: i, - })) - const topWalletInfos: WalletInfo[] = top3.map(a => createStandardWalletInfo(a)) - // if (isIosAndRedirectable()) { // const redirectableWalletInfos: WalletInfo[] = HARDCODED_WALLET_STANDARDS.filter( // w => !!w.deepUrl, @@ -476,16 +474,14 @@ const UnifiedWalletModal: Component = props => { // topWalletInfos.push(...redirectableWalletInfos) // } - const highlight: WalletInfo[] = [ - ...installedWalletInfos.slice(0, 3), - ...topWalletInfos.filter(Boolean), - ].filter(Boolean) + const highlight: WalletInfo[] = [...installed.slice(0, 3), ...top3.filter(Boolean)].filter( + Boolean, + ) const others: WalletInfo[] = Object.values(rest) .flat() .sort(sortByPrecedence(walletPrecedence || [])) - .map(a => createStandardWalletInfo(a)) - others.unshift(...installedWalletInfos.slice(3, installed.length)) + others.unshift(...installed.slice(3, installed.length)) return { highlightedBy: "TopAndRecommended", highlight, others } } @@ -499,7 +495,6 @@ const UnifiedWalletModal: Component = props => { } const { top3, ...rest } = filtered - const topWalletInfos: WalletInfo[] = top3.map(a => createStandardWalletInfo(a)) // if (isIosAndRedirectable()) { // const redirectableWalletInfos: WalletInfo[] = HARDCODED_WALLET_STANDARDS.filter( // w => !!w.deepUrl, @@ -515,9 +510,8 @@ const UnifiedWalletModal: Component = props => { const others: WalletInfo[] = Object.values(rest) .flat() .sort(sortByPrecedence(walletPrecedence || [])) - .map(a => createStandardWalletInfo(a)) - return { highlightedBy: "TopWallet", highlight: topWalletInfos, others } + return { highlightedBy: "TopWallet", highlight: top3, others } }) // {walletModalAttachments?.footer} diff --git a/packages/unified/src/components/UnifiedWalletModal/test.tsx b/packages/unified/src/components/UnifiedWalletModal/test.tsx deleted file mode 100644 index 96b3acc..0000000 --- a/packages/unified/src/components/UnifiedWalletModal/test.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Component, createSignal } from "solid-js" -import { UnifiedWalletModalProps } from "." -import Dialog from "@corvu/dialog" -import CloseIcon from "../../icons/CloseIcon" - -export const TestModal: Component = () => { - const [isOpen, setIsOpen] = createSignal(true) - - return ( - - - - - - - - - - - {`Connect Wallet`} - - - - - {`You need to connect a Solana wallet.`} - - - - - - - - - - - - ) -} diff --git a/packages/unified/src/components/UnifiedWalletModal/types.ts b/packages/unified/src/components/UnifiedWalletModal/types.ts deleted file mode 100644 index 61b8876..0000000 --- a/packages/unified/src/components/UnifiedWalletModal/types.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { WalletAdapterCompatibleStandardWallet } from "@solana/wallet-adapter-base" - -type StandardWalletInfo = { - type: "standard-wallet" - wallet: WalletAdapterCompatibleStandardWallet -} - -type DeeplinkWalletInfo = { - type: "mobile-deeplink" - name: string - icon: string - deeplink: string -} - -export type WalletInfo = StandardWalletInfo | DeeplinkWalletInfo diff --git a/packages/unified/src/contexts/index.ts b/packages/unified/src/contexts/index.ts index ad14c45..5112749 100644 --- a/packages/unified/src/contexts/index.ts +++ b/packages/unified/src/contexts/index.ts @@ -1,3 +1,2 @@ export * from "./unified/useUnifiedWallet" export * from "./translation/useTranslation" -export * from "./translation/i18" diff --git a/packages/unified/src/contexts/unified/HardcodedWalletStandardAdapter.ts b/packages/unified/src/contexts/unified/HardcodedWalletStandardAdapter.ts deleted file mode 100644 index c128195..0000000 --- a/packages/unified/src/contexts/unified/HardcodedWalletStandardAdapter.ts +++ /dev/null @@ -1,77 +0,0 @@ -// import { -// BaseSignerWalletAdapter, -// isIosAndRedirectable, -// isVersionedTransaction, -// WalletName, -// WalletNotConnectedError, -// WalletReadyState, -// } from "@solana/wallet-adapter-base" -// -// export type THardcodedWalletStandardAdapter = { -// id: string -// name: WalletName -// url: string -// icon: string -// deepUrl?: (loc: Location) => string -// } -// -// export default class HardcodedWalletStandardAdapter extends BaseSignerWalletAdapter { -// name = "" as WalletName -// url = "" -// icon = "" -// deepUrl?: (loc: Location) => string -// supportedTransactionVersions: ReadonlySet = new Set(["legacy", 0]) -// -// /** -// * Storing a keypair locally like this is not safe because any application using this adapter could retrieve the -// * secret key, and because the keypair will be lost any time the wallet is disconnected or the window is refreshed. -// */ -// private _keypair: Keypair | null = null -// public readyState: WalletReadyState = WalletReadyState.NotDetected -// -// constructor({ name, url, icon, deepUrl }: Omit) { -// super() -// this.name = name -// this.url = url -// this.icon = icon -// this.deepUrl = deepUrl -// -// if (this.deepUrl && isIosAndRedirectable()) { -// this.readyState = WalletReadyState.Loadable -// } -// } -// -// get connecting() { -// return false -// } -// -// get publicKey() { -// return this._keypair && this._keypair.publicKey -// } -// -// async connect(): Promise { -// if (this.readyState === WalletReadyState.Loadable && this.deepUrl) { -// const url = this.deepUrl(window.location) -// window.location.href = url -// return -// } -// throw new WalletNotConnectedError() -// } -// -// async disconnect(): Promise { -// this._keypair = null -// this.emit("disconnect") -// } -// -// async signTransaction(transaction: T): Promise { -// if (!this._keypair) throw new WalletNotConnectedError() -// -// if (isVersionedTransaction(transaction)) { -// transaction.sign([this._keypair]) -// } else { -// transaction.partialSign(this._keypair) -// } -// -// return transaction -// } -// } diff --git a/packages/unified/src/contexts/unified/localstorage.ts b/packages/unified/src/contexts/unified/localstorage.ts new file mode 100644 index 0000000..b8c2e24 --- /dev/null +++ b/packages/unified/src/contexts/unified/localstorage.ts @@ -0,0 +1,4 @@ +export const KEY = { + PREVIOUSLY_CONNECTED: "unified:previously-connected", +} as const +export type Key = (typeof KEY)[keyof typeof KEY] diff --git a/packages/unified/src/contexts/unified/useUnifiedWallet.tsx b/packages/unified/src/contexts/unified/useUnifiedWallet.tsx index bf23f64..b007099 100644 --- a/packages/unified/src/contexts/unified/useUnifiedWallet.tsx +++ b/packages/unified/src/contexts/unified/useUnifiedWallet.tsx @@ -1,33 +1,29 @@ -import { - SupportedTransactionVersions, - WalletAdapterCompatibleStandardWallet, - WalletName, -} from "@solana/wallet-adapter-base" +import { SupportedTransactionVersions, WalletName } from "@solana/wallet-adapter-base" import { batch, Component, + createEffect, createMemo, createSignal, JSXElement, + on, onCleanup, onMount, } from "solid-js" import { createContextProvider } from "@solid-primitives/context" import { AvailableWalletsChangedEvent, - Cluster, ConnectingEvent, StandardWalletConnectResult, WalletChangedEvent, WalletEvent, + WalletInfo, } from "@solana-wallets-solid/core" import { DEFAULT_LOCALE, Locale } from "../translation/i18" import { TranslationProvider, useTranslation } from "../translation/useTranslation" -// import { THardcodedWalletStandardAdapter } from "./HardcodedWalletStandardAdapter" - import UnifiedWalletModal from "../../components/UnifiedWalletModal" -import { UnifiedWalletButtonProps } from "../../components" +import { KEY } from "./localstorage" export const MWA_NOT_FOUND_ERROR = "MWA_NOT_FOUND_ERROR" export type UnifiedTheme = "light" | "dark" | "jupiter" @@ -45,8 +41,7 @@ export type WalletNotification = { } export type UnifiedWalletConfig = { - metadata: UnifiedWalletMetadata - env: Cluster + // metadata: UnifiedWalletMetadata walletPrecedence?: WalletName[] // hardcodedWallets?: THardcodedWalletStandardAdapter[] notificationCallback?: { @@ -69,10 +64,6 @@ export type UnifiedWalletConfig = { } export type UnifiedWalletProviderProps = { - autoConnect: boolean - disconnectOnAccountChange: boolean - // wallets: WalletProviderProps["wallets"] - config: UnifiedWalletConfig locale?: Locale } @@ -102,18 +93,22 @@ export function dispatchUpdateModal(open: boolean) { const [_UnifiedWalletProvider, _useUnifiedWallet] = createContextProvider( (props: UnifiedWalletConfig) => { + // Locale info const { t, locale, setLocale } = useTranslation() - const [wallets, setWallets] = createSignal([]) - const [isModalOpen, setIsModalOpen] = createSignal(false) + // Wallet info + const [wallets, setWallets] = createSignal() const [wallet, setWallet] = createSignal() const [connecting, setConnecting] = createSignal(false) const name = createMemo(() => wallet()?.name) const publicKey = createMemo(() => wallet()?.pubKey) + // Modal state + const [isModalOpen, setIsModalOpen] = createSignal(false) + function getPreviouslyConnected() { try { - const value = localStorage.getItem("unified-wallet-previously-connected") + const value = localStorage.getItem(KEY.PREVIOUSLY_CONNECTED) if (!value) { return [] } @@ -127,7 +122,7 @@ const [_UnifiedWalletProvider, _useUnifiedWallet] = createContextProvider( } function setPreviouslyConnected(connected: string[]) { - localStorage.setItem("unified-wallet-previously-connected", JSON.stringify(connected)) + localStorage.setItem(KEY.PREVIOUSLY_CONNECTED, JSON.stringify(connected)) } function onWalletChangedHandler(event: Event) { @@ -174,64 +169,29 @@ const [_UnifiedWalletProvider, _useUnifiedWallet] = createContextProvider( }) }) - // createEffect( - // on([wallet, publicKey], ([wallet, pubKey]) => { - // if (!pubKey || !wallet) { - // return - // } - // const prevConnected = getPreviouslyConnected() - // - // // make sure the most recently connected wallet is first - // const combined = new Set([wallet.name, ...prevConnected]) - // setPreviouslyConnected([...combined]) - // }), - // ) - // - // createEffect( - // on([wallet, publicKey], ([wallet, pubKey], prev) => { - // if (wallet && pubKey) { - // // props.notificationCallback?.onConnect({ - // // publicKey: publicKey.toString(), - // // shortAddress: shortenAddress(publicKey.toString()), - // // walletName: adapter.name, - // // metadata: { - // // name: adapter.name, - // // url: adapter.url, - // // icon: adapter.icon, - // // supportedTransactionVersions: adapter.supportedTransactionVersions, - // // }, - // // }) - // return - // } - // - // if (!prev) { - // return - // } - // - // const [prevAdapter] = prev - // if (prevAdapter && !wallet) { - // // props.notificationCallback?.onDisconnect({ - // // publicKey: prevPubKey?.toString() || "", - // // shortAddress: shortenAddress(prevPubKey?.toString() || ""), - // // walletName: prevAdapter.name || "", - // // metadata: { - // // name: prevAdapter.name, - // // url: prevAdapter.url, - // // icon: prevAdapter.icon, - // // supportedTransactionVersions: prevAdapter.supportedTransactionVersions, - // // }, - // // }) - // } - // }), - // ) + createEffect(() => { + console.log("unified modal wallet store changed: ", { wallets: wallets() }) + }) + + createEffect( + on([wallet, publicKey], ([wallet, pubKey]) => { + if (!pubKey || !wallet) { + return + } + const prevConnected = getPreviouslyConnected() + + // make sure the most recently connected wallet is first + const combined = new Set([wallet.name, ...prevConnected]) + setPreviouslyConnected([...combined]) + }), + ) return { t, locale, setLocale, theme: props.theme, - env: props.env, - metadata: props.metadata, + // metadata: props.metadata, getPreviouslyConnected, setPreviouslyConnected, @@ -258,22 +218,12 @@ const [_UnifiedWalletProvider, _useUnifiedWallet] = createContextProvider( export type UnifiedWalletModalProps = { isExpanded?: boolean } -declare module "solid-js" { - namespace JSX { - interface IntrinsicElements { - "unified-wallet-modal": UnifiedWalletProviderProps - } - interface IntrinsicElements { - "unified-wallet-modal-button": UnifiedWalletButtonProps - } - } -} const UnifiedWalletModalProvider: Component = props => { return ( <> - <_UnifiedWalletProvider {...props.config}> + <_UnifiedWalletProvider> diff --git a/packages/unified/src/custom-element.tsx b/packages/unified/src/custom-element.tsx index eb94413..9ec34bb 100644 --- a/packages/unified/src/custom-element.tsx +++ b/packages/unified/src/custom-element.tsx @@ -3,7 +3,7 @@ import { customElement, noShadowDOM } from "solid-element" import { UnifiedWalletModalProvider, UnifiedWalletProviderProps } from "./contexts" import { UnifiedWalletButton, UnifiedWalletButtonProps } from "./components" -export function loadCustomElements() { +function loadCustomElements() { customElement("unified-wallet-modal", (props: UnifiedWalletProviderProps, {}) => { noShadowDOM() // ... Solid code return @@ -13,3 +13,6 @@ export function loadCustomElements() { return }) } + +export { loadCustomElements } +export type { UnifiedWalletButtonProps, UnifiedWalletProviderProps }