Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Refactor Wallet Implementations #276

Merged
merged 64 commits into from
May 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
45a82a6
Simplified state to store modules instead of wallets.
lewis-sqa Apr 29, 2022
bd606c4
Removed wallet function and renamed wallets to modules.
lewis-sqa Apr 29, 2022
735e5dd
Updated setup logic.
lewis-sqa Apr 29, 2022
c55697d
Brought back wallet function.
lewis-sqa Apr 29, 2022
359d239
Made wallet behaviour async.
lewis-sqa Apr 29, 2022
35413c9
Initial attempt at improved types.
lewis-sqa Apr 29, 2022
1163a5f
Added hardware and bridge wallets.
lewis-sqa Apr 29, 2022
3e110c5
Removed the need for isAvailable.
lewis-sqa Apr 29, 2022
555b42a
Resolved setup type issues.
lewis-sqa Apr 29, 2022
0ffa78d
Refactored types.
lewis-sqa Apr 29, 2022
9aea448
Added comment.
lewis-sqa Apr 29, 2022
a2ac6ce
Added comment.
lewis-sqa Apr 29, 2022
5375053
More refactoring.
lewis-sqa Apr 29, 2022
8f72592
More experiementing with setup and types.
lewis-sqa Apr 29, 2022
0087147
Fixed Modal types.
lewis-sqa Apr 29, 2022
812eeac
Initial attempt at refactoring the controller.
lewis-sqa Apr 29, 2022
0acfaa8
More refactoring.
lewis-sqa Apr 29, 2022
8dcc1f6
Initial approach to caching init method.
lewis-sqa Apr 29, 2022
37d82d1
More changes to wallet types.
lewis-sqa Apr 29, 2022
8265277
Added id and metadata.
lewis-sqa Apr 29, 2022
a9a5638
Added type.
lewis-sqa Apr 29, 2022
fc10621
Fixed wallet types.
lewis-sqa Apr 30, 2022
46b62d9
Added default.
lewis-sqa Apr 30, 2022
1d2fe14
Simplified getWallet.
lewis-sqa May 3, 2022
fd3dfa8
Added back commented code.
lewis-sqa May 3, 2022
b14fddd
Initial attempt to decorate wallets.
lewis-sqa May 3, 2022
48bc573
Simplified math wallet.
lewis-sqa May 3, 2022
a8dfe47
Improved checks in store.
lewis-sqa May 3, 2022
26b9186
Cleaned up method decorating.
lewis-sqa May 3, 2022
5d633c3
Refactored Sender.
lewis-sqa May 3, 2022
b456631
Fixed rpc changed logic.
lewis-sqa May 3, 2022
3ea72ed
Refactored NEAR Wallet.
lewis-sqa May 3, 2022
99ce621
Refactored WalletConnect.
lewis-sqa May 3, 2022
94b27ab
Minor refactoring.
lewis-sqa May 3, 2022
0bad4a9
Refactored Ledger.
lewis-sqa May 3, 2022
4468bb1
Added back Ledger.
lewis-sqa May 3, 2022
edb0149
More refactoring.
lewis-sqa May 3, 2022
4ec002d
Removed key.
lewis-sqa May 3, 2022
ca1f03a
Removed need for wallet not installed case.
lewis-sqa May 3, 2022
6ec2e38
Missing import.
lewis-sqa May 3, 2022
4251a2f
Removed unused enum.
lewis-sqa May 3, 2022
6cf40dd
Added checks for signAndSendTransaction(s).
lewis-sqa May 3, 2022
bcd974c
Added session assignment.
lewis-sqa May 4, 2022
698a43a
More fixes.
lewis-sqa May 4, 2022
21f2164
Removed unused utils.
lewis-sqa May 4, 2022
85a1f61
Minor formatting.
lewis-sqa May 4, 2022
7146680
Removed error.
lewis-sqa May 4, 2022
18fd639
Improved browser wallet connect/disconnect flow.
lewis-sqa May 4, 2022
0ea07e1
Simplified params.
lewis-sqa May 4, 2022
9c34c17
Fixed tests.
lewis-sqa May 4, 2022
7be13d5
Updated example implementation.
lewis-sqa May 4, 2022
9c56919
Updated reset of guide.
lewis-sqa May 4, 2022
a358ebc
Minor wording improvement.
lewis-sqa May 4, 2022
ed4a05e
Improved formatting.
lewis-sqa May 4, 2022
18ef481
Renamed module.
lewis-sqa May 4, 2022
abea76f
Improved accounts changed event.
lewis-sqa May 4, 2022
91dc788
Minor refactor.
lewis-sqa May 4, 2022
105f0db
Minor refactor.
lewis-sqa May 4, 2022
0c23edd
More refactoring.
lewis-sqa May 4, 2022
dfe0c96
Cleaned up init validation setup.
lewis-sqa May 4, 2022
afbb113
More refactoring.
lewis-sqa May 4, 2022
211618b
Fixed connected logic in LedgerClient
lewis-sqa May 4, 2022
6fbe562
Removed need for setupEvents.
lewis-sqa May 4, 2022
8c8d0fc
Fixed tests.
lewis-sqa May 4, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 26 additions & 47 deletions docs/guides/custom-wallets.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@ The basic structure of a (browser) wallet should look like:

```ts
import {
WalletModule,
WalletModuleFactory,
WalletBehaviourFactory,
BrowserWallet,
Action,
Transaction,
} from "@near-wallet-selector/core";

export interface MyWalletParams {
Expand All @@ -21,30 +19,19 @@ export interface MyWalletParams {

const MyWallet: WalletBehaviourFactory<BrowserWallet> = ({
options,
emitter,
provider,
}) => {
// Initialise wallet-sepecific client(s) here.

return {
async isAvailable() {
// Determine whether My Wallet is available.
// For example, some wallets aren't supported on mobile.

return true;
},

async connect() {
// Connect to My Wallet for access to account(s).

const accounts = [];
emitter.emit("connected", { accounts });

return accounts;
return [];
},

async disconnect() {
// Disconnect from accounts and cleanup (e.g. listeners).

emitter.emit("disconnected", null);
},

async getAccounts() {
Expand Down Expand Up @@ -79,23 +66,31 @@ const MyWallet: WalletBehaviourFactory<BrowserWallet> = ({

export function setupMyWallet({
iconUrl = "./assets/my-wallet-icon.png",
}: MyWalletParams = {}): WalletModule<BrowserWallet> {
return {
id: "my-wallet",
type: "browser",
name: "My Wallet",
description: null,
iconUrl,
wallet: MyWallet,
}: MyWalletParams = {}): WalletModuleFactory<BrowserWallet> {
return async () => {
// Return null here when wallet is unavailable.

return {
id: "my-wallet",
type: "browser",
metadata: {
name: "My Wallet",
description: null,
iconUrl,
},
init: MyWallet,
};
};
}
```

`WalletModule` is made up of two main parts:
- Behaviour: `wallet`.
- Metadata: `id`, `type`, `name`, `description` and `iconUrl`.
`WalletModule` (return type of `WalletModuleFactory`) is made up of four properties:
- `id`: Unique identifier for the wallet.
- `type`: Type of wallet to infer the behaviour and metadata.
- `metadata`: Metadata for displaying information to the user.
- `init`: The implementation (behaviour) of the wallet.

The metadata of a wallet is accessible as part of the selector's `wallets` state. It's important that `id` is unique to avoid conflicts with other wallets installed by a dApp. The `type` property is coupled to the parameter we pass to `WalletModule` and `WalletBehaviourFactory`.
A variation of `WalletModule` is added to state during setup under `modules` (`ModuleState`) and accessed by the UI to display the available wallets. It's important that `id` is unique to avoid conflicts with other wallets installed by a dApp. The `type` property is coupled to the parameter we pass to `WalletModuleFactory` and `WalletBehaviourFactory`.

Although we've tried to implement a polymorphic approach to wallets, there are some differences between wallet types that means your implementation won't always mirror other wallets such as Sender vs. Ledger. There are currently four types of wallet:

Expand All @@ -106,25 +101,15 @@ Although we've tried to implement a polymorphic approach to wallets, there are s

## Methods

### `isAvailable`

This method is used to determine whether a wallet is available for connecting. For example, injected wallets such as Sender are unavailable on mobile where browser extensions are not supported. The UI will hide the wallet when `false` is returned.

> Note: Injected wallets should be considered available if they aren't installed. The modal handles this case by displaying a download link (using `getDownloadUrl`) when attempting to connect.

### `connect`

This method handles wallet setup (e.g. initialising wallet-specific libraries) and requesting access to accounts via `FunctionCall` access keys. It's important that `connected` is emitted only when we successfully gain access to at least one account.
This method handles access to accounts via `FunctionCall` access keys. It's important that at least one account is returned to be in a connected state.

> Note: Hardware wallets are passed a `derivationPath` where other wallets types are called without any parameters.

> Note: The combination of setup and connecting is still under review.

### `disconnect`

This method handles disconnecting from accounts and cleanup such as event listeners. It's called when either the user specifically disconnects or when switching to a different wallet. It's important that `disconnected` is emitted regardless of exceptions.

> Note: The requirement to emit "disconnected" is still under review and may be removed in favour of accepting the Promise settling as a signal that this method has completed.
This method handles disconnecting from accounts and cleanup such as event listeners. It's called when either the user specifically disconnects or when switching to a different wallet.

### `getAccounts`

Expand All @@ -143,9 +128,3 @@ Where you might have to construct NEAR Transactions and send them yourself, you
This method is similar to `signAndSendTransaction` but instead sends a batch of Transactions.

> Note: Exactly how this method should behave when transactions fail is still under review with no clear "right" way to do it. NEAR Wallet (website) seems to ignore any transactions that fail and continue executing the rest. Our approach attempts to execute the transactions in a series and bail if any fail (we will look to improve this in the future by implementing a retry feature).

### `getDownloadUrl`

This method returns the download link for users who haven't installed the wallet. This is usually the Chrome Web Store but ideally this should be browser aware to give the best experience for the user.

> Note: This method is only applicable to injected wallets.
28 changes: 15 additions & 13 deletions examples/react/src/components/Content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,13 @@ const Content: React.FC = () => {
selector.show();
};

const handleSignOut = () => {
selector
.wallet()
.disconnect()
.catch((err) => {
console.log("Failed to sign out");
console.error(err);
});
const handleSignOut = async () => {
const wallet = await selector.wallet();

wallet.disconnect().catch((err) => {
console.log("Failed to sign out");
console.error(err);
});
};

const handleSwitchProvider = () => {
Expand All @@ -101,10 +100,11 @@ const Content: React.FC = () => {
alert("Switched account to " + nextAccountId);
};

const handleSendMultipleTransactions = () => {
const handleSendMultipleTransactions = async () => {
const { contractId } = selector.options;
const wallet = await selector.wallet();

selector.wallet().signAndSendTransactions({
await wallet.signAndSendTransactions({
transactions: [
{
// Deploy your own version of https://github.com/near-examples/rust-counter using Gitpod to get a valid receiverId.
Expand Down Expand Up @@ -140,7 +140,7 @@ const Content: React.FC = () => {
};

const handleSubmit = useCallback(
(e: SubmitEvent) => {
async (e: SubmitEvent) => {
e.preventDefault();

// TODO: Fix the typing so that target.elements exists..
Expand All @@ -153,8 +153,10 @@ const Content: React.FC = () => {
// TODO: optimistically update page with new message,
// update blockchain data in background
// add uuid to each message, so we know which one is already known
selector
.wallet()

const wallet = await selector.wallet();

wallet
.signAndSendTransaction({
signerId: accountId!,
actions: [
Expand Down
5 changes: 3 additions & 2 deletions examples/react/src/contexts/WalletSelectorContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,15 @@ export const WalletSelectorContextProvider: React.FC = ({ children }) => {
setupWalletSelector({
network: "testnet",
contractId: "guest-book.testnet",
wallets: [
debug: true,
modules: [
setupNearWallet(),
setupSender(),
setupMathWallet(),
setupLedger(),
setupWalletConnect({
projectId: "c4f79cc...",
appMetadata: {
metadata: {
name: "NEAR Wallet Selector",
description: "Example dApp used by NEAR Wallet Selector",
url: "https://github.com/near/wallet-selector",
Expand Down
22 changes: 14 additions & 8 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,29 @@ export { Optional } from "./lib/utils.types";

export {
WalletSelectorState,
WalletState,
ModuleState,
AccountState,
} from "./lib/store.types";

export {
Wallet,
WalletType,
WalletMetadata,
WalletBehaviour,
WalletModuleFactory,
WalletModule,
WalletBehaviourFactory,
WalletBehaviourOptions,
Wallet,
WalletType,
BrowserWalletMetadata,
BrowserWalletBehaviour,
BrowserWallet,
InjectedWalletMetadata,
InjectedWalletBehaviour,
InjectedWallet,
HardwareWallet,
HardwareWalletMetadata,
HardwareWalletConnectParams,
HardwareWalletBehaviour,
HardwareWallet,
BridgeWalletMetadata,
BridgeWalletBehaviour,
BridgeWallet,
Transaction,
Action,
Expand All @@ -39,7 +47,5 @@ export {
DeleteAccountAction,
} from "./lib/wallet";

export { errors } from "./lib/errors";

export { transformActions } from "./lib/wallet";
export { waitFor } from "./lib/helpers";
3 changes: 2 additions & 1 deletion packages/core/src/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const PACKAGE_NAME = "near-wallet-selector";
export const LOCAL_STORAGE_SELECTED_WALLET_ID = `selectedWalletId`;
export const SELECTED_WALLET_ID = `selectedWalletId`;
export const PENDING_SELECTED_WALLET_ID = `selectedWalletId:pending`;

export const DEFAULT_DERIVATION_PATH = "44'/397'/0'/0'/1'";
35 changes: 0 additions & 35 deletions packages/core/src/lib/errors.ts

This file was deleted.

12 changes: 7 additions & 5 deletions packages/core/src/lib/modal/components/LedgerDerivationPath.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,29 @@ export const LedgerDerivationPath: React.FC<LedgerDerivationPathProps> = ({
setLedgerDerivationPath(e.target.value);
};

const handleConnectClick = () => {
const handleConnectClick = async () => {
setIsLoading(true);
// TODO: Can't assume "ledger" once we implement more hardware wallets.
const wallet = selector.wallet("ledger");
const wallet = await selector.wallet("ledger");

if (wallet.type !== "hardware") {
return;
}

setIsLoading(true);

wallet
return wallet
.connect({ derivationPath: ledgerDerivationPath })
.then(() => onConnected())
.catch((err) => setLedgerError(`Error: ${err.message}`))
.finally(() => setIsLoading(false));
};

const handleEnterClick: KeyboardEventHandler<HTMLInputElement> = (e) => {
const handleEnterClick: KeyboardEventHandler<HTMLInputElement> = async (
e
) => {
if (e.key === "Enter") {
handleConnectClick();
await handleConnectClick();
}
};

Expand Down
23 changes: 2 additions & 21 deletions packages/core/src/lib/modal/components/Modal.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import React, { MouseEvent, useCallback, useEffect, useState } from "react";

import { Wallet } from "../../wallet";
import { WalletSelectorModal, ModalOptions, Theme } from "../modal.types";
import { WalletSelector } from "../../wallet-selector.types";
import { ModalRouteName } from "./Modal.types";
import { LedgerDerivationPath } from "./LedgerDerivationPath";
import { WalletNotInstalled } from "./WalletNotInstalled";
import { WalletNetworkChanged } from "./WalletNetworkChanged";
import { WalletOptions } from "./WalletOptions";
import { AlertMessage } from "./AlertMessage";
Expand Down Expand Up @@ -38,9 +36,6 @@ export const Modal: React.FC<ModalProps> = ({
hide,
}) => {
const [routeName, setRouteName] = useState<ModalRouteName>("WalletOptions");
const [notInstalledWallet, setNotInstalledWallet] = useState<Wallet | null>(
null
);
const [alertMessage, setAlertMessage] = useState<string | null>(null);

useEffect(() => {
Expand All @@ -63,7 +58,6 @@ export const Modal: React.FC<ModalProps> = ({

const handleDismissClick = useCallback(() => {
setAlertMessage(null);
setNotInstalledWallet(null);
setRouteName("WalletOptions");
hide();
}, [hide]);
Expand Down Expand Up @@ -113,16 +107,12 @@ export const Modal: React.FC<ModalProps> = ({
<WalletOptions
selector={selector}
options={options}
onWalletNotInstalled={(wallet) => {
setNotInstalledWallet(wallet);
return setRouteName("WalletNotInstalled");
}}
onConnectHardwareWallet={() => {
setRouteName("LedgerDerivationPath");
}}
onConnected={handleDismissClick}
onError={(message) => {
setAlertMessage(message);
onError={(err) => {
setAlertMessage(err.message);
setRouteName("AlertMessage");
}}
/>
Expand All @@ -134,15 +124,6 @@ export const Modal: React.FC<ModalProps> = ({
onBack={() => setRouteName("WalletOptions")}
/>
)}
{routeName === "WalletNotInstalled" && notInstalledWallet && (
<WalletNotInstalled
notInstalledWallet={notInstalledWallet}
onBack={() => {
setNotInstalledWallet(null);
setRouteName("WalletOptions");
}}
/>
)}
{routeName === "WalletNetworkChanged" && (
<WalletNetworkChanged
selector={selector}
Expand Down
Loading