diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..0607621c7 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + target-branch: "dev" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7b06d3b7a..c9c2cf40e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,7 +72,7 @@ Criteria for Including New Wallets for Wallet Selector A wallet project must have comply the following product criteria to listed on Wallet Selector. 1. Non-custodial: The user controls their fund. -2. Conformity to Wallet Standards: The wallet product conforms to NEAR NEP wallet standards. (Injected Wallet https://github.com/near/NEPs/pull/370 and Bridged Wallet https://github.com/near/NEPs/pull/368) +2. Conformity to Wallet Standards: The wallet product conforms to NEAR NEP wallet standards. (Injected Wallet https://github.com/near/NEPs/pull/408 and Bridged Wallet https://github.com/near/NEPs/pull/368) 3. Ease of use: The wallet product provides a usable interface for the end users. Please provide a user guide. 4. Ability to recover accounts: The wallet product allows users to be able to recover accounts. 5. Actively maintained: The wallet is actively maintained by a team and can provide user support. diff --git a/README.md b/README.md index 98ceea2fe..b14eebf46 100644 --- a/README.md +++ b/README.md @@ -8,15 +8,18 @@ NEAR Wallet Selector makes it easy for users to interact with your dApp by provi - [Math Wallet](https://www.npmjs.com/package/@near-wallet-selector/math-wallet) - Injected wallet. - [Nightly](https://www.npmjs.com/package/@near-wallet-selector/nightly) - Injected wallet. - [Meteor Wallet](https://www.npmjs.com/package/@near-wallet-selector/meteor-wallet) - Injected wallet. +- [Coin98 Wallet](https://www.npmjs.com/package/@near-wallet-selector/coin98-wallet) - Injected wallet. - [Ledger](https://www.npmjs.com/package/@near-wallet-selector/ledger) - Hardware wallet. - [WalletConnect](https://www.npmjs.com/package/@near-wallet-selector/wallet-connect) - Bridge wallet. - [Nightly Connect](https://www.npmjs.com/package/@near-wallet-selector/nightly-connect) - Bridge wallet. +- [Here Wallet](https://www.npmjs.com/package/@near-wallet-selector/here-wallet) - Mobile wallet. +- [NearFi Wallet](https://www.npmjs.com/package/@near-wallet-selector/nearfi) - Mobile wallet. ## Preview [React](https://reactjs.org/) / [Next.js](https://nextjs.org/) and [Angular](https://angular.io/) variations of the [Guest Book](https://github.com/near-examples/guest-book/) dApp can be found in the [`examples`](/examples) directory. You can use these to gain a concrete understanding of how to integrate NEAR Wallet Selector into your own dApp. -![Preview](./images/preview-img.png) +![Preview](./images/preview-img.gif) ## Installation and Usage @@ -46,26 +49,32 @@ yarn add \ @near-wallet-selector/near-wallet \ @near-wallet-selector/my-near-wallet \ @near-wallet-selector/sender \ + @near-wallet-selector/nearfi \ + @near-wallet-selector/here-wallet \ @near-wallet-selector/math-wallet \ @near-wallet-selector/nightly \ @near-wallet-selector/meteor-wallet \ @near-wallet-selector/ledger \ @near-wallet-selector/wallet-connect \ @near-wallet-selector/nightly-connect \ - @near-wallet-selector/default-wallets + @near-wallet-selector/default-wallets \ + @near-wallet-selector/coin98-wallet # Using NPM. npm install \ @near-wallet-selector/near-wallet \ @near-wallet-selector/my-near-wallet \ @near-wallet-selector/sender \ + @near-wallet-selector/nearfi \ + @near-wallet-selector/here-wallet \ @near-wallet-selector/math-wallet \ @near-wallet-selector/nightly \ @near-wallet-selector/meteor-wallet \ @near-wallet-selector/ledger \ @near-wallet-selector/wallet-connect \ @near-wallet-selector/nightly-connect \ - @near-wallet-selector/default-wallets + @near-wallet-selector/default-wallets \ + @near-wallet-selector/coin98-wallet ``` Optionally, you can install our [`modal-ui`](https://www.npmjs.com/package/@near-wallet-selector/modal-ui) or [`modal-ui-js`](https://www.npmjs.com/package/@near-wallet-selector/modal-ui-js) package for a pre-built interface that wraps the `core` API and presents the supported wallets: @@ -86,6 +95,7 @@ import { setupModal } from "@near-wallet-selector/modal-ui"; import { setupNearWallet } from "@near-wallet-selector/near-wallet"; import { setupMyNearWallet } from "@near-wallet-selector/my-near-wallet"; import { setupSender } from "@near-wallet-selector/sender"; +import { setupHereWallet } from "@near-wallet-selector/here-wallet"; import { setupMathWallet } from "@near-wallet-selector/math-wallet"; import { setupNightly } from "@near-wallet-selector/nightly"; import { setupMeteorWallet } from "@near-wallet-selector/meteor-wallet"; @@ -93,6 +103,8 @@ import { setupLedger } from "@near-wallet-selector/ledger"; import { setupWalletConnect } from "@near-wallet-selector/wallet-connect"; import { setupNightlyConnect } from "@near-wallet-selector/nightly-connect"; import { setupDefaultWallets } from "@near-wallet-selector/default-wallets"; +import { setupNearFi } from "@near-wallet-selector/nearfi"; +import { setupCoin98Wallet } from "@near-wallet-selector/coin98-wallet"; const selector = await setupWalletSelector({ network: "testnet", @@ -101,10 +113,13 @@ const selector = await setupWalletSelector({ setupNearWallet(), setupMyNearWallet(), setupSender(), + setupHereWallet(), setupMathWallet(), setupNightly(), setupMeteorWallet(), setupLedger(), + setupNearFi(), + setupCoin98Wallet(), setupWalletConnect({ projectId: "c4f79cc...", metadata: { diff --git a/examples/angular/project.json b/examples/angular/project.json index 4e4f873f8..1c28b8d93 100644 --- a/examples/angular/project.json +++ b/examples/angular/project.json @@ -33,6 +33,11 @@ "input": "packages/sender/assets/", "output": "assets/" }, + { + "glob": "**/*", + "input": "packages/nearfi/assets/", + "output": "assets/" + }, { "glob": "**/*", "input": "packages/nightly/assets/", @@ -62,6 +67,11 @@ "glob": "**/*", "input": "packages/meteor-wallet/assets/", "output": "assets/" + }, + { + "glob": "**/*", + "input": "packages/coin98-wallet/assets/", + "output": "assets/" } ], "styles": ["examples/angular/src/styles.scss"], diff --git a/examples/angular/src/app/app.component.ts b/examples/angular/src/app/app.component.ts index 1a036f81d..47d43182c 100644 --- a/examples/angular/src/app/app.component.ts +++ b/examples/angular/src/app/app.component.ts @@ -4,11 +4,14 @@ import type { WalletSelector, AccountState } from "@near-wallet-selector/core"; import { setupDefaultWallets } from "@near-wallet-selector/default-wallets"; import { setupNearWallet } from "@near-wallet-selector/near-wallet"; import { setupSender } from "@near-wallet-selector/sender"; +import { setupNearFi } from "@near-wallet-selector/nearfi"; import { setupMathWallet } from "@near-wallet-selector/math-wallet"; import { setupNightly } from "@near-wallet-selector/nightly"; import { setupMeteorWallet } from "@near-wallet-selector/meteor-wallet"; import { setupNightlyConnect } from "@near-wallet-selector/nightly-connect"; import { setupWalletConnect } from "@near-wallet-selector/wallet-connect"; +import { setupCoin98Wallet } from "@near-wallet-selector/coin98-wallet"; +import { setupHereWallet } from "@near-wallet-selector/here-wallet"; // import { setupModal } from "@near-wallet-selector/modal-ui"; // import type { WalletSelectorModal } from "@near-wallet-selector/modal-ui"; import { setupModal } from "@near-wallet-selector/modal-ui-js"; @@ -51,6 +54,9 @@ export class AppComponent implements OnInit { setupMathWallet(), setupNightly(), setupMeteorWallet(), + setupHereWallet(), + setupCoin98Wallet(), + setupNearFi(), setupWalletConnect({ projectId: "c4f79cc...", metadata: { diff --git a/examples/angular/src/styles.scss b/examples/angular/src/styles.scss index a8213b719..f32a98dde 100644 --- a/examples/angular/src/styles.scss +++ b/examples/angular/src/styles.scss @@ -139,4 +139,4 @@ button { border-left: 0.25em solid var(--secondary); padding-left: 0.25em; margin-left: -0.5em; -} +} \ No newline at end of file diff --git a/examples/react/components/Content.tsx b/examples/react/components/Content.tsx index b6dd07456..74f135b90 100644 --- a/examples/react/components/Content.tsx +++ b/examples/react/components/Content.tsx @@ -155,7 +155,7 @@ const Content: React.FC = () => { } return wallet.signAndSendTransactions({ transactions }).catch((err) => { - alert("Failed to add messages"); + alert("Failed to add messages exception " + err); console.log("Failed to add messages"); throw err; diff --git a/examples/react/contexts/WalletSelectorContext.tsx b/examples/react/contexts/WalletSelectorContext.tsx index f9dbb3fd7..49c14cfa8 100644 --- a/examples/react/contexts/WalletSelectorContext.tsx +++ b/examples/react/contexts/WalletSelectorContext.tsx @@ -6,12 +6,15 @@ import { setupModal } from "@near-wallet-selector/modal-ui"; import type { WalletSelectorModal } from "@near-wallet-selector/modal-ui"; import { setupDefaultWallets } from "@near-wallet-selector/default-wallets"; import { setupNearWallet } from "@near-wallet-selector/near-wallet"; +import { setupHereWallet } from "@near-wallet-selector/here-wallet"; import { setupSender } from "@near-wallet-selector/sender"; import { setupMathWallet } from "@near-wallet-selector/math-wallet"; import { setupNightly } from "@near-wallet-selector/nightly"; import { setupMeteorWallet } from "@near-wallet-selector/meteor-wallet"; import { setupNightlyConnect } from "@near-wallet-selector/nightly-connect"; +import { setupNearFi } from "@near-wallet-selector/nearfi"; import { setupWalletConnect } from "@near-wallet-selector/wallet-connect"; +import { setupCoin98Wallet } from "@near-wallet-selector/coin98-wallet"; import { CONTRACT_ID } from "../constants"; declare global { @@ -47,8 +50,11 @@ export const WalletSelectorContextProvider: React.FC = ({ children }) => { setupMathWallet(), setupNightly(), setupMeteorWallet(), + setupHereWallet(), + setupCoin98Wallet(), + setupNearFi(), setupWalletConnect({ - projectId: "test...", + projectId: "c4f79cc...", metadata: { name: "NEAR Wallet Selector", description: "Example dApp used by NEAR Wallet Selector", @@ -69,7 +75,6 @@ export const WalletSelectorContextProvider: React.FC = ({ children }) => { }); const _modal = setupModal(_selector, { contractId: CONTRACT_ID }); const state = _selector.store.getState(); - setAccounts(state.accounts); window.selector = _selector; diff --git a/examples/react/project.json b/examples/react/project.json index 4073c27e3..fb04ffc31 100644 --- a/examples/react/project.json +++ b/examples/react/project.json @@ -26,6 +26,11 @@ "input": "packages/sender/assets/", "output": "assets/" }, + { + "glob": "**/*", + "input": "packages/nearfi/assets/", + "output": "assets/" + }, { "glob": "**/*", "input": "packages/nightly/assets/", @@ -55,6 +60,11 @@ "glob": "**/*", "input": "packages/meteor-wallet/assets/", "output": "assets/" + }, + { + "glob": "**/*", + "input": "packages/coin98-wallet/assets/", + "output": "assets/" } ] }, diff --git a/images/preview-img.gif b/images/preview-img.gif new file mode 100644 index 000000000..d22480a98 Binary files /dev/null and b/images/preview-img.gif differ diff --git a/images/preview-img.png b/images/preview-img.png deleted file mode 100644 index 7fb62a442..000000000 Binary files a/images/preview-img.png and /dev/null differ diff --git a/package.json b/package.json index b7544d52c..b381daf82 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "near-wallet-selector", - "version": "7.0.3", + "version": "7.1.0", "description": "NEAR Wallet Selector makes it easy for users to interact with your dApp by providing an abstraction over various wallets within the NEAR ecosystem", "keywords": [ "near", @@ -17,6 +17,7 @@ "ledger", "wallet-connect", "nightly-connect", + "nearfi", "meteor-wallet" ], "homepage": "https://github.com/near/wallet-selector#README", @@ -40,8 +41,10 @@ "build:ledger": "nx run-many --target=build --projects=ledger --configuration=production", "build:math-wallet": "nx run-many --target=build --projects=math-wallet --configuration=production", "build:near-wallet": "nx run-many --target=build --projects=near-wallet --configuration=production", + "build:here-wallet": "nx run-many --target=build --projects=here-wallet --configuration=production", "build:my-near-wallet": "nx run-many --target=build --projects=my-near-wallet --configuration=production", "build:sender": "nx run-many --target=build --projects=sender --configuration=production", + "build:nearfi": "nx run-many --target=build --projects=nearfi --configuration=production", "build:nightly": "nx run-many --target=build --projects=nightly --configuration=production", "build:meteor-wallet": "nx run-many --target=build --projects=meteor-wallet --configuration=production", "build:wallet-connect": "nx run-many --target=build --projects=wallet-connect --configuration=production", @@ -50,13 +53,14 @@ "build:default-wallets": "nx run-many --target=build --projects=default-wallets --configuration=production", "lint": "nx workspace-lint && nx run-many --target=lint --all --parallel", "lint:fix": "nx run-many --target=lint --all --fix", - "serve:react": "nx serve react", + "serve:react": "nx serve react --host=0.0.0.0", "serve:angular": "nx serve angular", "prepack": "yarn build:core && yarn build:all", "test": "nx run-many --target=test --all", "postinstall": "ngcc --properties es2020 browser module main" }, "dependencies": { + "@angular/animations": "~14.0.0", "@angular/common": "~14.0.0", "@angular/compiler": "~14.0.0", @@ -76,13 +80,16 @@ "big.js": "^6.1.1", "bn.js": "^5.2.0", "buffer": "^6.0.3", + "copy-to-clipboard": "^3.3.2", "core-js": "^3.6.5", "is-mobile": "^3.1.1", "near-api-js": "^0.44.2", "next": "12.2.3", "ngx-deploy-npm": "^4.1.1", + "qrcode": "^1.5.1", "react": "18.2.0", "react-dom": "18.2.0", + "react-qr-code": "^2.0.8", "regenerator-runtime": "0.13.7", "rxjs": "^7.5.5", "tslib": "^2.3.0", @@ -121,6 +128,7 @@ "@types/gh-pages": "^3.2.1", "@types/jest": "27.4.1", "@types/node": "16.11.7", + "@types/qrcode": "^1.5.0", "@types/react": "18.0.14", "@types/react-dom": "18.0.5", "@types/regenerator-runtime": "^0.13.1", @@ -141,11 +149,11 @@ "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-react": "7.30.1", "eslint-plugin-react-hooks": "4.6.0", + "gh-pages": "^4.0.0", "jest": "27.5.1", "jest-mock-extended": "^2.0.6", "jest-preset-angular": "^12.2.0", "nx": "14.4.2", - "gh-pages": "^4.0.0", "prettier": "^2.7.1", "react-test-renderer": "18.2.0", "sass": "1.52.3", diff --git a/packages/coin98-wallet/.babelrc b/packages/coin98-wallet/.babelrc new file mode 100644 index 000000000..cf7ddd99c --- /dev/null +++ b/packages/coin98-wallet/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nrwl/web/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/coin98-wallet/.eslintrc.json b/packages/coin98-wallet/.eslintrc.json new file mode 100644 index 000000000..9d9c0db55 --- /dev/null +++ b/packages/coin98-wallet/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/coin98-wallet/README.md b/packages/coin98-wallet/README.md new file mode 100644 index 000000000..df7e13e1f --- /dev/null +++ b/packages/coin98-wallet/README.md @@ -0,0 +1,49 @@ +# @near-wallet-selector/coin98-wallet + +Coin98 Wallet [Coin98 Wallet](https://chrome.google.com/webstore/detail/coin98-wallet/aeachknmefphepccionboohckonoeemg) Package for NEAR Wallet Selector + +## Installation + +This package requires `near-api-js` v0.44.2 or above: + +```bash +# Using Yarn +yarn add near-api-js + +# Using NPM. +npm install near-api-js +``` + +```bash +# Using Yarn +yarn add @near-wallet-selector/coin98-wallet + +# Using NPM. +npm install @near-wallet-selector/coin98-wallet +``` + +## Usage + +```ts +import { setupWalletSelector } from "@near-wallet-selector/core"; +import { setupCoin98Wallet } from "@near-wallet-selector/coin98-wallet"; + +// Coin98 Wallet for Wallet Selector can be setup without any params or it can take one optional param. +const coin98Wallet = setupCoin98Wallet({ + iconUrl: "https://" +}); + + +const selector = await setupWalletSelector({ + network: "testnet", + modules: [coin98Wallet], +}); +``` + +## Options + +- `iconUrl`: (`string?`): Icon is optional. Default image point to Coin98 Wallet Logo in base64 format. + +## License + +This repository is distributed under the terms of both the MIT license and the Apache License (Version 2.0). diff --git a/packages/coin98-wallet/assets/coin98-wallet-icon.png b/packages/coin98-wallet/assets/coin98-wallet-icon.png new file mode 100644 index 000000000..c926f3fd4 Binary files /dev/null and b/packages/coin98-wallet/assets/coin98-wallet-icon.png differ diff --git a/packages/coin98-wallet/jest.config.js b/packages/coin98-wallet/jest.config.js new file mode 100644 index 000000000..5adb314a8 --- /dev/null +++ b/packages/coin98-wallet/jest.config.js @@ -0,0 +1,14 @@ +module.exports = { + displayName: "coin98-wallet", + preset: "../../jest.preset.js", + globals: { + "ts-jest": { + tsconfig: "/tsconfig.spec.json", + }, + }, + transform: { + "^.+\\.[tj]sx?$": "ts-jest", + }, + moduleFileExtensions: ["ts", "tsx", "js", "jsx"], + coverageDirectory: "../../coverage/packages/coin98-wallet", +}; diff --git a/packages/coin98-wallet/package.json b/packages/coin98-wallet/package.json new file mode 100644 index 000000000..632d307a8 --- /dev/null +++ b/packages/coin98-wallet/package.json @@ -0,0 +1,7 @@ +{ + "name": "@near-wallet-selector/coin98-wallet", + "version": "7.1.0", + "peerDependencies": { + "near-api-js": "^0.44.2" + } +} diff --git a/packages/coin98-wallet/project.json b/packages/coin98-wallet/project.json new file mode 100644 index 000000000..83d75e42b --- /dev/null +++ b/packages/coin98-wallet/project.json @@ -0,0 +1,48 @@ +{ + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/coin98-wallet/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nrwl/web:rollup", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/packages/coin98-wallet", + "tsConfig": "packages/coin98-wallet/tsconfig.lib.json", + "project": "packages/coin98-wallet/package.json", + "entryFile": "packages/coin98-wallet/src/index.ts", + "buildableProjectDepsInPackageJsonType": "dependencies", + "compiler": "babel", + "format": ["esm", "cjs"], + "assets": [ + { + "glob": "packages/coin98-wallet/README.md", + "input": ".", + "output": "." + }, + { + "glob": "packages/coin98-wallet/assets/*", + "input": ".", + "output": "assets" + } + ] + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["packages/coin98-wallet/**/*.ts"] + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["coverage/packages/coin98-wallet"], + "options": { + "jestConfig": "packages/coin98-wallet/jest.config.js", + "passWithNoTests": true + } + } + }, + "tags": [] +} diff --git a/packages/coin98-wallet/src/index.ts b/packages/coin98-wallet/src/index.ts new file mode 100644 index 000000000..539515a0a --- /dev/null +++ b/packages/coin98-wallet/src/index.ts @@ -0,0 +1 @@ +export * from "./lib/coin98-wallet"; diff --git a/packages/coin98-wallet/src/lib/coin98-wallet.ts b/packages/coin98-wallet/src/lib/coin98-wallet.ts new file mode 100644 index 000000000..465a7c048 --- /dev/null +++ b/packages/coin98-wallet/src/lib/coin98-wallet.ts @@ -0,0 +1,207 @@ +import { isMobile } from "is-mobile"; +import type { + WalletModuleFactory, + WalletBehaviourFactory, + InjectedWallet, + Account, + Optional, + Transaction, +} from "@near-wallet-selector/core"; +import { getActiveAccount } from "@near-wallet-selector/core"; +import { waitFor } from "@near-wallet-selector/core"; +import type { InjectedCoin98 } from "./injected-coin98-wallet"; +import { signTransactions } from "@near-wallet-selector/wallet-utils"; +import type { FinalExecutionOutcome } from "near-api-js/lib/providers"; +import icon from "./icon"; + +declare global { + interface Window { + coin98: InjectedCoin98; + } +} + +export interface Coin98WalletParams { + iconUrl?: string; + deprecated?: boolean; +} + +interface Coin98WalletState { + wallet: InjectedCoin98; +} + +const isInstalled = () => { + return waitFor(() => !!window.coin98).catch(() => false); +}; + +const setupCoin98WalletState = (): Coin98WalletState => { + const wallet = window.coin98!; + + return { + wallet, + }; +}; + +const Coin98Wallet: WalletBehaviourFactory = async ({ + metadata, + options, + store, + provider, + logger, +}) => { + const _state = setupCoin98WalletState(); + + const getAccounts = (): Array => { + const accountId = _state.wallet.near.account; + + if (!accountId) { + return []; + } + + return [{ accountId: _state.wallet.near.account }]; + }; + + const transformTransactions = ( + transactions: Array> + ): Array => { + const { contract } = store.getState(); + + if (!contract) { + throw new Error("Wallet not signed in"); + } + + const account = getActiveAccount(store.getState()); + + if (!account) { + throw new Error("No active account"); + } + + return transactions.map((transaction) => { + return { + signerId: transaction.signerId || account.accountId, + receiverId: transaction.receiverId || contract.contractId, + actions: transaction.actions, + }; + }); + }; + + return { + async signIn({ contractId }) { + const existingAccounts = getAccounts(); + + if (existingAccounts.length) { + return existingAccounts; + } + + await _state.wallet.near.connect({ prefix: "near_selector", contractId }); + return getAccounts(); + }, + + async signOut() { + // Ignore if unsuccessful (returns false). + await _state.wallet.near.disconnect(); + }, + + async getAccounts() { + return getAccounts(); + }, + + async verifyOwner({ message }) { + const account = getActiveAccount(store.getState()); + + if (!account) { + throw new Error("No active account"); + } + + const accountId = account.accountId; + const pubKey = await _state.wallet.near.signer.getPublicKey(accountId); + const block = await provider.block({ finality: "final" }); + + const data = { + accountId, + message, + blockId: block.header.hash, + publicKey: Buffer.from(pubKey.data).toString("base64"), + keyType: pubKey.keyType, + }; + const encoded = JSON.stringify(data); + + throw new Error(`Method not supported by ${metadata.name}`); + + const signed = await _state.wallet.near.signer.signMessage( + new Uint8Array(Buffer.from(encoded)), + accountId, + options.network.networkId + ); + + return { + ...data, + signature: Buffer.from(signed.signature).toString("base64"), + keyType: signed.publicKey.keyType, + }; + }, + + async signAndSendTransaction({ signerId, receiverId, actions }) { + logger.log("signAndSendTransaction", { signerId, receiverId, actions }); + const signedTransactions = await signTransactions( + transformTransactions([{ signerId, receiverId, actions }]), + _state.wallet.near.signer, + options.network + ); + + return provider.sendTransaction(signedTransactions[0]); + }, + + async signAndSendTransactions({ transactions }) { + logger.log("signAndSendTransactions", { transactions }); + + const signedTransactions = await signTransactions( + transformTransactions(transactions), + _state.wallet.near.signer, + options.network + ); + + logger.log( + "signAndSendTransactions:signedTransactions", + signedTransactions + ); + + const results: Array = []; + + for (let i = 0; i < signedTransactions.length; i++) { + results.push(await provider.sendTransaction(signedTransactions[i])); + } + + return results; + }, + }; +}; + +export const setupCoin98Wallet = ({ + iconUrl = icon, + deprecated = false, +}: Coin98WalletParams = {}): WalletModuleFactory => { + return async () => { + const mobile = isMobile(); + const installed = await isInstalled(); + + if (mobile) { + return null; + } + + return { + id: "coin98-wallet", + type: "injected", + metadata: { + name: "Coin98 Wallet", + description: + "Using a Decentralized Wallet With Experiences of a Centralized One", + iconUrl, + downloadUrl: + "https://chrome.google.com/webstore/detail/coin98-wallet/aeachknmefphepccionboohckonoeemg", + deprecated, + available: installed, + }, + init: Coin98Wallet, + }; + }; +}; diff --git a/packages/coin98-wallet/src/lib/icon.ts b/packages/coin98-wallet/src/lib/icon.ts new file mode 100644 index 000000000..85f010175 --- /dev/null +++ b/packages/coin98-wallet/src/lib/icon.ts @@ -0,0 +1 @@ +export default `data:image/png;base64,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`; diff --git a/packages/coin98-wallet/src/lib/injected-coin98-wallet.ts b/packages/coin98-wallet/src/lib/injected-coin98-wallet.ts new file mode 100644 index 000000000..50b672be9 --- /dev/null +++ b/packages/coin98-wallet/src/lib/injected-coin98-wallet.ts @@ -0,0 +1,17 @@ +import { Signer } from "near-api-js/lib/signer"; + +interface IConnectParams { + prefix: string; + contractId: string; +} + +interface ICoin98Near { + account: string; + signer: Signer; + connect: (params: IConnectParams) => Promise; + disconnect: () => Promise; +} + +export interface InjectedCoin98 { + near: ICoin98Near; +} diff --git a/packages/coin98-wallet/tsconfig.json b/packages/coin98-wallet/tsconfig.json new file mode 100644 index 000000000..e258886ff --- /dev/null +++ b/packages/coin98-wallet/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/coin98-wallet/tsconfig.lib.json b/packages/coin98-wallet/tsconfig.lib.json new file mode 100644 index 000000000..a8b9431f9 --- /dev/null +++ b/packages/coin98-wallet/tsconfig.lib.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true, + "types": [] + }, + "include": ["**/*.ts"], + "exclude": ["**/*.spec.ts"] +} diff --git a/packages/coin98-wallet/tsconfig.spec.json b/packages/coin98-wallet/tsconfig.spec.json new file mode 100644 index 000000000..67f149c4c --- /dev/null +++ b/packages/coin98-wallet/tsconfig.spec.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "**/*.test.ts", + "**/*.spec.ts", + "**/*.test.tsx", + "**/*.spec.tsx", + "**/*.test.js", + "**/*.spec.js", + "**/*.test.jsx", + "**/*.spec.jsx", + "**/*.d.ts" + ] +} diff --git a/packages/core/docs/api/selector.md b/packages/core/docs/api/selector.md index 12199ee59..8655e13f7 100644 --- a/packages/core/docs/api/selector.md +++ b/packages/core/docs/api/selector.md @@ -151,7 +151,7 @@ selector.setActiveAccount("sometestaccount.testnet"); **Parameters** -- `event` (`string`): Name of the event. This can be: `networkChanged`. +- `event` (`string`): Name of the event. This can be: `networkChanged | uriChanged`. - `callback` (`Function`): Handler to be triggered when the `event` fires. **Returns** @@ -177,7 +177,7 @@ subscription.remove(); **Parameters** -- `event` (`string`): Name of the event. This can be: `networkChanged`. +- `event` (`string`): Name of the event. This can be: `networkChanged | uriChanged`. - `callback` (`Function`): Original handler passed to `.on(event, callback)`. **Returns** diff --git a/packages/core/docs/api/wallet.md b/packages/core/docs/api/wallet.md index bef7a4858..55b2022eb 100644 --- a/packages/core/docs/api/wallet.md +++ b/packages/core/docs/api/wallet.md @@ -81,6 +81,7 @@ Returns meta information about the wallet such as `name`, `description`, `iconUr - `contractId` (`string`): Account ID of the Smart Contract. - `methodNames` (`Array?`): Specify limited access to particular methods on the Smart Contract. - `accounts` (`Array<{derivationPath: string, publicKey: string, accountId: string}>?`): Required for hardware wallets (e.g. Ledger). This is a list of `accounts` linked to public keys on your device. + - `qrCodeModal` (`boolean?`): Optional for bridge wallets (e.g Wallet Connect). This indicates whether to render QR Code in wallet selector modal or use the default vendor modal. **Returns** @@ -197,7 +198,7 @@ Returns one or more accounts when signed in. This method can be useful for walle Signs the message and verifies the owner. Message is not sent to blockchain. -> Note: This feature is currently supported only by MyNearWallet on **testnet**. Sender can sign messages when unlocked. +> Note: This feature is currently supported only by MyNearWallet, Meteor Wallet and WalletConnect on **testnet**. Sender can sign messages when unlocked. **Example** ```ts diff --git a/packages/core/package.json b/packages/core/package.json index f05a906e7..252192a79 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/core", - "version": "7.0.3", + "version": "7.1.0", "peerDependencies": { "near-api-js": "^0.44.2" } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 6b6584a6c..77833997d 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -11,6 +11,7 @@ export type { Subscription, StorageService, JsonStorageService, + EventEmitterService, } from "./lib/services"; export type { Optional } from "./lib/utils.types"; diff --git a/packages/core/src/lib/services/wallet-modules/wallet-modules.service.ts b/packages/core/src/lib/services/wallet-modules/wallet-modules.service.ts index 5723393e3..4dfb8eaac 100644 --- a/packages/core/src/lib/services/wallet-modules/wallet-modules.service.ts +++ b/packages/core/src/lib/services/wallet-modules/wallet-modules.service.ts @@ -196,6 +196,10 @@ export class WalletModules { this.emitter.emit("networkChanged", { walletId: module.id, networkId }); }); + emitter.on("uriChanged", ({ uri }) => { + this.emitter.emit("uriChanged", { walletId: module.id, uri }); + }); + return emitter; } diff --git a/packages/core/src/lib/wallet-selector.types.ts b/packages/core/src/lib/wallet-selector.types.ts index b2332dd58..5eccfa030 100644 --- a/packages/core/src/lib/wallet-selector.types.ts +++ b/packages/core/src/lib/wallet-selector.types.ts @@ -14,6 +14,7 @@ export type WalletSelectorStore = ReadOnlyStore; export type WalletSelectorEvents = { networkChanged: { walletId: string; networkId: string }; + uriChanged: { walletId: string; uri: string }; }; export interface WalletSelector { diff --git a/packages/core/src/lib/wallet/wallet.types.ts b/packages/core/src/lib/wallet/wallet.types.ts index df06fa38f..637733899 100644 --- a/packages/core/src/lib/wallet/wallet.types.ts +++ b/packages/core/src/lib/wallet/wallet.types.ts @@ -86,6 +86,7 @@ export type WalletEvents = { signedOut: null; accountsChanged: { accounts: Array }; networkChanged: { networkId: string }; + uriChanged: { uri: string }; }; // ----- Browser Wallet ----- // @@ -163,9 +164,16 @@ export type HardwareWallet = BaseWallet< // ----- Bridge Wallet ----- // +export interface BridgeWalletSignInParams extends SignInParams { + qrCodeModal?: boolean; +} + export type BridgeWalletMetadata = BaseWalletMetadata; -export type BridgeWalletBehaviour = BaseWalletBehaviour; +export type BridgeWalletBehaviour = Modify< + BaseWalletBehaviour, + { signIn(params: BridgeWalletSignInParams): Promise> } +>; export type BridgeWallet = BaseWallet< "bridge", diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index cd6006bcf..9b688bfe7 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -28,6 +28,6 @@ "isolatedModules": true, "incremental": true, "noImplicitOverride": true, - "noPropertyAccessFromIndexSignature": true, + "noPropertyAccessFromIndexSignature": true } } diff --git a/packages/default-wallets/package.json b/packages/default-wallets/package.json index b1759fd53..a2ad10cc8 100644 --- a/packages/default-wallets/package.json +++ b/packages/default-wallets/package.json @@ -1,4 +1,4 @@ { "name": "@near-wallet-selector/default-wallets", - "version": "7.0.3" + "version": "7.1.0" } diff --git a/packages/here-wallet/.babelrc b/packages/here-wallet/.babelrc new file mode 100644 index 000000000..38f3c9c24 --- /dev/null +++ b/packages/here-wallet/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "@nrwl/web/babel", + { + "useBuiltIns": "usage" + } + ] + ] +} \ No newline at end of file diff --git a/packages/here-wallet/.eslintrc.json b/packages/here-wallet/.eslintrc.json new file mode 100644 index 000000000..9d9c0db55 --- /dev/null +++ b/packages/here-wallet/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/here-wallet/README.md b/packages/here-wallet/README.md new file mode 100644 index 000000000..45ca14606 --- /dev/null +++ b/packages/here-wallet/README.md @@ -0,0 +1,75 @@ +# @near-wallet-selector/here-wallet + +This is the [Here Wallet](https://herewallet.app/) package for NEAR Wallet Selector. + +## Installation and Usage + +The easiest way to use this package is to install it from the NPM registry, this package requires `near-api-js` v0.44.2 or above: + +```bash +# Using Yarn +yarn add near-api-js@^0.44.2 + +# Using NPM. +npm install near-api-js@^0.44.2 +``` +```bash +# Using Yarn +yarn add @near-wallet-selector/here-wallet + +# Using NPM. +npm install @near-wallet-selector/here-wallet +``` + +Then use it in your dApp: + +```ts +import { setupWalletSelector } from "@near-wallet-selector/core"; +import { setupHereWallet } from "@near-wallet-selector/here-wallet"; + +const hereWallet = setupHereWallet(); +const selector = await setupWalletSelector({ + network: "testnet", + modules: [hereWallet], +}); +``` + +## Options + +- `iconUrl`: (`string?`): Image URL for the icon shown in the modal. This can also be a relative path or base64 encoded image. Defaults to base64 from `src/icon.ts` + +## Additional Methods + +* `getHereBalance(): Promise`
+ Return available yoktoNears from Here smart contract + +* `getAvailableBalance(): Promise`
+ Return available yoktoNears from near account + getHereBalance() + +You can use it with Typescript: +```ts +const isHereWallet = (w: Wallet): w is HereWallet => + w.id === "here-wallet"; + +if (isHereWallet(wallet)) { + wallet.getAvailableBalance(); // correct typings +} +``` + +## Assets + +Assets such as icons can be found in the `/assets` directory of the package. Below is an example using Webpack: + +```ts +import { setupHereWallet } from "@near-wallet-selector/here-wallet"; +import HereWalletIconUrl from "@near-wallet-selector/here-wallet/assets/here-wallet-icon.png"; + +const hereWallet = setupHereWallet({ + iconUrl: HereWalletIconUrl +}); + +``` + +## License + +This repository is distributed under the terms of both the MIT license and the Apache License (Version 2.0). diff --git a/packages/here-wallet/assets/here-wallet-icon.png b/packages/here-wallet/assets/here-wallet-icon.png new file mode 100644 index 000000000..28d63c5ef Binary files /dev/null and b/packages/here-wallet/assets/here-wallet-icon.png differ diff --git a/packages/here-wallet/jest.config.js b/packages/here-wallet/jest.config.js new file mode 100644 index 000000000..c9a7e4042 --- /dev/null +++ b/packages/here-wallet/jest.config.js @@ -0,0 +1,14 @@ +module.exports = { + displayName: "here-wallet", + preset: "../../jest.preset.js", + globals: { + "ts-jest": { + tsconfig: "/tsconfig.spec.json", + }, + }, + transform: { + "^.+\\.[tj]sx?$": "ts-jest", + }, + moduleFileExtensions: ["ts", "tsx", "js", "jsx"], + coverageDirectory: "../../coverage/packages/here-wallet", +}; diff --git a/packages/here-wallet/jest.config.ts b/packages/here-wallet/jest.config.ts new file mode 100644 index 000000000..f9fb16c78 --- /dev/null +++ b/packages/here-wallet/jest.config.ts @@ -0,0 +1,15 @@ +/* eslint-disable */ +export default { + displayName: "here-wallet", + preset: "../../jest.preset.js", + globals: { + "ts-jest": { + tsconfig: "/tsconfig.spec.json", + }, + }, + transform: { + "^.+\\.[tj]s$": "ts-jest", + }, + moduleFileExtensions: ["ts", "tsx", "js", "jsx"], + coverageDirectory: "../../coverage/packages/here-wallet", +}; diff --git a/packages/here-wallet/package.json b/packages/here-wallet/package.json new file mode 100644 index 000000000..634911c5c --- /dev/null +++ b/packages/here-wallet/package.json @@ -0,0 +1,7 @@ +{ + "name": "@near-wallet-selector/here-wallet", + "version": "7.1.0", + "peerDependencies": { + "near-api-js": "^0.44.2" + } +} diff --git a/packages/here-wallet/project.json b/packages/here-wallet/project.json new file mode 100644 index 000000000..598fc6533 --- /dev/null +++ b/packages/here-wallet/project.json @@ -0,0 +1,67 @@ +{ + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/here-wallet/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nrwl/web:rollup", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/here-wallet", + "tsConfig": "packages/here-wallet/tsconfig.lib.json", + "project": "packages/here-wallet/package.json", + "entryFile": "packages/here-wallet/src/index.ts", + "buildableProjectDepsInPackageJsonType": "dependencies", + "compiler": "babel", + "format": [ + "esm", + "cjs" + ], + "assets": [ + { + "glob": "packages/here-wallet/README.md", + "input": ".", + "output": "." + }, + { + "glob": "packages/here-wallet/assets/*", + "input": ".", + "output": "assets" + } + ] + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": [ + "{options.outputFile}" + ], + "options": { + "lintFilePatterns": [ + "packages/here-wallet/**/*.ts" + ] + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": [ + "coverage/packages/here-wallet" + ], + "options": { + "jestConfig": "packages/here-wallet/jest.config.ts", + "passWithNoTests": true + } + }, + "deploy": { + "executor": "ngx-deploy-npm:deploy", + "options": { + "access": "public" + } + } + }, + "tags": [ + "browser-wallet" + ] +} \ No newline at end of file diff --git a/packages/here-wallet/src/index.ts b/packages/here-wallet/src/index.ts new file mode 100644 index 000000000..405e0c30e --- /dev/null +++ b/packages/here-wallet/src/index.ts @@ -0,0 +1,2 @@ +export { setupHereWallet } from "./lib/here-wallet"; +export type { HereWallet } from "./lib/here-wallet"; diff --git a/packages/here-wallet/src/lib/here-wallet.ts b/packages/here-wallet/src/lib/here-wallet.ts new file mode 100644 index 000000000..acfca102b --- /dev/null +++ b/packages/here-wallet/src/lib/here-wallet.ts @@ -0,0 +1,142 @@ +import type { + WalletModuleFactory, + WalletBehaviourFactory, + BrowserWallet, +} from "@near-wallet-selector/core"; +import { createAction } from "@near-wallet-selector/wallet-utils"; +import * as BN from "bn.js"; +import icon from "./icon"; +import { + getHereBalance, + HereConfiguration, + hereConfigurations, + setupWalletState, + transformTransactions, +} from "./utils"; + +export type HereWallet = BrowserWallet & { + getHereBalance: () => Promise; + getAvailableBalance: () => Promise; +}; + +export const initHereWallet: WalletBehaviourFactory< + HereWallet, + { configuration: HereConfiguration } +> = async ({ store, logger, options, configuration }) => { + const _state = await setupWalletState(configuration, options.network); + + const getAccounts = () => { + const accountId: string | null = _state.wallet.getAccountId(); + if (!accountId) { + return []; + } + + return [{ accountId }]; + }; + + return { + async signIn({ contractId, methodNames }) { + const existingAccounts = getAccounts(); + + if (existingAccounts.length) { + return existingAccounts; + } + + await _state.wallet.requestSignIn({ contractId, methodNames }); + return getAccounts(); + }, + + async getHereBalance() { + return await getHereBalance(_state, configuration); + }, + + async getAvailableBalance() { + const result = await _state.wallet.account().getAccountBalance(); + const hereBalance = await getHereBalance(_state, configuration); + return new BN(result.available).add(new BN(hereBalance)); + }, + + async signOut() { + if (_state.wallet.isSignedIn()) { + _state.wallet.signOut(); + } + }, + + async getAccounts() { + return getAccounts(); + }, + + async verifyOwner({ message }) { + logger.log("HereWallet:verifyOwner", { message }); + throw new Error(`Method not supported by Here Wallet`); + }, + + async signAndSendTransaction({ + signerId, + receiverId, + actions, + callbackUrl, + }) { + logger.log("HereWallet:signAndSendTransaction", { + signerId, + receiverId, + actions, + callbackUrl, + }); + + const { contract } = store.getState(); + + if (!_state.wallet.isSignedIn() || !contract) { + throw new Error("Wallet not signed in"); + } + + const account = _state.wallet.account(); + return account["signAndSendTransaction"]({ + receiverId: receiverId || contract.contractId, + actions: actions.map((action) => createAction(action)), + walletCallbackUrl: callbackUrl, + }); + }, + + async signAndSendTransactions({ transactions, callbackUrl }) { + logger.log("HereWallet:signAndSendTransactions", { + transactions, + callbackUrl, + }); + + if (!_state.wallet.isSignedIn()) { + throw new Error("Wallet not signed in"); + } + + return _state.wallet.requestSignTransactions({ + transactions: await transformTransactions(_state, transactions), + callbackUrl, + }); + }, + }; +}; + +export function setupHereWallet({ + deprecated = false, + iconUrl = icon, +} = {}): WalletModuleFactory { + return async ({ options }) => { + const configuration = hereConfigurations[options.network.networkId]; + if (configuration == null) { + return null; + } + + return { + id: "here-wallet", + type: "browser", + metadata: { + name: "Here Wallet (mobile)", + description: "Mobile wallet for NEAR Protocol", + iconUrl, + deprecated, + available: true, + }, + init: (config) => initHereWallet({ ...config, configuration }), + }; + }; +} diff --git a/packages/here-wallet/src/lib/icon.ts b/packages/here-wallet/src/lib/icon.ts new file mode 100644 index 000000000..cd482a002 --- /dev/null +++ b/packages/here-wallet/src/lib/icon.ts @@ -0,0 +1 @@ +export default "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHZpZXdCb3g9IjAgMCA0MCA0MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xOS43MzM1IDExLjY4MzFMMjEuNzA4OSA4TDI0LjI5ODMgMTYuNjc2NkwyMC40MTYgMTQuOTIxNUwxOS43MzM1IDExLjY4MzFaTTUuNzYzODggMTUuNTQwOUwzLjc1NTYyIDE5LjE2MjFMOS44OTYwNSAyMi4xNjYxTDE4LjM0NzMgMjEuNTgyOEw1Ljc2Mzg4IDE1LjU0MDlaTTQuNjQ0NjQgMjIuMzY5NEwyLjY0ODY1IDI1Ljk3MTNMMTQuOTA4MyAzMi4wNTc1TDM1LjMzODggMzAuODE3NEwzNy4zMzQ4IDI3LjIxNTRMMTYuOTA0MyAyOC40NTU3TDQuNjQ0NjQgMjIuMzY5NFoiIGZpbGw9IiMyQzMwMzQiLz4KPHBhdGggZD0iTTM3LjMzNTkgMjcuMjE1N0wxNi45MTEyIDI4LjQ1NjFMNC41NDYzMyAyMi4zNTU0TDE3LjkzNTggMjEuNTQ1M0w1Ljc2Mzg3IDE1LjU0MDlMMTEuNzQxIDEwLjQ3ODZMMjQuMTQwMSAxNi41NzYzTDIxLjcwOSA4TDMzLjU1NyAxMy44MUwzNy4zMzU5IDI3LjIxNTdaIiBmaWxsPSIjRkRCRjFDIi8+Cjwvc3ZnPgo="; diff --git a/packages/here-wallet/src/lib/utils.ts b/packages/here-wallet/src/lib/utils.ts new file mode 100644 index 000000000..95a214c89 --- /dev/null +++ b/packages/here-wallet/src/lib/utils.ts @@ -0,0 +1,106 @@ +import { Network, Optional, Transaction } from "@near-wallet-selector/core"; +import { createAction } from "@near-wallet-selector/wallet-utils"; +import * as BN from "bn.js"; +import { + utils, + connect, + keyStores, + WalletConnection, + transactions as nearTransactions, +} from "near-api-js"; + +export interface HereWalletState { + wallet: WalletConnection; + keyStore: keyStores.BrowserLocalStorageKeyStore; +} + +export interface HereConfiguration { + hereWallet: string; + hereContract: string; +} + +export const hereConfigurations: Record = { + mainnet: { + hereWallet: "https://web.herewallet.app", + hereContract: "storage.herewallet.near", + }, + testnet: { + hereWallet: "https://web.testnet.herewallet.app", + hereContract: "storage.herewallet.testnet", + }, +}; + +const setupWalletState = async ( + config: HereConfiguration, + network: Network +): Promise => { + const keyStore = new keyStores.BrowserLocalStorageKeyStore(); + const near = await connect({ + keyStore, + walletUrl: config.hereWallet, + headers: {}, + ...network, + }); + + const wallet = new WalletConnection(near, "here_app"); + + return { wallet, keyStore }; +}; + +const getHereBalance = async ( + state: HereWalletState, + config: HereConfiguration +): Promise => { + const params = { account_id: state.wallet.getAccountId() }; + const hereCoins = await state.wallet + .account() + .viewFunction(config.hereContract, "ft_balance_of", params) + .catch(() => "0"); + + return new BN(hereCoins); +}; + +const transformTransactions = async ( + state: HereWalletState, + transactions: Array> +) => { + const account = state.wallet.account(); + const { networkId, signer, provider } = account.connection; + const localKey = await signer.getPublicKey(account.accountId, networkId); + + const transformed: Array = []; + let index = 0; + + for (const transaction of transactions) { + index += 1; + + const actions = transaction.actions.map((action) => createAction(action)); + const accessKey = await account.accessKeyForTransaction( + transaction.receiverId, + actions, + localKey + ); + + if (!accessKey) { + throw new Error( + `Failed to find matching key for transaction sent to ${transaction.receiverId}` + ); + } + + const block = await provider.block({ finality: "final" }); + transformed.push( + nearTransactions.createTransaction( + account.accountId, + utils.PublicKey.from(accessKey.public_key), + transaction.receiverId, + accessKey.access_key.nonce + index, + actions, + utils.serialize.base_decode(block.header.hash) + ) + ); + } + + return transformed; +}; + +export { setupWalletState, getHereBalance, transformTransactions }; diff --git a/packages/here-wallet/tsconfig.json b/packages/here-wallet/tsconfig.json new file mode 100644 index 000000000..8b6d6acaf --- /dev/null +++ b/packages/here-wallet/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/packages/here-wallet/tsconfig.lib.json b/packages/here-wallet/tsconfig.lib.json new file mode 100644 index 000000000..e85ef50f6 --- /dev/null +++ b/packages/here-wallet/tsconfig.lib.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true, + "types": [] + }, + "include": ["**/*.ts"], + "exclude": ["jest.config.ts", "**/*.spec.ts", "**/*.test.ts"] +} diff --git a/packages/here-wallet/tsconfig.spec.json b/packages/here-wallet/tsconfig.spec.json new file mode 100644 index 000000000..1189d559e --- /dev/null +++ b/packages/here-wallet/tsconfig.spec.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "jest.config.ts", + "**/*.spec.ts" + ] +} \ No newline at end of file diff --git a/packages/ledger/package.json b/packages/ledger/package.json index 702b01c3d..b28858bea 100644 --- a/packages/ledger/package.json +++ b/packages/ledger/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/ledger", - "version": "7.0.3", + "version": "7.1.0", "peerDependencies": { "near-api-js": "^0.44.2" } diff --git a/packages/math-wallet/package.json b/packages/math-wallet/package.json index 68a02882f..db3138139 100644 --- a/packages/math-wallet/package.json +++ b/packages/math-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/math-wallet", - "version": "7.0.3", + "version": "7.1.0", "peerDependencies": { "near-api-js": "^0.44.2" } diff --git a/packages/meteor-wallet/package.json b/packages/meteor-wallet/package.json index 99c03deaa..ff4c879db 100644 --- a/packages/meteor-wallet/package.json +++ b/packages/meteor-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/meteor-wallet", - "version": "7.0.3", + "version": "7.1.0", "peerDependencies": { "near-api-js": "^0.44.2" } diff --git a/packages/meteor-wallet/src/lib/meteor-wallet.ts b/packages/meteor-wallet/src/lib/meteor-wallet.ts index a572ead41..7331af3a8 100644 --- a/packages/meteor-wallet/src/lib/meteor-wallet.ts +++ b/packages/meteor-wallet/src/lib/meteor-wallet.ts @@ -52,10 +52,6 @@ const createMeteorWalletInjected: WalletBehaviourFactory< > = async ({ options, logger, store, params }) => { const _state = await setupWalletState(params, options.network); - const cleanup = () => { - _state.keyStore.clear(); - }; - const getAccounts = () => { const accountId = _state.wallet.getAccountId(); @@ -74,14 +70,6 @@ const createMeteorWalletInjected: WalletBehaviourFactory< const localKey = await signer.getPublicKey(account.accountId, networkId); - for (const trx of transactions) { - if (trx.signerId !== account.accountId) { - throw new Error( - `Transaction had a signerId which didn't match the currently logged in account` - ); - } - } - return Promise.all( transactions.map(async (transaction, index) => { const actions = transaction.actions.map((action) => @@ -140,8 +128,6 @@ const createMeteorWalletInjected: WalletBehaviourFactory< if (_state.wallet.isSignedIn()) { await _state.wallet.signOut(); } - - cleanup(); }, async isSignedIn() { diff --git a/packages/modal-ui-js/docs/api/modal.md b/packages/modal-ui-js/docs/api/modal.md new file mode 100644 index 000000000..cc899301b --- /dev/null +++ b/packages/modal-ui-js/docs/api/modal.md @@ -0,0 +1,41 @@ +## API Reference (Modal) + +### `.show()` + +****Parameters**** + +- N/A + +**Returns** + +- `void` + +**Description** + +Opens the modal for users to sign in to their preferred wallet. You can also use this method to switch wallets. + +**Example** + +```ts +modal.show(); +``` + +### `.hide()` + +**Parameters** + +- N/A + +**Returns** + +- `void` + +**Description** + +Closes the modal. + +**Example** + +```ts +modal.hide(); +``` diff --git a/packages/modal-ui-js/package.json b/packages/modal-ui-js/package.json index 8e9e08655..0f56574f2 100644 --- a/packages/modal-ui-js/package.json +++ b/packages/modal-ui-js/package.json @@ -1,4 +1,4 @@ { "name": "@near-wallet-selector/modal-ui-js", - "version": "7.0.3" + "version": "7.1.0" } diff --git a/packages/modal-ui-js/src/lib/components/ConnectHardwareWallet.ts b/packages/modal-ui-js/src/lib/components/ConnectHardwareWallet.ts index b41f979a1..c2ca60452 100644 --- a/packages/modal-ui-js/src/lib/components/ConnectHardwareWallet.ts +++ b/packages/modal-ui-js/src/lib/components/ConnectHardwareWallet.ts @@ -118,7 +118,7 @@ export function renderConnectHardwareWallet(module: ModuleState) { `; document.getElementById("continue-button")?.addEventListener("click", () => { - connectToWallet(module); + connectToWallet(module, false); }); document diff --git a/packages/modal-ui-js/src/lib/components/GetAWallet.ts b/packages/modal-ui-js/src/lib/components/GetAWallet.ts index 7dd0ac916..6b1997ff9 100644 --- a/packages/modal-ui-js/src/lib/components/GetAWallet.ts +++ b/packages/modal-ui-js/src/lib/components/GetAWallet.ts @@ -25,6 +25,10 @@ function goToWallet(module: ModuleState) { url = `https://wallet.${subdomain}near.org`; } + if (module.id === "here-wallet") { + url = "https://herewallet.app/"; + } + if ((url === "" && module.type === "bridge") || module.type === "hardware") { return; } diff --git a/packages/modal-ui-js/src/lib/components/ScanQRCode.ts b/packages/modal-ui-js/src/lib/components/ScanQRCode.ts new file mode 100644 index 000000000..ab4b0bfdc --- /dev/null +++ b/packages/modal-ui-js/src/lib/components/ScanQRCode.ts @@ -0,0 +1,110 @@ +import { ModuleState, Wallet } from "@near-wallet-selector/core"; + +import { connectToWallet } from "../render-modal"; +import copy from "copy-to-clipboard"; +import * as QRCode from "qrcode"; + +export async function renderScanQRCode( + module: ModuleState, + params: { uri: string; handleOpenDefaultModal: () => void } +) { + async function formatQRCodeImage(data: string) { + const dataString = await QRCode.toString(data, { margin: 0, type: "svg" }); + return dataString; + } + + const svg = await formatQRCodeImage(params.uri); + + document.querySelector(".modal-right")!.innerHTML = ` +
+
+

Scan with Your Mobile Device

+ +
+
+
+ ${svg} +
+
+
+ + + + + Copy to clipboard +
+
+
+

Prefer the official WalletConnect dialogue?

+ +
+
+ `; + + document.getElementById("continue-button")?.addEventListener("click", () => { + connectToWallet(module, false); + }); + + const copyBtnElement = document.getElementById("copy-uri-to-clipboard"); + const notificationElement = document.getElementById("uri-copy-notification"); + + const showURICopyNotification = (message: string) => { + if (notificationElement && copyBtnElement) { + notificationElement.innerHTML = message; + notificationElement.style.display = "block"; + copyBtnElement.style.display = "none"; + } + }; + + const hideNotification = () => { + if (notificationElement && copyBtnElement) { + copyBtnElement.style.display = "flex"; + notificationElement.style.display = "none"; + } + }; + + document + .getElementById("copy-uri-to-clipboard") + ?.addEventListener("click", () => { + if (!params.uri) { + return; + } + const success = copy(params.uri); + if (success) { + showURICopyNotification("Copied to clipboard"); + setTimeout(() => hideNotification(), 1200); + } else { + showURICopyNotification("Failed to copy to clipboard"); + setTimeout(() => hideNotification(), 1200); + } + }); + + document + .getElementById("default-modal-trigger") + ?.addEventListener("click", () => { + params.handleOpenDefaultModal(); + }); +} diff --git a/packages/modal-ui-js/src/lib/components/SpecifyDerivationPath.ts b/packages/modal-ui-js/src/lib/components/SpecifyDerivationPath.ts index 200708283..119a32633 100644 --- a/packages/modal-ui-js/src/lib/components/SpecifyDerivationPath.ts +++ b/packages/modal-ui-js/src/lib/components/SpecifyDerivationPath.ts @@ -55,6 +55,14 @@ export function renderSpecifyDerivationPath(module: ModuleState) {

Enter your preferred HD path, then scan for any active accounts.

+
diff --git a/packages/modal-ui-js/src/lib/components/WalletConnectionFailed.ts b/packages/modal-ui-js/src/lib/components/WalletConnectionFailed.ts index 8ca682b3d..ceafe31d8 100644 --- a/packages/modal-ui-js/src/lib/components/WalletConnectionFailed.ts +++ b/packages/modal-ui-js/src/lib/components/WalletConnectionFailed.ts @@ -49,6 +49,6 @@ export async function renderWalletConnectionFailed( `; document.getElementById("retry-button")?.addEventListener("click", () => { - connectToWallet(module); + connectToWallet(module, false); }); } diff --git a/packages/modal-ui-js/src/lib/render-modal.ts b/packages/modal-ui-js/src/lib/render-modal.ts index 47eda4351..912fb9b44 100644 --- a/packages/modal-ui-js/src/lib/render-modal.ts +++ b/packages/modal-ui-js/src/lib/render-modal.ts @@ -13,6 +13,7 @@ import { renderWalletConnectionFailed } from "./components/WalletConnectionFaile import { renderWalletNotInstalled } from "./components/WalletNotInstalled"; import { modalState } from "./modal"; import { renderWalletAccount } from "./components/WalletAccount"; +import { renderScanQRCode } from "./components/ScanQRCode"; export type HardwareWalletAccountState = HardwareWalletAccount & { selected: boolean; @@ -66,7 +67,10 @@ export const resolveAccounts = async ( } }; -export async function connectToWallet(module: ModuleState) { +export async function connectToWallet( + module: ModuleState, + qrCodeModal = false +) { if (!modalState) { return; } @@ -108,6 +112,27 @@ export async function connectToWallet(module: ModuleState) { } } + if (wallet.type === "bridge") { + const subscription = modalState.selector.on("uriChanged", ({ uri }) => { + renderScanQRCode(module, { + uri, + handleOpenDefaultModal: () => { + connectToWallet(module, true); + }, + }); + }); + + await wallet.signIn({ + contractId: modalState.options.contractId, + methodNames: modalState.options.methodNames, + qrCodeModal, + }); + + subscription.remove(); + modalState.container.children[0].classList.remove("open"); + return; + } + await wallet.signIn({ contractId: modalState.options.contractId, methodNames: modalState.options.methodNames, @@ -196,7 +221,7 @@ export function renderModal() { if (module.type === "hardware") { return renderConnectHardwareWallet(module); } - connectToWallet(module); + connectToWallet(module, false); }); } diff --git a/packages/modal-ui-js/src/lib/styles.css b/packages/modal-ui-js/src/lib/styles.css index 1850bc022..518c802f8 100644 --- a/packages/modal-ui-js/src/lib/styles.css +++ b/packages/modal-ui-js/src/lib/styles.css @@ -316,7 +316,7 @@ } .nws-modal-wrapper .nws-modal .modal-right::-webkit-scrollbar { - width: 10px; + width: 10px; } .nws-modal-wrapper .nws-modal .wallet-home-wrapper .get-wallet-wrapper, @@ -329,8 +329,8 @@ } .nws-modal-wrapper .nws-modal .connecting-wrapper-err { - margin-top: 45px; - padding: 0 28px; + margin-top: 45px; + padding: 0 28px; } .nws-modal-wrapper .nws-modal .wallet-home-wrapper .get-wallet-wrapper { @@ -446,10 +446,14 @@ } .nws-modal-wrapper .nws-modal .nws-modal-header .close-button { + display: flex; + justify-content: center; + align-items: center; border: 0; cursor: pointer; height: 32px; - padding: 4px; + width: 32px; + padding: 0; background-color: var(--wallet-selector-close-button-bg-color, var(--close-button-bg-color)); border-radius: 50px; } @@ -582,6 +586,11 @@ cursor: pointer; } +.nws-modal-wrapper .nws-modal .specify-path-wrapper .what-link a { + text-decoration: none; + color: var(--selected-wallet-bg); + font-size: 14px; +} .nws-modal-wrapper .specify-path-wrapper .change-path-wrapper .change-path .buttons-wrapper { display: flex; @@ -1053,51 +1062,114 @@ margin-right: 5px; } + +/************* Scan QR Code **********/ +.scan-qr-code { + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; +} + +.scan-qr-code .qr-code { + height: calc(100% - 200px); + border: 1px solid var(--wallet-selector-content-bg, var(--content-bg)); + border-radius: 4px; + text-align: center; + margin-top: 64px; +} + +.scan-qr-code .qr-code svg { + width: 239px; + height: 239px; +} + +.scan-qr-code .qr-code .copy-btn { + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + color: var(--selected-wallet-bg); + font-size: 14px; +} + +.scan-qr-code .qr-code .copy-btn svg { + margin-right: 5px; + width: 24px; + height: 24px; +} + +.scan-qr-code .qr-code .notification { + font-size: 14px; +} + +.scan-qr-code .footer { + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + gap: 24px; + padding: 24px; + bottom: 0; + font-size: 14px; +} + +.scan-qr-code .footer .btn { + background: var(--home-button-bg); + color: var(--text-color) +} + + /************* Responsive and mobile **********/ @media (min-width: 577px) { - .nws-modal-wrapper .nws-modal .modal-right .nws-modal-body .what-wallet-mobile { - display: none; - } + .nws-modal-wrapper .nws-modal .modal-right .nws-modal-body .what-wallet-mobile { + display: none; + } + + .scan-qr-code .footer { + position: absolute; + } } @media (min-width: 769px) { - .button-spacing { - margin: 90px - } + .button-spacing { + margin: 90px + } } @media (max-width: 768px) { - .nws-modal-wrapper .nws-modal .wallet-home-wrapper .wallet-info-wrapper { - margin-top: 45px; - } + .nws-modal-wrapper .nws-modal .wallet-home-wrapper .wallet-info-wrapper { + margin-top: 45px; + } - .button-spacing { - margin: 45px - } + .button-spacing { + margin: 45px + } - .nws-modal-wrapper .nws-modal .modal-left { - width: 40%; - border-right: 1px solid var(--wallet-selector-sidebar-border-color, var(--sidebar-border-color)); - padding: 32px 16px; - height: 100%; - overflow: auto; - } + .nws-modal-wrapper .nws-modal .modal-left { + width: 40%; + border-right: 1px solid var(--wallet-selector-sidebar-border-color, var(--sidebar-border-color)); + padding: 32px 16px; + height: 100%; + overflow: auto; + } - .nws-modal-wrapper .nws-modal .modal-right { - width: 60%; - padding: 32px 16px; - overflow: auto; - } + .nws-modal-wrapper .nws-modal .modal-right { + width: 60%; + padding: 32px 16px; + overflow: auto; + } - .nws-modal-wrapper .nws-modal .wallet-home-wrapper .wallet-info-wrapper { - padding: 0 0 0 10px; - } + .nws-modal-wrapper .nws-modal .wallet-home-wrapper .wallet-info-wrapper { + padding: 0 0 0 10px; + } - .nws-modal-wrapper .nws-modal .derivation-path-wrapper .enter-derivation-path .ledger-image { - margin-top: 30px; - margin-bottom: 35px; - } + .nws-modal-wrapper .nws-modal .derivation-path-wrapper .enter-derivation-path .ledger-image { + margin-top: 30px; + margin-bottom: 35px; + } } @@ -1105,170 +1177,170 @@ @media (max-width: 576px) { - .nws-modal-wrapper .nws-modal .wallet-home-wrapper .get-wallet-wrapper, - .nws-modal-wrapper .nws-modal .wallet-home-wrapper .wallet-info-wrapper, - .nws-modal-wrapper .nws-modal .connecting-wrapper, - .nws-modal-wrapper .nws-modal .wallet-not-installed-wrapper, - .nws-modal-wrapper .nws-modal .switch-network-message-wrapper { - margin-top: 20px; - } - - .nws-modal-wrapper .nws-modal .modal-left .modal-left-title h2 { - text-align: center; - } - - .nws-modal-wrapper .nws-modal .nws-modal-body button.get-wallet { - background-color: var(--wallet-selector-home-button-bg, var(--content-bg)); - } - - .nws-modal-wrapper .wallet-not-installed-wrapper>p { - margin: 20px 0px 30px 0px; - max-width: 500px; - } - - .nws-modal-wrapper .nws-modal .modal-right .nws-modal-body .what-wallet-hide { - display: none; - } - - .nws-modal-wrapper .nws-modal .modal-right .nws-modal-body .what-wallet-mobile p { - font-size: 14px; - margin-bottom: 0; - text-align: center; - margin: auto; - } - - .nws-modal-wrapper .nws-modal { - width: 100%; - display: block; - overflow: auto; - bottom: 0; - height: 500px; - background: var(--wallet-selector-mobile-bottom-section, var(--bottom-section)); - border-radius: 16px 16px 0px 0px; - } - - .nws-modal-wrapper .nws-modal .modal-left { - width: 100%; - background-color: var(--wallet-selector-content-bg, var(--content-bg)); - height: auto; - padding: 32px 12px; - } - - .nws-modal-wrapper .nws-modal .modal-left .nws-modal-body { - display: flex; - overflow: auto; - } - - .nws-modal-wrapper .nws-modal .modal-left .nws-modal-body .wallet-options-wrapper { - display: flex; - margin: auto; - } - - .nws-modal-wrapper .nws-modal .modal-right { - width: 100%; - background-color: var(--wallet-selector-mobile-bottom-section, var(--bottom-section)); - } - - .nws-modal-wrapper .nws-modal .modal-right .nws-modal-header h3.middleTitle { - text-align: center; - font-size: 16px; - margin: 4px auto; - } - - .nws-modal-wrapper .nws-modal .modal-right .nws-modal-body .content { - font-size: 14px; - text-align: center; - color: var(--mobile-text); - margin: 0 - } - - .nws-modal-wrapper .nws-modal .modal-right .nws-modal-body { - margin-top: 10px; - padding: 0; - } - - .nws-modal-wrapper .nws-modal .nws-modal-body button.middleButton { - margin: 25px auto 12px auto; - } - - .nws-modal-wrapper .nws-modal .modal-header { - display: block; - font-size: 18px; - text-align: center; - } - - .nws-modal-wrapper .nws-modal .nws-modal-header .close-button { - position: absolute; - right: 30px; - top: 30px; - } - - .nws-modal-wrapper .nws-modal .nws-modal-header h2 { - font-size: 18px; - text-align: center; - } - - .nws-modal-wrapper .nws-modal .wallet-options-wrapper .description { - display: none; - } - - .nws-modal-wrapper .nws-modal .wallet-options-wrapper .options-list { - display: flex; - overflow-x: auto; - } - - .nws-modal-wrapper .nws-modal .info { - display: none; - width: 90px; - } - - .single-wallet { - display: block; - width: 85px; - } - - .single-wallet.sidebar .icon { - width: 56px; - height: 56px; - margin: auto; - } - - .single-wallet.sidebar .content { - width: auto; - } - - .single-wallet .content .title { - margin-top: 10px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - .nws-modal-wrapper .nws-modal .wallet-home-wrapper .get-wallet-wrapper { - margin-top: 0; - } - - .nws-modal-wrapper .nws-modal .derivation-path-wrapper .enter-derivation-path .ledger-image, - .nws-modal-wrapper .specify-path-wrapper .change-path-wrapper { - margin-top: 30px; - margin-bottom: 30px; - } - - .nws-modal-wrapper .nws-modal .derivation-path-wrapper .enter-derivation-path .ledger-description>p { - max-width: 450px; - margin-left: auto; - margin-right: auto; - } - - ::-webkit-scrollbar { - height: 4px; - width: 4px; - background: var(--backdrop-bg); - - } - - ::-webkit-scrollbar-thumb:horizontal { - background: var(--close-button-fill-icon-color); - border-radius: 10px; - } + .nws-modal-wrapper .nws-modal .wallet-home-wrapper .get-wallet-wrapper, + .nws-modal-wrapper .nws-modal .wallet-home-wrapper .wallet-info-wrapper, + .nws-modal-wrapper .nws-modal .connecting-wrapper, + .nws-modal-wrapper .nws-modal .wallet-not-installed-wrapper, + .nws-modal-wrapper .nws-modal .switch-network-message-wrapper { + margin-top: 20px; + } + + .nws-modal-wrapper .nws-modal .modal-left .modal-left-title h2 { + text-align: center; + } + + .nws-modal-wrapper .nws-modal .nws-modal-body button.get-wallet { + background-color: var(--wallet-selector-home-button-bg, var(--content-bg)); + } + + .nws-modal-wrapper .wallet-not-installed-wrapper>p { + margin: 20px 0px 30px 0px; + max-width: 500px; + } + + .nws-modal-wrapper .nws-modal .modal-right .nws-modal-body .what-wallet-hide { + display: none; + } + + .nws-modal-wrapper .nws-modal .modal-right .nws-modal-body .what-wallet-mobile p { + font-size: 14px; + margin-bottom: 0; + text-align: center; + margin: auto; + } + + .nws-modal-wrapper .nws-modal { + width: 100%; + display: block; + overflow: auto; + bottom: 0; + height: 500px; + background: var(--wallet-selector-mobile-bottom-section, var(--bottom-section)); + border-radius: 16px 16px 0px 0px; + } + + .nws-modal-wrapper .nws-modal .modal-left { + width: 100%; + background-color: var(--wallet-selector-content-bg, var(--content-bg)); + height: auto; + padding: 32px 12px; + } + + .nws-modal-wrapper .nws-modal .modal-left .nws-modal-body { + display: flex; + overflow: auto; + } + + .nws-modal-wrapper .nws-modal .modal-left .nws-modal-body .wallet-options-wrapper { + display: flex; + margin: auto; + } + + .nws-modal-wrapper .nws-modal .modal-right { + width: 100%; + background-color: var(--wallet-selector-mobile-bottom-section, var(--bottom-section)); + } + + .nws-modal-wrapper .nws-modal .modal-right .nws-modal-header h3.middleTitle { + text-align: center; + font-size: 16px; + margin: 4px auto; + } + + .nws-modal-wrapper .nws-modal .modal-right .nws-modal-body .content { + font-size: 14px; + text-align: center; + color: var(--mobile-text); + margin: 0 + } + + .nws-modal-wrapper .nws-modal .modal-right .nws-modal-body { + margin-top: 10px; + padding: 0; + } + + .nws-modal-wrapper .nws-modal .nws-modal-body button.middleButton { + margin: 25px auto 12px auto; + } + + .nws-modal-wrapper .nws-modal .modal-header { + display: block; + font-size: 18px; + text-align: center; + } + + .nws-modal-wrapper .nws-modal .nws-modal-header .close-button { + position: absolute; + right: 30px; + top: 30px; + } + + .nws-modal-wrapper .nws-modal .nws-modal-header h2 { + font-size: 18px; + text-align: center; + } + + .nws-modal-wrapper .nws-modal .wallet-options-wrapper .description { + display: none; + } + + .nws-modal-wrapper .nws-modal .wallet-options-wrapper .options-list { + display: flex; + overflow-x: auto; + } + + .nws-modal-wrapper .nws-modal .info { + display: none; + width: 90px; + } + + .single-wallet { + display: block; + width: 85px; + } + + .single-wallet.sidebar .icon { + width: 56px; + height: 56px; + margin: auto; + } + + .single-wallet.sidebar .content { + width: auto; + } + + .single-wallet .content .title { + margin-top: 10px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .nws-modal-wrapper .nws-modal .wallet-home-wrapper .get-wallet-wrapper { + margin-top: 0; + } + + .nws-modal-wrapper .nws-modal .derivation-path-wrapper .enter-derivation-path .ledger-image, + .nws-modal-wrapper .specify-path-wrapper .change-path-wrapper { + margin-top: 30px; + margin-bottom: 30px; + } + + .nws-modal-wrapper .nws-modal .derivation-path-wrapper .enter-derivation-path .ledger-description>p { + max-width: 450px; + margin-left: auto; + margin-right: auto; + } + + ::-webkit-scrollbar { + height: 4px; + width: 4px; + background: var(--backdrop-bg); + + } + + ::-webkit-scrollbar-thumb:horizontal { + background: var(--close-button-fill-icon-color); + border-radius: 10px; + } } diff --git a/packages/modal-ui/package.json b/packages/modal-ui/package.json index ce6b9b0dc..d216985df 100644 --- a/packages/modal-ui/package.json +++ b/packages/modal-ui/package.json @@ -1,4 +1,4 @@ { "name": "@near-wallet-selector/modal-ui", - "version": "7.0.3" + "version": "7.1.0" } diff --git a/packages/modal-ui/src/lib/components/CopyIcon.tsx b/packages/modal-ui/src/lib/components/CopyIcon.tsx new file mode 100644 index 000000000..2f2067575 --- /dev/null +++ b/packages/modal-ui/src/lib/components/CopyIcon.tsx @@ -0,0 +1,26 @@ +import React from "react"; + +export const CopyIcon = () => ( + + + + +); diff --git a/packages/modal-ui/src/lib/components/DerivationPath.tsx b/packages/modal-ui/src/lib/components/DerivationPath.tsx index 4fc5637f6..953b86a44 100644 --- a/packages/modal-ui/src/lib/components/DerivationPath.tsx +++ b/packages/modal-ui/src/lib/components/DerivationPath.tsx @@ -339,6 +339,14 @@ export const DerivationPath: React.FC = ({

Enter your preferred HD path, then scan for any active accounts.

+

+ + What's this? + +

{ + handleWalletClick(module, false); + }} selector={selector} /> @@ -202,7 +233,7 @@ export const Modal: React.FC = ({ module={route.params?.module} onBack={(retry) => { if (retry) { - handleWalletClick(selectedWallet!); + handleWalletClick(selectedWallet!, false); } setAlertMessage(null); setRoute({ @@ -285,6 +316,16 @@ export const Modal: React.FC = ({ onCloseModal={handleDismissClick} /> )} + + {route.name === "ScanQRCode" && ( + { + handleWalletClick(selectedWallet!, true); + }} + onCloseModal={handleDismissClick} + uri={bridgeWalletUri} + /> + )} diff --git a/packages/modal-ui/src/lib/components/Modal.types.ts b/packages/modal-ui/src/lib/components/Modal.types.ts index f468b6d25..1e76cd63a 100644 --- a/packages/modal-ui/src/lib/components/Modal.types.ts +++ b/packages/modal-ui/src/lib/components/Modal.types.ts @@ -29,6 +29,11 @@ export type WalletConnectedParams = { module: ModuleState | undefined; }; +export type ScanQRCodeParams = { + wallet: Wallet; + uri: string | undefined; +}; + export type AlertMessageModalRoute = { name: "AlertMessage"; params?: AlertMessageModalRouteParams; @@ -68,6 +73,11 @@ export type WalletConnected = { params?: WalletConnectedParams; }; +export type ScanQRCode = { + name: "ScanQRCode"; + params?: ScanQRCodeParams; +}; + export type ModalRoute = | AlertMessageModalRoute | WalletOptionsModalRoute @@ -76,4 +86,5 @@ export type ModalRoute = | WalletNetworkChangedModalRoute | WalletConnectingModalRoute | WalletHome - | WalletConnected; + | WalletConnected + | ScanQRCode; diff --git a/packages/modal-ui/src/lib/components/ScanQRCode.tsx b/packages/modal-ui/src/lib/components/ScanQRCode.tsx new file mode 100644 index 000000000..dcd0eff42 --- /dev/null +++ b/packages/modal-ui/src/lib/components/ScanQRCode.tsx @@ -0,0 +1,73 @@ +import React from "react"; +import QRCode from "qrcode"; +import copy from "copy-to-clipboard"; +import { ModalHeader } from "./ModalHeader"; +import { CopyIcon } from "./CopyIcon"; + +interface ScanQRCodeProps { + uri?: string; + onCloseModal: () => void; + handleOpenDefaultModal?: () => void; +} + +async function formatQRCodeImage(data: string) { + return await QRCode.toString(data, { margin: 0, type: "svg" }); +} + +export const ScanQRCode: React.FC = ({ + uri, + onCloseModal, + handleOpenDefaultModal, +}) => { + const [notification, setNotification] = React.useState(""); + const [svg, setSvg] = React.useState(""); + + const copyToClipboard = () => { + if (!uri) { + return; + } + const success = copy(uri); + if (success) { + setNotification("Copied to clipboard"); + setTimeout(() => setNotification(""), 1200); + } else { + setNotification("Failed to copy to clipboard"); + setTimeout(() => setNotification(""), 1200); + } + }; + + React.useEffect(() => { + (async () => { + if (uri) { + setSvg(await formatQRCodeImage(uri)); + } + })(); + }, [uri]); + + return ( +
+ + +
+
+ {notification ? ( +
{notification}
+ ) : ( +
+ + Copy to clipboard +
+ )} +
+
+

Prefer the official WalletConnect dialogue?

+ +
+
+ ); +}; diff --git a/packages/modal-ui/src/lib/components/WalletHome.tsx b/packages/modal-ui/src/lib/components/WalletHome.tsx index 3fde2bae0..879ed1158 100644 --- a/packages/modal-ui/src/lib/components/WalletHome.tsx +++ b/packages/modal-ui/src/lib/components/WalletHome.tsx @@ -56,6 +56,10 @@ export const WalletHome: React.FC = ({ url = `https://wallet.${subdomain}near.org`; } + if (module.id === "here-wallet") { + url = "https://herewallet.app/"; + } + if ( (url === "" && module.type === "bridge") || module.type === "hardware" diff --git a/packages/modal-ui/src/lib/components/styles.css b/packages/modal-ui/src/lib/components/styles.css index f8d677a3d..4f0d38cbe 100644 --- a/packages/modal-ui/src/lib/components/styles.css +++ b/packages/modal-ui/src/lib/components/styles.css @@ -445,10 +445,14 @@ } .nws-modal-wrapper .nws-modal .nws-modal-header .close-button { + display: flex; + justify-content: center; + align-items: center; border: 0; cursor: pointer; height: 32px; - padding: 4px; + width: 32px; + padding: 0; background-color: var(--wallet-selector-close-button-bg-color, var(--close-button-bg-color)); border-radius: 50px; } @@ -581,6 +585,11 @@ cursor: pointer; } +.nws-modal-wrapper .nws-modal .specify-path-wrapper .what-link a { + text-decoration: none; + color: var(--selected-wallet-bg); + font-size: 14px; +} .nws-modal-wrapper .specify-path-wrapper .change-path-wrapper .change-path .buttons-wrapper { display: flex; @@ -1050,12 +1059,72 @@ margin-right: 5px; } +/************* Scan QR Code **********/ +.scan-qr-code{ + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + height: 100%; +} + +.scan-qr-code .qr-code{ + height: calc(100% - 200px); + border: 1px solid var(--wallet-selector-content-bg, var(--content-bg)); + border-radius: 4px; + text-align: center; + margin-top: 64px; +} + +.scan-qr-code .qr-code svg{ + width: 239px; + height: 239px; +} + +.scan-qr-code .qr-code .copy-btn{ + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + color: var(--selected-wallet-bg); + font-size: 14px; +} +.scan-qr-code .qr-code .copy-btn svg{ + margin-right: 5px; + width: 24px; + height: 24px; +} +.scan-qr-code .qr-code .notification{ + font-size: 14px; +} + + +.scan-qr-code .footer{ + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + gap: 24px; + padding:24px; + bottom: 0; + font-size: 14px; +} + +.scan-qr-code .footer .btn{ + background: var(--home-button-bg); + color: var(--text-color) +} + /************* Responsive and mobile **********/ @media (min-width: 577px) { .nws-modal-wrapper .nws-modal .modal-right .nws-modal-body .what-wallet-mobile { display: none; } + .scan-qr-code .footer { + position: absolute; + } } @media (min-width: 769px) { diff --git a/packages/my-near-wallet/package.json b/packages/my-near-wallet/package.json index af7d6152e..634891b83 100644 --- a/packages/my-near-wallet/package.json +++ b/packages/my-near-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/my-near-wallet", - "version": "7.0.3", + "version": "7.1.0", "peerDependencies": { "near-api-js": "^0.44.2" } diff --git a/packages/my-near-wallet/src/lib/my-near-wallet.ts b/packages/my-near-wallet/src/lib/my-near-wallet.ts index 5a35a68f8..7a55e0c5d 100644 --- a/packages/my-near-wallet/src/lib/my-near-wallet.ts +++ b/packages/my-near-wallet/src/lib/my-near-wallet.ts @@ -61,11 +61,6 @@ const setupWalletState = async ( const wallet = new WalletConnection(near, "near_app"); - // Cleanup up any pending keys (cancelled logins). - if (!wallet.isSignedIn()) { - await keyStore.clear(); - } - return { wallet, keyStore, @@ -78,10 +73,6 @@ const MyNearWallet: WalletBehaviourFactory< > = async ({ metadata, options, store, params, logger }) => { const _state = await setupWalletState(params, options.network); - const cleanup = () => { - _state.keyStore.clear(); - }; - const getAccounts = () => { const accountId: string | null = _state.wallet.getAccountId(); @@ -148,8 +139,6 @@ const MyNearWallet: WalletBehaviourFactory< if (_state.wallet.isSignedIn()) { _state.wallet.signOut(); } - - cleanup(); }, async getAccounts() { diff --git a/packages/near-wallet/package.json b/packages/near-wallet/package.json index 9d6803be5..37113043f 100644 --- a/packages/near-wallet/package.json +++ b/packages/near-wallet/package.json @@ -1,4 +1,4 @@ { "name": "@near-wallet-selector/near-wallet", - "version": "7.0.3" + "version": "7.1.0" } diff --git a/packages/nearfi/.babelrc b/packages/nearfi/.babelrc new file mode 100644 index 000000000..cf7ddd99c --- /dev/null +++ b/packages/nearfi/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@nrwl/web/babel", { "useBuiltIns": "usage" }]] +} diff --git a/packages/nearfi/.eslintrc.json b/packages/nearfi/.eslintrc.json new file mode 100644 index 000000000..9d9c0db55 --- /dev/null +++ b/packages/nearfi/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/nearfi/README.md b/packages/nearfi/README.md new file mode 100644 index 000000000..1839c9566 --- /dev/null +++ b/packages/nearfi/README.md @@ -0,0 +1,64 @@ +# @near-wallet-selector/nearfi + +This is the [NearFi wallet](https://nearfi.finance) package for NEAR Wallet Selector. + +## Installation and Usage + +The easiest way to use this package is to install it from the NPM registry, this package requires near-api-js v0.44.2 or above: + +```bash +# Using Yarn +yarn add near-api-js@^0.44.2 + +# Using NPM. +npm install near-api-js@^0.44.2 +``` + +```bash +# Using Yarn +yarn add @near-wallet-selector/nearfi + +# Using NPM. +npm install @near-wallet-selector/nearfi +``` + +Then use it in your dApp: + +```ts +import { setupWalletSelector } from "@near-wallet-selector/core"; +import { setupNearFi } from "@near-wallet-selector/nearfi"; + +// NearFi for Wallet Selector can be setup without any params or it can take one optional param. +const nearFi = setupNearFi({ + iconUrl: "https://yourdomain.com/yourwallet-icon.png" //optional +}); + +const selector = await setupWalletSelector({ + network: "testnet", + modules: [nearFi], +}); +``` + +> Note: NearFi wallet option is available only in the in-built browser of NearFi mobile app. + + +## Options + +- `iconUrl`: (`string?`): Image URL for the icon shown in the modal. This can also be a relative path or base64 encoded image. Defaults to `./assets/nearfi-icon.png`. + +## Assets + +Assets such as icons can be found in the `/assets` directory of the package. Below is an example using Webpack: + +```ts +import { setupNearFi } from "@near-wallet-selector/nearfi"; +import nearfiIconUrl from "@near-wallet-selector/nearfi/assets/nearfi-icon.png"; + +const nearfi = setupNearFi({ + iconUrl: nearfiIconUrl +}); +``` + +## License + +This repository is distributed under the terms of both the MIT license and the Apache License (Version 2.0). diff --git a/packages/nearfi/assets/nearfi-icon.png b/packages/nearfi/assets/nearfi-icon.png new file mode 100644 index 000000000..14411e47b Binary files /dev/null and b/packages/nearfi/assets/nearfi-icon.png differ diff --git a/packages/nearfi/jest.config.js b/packages/nearfi/jest.config.js new file mode 100644 index 000000000..6782b253c --- /dev/null +++ b/packages/nearfi/jest.config.js @@ -0,0 +1,14 @@ +module.exports = { + displayName: "nearfi", + preset: "../../jest.preset.js", + globals: { + "ts-jest": { + tsconfig: "/tsconfig.spec.json", + }, + }, + transform: { + "^.+\\.[tj]sx?$": "ts-jest", + }, + moduleFileExtensions: ["ts", "tsx", "js", "jsx"], + coverageDirectory: "../../coverage/packages/nearfi", +}; diff --git a/packages/nearfi/package.json b/packages/nearfi/package.json new file mode 100644 index 000000000..03f1a688d --- /dev/null +++ b/packages/nearfi/package.json @@ -0,0 +1,7 @@ +{ + "name": "@near-wallet-selector/nearfi", + "version": "7.1.0", + "peerDependencies": { + "near-api-js": "^0.44.2" + } +} diff --git a/packages/nearfi/project.json b/packages/nearfi/project.json new file mode 100644 index 000000000..29b2f246a --- /dev/null +++ b/packages/nearfi/project.json @@ -0,0 +1,54 @@ +{ + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/nearfi/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nrwl/web:rollup", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/packages/nearfi", + "tsConfig": "packages/nearfi/tsconfig.lib.json", + "project": "packages/nearfi/package.json", + "entryFile": "packages/nearfi/src/index.ts", + "buildableProjectDepsInPackageJsonType": "dependencies", + "compiler": "babel", + "format": ["esm", "cjs"], + "assets": [ + { + "glob": "packages/nearfi/README.md", + "input": ".", + "output": "." + }, + { + "glob": "packages/nearfi/assets/*", + "input": ".", + "output": "assets" + } + ] + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["packages/nearfi/**/*.ts"] + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["coverage/packages/nearfi"], + "options": { + "jestConfig": "packages/nearfi/jest.config.js", + "passWithNoTests": true + } + }, + "deploy": { + "executor": "ngx-deploy-npm:deploy", + "options": { + "access": "public" + } + } + }, + "tags": ["injected-wallet"] +} diff --git a/packages/nearfi/src/index.ts b/packages/nearfi/src/index.ts new file mode 100644 index 000000000..6b9620dd4 --- /dev/null +++ b/packages/nearfi/src/index.ts @@ -0,0 +1,2 @@ +export { setupNearFi } from "./lib/nearfi"; +export type { NearFiParams } from "./lib/nearfi"; diff --git a/packages/nearfi/src/lib/icon.ts b/packages/nearfi/src/lib/icon.ts new file mode 100644 index 000000000..71651e91b --- /dev/null +++ b/packages/nearfi/src/lib/icon.ts @@ -0,0 +1 @@ +export default `data:image/png;base64,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`; diff --git a/packages/nearfi/src/lib/injected-nearfi.ts b/packages/nearfi/src/lib/injected-nearfi.ts new file mode 100644 index 000000000..554bd6818 --- /dev/null +++ b/packages/nearfi/src/lib/injected-nearfi.ts @@ -0,0 +1,141 @@ +// Interfaces based on "documentation": https://github.com/SenderWallet/sender-wallet-integration-tutorial + +// Empty string if we haven't signed in before. +import { providers } from "near-api-js"; + +interface AccessKey { + publicKey: { + data: Uint8Array; + keyType: number; + }; + secretKey: string; +} + +export interface RequestSignInResponse { + accessKey: AccessKey; + error: string | { type: string }; + notificationId: number; + type: "nearfi-wallet-result"; +} + +export type SignOutResponse = true | { error: string | { type: string } }; + +export interface RpcInfo { + explorerUrl: string; + helperUrl: string; + index: number; + name: string; + network: string; + networkId: string; + nodeUrl: string; + walletUrl: string; + wrapNearContract: string; +} + +export interface GetRpcResponse { + method: "getRpc"; + notificationId: number; + rpc: RpcInfo; + type: "nearfi-wallet-result"; +} + +export interface RequestSignInParams { + contractId: string; + methodNames?: Array; + amount?: string; // in yoctoⓃ +} + +export interface RpcChangedResponse { + explorerUrl: string; + helperUrl: string; + index: number; + name: string; + network: string; + networkId: string; + nodeUrl: string; + walletUrl: string; + wrapNearContract: string; +} + +export interface SendMoneyParams { + receiverId: string; + amount: string; +} + +export interface SendMoneyResponse { + transactionHash: string; + error?: string; +} + +export interface Action { + methodName: string; + args: object; + gas: string; + deposit: string; +} + +export interface SignAndSendTransactionParams { + receiverId: string; + actions: Array; +} + +// Seems to reuse signAndSendTransactions internally, hence the wrong method name and list of responses. +export interface SignAndSendTransactionResponse { + actionType: "DAPP/DAPP_POPUP_RESPONSE"; + method: "signAndSendTransactions"; + notificationId: number; + error?: string; + response?: Array; + type: "nearfi-wallet-extensionResult"; +} + +export interface SignAndSendTransactionsResponse { + actionType: "DAPP/DAPP_POPUP_RESPONSE"; + method: "signAndSendTransactions"; + notificationId: number; + error?: string; + response?: Array; + type: "nearfi-wallet-extensionResult"; +} + +export interface Transaction { + receiverId: string; + actions: Array; +} + +export interface RequestSignTransactionsParams { + transactions: Array; +} + +export interface NearFiEvents { + signIn: () => void; + signOut: () => void; + accountChanged: (changedAccountId: string) => void; + rpcChanged: (response: RpcChangedResponse) => void; +} + +export interface InjectedNearFi { + isNearFi: boolean; + getAccountId: () => string | null; + getRpc: () => Promise; + requestSignIn: ( + params: RequestSignInParams + ) => Promise; + signOut: () => Promise; + isSignedIn: () => boolean; + removeEventListener: (event: string) => void; + on: ( + event: Event, + callback: NearFiEvents[Event] + ) => void; + // TODO: Determine return type. + sendMoney: (params: SendMoneyParams) => Promise; + signAndSendTransaction: ( + params: SignAndSendTransactionParams + ) => Promise; + requestSignTransactions: ( + params: RequestSignTransactionsParams + ) => Promise; + log: (...msg: Array) => void; + resolveSignInState: () => Promise; +} diff --git a/packages/nearfi/src/lib/nearfi.ts b/packages/nearfi/src/lib/nearfi.ts new file mode 100644 index 000000000..928bb826b --- /dev/null +++ b/packages/nearfi/src/lib/nearfi.ts @@ -0,0 +1,261 @@ +import { isMobile } from "is-mobile"; +import { + WalletModuleFactory, + WalletBehaviourFactory, + InjectedWallet, + Action, + Transaction, + FunctionCallAction, + Optional, +} from "@near-wallet-selector/core"; +import { waitFor } from "@near-wallet-selector/core"; +import type { InjectedNearFi } from "./injected-nearfi"; +import icon from "./icon"; + +declare global { + interface Window { + nearFiWallet: InjectedNearFi | undefined; + } +} + +export interface NearFiParams { + iconUrl?: string; + deprecated?: boolean; +} + +interface NearFiState { + wallet: InjectedNearFi; +} + +const isInstalled = () => { + return waitFor(() => !!window.nearFiWallet?.isNearFi).catch(() => false); +}; + +const setupNearFiState = (): NearFiState => { + const wallet = window.nearFiWallet!; + + return { + wallet, + }; +}; + +const NearFi: WalletBehaviourFactory = async ({ + options, + metadata, + store, + emitter, + logger, +}) => { + const _state = setupNearFiState(); + + const signOut = async () => { + if (!_state.wallet.isSignedIn()) { + return; + } + + const res = await _state.wallet.signOut(); + + if (res === true) { + return; + } + + const error = new Error( + typeof res.error === "string" ? res.error : res.error.type + ); + + // Prevent signing out by throwing. + if (error.message === "User reject") { + throw error; + } + + // Continue signing out but log the issue. + logger.log("Failed to sign out"); + logger.error(error); + }; + + const setupEvents = () => { + _state.wallet.on("accountChanged", async (newAccountId) => { + logger.log("onAccountChange", newAccountId); + emitter.emit("signedOut", null); + }); + + _state.wallet.on("rpcChanged", async (rpc) => { + logger.log("onNetworkChange", rpc); + + if (options.network.networkId !== rpc.networkId) { + await signOut(); + + emitter.emit("signedOut", null); + emitter.emit("networkChanged", { networkId: rpc.networkId }); + } + }); + }; + + const getAccounts = async () => { + let accountId = _state.wallet.getAccountId(); + if (!accountId) { + await _state.wallet.resolveSignInState(); + accountId = _state.wallet.getAccountId(); + if (!accountId) { + return []; + } + } + return [{ accountId }]; + }; + + const isValidActions = ( + actions: Array + ): actions is Array => { + return actions.every((x) => x.type === "FunctionCall"); + }; + + const transformActions = (actions: Array) => { + const validActions = isValidActions(actions); + + if (!validActions) { + throw new Error( + `Only 'FunctionCall' actions types are supported by ${metadata.name}` + ); + } + + return actions.map((x) => x.params); + }; + + const transformTransactions = ( + transactions: Array> + ) => { + return transactions.map((transaction) => { + return { + receiverId: transaction.receiverId, + actions: transformActions(transaction.actions), + }; + }); + }; + if (_state.wallet && _state.wallet.isSignedIn()) { + setupEvents(); + } + + return { + async signIn({ contractId, methodNames }) { + const existingAccounts = await getAccounts(); + if (existingAccounts.length) { + return existingAccounts; + } + + const { accessKey, error } = await _state.wallet.requestSignIn({ + contractId, + methodNames, + }); + if (!accessKey || error) { + await signOut(); + + throw new Error( + (typeof error === "string" ? error : error.type) || + "Failed to sign in" + ); + } + + setupEvents(); + return await getAccounts(); + }, + + signOut, + + async getAccounts() { + return await getAccounts(); + }, + + async verifyOwner({ message }) { + logger.log("NearFi:verifyOwner", { message }); + + throw new Error(`Method not supported by ${metadata.name}`); + }, + + async signAndSendTransaction({ signerId, receiverId, actions }) { + logger.log("signAndSendTransaction", { signerId, receiverId, actions }); + + const { contract } = store.getState(); + + if (!_state.wallet.isSignedIn() || !contract) { + throw new Error("Wallet not signed in"); + } + + return _state.wallet + .signAndSendTransaction({ + receiverId: receiverId || contract.contractId, + actions: transformActions(actions), + }) + .then((res) => { + if (res.error) { + throw new Error(res.error); + } + + // Shouldn't happen but avoids inconsistent responses. + if (!res.response?.length) { + throw new Error("Invalid response"); + } + + return res.response[0]; + }); + }, + + async signAndSendTransactions({ transactions }) { + logger.log("signAndSendTransactions", { transactions }); + + if (!_state.wallet.isSignedIn()) { + throw new Error("Wallet not signed in"); + } + + return _state.wallet + .requestSignTransactions({ + transactions: transformTransactions(transactions), + }) + .then((res) => { + if (res.error) { + throw new Error(res.error); + } + + // Shouldn't happen but avoids inconsistent responses. + if (!res.response?.length) { + throw new Error("Invalid response"); + } + + return res.response; + }); + }, + }; +}; + +export function setupNearFi({ + iconUrl = icon, + deprecated = false, +}: NearFiParams = {}): WalletModuleFactory { + return async () => { + const mobile = isMobile(); + const installed = await isInstalled(); + + if (!mobile || !installed) { + return null; + } + + // Add extra wait to ensure NearFi's sign in status is read from the + // browser extension background env. + await waitFor(() => !!window.nearFiWallet?.isSignedIn(), { + timeout: 300, + }).catch(() => false); + + return { + id: "nearfi", + type: "injected", + metadata: { + name: "NearFi", + description: "Your NEAR DeFi experience On The Go", + iconUrl, + downloadUrl: "https://nearfi.finance", + deprecated, + available: installed, + }, + init: NearFi, + }; + }; +} diff --git a/packages/nearfi/tsconfig.json b/packages/nearfi/tsconfig.json new file mode 100644 index 000000000..e258886ff --- /dev/null +++ b/packages/nearfi/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/packages/nearfi/tsconfig.lib.json b/packages/nearfi/tsconfig.lib.json new file mode 100644 index 000000000..a8b9431f9 --- /dev/null +++ b/packages/nearfi/tsconfig.lib.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true, + "types": [] + }, + "include": ["**/*.ts"], + "exclude": ["**/*.spec.ts"] +} diff --git a/packages/nearfi/tsconfig.spec.json b/packages/nearfi/tsconfig.spec.json new file mode 100644 index 000000000..67f149c4c --- /dev/null +++ b/packages/nearfi/tsconfig.spec.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "**/*.test.ts", + "**/*.spec.ts", + "**/*.test.tsx", + "**/*.spec.tsx", + "**/*.test.js", + "**/*.spec.js", + "**/*.test.jsx", + "**/*.spec.jsx", + "**/*.d.ts" + ] +} diff --git a/packages/nightly-connect/package.json b/packages/nightly-connect/package.json index fccf557d4..a034f7239 100644 --- a/packages/nightly-connect/package.json +++ b/packages/nightly-connect/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/nightly-connect", - "version": "7.0.3", + "version": "7.1.0", "peerDependencies": { "near-api-js": "^0.44.2" } diff --git a/packages/nightly/package.json b/packages/nightly/package.json index 72b81f341..eb0835502 100644 --- a/packages/nightly/package.json +++ b/packages/nightly/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/nightly", - "version": "7.0.3", + "version": "7.1.0", "peerDependencies": { "near-api-js": "^0.44.2" } diff --git a/packages/sender/package.json b/packages/sender/package.json index 01d5db461..d753e6047 100644 --- a/packages/sender/package.json +++ b/packages/sender/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/sender", - "version": "7.0.3", + "version": "7.1.0", "peerDependencies": { "near-api-js": "^0.44.2" } diff --git a/packages/wallet-connect/README.md b/packages/wallet-connect/README.md index 5cbc9f21a..19a867473 100644 --- a/packages/wallet-connect/README.md +++ b/packages/wallet-connect/README.md @@ -4,8 +4,15 @@ This is the [WalletConnect](https://walletconnect.com/) package for NEAR Wallet ## Installation and Usage -The easiest way to use this package is to install it from the NPM registry: +The easiest way to use this package is to install it from the NPM registry, this package requires `near-api-js` v0.44.2 or above: +```bash +# Using Yarn +yarn add near-api-js@^0.44.2 + +# Using NPM. +npm install near-api-js@^0.44.2 +``` ```bash # Using Yarn yarn add @near-wallet-selector/wallet-connect diff --git a/packages/wallet-connect/package.json b/packages/wallet-connect/package.json index 0ec70506d..0d90dcaf4 100644 --- a/packages/wallet-connect/package.json +++ b/packages/wallet-connect/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/wallet-connect", - "version": "7.0.3", + "version": "7.1.0", "peerDependencies": { "near-api-js": "^0.44.2" } diff --git a/packages/wallet-connect/src/lib/wallet-connect-client.ts b/packages/wallet-connect/src/lib/wallet-connect-client.ts index b14bf6465..28e1ba0a9 100644 --- a/packages/wallet-connect/src/lib/wallet-connect-client.ts +++ b/packages/wallet-connect/src/lib/wallet-connect-client.ts @@ -2,14 +2,23 @@ import Client from "@walletconnect/sign-client"; import type { SignClientTypes, EngineTypes } from "@walletconnect/types"; import QRCodeModal from "@walletconnect/qrcode-modal"; import { SessionTypes } from "@walletconnect/types"; +import type { + EventEmitterService, + WalletEvents, +} from "@near-wallet-selector/core"; class WalletConnectClient { private client: Client; + private emitter: EventEmitterService; async init(opts: SignClientTypes.Options) { this.client = await Client.init(opts); } + constructor(emitter: EventEmitterService) { + this.emitter = emitter; + } + get session() { return this.client.session; } @@ -32,15 +41,19 @@ class WalletConnectClient { this.client.once(event, callback); } - async connect(params: EngineTypes.ConnectParams) { + async connect(params: EngineTypes.ConnectParams, qrCodeModal: boolean) { return new Promise((resolve, reject) => { this.client .connect(params) .then(({ uri, approval }) => { if (uri) { - QRCodeModal.open(uri, () => { - reject(new Error("User cancelled pairing")); - }); + if (qrCodeModal) { + QRCodeModal.open(uri, () => { + reject(new Error("User cancelled pairing")); + }); + } else { + this.emitter.emit("uriChanged", { uri }); + } } approval() diff --git a/packages/wallet-connect/src/lib/wallet-connect.ts b/packages/wallet-connect/src/lib/wallet-connect.ts index fb51a4b92..80355e49b 100644 --- a/packages/wallet-connect/src/lib/wallet-connect.ts +++ b/packages/wallet-connect/src/lib/wallet-connect.ts @@ -14,6 +14,9 @@ import type { BridgeWallet, Subscription, Transaction, + WalletEvents, + EventEmitterService, + VerifiedOwner, } from "@near-wallet-selector/core"; import { getActiveAccount } from "@near-wallet-selector/core"; import { createAction } from "@near-wallet-selector/wallet-utils"; @@ -65,15 +68,17 @@ const WC_METHODS = [ "near_getAccounts", "near_signTransaction", "near_signTransactions", + "near_verifyOwner", ]; const WC_EVENTS = ["chainChanged", "accountsChanged"]; const setupWalletConnectState = async ( id: string, - params: WalletConnectExtraOptions + params: WalletConnectExtraOptions, + emitter: EventEmitterService ): Promise => { - const client = new WalletConnectClient(); + const client = new WalletConnectClient(emitter); let session: SessionTypes.Struct | null = null; const keystore = new keyStores.BrowserLocalStorageKeyStore( window.localStorage, @@ -102,17 +107,8 @@ const setupWalletConnectState = async ( const WalletConnect: WalletBehaviourFactory< BridgeWallet, { params: WalletConnectExtraOptions } -> = async ({ - id, - options, - store, - params, - provider, - emitter, - logger, - metadata, -}) => { - const _state = await setupWalletConnectState(id, params); +> = async ({ id, options, store, params, provider, emitter, logger }) => { + const _state = await setupWalletConnectState(id, params, emitter); const getChainId = () => { if (params.chainId) { @@ -139,8 +135,6 @@ const WalletConnect: WalletBehaviourFactory< _state.subscriptions = []; _state.session = null; - - await _state.keystore.clear(); }; const validateAccessKey = ( @@ -234,6 +228,17 @@ const WalletConnect: WalletBehaviourFactory< }); }; + const requestVerifyOwner = async (accountId: string, message: string) => { + return _state.client.request({ + topic: _state.session!.topic, + chainId: getChainId(), + request: { + method: "near_verifyOwner", + params: { accountId, message }, + }, + }); + }; + const requestSignTransaction = async (transaction: Transaction) => { const accounts = await requestAccounts(); const account = accounts.find((x) => x.accountId === transaction.signerId); @@ -406,12 +411,6 @@ const WalletConnect: WalletBehaviourFactory< }, }, }); - - for (let i = 0; i < limitedAccessAccounts.length; i += 1) { - const { accountId } = limitedAccessAccounts[i]; - - await _state.keystore.removeKey(options.network.networkId, accountId); - } }; const signOut = async () => { @@ -463,7 +462,7 @@ const WalletConnect: WalletBehaviourFactory< } return { - async signIn({ contractId, methodNames = [] }) { + async signIn({ contractId, methodNames = [], qrCodeModal = true }) { const existingAccounts = getAccounts(); if (existingAccounts.length) { @@ -471,15 +470,18 @@ const WalletConnect: WalletBehaviourFactory< } try { - _state.session = await _state.client.connect({ - requiredNamespaces: { - near: { - chains: [getChainId()], - methods: WC_METHODS, - events: WC_EVENTS, + _state.session = await _state.client.connect( + { + requiredNamespaces: { + near: { + chains: [getChainId()], + methods: WC_METHODS, + events: WC_EVENTS, + }, }, }, - }); + qrCodeModal + ); await requestSignIn({ receiverId: contractId, methodNames }); @@ -502,7 +504,19 @@ const WalletConnect: WalletBehaviourFactory< async verifyOwner({ message }) { logger.log("WalletConnect:verifyOwner", { message }); - throw new Error(`Method not supported by ${metadata.name}`); + const { contract } = store.getState(); + + if (!_state.session || !contract) { + throw new Error("Wallet not signed in"); + } + + const account = getActiveAccount(store.getState()); + + if (!account) { + throw new Error("No active account"); + } + + return requestVerifyOwner(account.accountId, message); }, async signAndSendTransaction({ signerId, receiverId, actions }) { diff --git a/packages/wallet-utils/package.json b/packages/wallet-utils/package.json index 27d1c9518..d313236c9 100644 --- a/packages/wallet-utils/package.json +++ b/packages/wallet-utils/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/wallet-utils", - "version": "7.0.3", + "version": "7.1.0", "peerDependencies": { "near-api-js": "^0.44.2" } diff --git a/scripts/release-packages.bash b/scripts/release-packages.bash index 506df6ae5..2114916fa 100644 --- a/scripts/release-packages.bash +++ b/scripts/release-packages.bash @@ -5,6 +5,8 @@ TAG=latest npm publish dist/packages/core --tag "${TAG}" --otp "${OTP}" npm publish dist/packages/modal-ui --tag "${TAG}" --otp "${OTP}" +npm publish dist/packages/modal-ui-js --tag "${TAG}" --otp "${OTP}" +npm publish dist/packages/default-wallets --tag "${TAG}" --otp "${OTP}" npm publish dist/packages/wallet-utils --tag "${TAG}" --otp "${OTP}" npm publish dist/packages/near-wallet --tag "${TAG}" --otp "${OTP}" npm publish dist/packages/my-near-wallet --tag "${TAG}" --otp "${OTP}" @@ -15,5 +17,7 @@ npm publish dist/packages/ledger --tag "${TAG}" --otp "${OTP}" npm publish dist/packages/wallet-connect --tag "${TAG}" --otp "${OTP}" npm publish dist/packages/nightly-connect --tag "${TAG}" --otp "${OTP}" npm publish dist/packages/meteor-wallet --tag "${TAG}" --otp "${OTP}" -npm publish dist/packages/default-wallets --tag "${TAG}" --otp "${OTP}" -npm publish dist/packages/modal-ui-js --tag "${TAG}" --otp "${OTP}" +npm publish dist/packages/here-wallet --tag "${TAG}" --otp "${OTP}" +npm publish dist/packages/coin98-wallet --tag "${TAG}" --otp "${OTP}" +npm publish dist/packages/nearfi --tag "${TAG}" --otp "${OTP}" + diff --git a/tsconfig.base.json b/tsconfig.base.json index c4e41dfe8..57d28170a 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -8,26 +8,36 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true, "importHelpers": true, + "allowSyntheticDefaultImports": true, "target": "es2015", "module": "esnext", - "lib": ["es2017", "dom"], + "lib": [ + "es2017", + "dom" + ], "skipLibCheck": true, "skipDefaultLibCheck": true, "baseUrl": ".", "strictPropertyInitialization": false, "paths": { - "@near-wallet-selector/core": ["packages/core/src/index.ts"], + "@near-wallet-selector/core": [ + "packages/core/src/index.ts" + ], "@near-wallet-selector/default-wallets": [ "packages/default-wallets/src/index.ts" ], - "@near-wallet-selector/ledger": ["packages/ledger/src/index.ts"], + "@near-wallet-selector/ledger": [ + "packages/ledger/src/index.ts" + ], "@near-wallet-selector/math-wallet": [ "packages/math-wallet/src/index.ts" ], "@near-wallet-selector/meteor-wallet": [ "packages/meteor-wallet/src/index.ts" ], - "@near-wallet-selector/modal-ui": ["dist/packages/modal-ui"], + "@near-wallet-selector/modal-ui": [ + "dist/packages/modal-ui" + ], "@near-wallet-selector/modal-ui-js": [ "packages/modal-ui-js/src/index.ts" ], @@ -37,18 +47,34 @@ "@near-wallet-selector/near-wallet": [ "packages/near-wallet/src/index.ts" ], - "@near-wallet-selector/nightly": ["packages/nightly/src/index.ts"], + "@near-wallet-selector/nearfi": [ + "packages/nearfi/src/index.ts" + ], + "@near-wallet-selector/here-wallet": [ + "packages/here-wallet/src/index.ts" + ], + "@near-wallet-selector/nightly": [ + "packages/nightly/src/index.ts" + ], "@near-wallet-selector/nightly-connect": [ "packages/nightly-connect/src/index.ts" ], - "@near-wallet-selector/sender": ["packages/sender/src/index.ts"], + "@near-wallet-selector/sender": [ + "packages/sender/src/index.ts" + ], "@near-wallet-selector/wallet-connect": [ "packages/wallet-connect/src/index.ts" ], "@near-wallet-selector/wallet-utils": [ "packages/wallet-utils/src/index.ts" + ], + "@near-wallet-selector/coin98-wallet": [ + "packages/coin98-wallet/src/index.ts" ] } }, - "exclude": ["node_modules", "tmp"] + "exclude": [ + "node_modules", + "tmp" + ] } diff --git a/workspace.json b/workspace.json index 7b76fc08b..1ffc96b95 100644 --- a/workspace.json +++ b/workspace.json @@ -11,12 +11,15 @@ "modal-ui": "packages/modal-ui", "modal-ui-js": "packages/modal-ui-js", "my-near-wallet": "packages/my-near-wallet", + "nearfi": "packages/nearfi", "near-wallet": "packages/near-wallet", "nightly": "packages/nightly", "nightly-connect": "packages/nightly-connect", "react": "examples/react", "sender": "packages/sender", + "here-wallet": "packages/here-wallet", "wallet-connect": "packages/wallet-connect", - "wallet-utils": "packages/wallet-utils" + "wallet-utils": "packages/wallet-utils", + "coin98-wallet": "packages/coin98-wallet" } -} +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 0073dab02..f1e4ac3d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4018,6 +4018,13 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== +"@types/qrcode@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.5.0.tgz#6a98fe9a9a7b2a9a3167b6dde17eff999eabe40b" + integrity sha512-x5ilHXRxUPIMfjtM+1vf/GPTRWZ81nqscursm5gMznJeK9M0YnZ1c3bEvRLQ0zSSgedLx1J6MGL231ObQGGhaA== + dependencies: + "@types/node" "*" + "@types/qs@*": version "6.9.7" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" @@ -6377,6 +6384,13 @@ copy-anything@^2.0.1: dependencies: is-what "^3.14.1" +copy-to-clipboard@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.2.tgz#5b263ec2366224b100181dded7ce0579b340c107" + integrity sha512-Vme1Z6RUDzrb6xAI7EZlVZ5uvOk2F//GaxKUxajDqm9LhOVM1inxNAD2vy+UZDYsd0uyA9s7b3/FVZPSxqrCfg== + dependencies: + toggle-selection "^1.0.6" + copy-webpack-plugin@10.2.4: version "10.2.4" resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz#6c854be3fdaae22025da34b9112ccf81c63308fe" @@ -13159,6 +13173,11 @@ q@^1.5.1: resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== +qr.js@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/qr.js/-/qr.js-0.0.0.tgz#cace86386f59a0db8050fa90d9b6b0e88a1e364f" + integrity sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ== + qrcode-generator@^1.4.3: version "1.4.4" resolved "https://registry.yarnpkg.com/qrcode-generator/-/qrcode-generator-1.4.4.tgz#63f771224854759329a99048806a53ed278740e7" @@ -13169,7 +13188,7 @@ qrcode-terminal@^0.12.0: resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ== -qrcode@^1.4.4: +qrcode@^1.4.4, qrcode@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.1.tgz#0103f97317409f7bc91772ef30793a54cd59f0cb" integrity sha512-nS8NJ1Z3md8uTjKtP+SGGhfqmTCs5flU/xR623oI0JX+Wepz9R8UrRVCTBTJm3qGw3rH6jJ6MUHjkDx15cxSSg== @@ -13303,6 +13322,14 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== +react-qr-code@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/react-qr-code/-/react-qr-code-2.0.8.tgz#d34a766fb5b664a40dbdc7020f7ac801bacb2851" + integrity sha512-zYO9EAPQU8IIeD6c6uAle7NlKOiVKs8ji9hpbWPTGxO+FLqBN2on+XCXQvnhm91nrRd306RvNXUkUNcXXSfhWA== + dependencies: + prop-types "^15.8.1" + qr.js "0.0.0" + react-refresh@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.10.0.tgz#2f536c9660c0b9b1d500684d9e52a65e7404f7e3" @@ -14858,6 +14885,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +toggle-selection@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" + integrity sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ== + toidentifier@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"