diff --git a/README.md b/README.md index 30d8c05d8..3150d9989 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ yarn add \ @near-wallet-selector/math-wallet \ @near-wallet-selector/ledger \ @near-wallet-selector/wallet-connect + @near-wallet-selector/nightly # Using NPM. npm install \ @@ -47,6 +48,7 @@ npm install \ @near-wallet-selector/math-wallet \ @near-wallet-selector/ledger \ @near-wallet-selector/wallet-connect + @near-wallet-selector/nightly ``` Optionally, you can install our [`modal-ui`](https://www.npmjs.com/package/@near-wallet-selector/modal-ui) package for a pre-built interface that wraps the `core` API and presents the supported wallets: @@ -70,6 +72,7 @@ import { setupSender } from "@near-wallet-selector/sender"; import { setupMathWallet } from "@near-wallet-selector/math-wallet"; import { setupLedger } from "@near-wallet-selector/ledger"; import { setupWalletConnect } from "@near-wallet-selector/wallet-connect"; +import { setupNightly } from "@near-wallet-selector/nightly"; const selector = await setupWalletSelector({ network: "testnet", @@ -79,6 +82,7 @@ const selector = await setupWalletSelector({ setupSender(), setupLedger(), setupMathWallet(), + setupNightly(), setupWalletConnect({ projectId: "c4f79cc...", metadata: { diff --git a/examples/angular/project.json b/examples/angular/project.json index 4c824270a..5916a5d78 100644 --- a/examples/angular/project.json +++ b/examples/angular/project.json @@ -33,6 +33,11 @@ "input": "packages/sender/assets/", "output": "assets/" }, + { + "glob": "**/*", + "input": "packages/nightly/assets/", + "output": "assets/" + }, { "glob": "**/*", "input": "packages/ledger/assets/", diff --git a/examples/react/project.json b/examples/react/project.json index 84763ce89..40b2cdd0a 100644 --- a/examples/react/project.json +++ b/examples/react/project.json @@ -33,6 +33,11 @@ "input": "packages/sender/assets/", "output": "assets/" }, + { + "glob": "**/*", + "input": "packages/nightly/assets/", + "output": "assets/" + }, { "glob": "**/*", "input": "packages/ledger/assets/", diff --git a/examples/react/src/contexts/WalletSelectorContext.tsx b/examples/react/src/contexts/WalletSelectorContext.tsx index d59e48a57..1359888b6 100644 --- a/examples/react/src/contexts/WalletSelectorContext.tsx +++ b/examples/react/src/contexts/WalletSelectorContext.tsx @@ -9,6 +9,7 @@ import { setupMyNearWallet } from "@near-wallet-selector/my-near-wallet"; import { setupSender } from "@near-wallet-selector/sender"; import { setupMathWallet } from "@near-wallet-selector/math-wallet"; import { setupLedger } from "@near-wallet-selector/ledger"; +import { setupNightly } from "@near-wallet-selector/nightly"; import { setupWalletConnect } from "@near-wallet-selector/wallet-connect"; import { CONTRACT_ID } from "../constants"; @@ -68,6 +69,7 @@ export const WalletSelectorContextProvider: React.FC = ({ children }) => { setupNearWallet(), setupMyNearWallet(), setupSender(), + setupNightly(), setupMathWallet(), setupLedger(), setupWalletConnect({ @@ -81,10 +83,8 @@ export const WalletSelectorContextProvider: React.FC = ({ children }) => { }), ], }); - const _modal = setupModal(_selector, { contractId: CONTRACT_ID }); const state = _selector.store.getState(); - syncAccountState(localStorage.getItem("accountId"), state.accounts); window.selector = _selector; diff --git a/package.json b/package.json index 511e242c7..ed76c6b1e 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "near-wallet", "my-near-wallet", "sender", + "nightly", "math-wallet", "ledger", "wallet-connect" @@ -39,6 +40,7 @@ "build:near-wallet": "nx run-many --target=build --projects=near-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:nightly": "nx run-many --target=build --projects=nightly --configuration=production", "build:wallet-connect": "nx run-many --target=build --projects=wallet-connect --configuration=production", "build:wallet-utils": "nx run-many --target=build --projects=wallet-utils --configuration=production", "lint": "nx workspace-lint && nx run-many --target=lint --all --parallel", diff --git a/packages/modal-ui/src/lib/components/WalletOptions.tsx b/packages/modal-ui/src/lib/components/WalletOptions.tsx index dd9a716de..dbff173dd 100644 --- a/packages/modal-ui/src/lib/components/WalletOptions.tsx +++ b/packages/modal-ui/src/lib/components/WalletOptions.tsx @@ -76,7 +76,6 @@ export const WalletOptions: React.FC = ({ const { selectedWalletId } = selector.store.getState(); const { name, description, iconUrl } = module.metadata; const selected = module.id === selectedWalletId; - result.push(
  • /tsconfig.spec.json", + }, + }, + transform: { + "^.+\\.[tj]sx?$": "ts-jest", + }, + moduleFileExtensions: ["ts", "tsx", "js", "jsx"], + coverageDirectory: "../../coverage/packages/nightly", +}; diff --git a/packages/nightly/package.json b/packages/nightly/package.json new file mode 100644 index 000000000..5c540e7de --- /dev/null +++ b/packages/nightly/package.json @@ -0,0 +1,4 @@ +{ + "name": "@near-wallet-selector/nightly", + "version": "4.0.0" +} diff --git a/packages/nightly/project.json b/packages/nightly/project.json new file mode 100644 index 000000000..051e93334 --- /dev/null +++ b/packages/nightly/project.json @@ -0,0 +1,54 @@ +{ + "root": "packages/nightly", + "sourceRoot": "packages/nightly/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nrwl/web:rollup", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/packages/nightly", + "tsConfig": "packages/nightly/tsconfig.lib.json", + "project": "packages/nightly/package.json", + "entryFile": "packages/nightly/src/index.ts", + "buildableProjectDepsInPackageJsonType": "dependencies", + "compiler": "babel", + "format": ["esm", "umd", "cjs"], + "assets": [ + { + "glob": "packages/nightly/README.md", + "input": ".", + "output": "." + }, + { + "glob": "packages/nightly/assets/*", + "input": ".", + "output": "assets" + } + ] + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["packages/nightly/**/*.ts"] + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["coverage/packages/nightly"], + "options": { + "jestConfig": "packages/nightly/jest.config.js", + "passWithNoTests": true + } + }, + "deploy": { + "executor": "ngx-deploy-npm:deploy", + "options": { + "access": "public" + } + } + }, + "tags": ["injected-wallet"] +} diff --git a/packages/nightly/src/index.ts b/packages/nightly/src/index.ts new file mode 100644 index 000000000..2bff821e3 --- /dev/null +++ b/packages/nightly/src/index.ts @@ -0,0 +1,2 @@ +export { setupNightly } from "./lib/nightly"; +export type { NightlyWalletParams } from "./lib/nightly"; diff --git a/packages/nightly/src/lib/injected-nightly.ts b/packages/nightly/src/lib/injected-nightly.ts new file mode 100644 index 000000000..cacf669a0 --- /dev/null +++ b/packages/nightly/src/lib/injected-nightly.ts @@ -0,0 +1,45 @@ +import { + SignedTransaction as NearSignedTransaction, + Transaction as NearTransaction, +} from "near-api-js/lib/transaction"; +import { PublicKey as NearPublicKey } from "near-api-js/lib/utils"; +export interface NearAccount { + accountId: string; + publicKey: NearPublicKey; +} +export interface WalletAdapter { + account: NearAccount; + connected: boolean; + signTransaction: ( + transaction: NearTransaction + ) => Promise; + signAllTransactions: ( + transaction: Array + ) => Promise>; + connect: (onDisconnect?: () => void) => Promise; + disconnect: () => Promise; +} + +export interface NightlyAccount { + accountId: string; + publicKey: NearPublicKey; +} +export interface NearNightly { + account: NightlyAccount; + connected: boolean; + signTransaction: ( + transaction: NearTransaction + ) => Promise; + signAllTransactions: ( + transaction: Array + ) => Promise>; + connect: ( + onDisconnect?: () => void, + eagerConnect?: boolean + ) => Promise; + disconnect: () => Promise; +} +export interface InjectedNightly { + near: NearNightly; + invalidate: () => void; +} diff --git a/packages/nightly/src/lib/nightly.ts b/packages/nightly/src/lib/nightly.ts new file mode 100644 index 000000000..10dd4838a --- /dev/null +++ b/packages/nightly/src/lib/nightly.ts @@ -0,0 +1,177 @@ +import type { + InjectedWallet, + WalletBehaviourFactory, + WalletModuleFactory, + WalletSelectorStore, +} from "@near-wallet-selector/core"; +import { Account, waitFor } from "@near-wallet-selector/core"; +import { createAction } from "@near-wallet-selector/wallet-utils"; +import { isMobile } from "is-mobile"; +import { utils } from "near-api-js"; +import { AccessKeyView } from "near-api-js/lib/providers/provider"; +import { createTransaction } from "near-api-js/lib/transaction"; +import { PublicKey } from "near-api-js/lib/utils"; +import type { NearNightly, InjectedNightly } from "./injected-nightly"; + +declare global { + interface Window { + nightly: InjectedNightly | undefined; + } +} + +interface NightlyState { + wallet: NearNightly; +} + +const setupNightlyState = async ( + store: WalletSelectorStore +): Promise => { + const { selectedWalletId } = store.getState(); + const wallet = window.nightly!.near!; + // Attempt to reconnect wallet if previously selected. + if (selectedWalletId === "nightly") { + await wallet.connect(undefined, true).catch(() => null); + } + return { + wallet, + }; +}; +const isInstalled = () => { + return waitFor(() => !!window.nightly!.near!).catch(() => false); +}; +const Nightly: WalletBehaviourFactory = async ({ + store, + logger, + provider, +}) => { + const _state = await setupNightlyState(store); + + const getAccounts = async () => { + if (!_state || _state.wallet.account.accountId === "") { + return []; + } + const nearAccount: Account = { + accountId: _state.wallet.account.accountId, + }; + return [nearAccount]; + }; + return { + // nightly does not support delegating signing right now + async signIn() { + const existingAccount = _state.wallet.account.accountId; + + if (existingAccount) { + return await getAccounts(); + } + + await _state.wallet.connect(); + + return await getAccounts(); + }, + + async signOut() { + await _state.wallet.disconnect(); + }, + + async getAccounts() { + return await getAccounts(); + }, + + async signAndSendTransaction({ signerId, receiverId, actions }) { + logger.log("signAndSendTransaction", { signerId, receiverId, actions }); + const { contract } = store.getState(); + + if (!receiverId && !contract) { + throw new Error("Recipient not found"); + } + + const blockInfo = await provider.query({ + account_id: _state.wallet.account.accountId, + public_key: _state.wallet.account.publicKey.toString(), + request_type: "view_access_key", + finality: "final", + }); + const blockHash = utils.serialize.base_decode(blockInfo.block_hash); + const tx = createTransaction( + signerId || _state.wallet.account.accountId, + new PublicKey(_state.wallet.account.publicKey), + receiverId || contract!.contractId, + ++blockInfo.nonce, + actions.map((a) => createAction(a)), + blockHash + ); + const signedTransactions = await _state.wallet.signTransaction(tx); + const result = await provider.sendTransaction(signedTransactions); + return result; + }, + + async signAndSendTransactions({ transactions }) { + logger.log("signAndSendTransactions", { transactions }); + const { contract } = store.getState(); + const blockInfo = await provider.query({ + account_id: _state.wallet.account.accountId, + public_key: _state.wallet.account.publicKey.toString(), + request_type: "view_access_key", + finality: "final", + }); + + const blockHash = utils.serialize.base_decode(blockInfo.block_hash); + const txs = transactions.map((txData) => { + if (!contract && txData.receiverId) { + throw new Error("Recipient not found"); + } + const tx = createTransaction( + txData.signerId || _state.wallet.account.accountId, + new PublicKey(_state.wallet.account.publicKey), + txData.receiverId || contract!.contractId, + ++blockInfo.nonce, + txData.actions.map((a) => createAction(a)), + blockHash + ); + return tx; + }); + const signedTransactions = await _state.wallet.signAllTransactions(txs); + logger.log( + "signAndSendTransactions:signedTransactions", + signedTransactions + ); + return Promise.all( + signedTransactions.map((tx) => provider.sendTransaction(tx)) + ); + }, + }; +}; + +export interface NightlyWalletParams { + iconUrl?: string; +} +export function setupNightly({ + iconUrl = "./assets/nightly.png", +}: NightlyWalletParams = {}): WalletModuleFactory { + return async () => { + const mobile = isMobile(); + const installed = await isInstalled(); + + if (mobile || !installed) { + return null; + } + + await waitFor(() => !!window.nightly?.near, { + timeout: 300, + }).catch(() => false); + + return { + id: "nightly", + type: "injected", + metadata: { + name: "Nightly", + description: null, + iconUrl, + // Will replace we open beta with stable version + downloadUrl: "https://www.nightly.app", + deprecated: false, + }, + init: Nightly, + }; + }; +} diff --git a/packages/nightly/tsconfig.json b/packages/nightly/tsconfig.json new file mode 100644 index 000000000..e258886ff --- /dev/null +++ b/packages/nightly/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/nightly/tsconfig.lib.json b/packages/nightly/tsconfig.lib.json new file mode 100644 index 000000000..a8b9431f9 --- /dev/null +++ b/packages/nightly/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/nightly/tsconfig.spec.json b/packages/nightly/tsconfig.spec.json new file mode 100644 index 000000000..67f149c4c --- /dev/null +++ b/packages/nightly/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/wallet-connect/package.json b/packages/wallet-connect/package.json index 300c7d4bc..cb140f20c 100644 --- a/packages/wallet-connect/package.json +++ b/packages/wallet-connect/package.json @@ -1,4 +1,4 @@ { "name": "@near-wallet-selector/wallet-connect", - "version": "4.0.0" + "version": "4.0.0" } diff --git a/tsconfig.base.json b/tsconfig.base.json index 825f720aa..312f8e30b 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -33,6 +33,9 @@ "@near-wallet-selector/sender": [ "packages/sender/src/index.ts" ], + "@near-wallet-selector/nightly": [ + "packages/nightly/src/index.ts" + ], "@near-wallet-selector/wallet-connect": [ "packages/wallet-connect/src/index.ts" ], diff --git a/workspace.json b/workspace.json index c8c3b2dc1..3dd52f59d 100644 --- a/workspace.json +++ b/workspace.json @@ -8,6 +8,7 @@ "near-wallet": "packages/near-wallet", "my-near-wallet": "packages/my-near-wallet", "sender": "packages/sender", + "nightly": "packages/nightly", "wallet-connect": "packages/wallet-connect", "angular": "examples/angular", "react": "examples/react",