From 66cae0940cc47cb52938d4600f7cc9a8ed80fb9b Mon Sep 17 00:00:00 2001 From: Rui Santiago Date: Wed, 26 Jun 2024 13:36:18 +0100 Subject: [PATCH] [feature]: add bitte-wallet in wallet package (#517) Co-authored-by: Ruben Marcus --- package-lock.json | 56 ++-- packages/auth/package.json | 2 +- packages/auth/src/bitte-wallet.ts | 225 +++++++++++++ packages/auth/src/index.ts | 1 + packages/react/package.json | 2 +- packages/react/src/BitteWalletContext.tsx | 194 +++++++++++ packages/react/src/index.ts | 1 + packages/react/src/wallet/bitte-wallet.ts | 237 +++++++++++++ packages/wallet/pnpm-lock.yaml | 388 ++++++++++++++++++++++ packages/wallet/src/bitte-wallet-setup.ts | 54 +++ packages/wallet/src/bitte-wallet.ts | 341 +++++++++++++++++++ packages/wallet/src/index.ts | 1 + packages/wallet/src/utils.ts | 19 +- 13 files changed, 1498 insertions(+), 23 deletions(-) create mode 100644 packages/auth/src/bitte-wallet.ts create mode 100644 packages/react/src/BitteWalletContext.tsx create mode 100644 packages/react/src/wallet/bitte-wallet.ts create mode 100644 packages/wallet/pnpm-lock.yaml create mode 100644 packages/wallet/src/bitte-wallet-setup.ts create mode 100644 packages/wallet/src/bitte-wallet.ts diff --git a/package-lock.json b/package-lock.json index 813aa3232..f3ee6b941 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26532,11 +26532,11 @@ }, "packages/auth": { "name": "@mintbase-js/auth", - "version": "0.6.0-beta.6", + "version": "0.6.0-beta-prerelease.1", "license": "MIT", "dependencies": { "@mintbase-js/sdk": "0.5.2-beta.0", - "@mintbase-js/wallet": "0.5.2-beta.0", + "@mintbase-js/wallet": "0.6.0-add-bitte-wallet-setup-f054e86.0", "@near-wallet-selector/core": "8.9.3", "@near-wallet-selector/here-wallet": "8.9.3", "@near-wallet-selector/ledger": "8.9.3", @@ -26566,13 +26566,31 @@ } }, "packages/auth/node_modules/@mintbase-js/wallet": { - "version": "0.5.2-beta.0", - "resolved": "https://registry.npmjs.org/@mintbase-js/wallet/-/wallet-0.5.2-beta.0.tgz", - "integrity": "sha512-8byHdddYMTn/QbrTV9Nhk7HocDSPxuye30W3e3TitgR8PvWXiY/mnj0QstiV9Cb+82dssH5vSrNQ1cUPet7xPw==", + "version": "0.6.0-add-bitte-wallet-setup-f054e86.0", + "resolved": "https://registry.npmjs.org/@mintbase-js/wallet/-/wallet-0.6.0-add-bitte-wallet-setup-f054e86.0.tgz", + "integrity": "sha512-oAI+PhVDrEYsNPaOcOCIM1ZPMmZun//wjlfwV+Bf6IEXlCNbWnB5sNPIHQX7lSo0Cn4DD0dH0S/VmSDlSLPVsw==", "dependencies": { - "@near-wallet-selector/core": "^8.5.4", + "@near-wallet-selector/core": "8.9.5", + "@near-wallet-selector/wallet-utils": "^8.9.5", "bn.js": "^5.2.1", - "near-api-js": "^2.1.4" + "near-api-js": "^2.1.3" + }, + "peerDependencies": { + "near-api-js": "^2.0.0" + } + }, + "packages/auth/node_modules/@mintbase-js/wallet/node_modules/@near-wallet-selector/core": { + "version": "8.9.5", + "resolved": "https://registry.npmjs.org/@near-wallet-selector/core/-/core-8.9.5.tgz", + "integrity": "sha512-wJiCL8M7z6tkNMY5H4n63/SZCmlW0Z15H6R1biWgpRuMDlVjhQOzxrmQggb1jbK4nYkzXyARNKyPh2gcRUuS+w==", + "dependencies": { + "borsh": "0.7.0", + "events": "3.3.0", + "js-sha256": "0.9.0", + "rxjs": "7.8.1" + }, + "peerDependencies": { + "near-api-js": "^1.0.0 || ^2.0.0" } }, "packages/auth/node_modules/@near-wallet-selector/core": { @@ -26591,7 +26609,7 @@ }, "packages/data": { "name": "@mintbase-js/data", - "version": "0.6.0-beta.6", + "version": "0.6.0-beta-prerelease.1", "license": "MIT", "dependencies": { "@mintbase-js/sdk": "^0.5.2-beta.0", @@ -26615,10 +26633,10 @@ }, "packages/react": { "name": "@mintbase-js/react", - "version": "0.6.0-beta.6", + "version": "0.6.0-beta-prerelease.1", "license": "MIT", "dependencies": { - "@mintbase-js/wallet": "0.6.0-beta.3", + "@mintbase-js/wallet": "0.6.0-add-bitte-wallet-setup-ac02910.0", "@near-wallet-selector/core": "8.9.3", "@near-wallet-selector/here-wallet": "8.9.3", "@near-wallet-selector/meteor-wallet": "8.9.3", @@ -26633,9 +26651,9 @@ } }, "packages/react/node_modules/@mintbase-js/wallet": { - "version": "0.6.0-beta.3", - "resolved": "https://registry.npmjs.org/@mintbase-js/wallet/-/wallet-0.6.0-beta.3.tgz", - "integrity": "sha512-fzkqS3LDnSHB4YG0ync8JRuv+xQlMOzcu0btpAN8SC1hZNGUak0BTXBiNxKZQTeuH/DL+6ghh+t9VzJknOorhA==", + "version": "0.6.0-add-bitte-wallet-setup-ac02910.0", + "resolved": "https://registry.npmjs.org/@mintbase-js/wallet/-/wallet-0.6.0-add-bitte-wallet-setup-ac02910.0.tgz", + "integrity": "sha512-wXf/oagWjbSVDqBzvURjHuT8zVqGefqdh/OY/T3Zvx34b6Z06BnEpKtBc8gJJNBmNhHTvqA4ldTmvf7zSDxI8A==", "dependencies": { "@near-wallet-selector/core": "8.9.5", "@near-wallet-selector/wallet-utils": "^8.9.5", @@ -26676,7 +26694,7 @@ }, "packages/rpc": { "name": "@mintbase-js/rpc", - "version": "0.6.0-beta.6", + "version": "0.6.0-beta-prerelease.1", "license": "MIT", "dependencies": { "@mintbase-js/sdk": "0.5.6-rpc-fix-1aec566.0", @@ -26699,7 +26717,7 @@ }, "packages/sdk": { "name": "@mintbase-js/sdk", - "version": "0.6.0-beta.6", + "version": "0.6.0-beta-prerelease.1", "license": "MIT", "dependencies": { "bn.js": "5.2.1", @@ -26726,7 +26744,7 @@ }, "packages/storage": { "name": "@mintbase-js/storage", - "version": "0.6.0-beta.6", + "version": "0.6.0-beta-prerelease.1", "license": "MIT", "dependencies": { "@mintbase-js/sdk": "0.5.2-beta.0", @@ -26761,12 +26779,12 @@ }, "packages/testing": { "name": "@mintbase-js/testing", - "version": "0.6.0-beta.6", + "version": "0.6.0-beta-prerelease.1", "license": "MIT", "dependencies": { "@google-cloud/firestore": "^6.8.0", "@google-cloud/secret-manager": "^4.2.2", - "@mintbase-js/auth": "^0.6.0-beta.6", + "@mintbase-js/auth": "^0.6.0-beta-prerelease.1", "@mintbase-js/sdk": "^0.3.2-upgrade-packages-3378beb.0", "graphql-request": "^5.2.0" }, @@ -26836,7 +26854,7 @@ }, "packages/wallet": { "name": "@mintbase-js/wallet", - "version": "0.6.0-beta.6", + "version": "0.6.0-beta-prerelease.1", "license": "MIT", "dependencies": { "@near-wallet-selector/core": "8.9.5", diff --git a/packages/auth/package.json b/packages/auth/package.json index 1d0bef558..b3632891a 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -20,7 +20,7 @@ "license": "MIT", "dependencies": { "@mintbase-js/sdk": "0.5.2-beta.0", - "@mintbase-js/wallet": "0.5.2-beta.0", + "@mintbase-js/wallet": "0.6.0-add-bitte-wallet-setup-f054e86.0", "@near-wallet-selector/core": "8.9.3", "@near-wallet-selector/here-wallet": "8.9.3", "@near-wallet-selector/ledger": "8.9.3", diff --git a/packages/auth/src/bitte-wallet.ts b/packages/auth/src/bitte-wallet.ts new file mode 100644 index 000000000..f554b278f --- /dev/null +++ b/packages/auth/src/bitte-wallet.ts @@ -0,0 +1,225 @@ +import { + setupWalletSelector, + VerifiedOwner, + VerifyOwnerParams, + Wallet, +} from '@near-wallet-selector/core'; +import { setupModal } from '@near-wallet-selector/modal-ui'; +import { map, distinctUntilChanged, Subscription } from 'rxjs'; + +import { + WALLET_CONNECTION_POLL_INTERVAL, + WALLET_CONNECTION_TIMEOUT, +} from './constants'; + +import type { + WalletSelector, + AccountState, + WalletModuleFactory, +} from '@near-wallet-selector/core'; +import type { WalletSelectorModal } from '@near-wallet-selector/modal-ui'; +import { SUPPORTED_NEAR_WALLETS } from './wallets.setup'; +import { ERROR_MESSAGES } from './errorMessages'; +import { mbjs } from '@mintbase-js/sdk'; +import { setupBitteWallet } from '@mintbase-js/wallet'; +import { ConnectionTimeoutError } from './wallet'; + +// mintbase SDK wallet functionality wraps +// Near Wallet Selector lib, provided by NEAR Protocol +// https://github.com/near/wallet-selector/ + +export type WalletSelectorComponents = { + selector: WalletSelector; + modal: WalletSelectorModal; +} + + +const walletUrls = { + testnet: 'https://testnet.wallet.bitte.ai/', + mainnet: 'https://wallet.bitte.ai', +}; + +export const BitteWalletAuth = { + walletSelectorComponents: { + selector: null, + modal: null, + }, + setupBitteWalletSelector: async ( + callbackUrl, + onlyMbWallet = false, + network?, + contractAddress?, + options?: { additionalWallets?: Array }, + ): Promise => { + + + if (onlyMbWallet === false) { + + BitteWalletAuth.walletSelectorComponents.selector = await setupWalletSelector({ + network: network, + debug: mbjs.keys.debugMode, + modules: [ + setupBitteWallet({ + walletUrl: walletUrls[network], + deprecated: false, + callbackUrl: callbackUrl, + }), + ...(options?.additionalWallets || []), + ...SUPPORTED_NEAR_WALLETS, + ], + }); + } else { + BitteWalletAuth.walletSelectorComponents.selector = await setupWalletSelector({ + network: network, + debug: mbjs.keys.debugMode, + modules: [ + setupBitteWallet({ + walletUrl: walletUrls[network], + deprecated: false, + callbackUrl: callbackUrl, + }), + ...(options?.additionalWallets || []), + ], + }); + } + + BitteWalletAuth.walletSelectorComponents.modal = setupModal( BitteWalletAuth.walletSelectorComponents.selector, { + contractId: contractAddress, + }); + + return BitteWalletAuth.walletSelectorComponents; + }, + setupWalletSelectorComponents: async ( + network?, + contractAddress?, + options?: { additionalWallets?: Array }, + ): Promise => { + const selector = await setupWalletSelector({ + network: network, + debug: mbjs.keys.debugMode, + modules: [ + ...SUPPORTED_NEAR_WALLETS, + ...(options?.additionalWallets || []), + ], + }); + + const modal = setupModal(selector, { + contractId: contractAddress, + }); + + BitteWalletAuth.walletSelectorComponents = { + selector, + modal, + }; + return BitteWalletAuth.walletSelectorComponents; + }, + SetupNotCalledError: class extends Error { + constructor(message?: string) { + super(message); + this.name = 'SetupNotCalledError'; + } + }, + ConnectionTimeoutError: class extends Error { + message: string + }, + validateWalletComponentsAreSetup:(): void => { + if (!BitteWalletAuth.walletSelectorComponents.selector) { + throw new BitteWalletAuth.SetupNotCalledError(ERROR_MESSAGES.WALLET_SETUP_NOT_CALLED_ERROR); + } + }, + registerWalletAccountsSubscriber: ( + callback: (accounts: AccountState[]) => void, + ): Subscription => { + BitteWalletAuth.validateWalletComponentsAreSetup(); + + return BitteWalletAuth.walletSelectorComponents.selector.store.observable + .pipe( + map((state:any) => state.accounts), + distinctUntilChanged(), + ) + .subscribe(callback); + }, + timerReference: null, + pollForWalletConnection: async (): Promise => { + BitteWalletAuth.validateWalletComponentsAreSetup(); + // clear any existing timer + clearTimeout(BitteWalletAuth.timerReference); + + const tryToResolveAccountsFromState = ( + resolve: (value: AccountState[]) => void, + reject: (err: ConnectionTimeoutError) => void, + elapsed = 0, + ): void => { + const { accounts } = + BitteWalletAuth.walletSelectorComponents.selector.store.getState() || {}; + + // accounts present in state + if (accounts) { + resolve(accounts); + } + + // timed out + if (elapsed > WALLET_CONNECTION_TIMEOUT) { + reject( + new ConnectionTimeoutError(ERROR_MESSAGES.WALLET_CONNECTION_NOT_FOUND), + ); + } + + // try again + clearTimeout(BitteWalletAuth.timerReference); + BitteWalletAuth.timerReference = setTimeout( + () => + tryToResolveAccountsFromState( + resolve, + reject, + elapsed + WALLET_CONNECTION_POLL_INTERVAL, + ), + WALLET_CONNECTION_POLL_INTERVAL, + ); + }; + + return new Promise((resolve, reject) => + tryToResolveAccountsFromState(resolve, reject), + ); + }, + getWallet: async (): Promise => { + BitteWalletAuth.validateWalletComponentsAreSetup(); + + return await BitteWalletAuth.walletSelectorComponents.selector.wallet(); + }, + connectWalletSelector:(): void => { + BitteWalletAuth.validateWalletComponentsAreSetup(); + + BitteWalletAuth.walletSelectorComponents.modal.show(); + }, + disconnectFromWalletSelector: async (): Promise => { + BitteWalletAuth.validateWalletComponentsAreSetup(); + + const wallet = await BitteWalletAuth.walletSelectorComponents.selector.wallet(); + wallet.signOut(); + }, + getVerifiedOwner: async ( + params: VerifyOwnerParams, + ): Promise => { + BitteWalletAuth.validateWalletComponentsAreSetup(); + + const { message, callbackUrl, meta } = params; + + const wallet = await BitteWalletAuth.walletSelectorComponents.selector.wallet(); + + const owner = (await wallet.verifyOwner({ + message: message, + callbackUrl: callbackUrl, + meta: meta, + })) as VerifiedOwner; + + return owner; + }, + signMessage: async ( + params: VerifyOwnerParams, + ): Promise => { + const owner = await BitteWalletAuth.getVerifiedOwner(params); + + return owner; + }, +}; diff --git a/packages/auth/src/index.ts b/packages/auth/src/index.ts index d31755551..3f672f5ff 100644 --- a/packages/auth/src/index.ts +++ b/packages/auth/src/index.ts @@ -1,5 +1,6 @@ export * from './account'; export * from './wallet'; +export { BitteWalletAuth } from './bitte-wallet'; export * from './constants'; // this is done to avoid importing near-api-js more than once diff --git a/packages/react/package.json b/packages/react/package.json index 5ce45352f..000abee8f 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -23,7 +23,7 @@ "@testing-library/user-event": "^14.5.2" }, "dependencies": { - "@mintbase-js/wallet": "0.6.0-beta.3", + "@mintbase-js/wallet": "0.6.0-add-bitte-wallet-setup-ac02910.0", "@near-wallet-selector/core": "8.9.3", "@near-wallet-selector/here-wallet": "8.9.3", "@near-wallet-selector/meteor-wallet": "8.9.3", diff --git a/packages/react/src/BitteWalletContext.tsx b/packages/react/src/BitteWalletContext.tsx new file mode 100644 index 000000000..576af4033 --- /dev/null +++ b/packages/react/src/BitteWalletContext.tsx @@ -0,0 +1,194 @@ +import React, { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useState, +} from 'react'; +import { +BitteWalletAuth +} from './wallet/bitte-wallet'; +import type { WalletSelectorComponents } from './wallet/bitte-wallet'; + +import type { + WalletSelector, + AccountState, + VerifiedOwner, + VerifyOwnerParams, + WalletModuleFactory, +} from '@near-wallet-selector/core'; +import type { WalletSelectorModal } from '@near-wallet-selector/modal-ui'; + +export type BitteWalletContext = { + selector: WalletSelector; + modal: WalletSelectorModal; + accounts: AccountState[]; + activeAccountId: string | null; + isConnected: boolean; + isWaitingForConnection: boolean; + isWalletSelectorSetup: boolean; + errorMessage: string | null; + connect: () => Promise; + disconnect: () => Promise; + signMessage: (params: VerifyOwnerParams) => Promise; +} + +interface ContextProviderType { + children: React.ReactNode; + callbackUrl?: string; + network?: string; onlyMbWallet?: boolean; + contractAddress?: string; + additionalWallets?: Array; + successUrl?: string; + failureUrl?: string; +} + + +export const BitteWalletContext = createContext(null); + +export const BitteWalletContextProvider: React.FC = ({ + children, + network, + contractAddress, + additionalWallets, + onlyMbWallet, + callbackUrl, + successUrl, + failureUrl, +}): JSX.Element => { + const [errorMessage, setErrorMessage] = useState(''); + const [components, setComponents] = useState( + null, + ); + const [accounts, setAccounts] = useState([]); + const [isWaitingForConnection, setIsWaitingForConnection] = + useState(false); + const [isWalletSelectorSetup, setIsWalletSelectorSetup] = + useState(false); + + + const selectedNetwork = network; + const selectedContract = contractAddress; + + const { setupBitteWalletSelector, registerWalletAccountsSubscriber, connectWalletSelector, pollForWalletConnection, disconnectFromWalletSelector, signMessage } = BitteWalletAuth + + const setupMbWallet = async (): Promise => { + const isOnlyMbWallet = !!onlyMbWallet || !!(additionalWallets && additionalWallets.length > 0); + + return await setupBitteWalletSelector( + callbackUrl, + isOnlyMbWallet, + selectedNetwork, + selectedContract, + isOnlyMbWallet ? { additionalWallets } : undefined, + successUrl, failureUrl, + ); + }; + + const setup = useCallback(async () => { + const components = await setupMbWallet(); + + setIsWalletSelectorSetup(true); + setComponents(components); + }, []); + + const onCloseModal = (): void => { + setIsWaitingForConnection(false); + }; + + const setupWallet = async (): Promise => { + const components = await setupMbWallet(); + + return components; + }; + + // call setup on wallet selector + + + useEffect(() => { + setupWallet(); + + setup().catch((err: Error) => { + if (err || err.message.length > 0) { + setErrorMessage((err as Error).message); + } + }); + + // Add the event listener here + const closeButton = document?.getElementsByClassName('close-button')[0]; + closeButton?.addEventListener('click', onCloseModal); + + // Cleanup the event listener on unmount + return (): void => { + closeButton?.removeEventListener('click', onCloseModal); + }; + }, [setup]); + + // subscribe to account state changes + useEffect(() => { + if (!components) { + return undefined; + } + + const subscription = registerWalletAccountsSubscriber( + (accounts: AccountState[]) => { + setAccounts(accounts); + }, + ); + + return (): void => { + subscription.unsubscribe(); + }; + }, [components]); + + const { selector, modal } = components || {}; + + const connect = async (): Promise => { + setIsWaitingForConnection(true); + + setErrorMessage(null); + connectWalletSelector(); + + try { + const accounts = await pollForWalletConnection(); + setIsWaitingForConnection(false); + setAccounts(accounts); + } catch (err: unknown) { + if (err) { + setErrorMessage((err as Error).message); + } + } + }; + + const disconnect = async (): Promise => { + await disconnectFromWalletSelector(); + setIsWaitingForConnection(false); + }; + + const contextVal = useMemo( + () => ({ + selector: selector, + modal: modal, + accounts: accounts, + activeAccountId: + accounts.find((account) => account.active)?.accountId || null, + isConnected: accounts && accounts.length > 0, + isWaitingForConnection: isWaitingForConnection, + isWalletSelectorSetup: isWalletSelectorSetup, + errorMessage: errorMessage, + connect, + disconnect, + signMessage, + }), + [selector, modal, accounts], + ); + + return ( + + {children} + + ); +}; + +export const useBitteWallet = (): BitteWalletContext => useContext(BitteWalletContext); diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 934f85cdd..9384fd9ff 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -1,3 +1,4 @@ export * from './MintbaseWalletContext'; +export * from './BitteWalletContext'; export * from './hooks/useNearPrice'; export { Wallet, WalletModuleFactory, WalletModule, WalletBehaviourFactory, BrowserWallet, Account } from '@near-wallet-selector/core'; diff --git a/packages/react/src/wallet/bitte-wallet.ts b/packages/react/src/wallet/bitte-wallet.ts new file mode 100644 index 000000000..57596268d --- /dev/null +++ b/packages/react/src/wallet/bitte-wallet.ts @@ -0,0 +1,237 @@ +import { + setupWalletSelector, + VerifiedOwner, + VerifyOwnerParams, + Wallet, +} from '@near-wallet-selector/core'; +import { setupModal } from '@near-wallet-selector/modal-ui'; +import { map, distinctUntilChanged, Subscription } from 'rxjs'; + +import { + WALLET_CONNECTION_POLL_INTERVAL, + WALLET_CONNECTION_TIMEOUT, +} from './constants'; + +import type { + WalletSelector, + AccountState, + WalletModuleFactory, +} from '@near-wallet-selector/core'; +import type { WalletSelectorModal } from '@near-wallet-selector/modal-ui'; + +import { mbjs } from '@mintbase-js/sdk'; +import { setupBitteWallet } from '@mintbase-js/wallet'; +import { ConnectionTimeoutError } from './wallet'; + +import { setupMeteorWallet } from '@near-wallet-selector/meteor-wallet'; +import { setupHereWallet } from '@near-wallet-selector/here-wallet'; +import { setupMyNearWallet } from '@near-wallet-selector/my-near-wallet'; + +const SUPPORT = '- further help available on our telegram channel: https://t.me/mintdev'; + +export const ERROR_MESSAGES = { + WALLET_SETUP_NOT_CALLED_ERROR: `Call and await setupWalletSelectorComponents() before registering a subscriber - ${SUPPORT}`, + WALLET_CONNECTION_NOT_FOUND: `Wallet connection not received after ${WALLET_CONNECTION_TIMEOUT}ms - ${SUPPORT}`, +}; + +export const SUPPORTED_NEAR_WALLETS: Array =[ + setupMeteorWallet(), + setupMyNearWallet(), + setupHereWallet(), +]; + +export type WalletSelectorComponents = { + selector: WalletSelector; + modal: WalletSelectorModal; +} + +const walletUrls = { + testnet: 'https://testnet.wallet.bitte.ai/', + mainnet: 'https://wallet.bitte.ai', +}; + +export const BitteWalletAuth = { + walletSelectorComponents: { + selector: null, + modal: null, + }, + setupBitteWalletSelector: async ( + callbackUrl, + onlyMbWallet = false, + network?, + contractAddress?, + options?: { additionalWallets?: Array }, + successUrl?: string, + failureUrl?: string, + ): Promise => { + + if (onlyMbWallet === false) { + BitteWalletAuth.walletSelectorComponents.selector = await setupWalletSelector({ + network: network, + modules: [ + setupBitteWallet({ + walletUrl: walletUrls[network], + callbackUrl: callbackUrl, + successUrl: successUrl || window.location.href, + failureUrl: successUrl || window.location.href, + contractId: contractAddress, + }), + ...(options?.additionalWallets || []), + ...SUPPORTED_NEAR_WALLETS, + ], + }); + } else { + BitteWalletAuth.walletSelectorComponents.selector = await setupWalletSelector({ + + network: network, + modules: [ + setupBitteWallet({ + walletUrl: walletUrls[network], + callbackUrl: callbackUrl, + contractId: contractAddress, + }), + ...(options?.additionalWallets || []), + ], + }); + } + + BitteWalletAuth.walletSelectorComponents.modal = setupModal(BitteWalletAuth.walletSelectorComponents.selector, { + contractId: contractAddress, + }); + + return BitteWalletAuth.walletSelectorComponents; + }, + setupWalletSelectorComponents: async ( + network?, + contractAddress?, + options?: { additionalWallets?: Array }, + ): Promise => { + const selector = await setupWalletSelector({ + network: network, + debug: mbjs.keys.debugMode, + modules: [ + ...SUPPORTED_NEAR_WALLETS, + ...(options?.additionalWallets || []), + ], + }); + + const modal = setupModal(selector, { + contractId: contractAddress, + }); + + BitteWalletAuth.walletSelectorComponents = { + selector, + modal, + }; + return BitteWalletAuth.walletSelectorComponents; + }, + SetupNotCalledError: class extends Error { + constructor(message?: string) { + super(message); + this.name = 'SetupNotCalledError'; + } + }, + ConnectionTimeoutError: class extends Error { + message: string + }, + validateWalletComponentsAreSetup:(): void => { + if (!BitteWalletAuth.walletSelectorComponents.selector) { + throw new BitteWalletAuth.SetupNotCalledError(ERROR_MESSAGES.WALLET_SETUP_NOT_CALLED_ERROR); + } + }, + registerWalletAccountsSubscriber: ( + callback: (accounts: AccountState[]) => void, + ): Subscription => { + BitteWalletAuth.validateWalletComponentsAreSetup(); + + return BitteWalletAuth.walletSelectorComponents.selector.store.observable + .pipe( + map((state:any) => state.accounts), + distinctUntilChanged(), + ) + .subscribe(callback); + }, + timerReference: null, + pollForWalletConnection: async (): Promise => { + BitteWalletAuth.validateWalletComponentsAreSetup(); + // clear any existing timer + clearTimeout(BitteWalletAuth.timerReference); + + const tryToResolveAccountsFromState = ( + resolve: (value: AccountState[]) => void, + reject: (err: ConnectionTimeoutError) => void, + elapsed = 0, + ): void => { + const { accounts } = + BitteWalletAuth.walletSelectorComponents.selector.store.getState() || {}; + + // accounts present in state + if (accounts) { + resolve(accounts); + } + + // timed out + if (elapsed > WALLET_CONNECTION_TIMEOUT) { + reject( + new ConnectionTimeoutError(ERROR_MESSAGES.WALLET_CONNECTION_NOT_FOUND), + ); + } + + // try again + clearTimeout(BitteWalletAuth.timerReference); + BitteWalletAuth.timerReference = setTimeout( + () => + tryToResolveAccountsFromState( + resolve, + reject, + elapsed + WALLET_CONNECTION_POLL_INTERVAL, + ), + WALLET_CONNECTION_POLL_INTERVAL, + ); + }; + + return new Promise((resolve, reject) => + tryToResolveAccountsFromState(resolve, reject), + ); + }, + getWallet: async (): Promise => { + BitteWalletAuth.validateWalletComponentsAreSetup(); + + return await BitteWalletAuth.walletSelectorComponents.selector.wallet(); + }, + connectWalletSelector:(): void => { + BitteWalletAuth.validateWalletComponentsAreSetup(); + + BitteWalletAuth.walletSelectorComponents.modal.show(); + }, + disconnectFromWalletSelector: async (): Promise => { + BitteWalletAuth.validateWalletComponentsAreSetup(); + + const wallet = await BitteWalletAuth.walletSelectorComponents.selector.wallet(); + wallet.signOut(); + }, + getVerifiedOwner: async ( + params: VerifyOwnerParams, + ): Promise => { + BitteWalletAuth.validateWalletComponentsAreSetup(); + + const { message, callbackUrl, meta } = params; + + const wallet = await BitteWalletAuth.walletSelectorComponents.selector.wallet(); + + const owner = (await wallet.verifyOwner({ + message: message, + callbackUrl: callbackUrl, + meta: meta, + })) as VerifiedOwner; + + return owner; + }, + signMessage: async ( + params: VerifyOwnerParams, + ): Promise => { + const owner = await BitteWalletAuth.getVerifiedOwner(params); + + return owner; + }, +}; diff --git a/packages/wallet/pnpm-lock.yaml b/packages/wallet/pnpm-lock.yaml new file mode 100644 index 000000000..f6ae40f82 --- /dev/null +++ b/packages/wallet/pnpm-lock.yaml @@ -0,0 +1,388 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +dependencies: + '@near-wallet-selector/core': + specifier: 8.9.5 + version: 8.9.5(near-api-js@2.1.3) + '@near-wallet-selector/wallet-utils': + specifier: ^8.9.5 + version: 8.9.5(near-api-js@2.1.3) + bn.js: + specifier: ^5.2.1 + version: 5.2.1 + near-api-js: + specifier: ^2.1.3 + version: 2.1.3 + +packages: + + /@near-js/accounts@0.1.3: + resolution: {integrity: sha512-rmS1/WwIAWlfSMxHlDN3Q0YLOAscfrU+fkg9PsNI0sdzvdJ+bmiFqAoXi6L3D3KWZemteIudVEXMcegjreHnMg==} + dependencies: + '@near-js/crypto': 0.0.4 + '@near-js/providers': 0.0.6 + '@near-js/signers': 0.0.4 + '@near-js/transactions': 0.2.0 + '@near-js/types': 0.0.4 + '@near-js/utils': 0.0.4 + ajv: 8.16.0 + ajv-formats: 2.1.1(ajv@8.16.0) + bn.js: 5.2.1 + borsh: 0.7.0 + depd: 2.0.0 + near-abi: 0.1.1 + transitivePeerDependencies: + - encoding + dev: false + + /@near-js/crypto@0.0.4: + resolution: {integrity: sha512-2mSIVv6mZway1rQvmkktrXAFoUvy7POjrHNH3LekKZCMCs7qMM/23Hz2+APgxZPqoV2kjarSNOEYJjxO7zQ/rQ==} + dependencies: + '@near-js/types': 0.0.4 + bn.js: 5.2.1 + borsh: 0.7.0 + tweetnacl: 1.0.3 + dev: false + + /@near-js/keystores-browser@0.0.4: + resolution: {integrity: sha512-bzwClm3jNlWJrb8wqMunP3rrcG1hS3rD58KKhDvHXy8Dtg9VVUgrfr3Csu9oTnjG+rAPZGOynunaoOQVqju/Aw==} + dependencies: + '@near-js/crypto': 0.0.4 + '@near-js/keystores': 0.0.4 + dev: false + + /@near-js/keystores-node@0.0.4: + resolution: {integrity: sha512-vOdVhAuQ8BVefEluj+TSNzjXHA/1xjEgK7pwBUA1kgpcY8/hZ0Jj4PcvPD17wQNSyP+NJF5H9ed3pP2h2VH+1A==} + dependencies: + '@near-js/crypto': 0.0.4 + '@near-js/keystores': 0.0.4 + dev: false + + /@near-js/keystores@0.0.4: + resolution: {integrity: sha512-+vKafmDpQGrz5py1liot2hYSjPGXwihveeN+BL11aJlLqZnWBgYJUWCXG+uyGjGXZORuy2hzkKK6Hi+lbKOfVA==} + dependencies: + '@near-js/crypto': 0.0.4 + '@near-js/types': 0.0.4 + dev: false + + /@near-js/providers@0.0.6: + resolution: {integrity: sha512-PgWCcgDgCAgnyxq2IPZD2vbpQzXt4XK4cN2SbUZsDwJkBaDQEozXMnyShG/Ie2eRoz5aD9dRHpdLDpTieAw5kA==} + dependencies: + '@near-js/transactions': 0.2.0 + '@near-js/types': 0.0.4 + '@near-js/utils': 0.0.4 + bn.js: 5.2.1 + borsh: 0.7.0 + http-errors: 1.8.1 + optionalDependencies: + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + dev: false + + /@near-js/signers@0.0.4: + resolution: {integrity: sha512-xCglo3U/WIGsz/izPGFMegS5Q3PxOHYB8a1E7RtVhNm5QdqTlQldLCm/BuMg2G/u1l1ZZ0wdvkqRTG9joauf3Q==} + dependencies: + '@near-js/crypto': 0.0.4 + '@near-js/keystores': 0.0.4 + js-sha256: 0.9.0 + dev: false + + /@near-js/transactions@0.2.0: + resolution: {integrity: sha512-ejcYkDz0tdQ40i/7ETV23fL5hp/pIiNXYmh4bNuZ9FjeowBODtlXGLqjG3wZbCygHCirJKilmVi5BtM+rh4ovQ==} + dependencies: + '@near-js/crypto': 0.0.4 + '@near-js/signers': 0.0.4 + '@near-js/types': 0.0.4 + '@near-js/utils': 0.0.4 + bn.js: 5.2.1 + borsh: 0.7.0 + js-sha256: 0.9.0 + dev: false + + /@near-js/types@0.0.4: + resolution: {integrity: sha512-8TTMbLMnmyG06R5YKWuS/qFG1tOA3/9lX4NgBqQPsvaWmDsa+D+QwOkrEHDegped0ZHQwcjAXjKML1S1TyGYKg==} + dependencies: + bn.js: 5.2.1 + dev: false + + /@near-js/utils@0.0.4: + resolution: {integrity: sha512-mPUEPJbTCMicGitjEGvQqOe8AS7O4KkRCxqd0xuE/X6gXF1jz1pYMZn4lNUeUz2C84YnVSGLAM0o9zcN6Y4hiA==} + dependencies: + '@near-js/types': 0.0.4 + bn.js: 5.2.1 + depd: 2.0.0 + mustache: 4.2.0 + dev: false + + /@near-js/wallet-account@0.0.6: + resolution: {integrity: sha512-oyxQM6tf2WG4it+8IMu0ZQ6pa4OQhF1o+Q33Rb2+4Mb1Fm+L7MO7PJoCPcveCIFYVPOjSVk0oyoz1KbE3y62gA==} + dependencies: + '@near-js/accounts': 0.1.3 + '@near-js/crypto': 0.0.4 + '@near-js/keystores': 0.0.4 + '@near-js/signers': 0.0.4 + '@near-js/transactions': 0.2.0 + '@near-js/types': 0.0.4 + '@near-js/utils': 0.0.4 + bn.js: 5.2.1 + borsh: 0.7.0 + transitivePeerDependencies: + - encoding + dev: false + + /@near-wallet-selector/core@8.9.5(near-api-js@2.1.3): + resolution: {integrity: sha512-wJiCL8M7z6tkNMY5H4n63/SZCmlW0Z15H6R1biWgpRuMDlVjhQOzxrmQggb1jbK4nYkzXyARNKyPh2gcRUuS+w==} + peerDependencies: + near-api-js: ^1.0.0 || ^2.0.0 + dependencies: + borsh: 0.7.0 + events: 3.3.0 + js-sha256: 0.9.0 + near-api-js: 2.1.3 + rxjs: 7.8.1 + dev: false + + /@near-wallet-selector/wallet-utils@8.9.5(near-api-js@2.1.3): + resolution: {integrity: sha512-TBeQheoizs4EQIGQPJxz44ZsxL4VbjLQJLlpDsNpwQfkxjcyThsZ19hOvcj5XZjwdJxwM10VBcf/qh1mKzv1uQ==} + peerDependencies: + near-api-js: ^1.0.0 || ^2.0.0 + dependencies: + '@near-wallet-selector/core': 8.9.5(near-api-js@2.1.3) + bn.js: 5.2.1 + near-api-js: 2.1.3 + dev: false + + /@types/json-schema@7.0.15: + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + dev: false + + /ajv-formats@2.1.1(ajv@8.16.0): + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + dependencies: + ajv: 8.16.0 + dev: false + + /ajv@8.16.0: + resolution: {integrity: sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==} + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + dev: false + + /base-x@3.0.9: + resolution: {integrity: sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /bn.js@5.2.1: + resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==} + dev: false + + /borsh@0.7.0: + resolution: {integrity: sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==} + dependencies: + bn.js: 5.2.1 + bs58: 4.0.1 + text-encoding-utf-8: 1.0.2 + dev: false + + /bs58@4.0.1: + resolution: {integrity: sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==} + dependencies: + base-x: 3.0.9 + dev: false + + /capability@0.2.5: + resolution: {integrity: sha512-rsJZYVCgXd08sPqwmaIqjAd5SUTfonV0z/gDJ8D6cN8wQphky1kkAYEqQ+hmDxTw7UihvBfjUVUSY+DBEe44jg==} + dev: false + + /depd@1.1.2: + resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} + engines: {node: '>= 0.6'} + dev: false + + /depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dev: false + + /error-polyfill@0.1.3: + resolution: {integrity: sha512-XHJk60ufE+TG/ydwp4lilOog549iiQF2OAPhkk9DdiYWMrltz5yhDz/xnKuenNwP7gy3dsibssO5QpVhkrSzzg==} + dependencies: + capability: 0.2.5 + o3: 1.0.3 + u3: 0.1.1 + dev: false + + /events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + dev: false + + /fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + dev: false + + /http-errors@1.8.1: + resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} + engines: {node: '>= 0.6'} + dependencies: + depd: 1.1.2 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 1.5.0 + toidentifier: 1.0.1 + dev: false + + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: false + + /js-sha256@0.9.0: + resolution: {integrity: sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==} + dev: false + + /json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + dev: false + + /mustache@4.2.0: + resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} + hasBin: true + dev: false + + /near-abi@0.1.1: + resolution: {integrity: sha512-RVDI8O+KVxRpC3KycJ1bpfVj9Zv+xvq9PlW1yIFl46GhrnLw83/72HqHGjGDjQ8DtltkcpSjY9X3YIGZ+1QyzQ==} + dependencies: + '@types/json-schema': 7.0.15 + dev: false + + /near-api-js@2.1.3: + resolution: {integrity: sha512-ggCQE/oGrrbr9dEtXZ9QU7XAf6RgISs+bfD7Q5I2QsQN45XgV85IA4c8KDLzo66u7FTX39gubKz3Ghieo6D7YA==} + dependencies: + '@near-js/accounts': 0.1.3 + '@near-js/crypto': 0.0.4 + '@near-js/keystores': 0.0.4 + '@near-js/keystores-browser': 0.0.4 + '@near-js/keystores-node': 0.0.4 + '@near-js/providers': 0.0.6 + '@near-js/signers': 0.0.4 + '@near-js/transactions': 0.2.0 + '@near-js/types': 0.0.4 + '@near-js/utils': 0.0.4 + '@near-js/wallet-account': 0.0.6 + ajv: 8.16.0 + ajv-formats: 2.1.1(ajv@8.16.0) + bn.js: 5.2.1 + borsh: 0.7.0 + depd: 2.0.0 + error-polyfill: 0.1.3 + http-errors: 1.8.1 + near-abi: 0.1.1 + node-fetch: 2.7.0 + tweetnacl: 1.0.3 + transitivePeerDependencies: + - encoding + dev: false + + /node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + dev: false + + /o3@1.0.3: + resolution: {integrity: sha512-f+4n+vC6s4ysy7YO7O2gslWZBUu8Qj2i2OUJOvjRxQva7jVjYjB29jrr9NCjmxZQR0gzrOcv1RnqoYOeMs5VRQ==} + dependencies: + capability: 0.2.5 + dev: false + + /punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + dev: false + + /require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + dev: false + + /rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + dependencies: + tslib: 2.6.3 + dev: false + + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: false + + /setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + dev: false + + /statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + dev: false + + /text-encoding-utf-8@1.0.2: + resolution: {integrity: sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==} + dev: false + + /toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + dev: false + + /tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + dev: false + + /tslib@2.6.3: + resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} + dev: false + + /tweetnacl@1.0.3: + resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==} + dev: false + + /u3@0.1.1: + resolution: {integrity: sha512-+J5D5ir763y+Am/QY6hXNRlwljIeRMZMGs0cT6qqZVVzzT3X3nFPXVyPOFRMOR4kupB0T8JnCdpWdp6Q/iXn3w==} + dev: false + + /uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + dependencies: + punycode: 2.3.1 + dev: false + + /webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + dev: false + + /whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + dev: false diff --git a/packages/wallet/src/bitte-wallet-setup.ts b/packages/wallet/src/bitte-wallet-setup.ts new file mode 100644 index 000000000..2b71713d8 --- /dev/null +++ b/packages/wallet/src/bitte-wallet-setup.ts @@ -0,0 +1,54 @@ + +import type { + BrowserWallet, + WalletModule, + WalletModuleFactory, +} from '@near-wallet-selector/core'; +import { BitteWallet } from './bitte-wallet'; +import { resolveBitteWallet } from './utils'; + + interface BitteWalletSetup { + callbackUrl?: string; + successUrl?: string; + walletUrl?: string; + failureUrl?: string; + deprecated?: boolean; + contractId?: string; + lak?: boolean; + } + + const icon = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iOTUiIGhlaWdodD0iOTUiIHZpZXdCb3g9IjAgMCA5NSA5NSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xLjc3NjE3IDU5LjE4MjZMNjIuNzIyNyA2My4zMTNDNjMuMTE4NSA2My40MzA5IDYzLjQ3ODggNjMuNjQ1NiA2My43NzA4IDYzLjkzNzdDNjQuMDYyOSA2NC4yMjk3IDY0LjI3NzUgNjQuNTg5OSA2NC4zOTU0IDY0Ljk4NThMNjguNTI1OSA3OC44NTU1QzY4LjY3ODcgNzkuMzY4NiA2OC45OTMyIDc5LjgxODYgNjkuNDIyNCA4MC4xMzg3QzY5Ljg1MTYgODAuNDU4NyA3MC4zNzI3IDgwLjYzMTYgNzAuOTA4MiA4MC42MzE2QzcxLjQ0MzYgODAuNjMxNiA3MS45NjQ3IDgwLjQ1ODcgNzIuMzkzOSA4MC4xMzg3QzcyLjgyMzEgNzkuODE4NiA3My4xMzc2IDc5LjM2ODYgNzMuMjkwNCA3OC44NTU1TDc3LjQyMDkgNjQuOTg1OEM3Ny41Mzg4IDY0LjU4OTkgNzcuNzUzNCA2NC4yMjk3IDc4LjA0NTUgNjMuOTM3N0M3OC4zMzc2IDYzLjY0NTYgNzguNjk3OCA2My40MzA5IDc5LjA5MzYgNjMuMzEzTDkyLjk2MzMgNTkuMTgyNkM5My40NzY0IDU5LjAyOTcgOTMuOTI2NSA1OC43MTUzIDk0LjI0NjYgNTguMjg2MUM5NC41NjY2IDU3Ljg1NjggOTQuNzM5NSA1Ny4zMzU3IDk0LjczOTUgNTYuODAwM0M5NC43Mzk1IDU2LjI2NDkgOTQuNTY2NiA1NS43NDM4IDk0LjI0NjYgNTUuMzE0NkM5My45MjY1IDU0Ljg4NTMgOTMuNDc2NCA1NC41NzA5IDkyLjk2MzMgNTQuNDE4MUw3OS4wOTM3IDUwLjI4NzZDNzguNjk3OCA1MC4xNjk3IDc4LjMzNzYgNDkuOTU1IDc4LjA0NTUgNDkuNjYzQzc3Ljc1MzUgNDkuMzcwOSA3Ny41Mzg4IDQ5LjAxMDcgNzcuNDIwOSA0OC42MTQ4TDczLjI5MDQgMzQuNzQ1MkM3My4xMzc2IDM0LjIzMiA3Mi44MjMyIDMzLjc4MTkgNzIuMzkzOSAzMy40NjE5QzcxLjk2NDcgMzMuMTQxOSA3MS40NDM2IDMyLjk2OSA3MC45MDgyIDMyLjk2OUM3MC4zNzI4IDMyLjk2OSA2OS44NTE3IDMzLjE0MTkgNjkuNDIyNCAzMy40NjE5QzY4Ljk5MzIgMzMuNzgxOSA2OC42Nzg4IDM0LjIzMiA2OC41MjYgMzQuNzQ1Mkw2NC4zOTU0IDQ4LjYxNDhDNjQuMjc3NiA0OS4wMTA3IDY0LjA2MjkgNDkuMzcwOSA2My43NzA4IDQ5LjY2M0M2My40Nzg4IDQ5Ljk1NSA2My4xMTg1IDUwLjE2OTcgNjIuNzIyNyA1MC4yODc2TDEuNzc2MTggNTQuNDE4MUMxLjI2MzA1IDU0LjU3MDkgMC44MTI5NTYgNTQuODg1MyAwLjQ5MjkyIDU1LjMxNDZDMC4xNzI4ODQgNTUuNzQzOCAyLjI0NzU1ZS0wNiA1Ni4yNjQ5IDAgNTYuODAwM0MtMi4yNDc1MWUtMDYgNTcuMzM1NyAwLjE3Mjg4NyA1Ny44NTY4IDAuNDkyOTIgNTguMjg2MUMwLjgxMjk1MyA1OC43MTUzIDEuMjYzMDMgNTkuMDI5NyAxLjc3NjE3IDU5LjE4MjZaIiBmaWxsPSJibGFjayIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTMyLjE2MzcgMjkuNDg2OEw0MC4zNTc4IDMxLjkyNzFDNDAuNTkxNyAzMS45OTY3IDQwLjgwNDUgMzIuMTIzNiA0MC45NzcgMzIuMjk2MUM0MS4xNDk2IDMyLjQ2ODcgNDEuMjc2NCAzMi42ODE1IDQxLjM0NjEgMzIuOTE1NEw0My43ODYzIDQxLjEwOTVDNDMuODc2NiA0MS40MTI2IDQ0LjA2MjQgNDEuNjc4NSA0NC4zMTYgNDEuODY3NkM0NC41Njk2IDQyLjA1NjcgNDQuODc3NCA0Mi4xNTg4IDQ1LjE5MzcgNDIuMTU4OEM0NS41MTAxIDQyLjE1ODggNDUuODE3OSA0Mi4wNTY3IDQ2LjA3MTUgNDEuODY3NkM0Ni4zMjUxIDQxLjY3ODUgNDYuNTEwOSA0MS40MTI2IDQ2LjYwMTEgNDEuMTA5NUw0OS4wNDE0IDMyLjkxNTRDNDkuMTExMSAzMi42ODE1IDQ5LjIzNzkgMzIuNDY4NyA0OS40MTA0IDMyLjI5NjFDNDkuNTgzIDMyLjEyMzYgNDkuNzk1OCAzMS45OTY3IDUwLjAyOTcgMzEuOTI3MUw1OC4yMjM4IDI5LjQ4NjhDNTguNTI2OSAyOS4zOTY2IDU4Ljc5MjggMjkuMjEwOCA1OC45ODE5IDI4Ljk1NzJDNTkuMTcxIDI4LjcwMzYgNTkuMjczMSAyOC4zOTU4IDU5LjI3MzEgMjguMDc5NEM1OS4yNzMxIDI3Ljc2MzEgNTkuMTcxIDI3LjQ1NTMgNTguOTgxOSAyNy4yMDE3QzU4Ljc5MjggMjYuOTQ4MSA1OC41MjY5IDI2Ljc2MjMgNTguMjIzOCAyNi42NzJMNTAuMDI5NiAyNC4yMzE3QzQ5Ljc5NTggMjQuMTYyMSA0OS41ODI5IDI0LjAzNTIgNDkuNDEwNCAyMy44NjI3QzQ5LjIzNzkgMjMuNjkwMiA0OS4xMTEgMjMuNDc3MyA0OS4wNDE0IDIzLjI0MzRMNDYuNjAxMSAxNS4wNDkzQzQ2LjUxMDggMTQuNzQ2MiA0Ni4zMjUgMTQuNDgwMyA0Ni4wNzE1IDE0LjI5MTJDNDUuODE3OSAxNC4xMDIxIDQ1LjUxIDE0IDQ1LjE5MzcgMTRDNDQuODc3NCAxNCA0NC41Njk1IDE0LjEwMjEgNDQuMzE1OSAxNC4yOTEyQzQ0LjA2MjMgMTQuNDgwMyA0My44NzY2IDE0Ljc0NjIgNDMuNzg2MyAxNS4wNDkzTDQxLjM0NiAyMy4yNDM0QzQxLjI3NjQgMjMuNDc3MyA0MS4xNDk1IDIzLjY5MDIgNDAuOTc3IDIzLjg2MjdDNDAuODA0NCAyNC4wMzUyIDQwLjU5MTYgMjQuMTYyMSA0MC4zNTc4IDI0LjIzMTdMMzIuMTYzNyAyNi42NzJDMzEuODYwNSAyNi43NjIyIDMxLjU5NDYgMjYuOTQ4IDMxLjQwNTUgMjcuMjAxNkMzMS4yMTY0IDI3LjQ1NTIgMzEuMTE0MyAyNy43NjMxIDMxLjExNDMgMjguMDc5NEMzMS4xMTQzIDI4LjM5NTggMzEuMjE2NCAyOC43MDM3IDMxLjQwNTUgMjguOTU3MkMzMS41OTQ2IDI5LjIxMDggMzEuODYwNSAyOS4zOTY2IDMyLjE2MzcgMjkuNDg2OFoiIGZpbGw9ImJsYWNrIi8+Cjwvc3ZnPgo='; + + export function setupBitteWallet({ + walletUrl = '', + deprecated = false, + successUrl = '', + failureUrl = '', + callbackUrl = '', + contractId = '', + }: BitteWalletSetup = {}): WalletModuleFactory { + + return async (moduleOptions): Promise | null> => { + + const wallet: WalletModule = { + id: 'bitte-wallet', + type: 'browser', + metadata: { + name: 'Bitte Wallet', + description: + 'NEAR wallet to store, buy, send and stake assets for DeFi.', + iconUrl: icon, + deprecated, + available: true, + successUrl, + failureUrl, + walletUrl: resolveBitteWallet(moduleOptions.options.network.networkId, walletUrl), + }, + init: (options) => { + return BitteWallet({ callback: callbackUrl, networkId: moduleOptions.options.network.networkId, successUrl, failureUrl, contractId, ...options }); + }, + }; + return wallet; + }; + } + \ No newline at end of file diff --git a/packages/wallet/src/bitte-wallet.ts b/packages/wallet/src/bitte-wallet.ts new file mode 100644 index 000000000..a9b104b50 --- /dev/null +++ b/packages/wallet/src/bitte-wallet.ts @@ -0,0 +1,341 @@ +import * as nearAPI from 'near-api-js'; + +import type { + Action, + BrowserWallet, + FinalExecutionOutcome, + WalletBehaviourFactory, +} from '@near-wallet-selector/core'; +import { getCallbackUrl } from './utils'; +import { createAction } from '@near-wallet-selector/wallet-utils'; + + +export enum TransactionSuccessEnum { + MINT = 'mint', + TRANSFER = 'transfer', + BURN = 'burn', + DEPLOY_STORE = 'deploy-store', + MAKE_OFFER = 'make-offer', + REVOKE_MINTER = 'revoke-minter', + ADD_MINTER = 'add-minter', + TRANSFER_STORE_OWNERSHIP = 'transfer-store-ownership', + AUCTION_LIST = 'list', + SIMPLE_SALE_LIST = 'simple-sale-list', + UNLIST = 'unlist', + TAKE_OFFER = 'take-offer', + WITHDRAW_OFFER = 'withdraw-offer', +} + +interface BitteWalletState { + wallet: nearAPI.WalletConnection; +} + +interface BitteWalletAccount { + accountId: string; + publicKey: string; +} + +export type CallBackArgs = { + args: object; + type: TransactionSuccessEnum; +} + + +export const BitteWallet: WalletBehaviourFactory< + BrowserWallet, + { + networkId: string; + callback: string; + successUrl?: string; + failureUrl?: string; + contractId?: string; + } +> = async ({ + metadata, + options, + successUrl, + failureUrl, + contractId, + callback, + networkId, +}) => { + + const setupWalletState = async (): Promise | null => { + if (typeof window !== undefined) { + const { connect, WalletConnection, keyStores } = nearAPI; + const connectionConfig = { + networkId: networkId, + keyStore: new keyStores.BrowserLocalStorageKeyStore(), + nodeUrl: options.network.nodeUrl, + walletUrl: metadata.walletUrl, + headers: {}, + }; + + const searchParams = new URL(window.location.href); + const acc = searchParams.searchParams.get('account_id'); + //make near-api-js not throw without lak + if (acc && !contractId) { + localStorage.setItem( + 'mintbase-wallet_wallet_auth_key', + JSON.stringify({ + accountId: acc as string, + allKeys: [], + }), + ); + } + + const nearConnection = await connect(connectionConfig); + const wallet = new WalletConnection(nearConnection, 'mintbase-wallet'); + localStorage.setItem('mintbase-wallet:callback_url', callback); + + return { + wallet, + }; + } + + return null; + }; + + const state = await setupWalletState(); + + let activeAccountId: string; + + const getAccountId = (): string => activeAccountId; + + const isSignedIn = async (): Promise => !!activeAccountId; + + const signIn = async (): Promise => { + const existingAccounts = await getAccounts(); + const href = encodeURI(window?.location?.href); + + if (existingAccounts.length) { + return existingAccounts; + } + + await state.wallet.requestSignIn({ + methodNames: [], + successUrl: successUrl || href, + failureUrl: failureUrl || href, + contractId: contractId, + }); + + return getAccounts(); + }; + + const signOut = async (): Promise => { + window.localStorage.removeItem('mintbase-wallet:account-data'); + + if (state.wallet.isSignedIn()) { + state.wallet.signOut(); + } + + return; + }; + + + const assertValidSigner = (signerId: string): void => { + if (signerId && signerId !== state.wallet.getAccountId()) { + throw new Error( + `Cannot sign transactions for ${signerId} while signed in as ${activeAccountId}`, + ); + } + }; + + const signAndSendTransactions = async ({ transactions, callbackUrl }): Promise => { + if (!state.wallet.isSignedIn()) { + throw new Error('Wallet not signed in'); + } + //// near-api-js code fails if no lak + // const { cbUrl } = getCallbackUrl(callbackUrl ?? ''); + + // return state.wallet.requestSignTransactions({ + // transactions: await transformTransactions(transactions), + // callbackUrl: cbUrl, + // }); + + const { cbUrl } = getCallbackUrl(callbackUrl ?? ''); + + for (const { signerId } of transactions) { + assertValidSigner(signerId); + } + const stringifiedParam = JSON.stringify(transactions); + + const urlParam = encodeURIComponent(stringifiedParam); + const newUrl = new URL(`${metadata.walletUrl}/sign-transaction`); + newUrl.searchParams.set('transactions_data', urlParam); + newUrl.searchParams.set('callback_url', cbUrl); + + window.location.assign(newUrl.toString()); + return; + }; + + const signAndSendTransaction = async ({ + receiverId, + actions, + signerId, + callbackUrl, + }: { + receiverId: string; + actions: Array; + signerId: string; + callbackUrl: string; + }): Promise => { + assertValidSigner(signerId); + + if (!receiverId && !contractId) { + throw new Error('No receiver found to send the transaction to'); + } + + const { cbUrl } = getCallbackUrl(callbackUrl ?? ''); + + const callback = cbUrl || successUrl; + + if (!contractId) { + const newUrl = new URL(`${metadata.walletUrl}/sign-transaction`); + const stringifiedParam = JSON.stringify([{ receiverId, signerId, actions }]); + const urlParam = encodeURIComponent(stringifiedParam); + newUrl.searchParams.set('transactions_data', urlParam); + newUrl.searchParams.set('callback_url', callback); + window.location.assign(newUrl.toString()); + } + const account = state.wallet.account(); + + return account.signAndSendTransaction({ + receiverId: receiverId || contractId, + actions: actions.map((action) => createAction(action)) as any, + walletCallbackUrl: callback, + }); + }; + + + const verifyOwner = async (): Promise => { + throw new Error(`The verifyOwner method is not supported by ${metadata.name}`); + }; + + const signMessage = async ({ message, nonce, recipient, callbackUrl }): Promise => { + const { cbUrl } = getCallbackUrl(callbackUrl ?? ''); + + const newUrl = new URL(`${metadata.walletUrl}/sign-message`); + newUrl.searchParams.set('message', message); + newUrl.searchParams.set('nonce', nonce); + newUrl.searchParams.set('recipient', recipient); + newUrl.searchParams.set('callbackUrl', cbUrl); + window.location.assign(newUrl.toString()); + }; + + const verifyMessage = async ({ accountId, publicKey, signature, message, nonce, recipient, callbackUrl }): Promise => { + + const newUrl = new URL(`${metadata.walletUrl}/api/verify-message`); + newUrl.searchParams.set('message', message); + newUrl.searchParams.set('accountId', accountId); + newUrl.searchParams.set('publicKey', publicKey); + newUrl.searchParams.set('signature', signature); + newUrl.searchParams.set('nonce', nonce); + newUrl.searchParams.set('recipient', recipient); + newUrl.searchParams.set('callbackUrl', callbackUrl); + + try { + const response = await fetch(newUrl.toString()) + const data = await response.json(); + + const { isValid } = data + return isValid + } catch (e) { + return false + } + } + + const getAvailableBalance = async (): Promise => { + // const accountId = state.wallet.getAccountId(); + // return await getBalance(accountId); + throw (`The getAvailableBalance method is not supported by ${metadata.name}`); + }; + + const getAccounts = async (): Promise => { + const accountId = state.wallet.getAccountId(); + const account = state.wallet.account(); + + if (!accountId || !account) { + return []; + } + + const currentAccount: string = window.localStorage.getItem( + 'mintbase-wallet:account-creation-data', + )!; + + return [ + { + accountId, + publicKey: JSON.parse(currentAccount)?.devicePublicKey, + }, + ]; + }; + + const switchAccount = async (id: string): Promise => { + //TODO fix + setActiveAccountId(id); + + return null; + }; + + const setActiveAccountId = (accountId: string): null => { + activeAccountId = accountId; + window.localStorage.setItem('mintbase-wallet:activeAccountId', accountId); + + return null; + }; + + // const transformTransactions = async ( + // transactions: Array>, + // ): Promise> => { + // const account = state.wallet.account(); + // const { networkId, signer, provider } = account.connection; + + // const localKey = await signer.getPublicKey(account.accountId, networkId); + + // return Promise.all( + // transactions.map(async (transaction, index) => { + // const actions = transaction.actions.map((action) => + // createAction(action), + // ); + // const accessKey = await account.accessKeyForTransaction( + // transaction.receiverId, + // actions as any, + // localKey, + // ); + + // if (!accessKey) { + // throw new Error( + // `Failed to find matching key for transaction sent to ${transaction.receiverId}`, + // ); + // } + + // const block = await provider.block({ finality: 'final' }); + + // return nearAPI.transactions.createTransaction( + // account.accountId, + // nearAPI.utils.PublicKey.from(accessKey.public_key), + // transaction.receiverId, + // accessKey.access_key.nonce + index + 1, + // actions as any, + // nearAPI.utils.serialize.base_decode(block.header.hash), + // ); + // }), + // ); + // }; + + return { + getAccountId, + isSignedIn, + signIn, + signOut, + signAndSendTransaction, + verifyOwner, + signMessage, + getAvailableBalance, + getAccounts, + switchAccount, + signAndSendTransactions, + verifyMessage + }; +}; diff --git a/packages/wallet/src/index.ts b/packages/wallet/src/index.ts index 3ea665677..8739e5335 100644 --- a/packages/wallet/src/index.ts +++ b/packages/wallet/src/index.ts @@ -1 +1,2 @@ +export { setupBitteWallet } from './bitte-wallet-setup'; export { setupMintbaseWallet } from './setup'; diff --git a/packages/wallet/src/utils.ts b/packages/wallet/src/utils.ts index e205d1449..160ffb90a 100644 --- a/packages/wallet/src/utils.ts +++ b/packages/wallet/src/utils.ts @@ -2,7 +2,6 @@ Mintbase Wallet Utils file */ -import { Network } from '@near-wallet-selector/core'; const checkCallbackUrl = (callbackUrl: string): string => { function isValidURL(url): boolean { @@ -81,4 +80,20 @@ const resolveWalletUrl = (network: string, walletUrl?: string): string => { } }; -export { checkCallbackUrl, getCallbackUrl, resolveWalletUrl }; +const resolveBitteWallet = (network: string, walletUrl?: string): string => { + if (walletUrl) { + return walletUrl; + } + + switch (network) { + case 'mainnet': + return 'https://wallet.bitte.ai'; + case 'testnet': + return 'https://testnet.wallet.bitte.ai/'; + default: + throw new Error('Invalid wallet url'); + } +}; + +export { checkCallbackUrl, getCallbackUrl, resolveBitteWallet, resolveWalletUrl }; +