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

feat: Add signInMulti method #811

Open
wants to merge 35 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
264f904
Added interfaces for signInMulti in wallet types.
kujtimprenkuSQA May 19, 2023
627d59b
Add signInMulti method to each wallet.
kujtimprenkuSQA May 19, 2023
142ecb3
Merge branch 'dev' into SQC-540/add-signin-multi
kujtimprenkuSQA May 19, 2023
d40159a
Fix jsdoc warnings and improved docs.
kujtimprenkuSQA May 19, 2023
35448f4
Added logic to allow sign-in with multiple contracts.
kujtimprenkuSQA May 22, 2023
60ac5a0
Fix build for here wallet.
kujtimprenkuSQA May 22, 2023
6e3d5f3
Added contracts to ModalOptions interface.
kujtimprenkuSQA May 23, 2023
512f12b
Added logic to handle signInMulti in modal-ui and modal-ui-js.
kujtimprenkuSQA May 23, 2023
fb519ea
Added docs for .
kujtimprenkuSQA May 23, 2023
5f6bfd4
Fix docs since permissions is required for signInMulti.
kujtimprenkuSQA May 23, 2023
da54351
Remove contracts from setupModal in angular example.
kujtimprenkuSQA May 23, 2023
72b83e5
Export MultiContractState interface.
kujtimprenkuSQA May 23, 2023
8121e5e
Removed signInMulti from inside the modules instead throw when decora…
kujtimprenkuSQA Jun 27, 2023
f910c39
Merge branch 'dev' into SQC-540/add-signin-multi
kujtimprenkuSQA Jun 27, 2023
8d0d967
Removed contractId and added made contracts required, signAndSendTran…
amirsaran3 Jul 3, 2023
2f559f7
Added method to update old contract state for signed in users
amirsaran3 Jul 5, 2023
1188905
Merge branch 'dev' into SQC-540/add-signin-multi
kujtimprenkuSQA Jul 20, 2023
668d7a4
Trigger signInMulti for ledger when there are multiple contracts.
kujtimprenkuSQA Jul 21, 2023
47bedc6
Merge branch 'dev' into SQC-540/add-signin-multi
kujtimprenkuSQA Jul 24, 2023
970b9d1
Merge branch 'SQC-540/add-signin-multi' into SQC-540/add-signin-multi…
kujtimprenkuSQA Jul 24, 2023
9c487aa
Merge branch 'SQC-540/add-signin-multi' of https://github.com/near/wa…
kujtimprenkuSQA Sep 18, 2023
8433139
Merge branch 'dev' into SQC-540/add-signin-multi
kujtimprenkuSQA Sep 18, 2023
9448d81
Merge branch 'SQC-540/add-signin-multi' into SQC-540/add-signin-multi…
kujtimprenkuSQA Sep 18, 2023
45da765
Merge branch 'dev' into SQC-540/add-signin-multi
kujtimprenkuSQA Feb 6, 2024
6c1e65c
Merge branch 'SQC-540/add-signin-multi' into SQC-540/add-signin-multi…
kujtimprenkuSQA Feb 6, 2024
f7bd15e
fix near-mobile-wallet, near-snap and ramper-wallet methods
gtsonevv Jul 24, 2024
1ff90b8
fix if check
gtsonevv Jul 24, 2024
142c447
update receiverId value
gtsonevv Jul 25, 2024
565665d
Merge pull request #1138 from near/SQC-540/add-signin-multi-breaking-…
gtsonevv Jul 25, 2024
f39d4b3
Update signedIn emit data; Fix resolveStorageState logic
gtsonevv Jul 29, 2024
7cf58bb
Update docs
gtsonevv Jul 30, 2024
ff6d042
Merge pull request #847 from near/SQC-540/add-signin-multi-breaking-c…
gtsonevv Aug 2, 2024
422d9fb
Merge branch 'dev' into SQC-540/add-signin-multi
gtsonevv Aug 2, 2024
aa0db8a
Fix merge issues
gtsonevv Aug 2, 2024
180f5b8
Fix modal errors
gtsonevv Aug 2, 2024
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
8 changes: 8 additions & 0 deletions packages/coin98-wallet/src/lib/coin98-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,14 @@ const Coin98Wallet: WalletBehaviourFactory<InjectedWallet> = async ({
return getAccounts();
},

async signInMulti({ permissions }) {
logger.log("signInMulti", { permissions });

throw new Error(
`The signInMulti method is not supported by ${metadata.name}`
);
},

async signOut() {
// Ignore if unsuccessful (returns false).
await _state.wallet.near.disconnect();
Expand Down
19 changes: 19 additions & 0 deletions packages/core/docs/api/state.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,25 @@ Returns the signed in contract.
const { contract } = selector.store.getState();
console.log(contract); // { contractId: "test.testnet", methodNames: [] }
```
### `.contracts`

**Returns**

- `MultiContractState | null`
- `contractId` (`string`): Account ID of the Smart Contract.
- `methodNames` (`Array<string>`): List of methods that can only be invoked on the Smart Contract. Empty list means no restriction.

**Description**

Returns the signed in contracts when signing-in with `signInMulti`.

**Example**

```ts
// MultiContractState = Array<ContractState>;
const { contracts } = selector.store.getState();
console.log(contracts); // [{ contractId: "test.testnet", methodNames: [] }, { contractId: "test1.testnet", methodNames: [] }]
```

### `.modules`

Expand Down
94 changes: 91 additions & 3 deletions packages/core/docs/api/wallet.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ Returns meta information about the wallet such as `name`, `description`, `iconUr
- `contractId` (`string`): Account ID of the Smart Contract.
- `methodNames` (`Array<string>?`): 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.
- `successUrl` (`string?`): Optional for browser wallets (e.g MyNearWallet and HERE Wallet). After successfully signing in where to redirect.
- `failureUrl` (`string?`): Optional for browser wallets (e.g MyNearWallet and HERE Wallet). After failing to sign in where to redirect.
- `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.
- `successUrl` (`string?`): Optional for browser wallets (e.g. MyNearWallet and NEAR Wallet). After successfully signing in where to redirect.
- `failureUrl` (`string?`): Optional for browser wallets (e.g. MyNearWallet and NEAR Wallet). After failing to sign in where to redirect.

**Returns**

Expand Down Expand Up @@ -139,6 +139,94 @@ Programmatically sign in. Hardware wallets (e.g. Ledger) require `derivationPath
})();
```

### `.signInMulti(params)`

**Parameters**

- `params` (`object`)

- `permissions`(`Array<{allowance?: BN, receiverId: string, methodNames: Array<string>}>`): List of Account ID-s of the Smart Contracts and the 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.
- `successUrl` (`string?`): Optional for browser wallets (e.g. MyNearWallet and NEAR Wallet). After successfully signing in where to redirect.
- `failureUrl` (`string?`): Optional for browser wallets (e.g. MyNearWallet and NEAR Wallet). After failing to sign in where to redirect.

**Returns**

- `Promise<Array<Account>>`

**Description**

Programmatically sign in. Hardware wallets (e.g. Ledger) require `derivationPaths` to validate access key permissions.

**Example**

```ts
// NEAR Wallet.
(async () => {
const wallet = await selector.wallet("near-wallet");
const accounts = await wallet.signInMulti({
permissions: [
{ receiverId: "test.testnet", methodNames: [] },
{ receiverId: "test1.testnet", methodNames: [] }
]
});
})();

// Sender.
(async () => {
const wallet = await selector.wallet("sender");
const accounts = await wallet.signInMulti({
permissions: [
{ receiverId: "test.testnet", methodNames: [] },
{ receiverId: "test1.testnet", methodNames: [] }
]
});
})();

// Math Wallet.
(async () => {
const wallet = await selector.wallet("math-wallet");
const accounts = await wallet.signInMulti({
permissions: [
{ receiverId: "test.testnet", methodNames: [] },
{ receiverId: "test1.testnet", methodNames: [] }
]
});
})();

// Ledger
(async () => {
const wallet = await selector.wallet("ledger");
const derivationPath = "44'/397'/0'/0'/1'";
const publicKey = await wallet.getPublicKey(derivationPath);
const accountId = "youraccountid.testnet"

const accounts = await wallet.signIn({
permissions: [
{ receiverId: "test.testnet", methodNames: [] },
{ receiverId: "test1.testnet", methodNames: [] }
],
accounts: [{
derivationPath,
publicKey,
accountId
}],
});
})();

// WalletConnect.
(async () => {
const wallet = await selector.wallet("wallet-connect");
const accounts = await wallet.signInMulti({
permissions: [
{ receiverId: "test.testnet", methodNames: [] },
{ receiverId: "test1.testnet", methodNames: [] }
]
});
})();
```

### `.signOut()`

**Parameters**
Expand Down
6 changes: 6 additions & 0 deletions packages/core/docs/guides/custom-wallets.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ const MyWallet: WalletBehaviourFactory<BrowserWallet> = ({
return [];
},

async signInMulti({ permissions }) {
// Sign in to My Wallet with multiple contracts.

return [];
},

async signOut() {
// Sign out from accounts and cleanup (e.g. listeners).
},
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export type { Optional } from "./lib/utils.types";
export type {
WalletSelectorState,
ContractState,
MultiContractState,
ModuleState,
AccountState,
} from "./lib/store.types";
Expand All @@ -35,6 +36,7 @@ export type {
WalletMetadata,
WalletEvents,
SignInParams,
SignInMultiParams,
BrowserWalletMetadata,
BrowserWalletBehaviour,
BrowserWallet,
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@ export const RECENTLY_SIGNED_IN_WALLETS = "recentlySignedInWallets";
export const CONTRACT = "contract";
export const PENDING_CONTRACT = "contract:pending";

export const CONTRACTS = "contracts";
export const PENDING_CONTRACTS = "contracts:pending";

export const SELECTED_WALLET_ID = `selectedWalletId`;
export const PENDING_SELECTED_WALLET_ID = `selectedWalletId:pending`;
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@ import type {
WalletModuleFactory,
Account,
InstantLinkWallet,
SignInMultiParams,
} from "../../wallet";
import type { StorageService } from "../storage/storage.service.types";
import type { Options } from "../../options.types";
import type { ContractState, ModuleState, Store } from "../../store.types";
import type {
ContractState,
ModuleState,
Store,
MultiContractState,
} from "../../store.types";
import { EventEmitter } from "../event-emitter/event-emitter.service";
import type { WalletSelectorEvents } from "../../wallet-selector.types";
import { Logger, logger } from "../logger/logger.service";
Expand All @@ -19,6 +25,7 @@ import {
PACKAGE_NAME,
PENDING_CONTRACT,
PENDING_SELECTED_WALLET_ID,
PENDING_CONTRACTS,
} from "../../constants";
import { JsonStorage } from "../storage/json-storage.service";
import type { ProviderService } from "../provider/provider.service.types";
Expand Down Expand Up @@ -80,12 +87,20 @@ export class WalletModules {
PENDING_CONTRACT
);

const pendingContracts = await jsonStorage.getItem<MultiContractState>(
PENDING_CONTRACTS
);

if (pendingSelectedWalletId && pendingContract) {
const accounts = await this.validateWallet(pendingSelectedWalletId);

await jsonStorage.removeItem(PENDING_SELECTED_WALLET_ID);
await jsonStorage.removeItem(PENDING_CONTRACT);

if (pendingContracts) {
await jsonStorage.removeItem(PENDING_CONTRACTS);
}

if (accounts.length) {
const { selectedWalletId } = this.store.getState();
const selectedWallet = await this.getWallet(selectedWalletId);
Expand All @@ -105,11 +120,12 @@ export class WalletModules {
contract: pendingContract,
selectedWalletId: pendingSelectedWalletId,
recentlySignedInWallets: recentlySignedInWalletsFromPending,
contracts: pendingContracts,
};
}
}

const { contract, selectedWalletId } = this.store.getState();
const { contract, selectedWalletId, contracts } = this.store.getState();
const accounts = await this.validateWallet(selectedWalletId);

const recentlySignedInWallets = await jsonStorage.getItem<Array<string>>(
Expand All @@ -122,6 +138,7 @@ export class WalletModules {
contract: null,
selectedWalletId: null,
recentlySignedInWallets: recentlySignedInWallets || [],
contracts: null,
};
}

Expand All @@ -130,6 +147,7 @@ export class WalletModules {
contract,
selectedWalletId,
recentlySignedInWallets: recentlySignedInWallets || [],
contracts,
};
}

Expand Down Expand Up @@ -170,7 +188,7 @@ export class WalletModules {

private async onWalletSignedIn(
walletId: string,
{ accounts, contractId, methodNames }: WalletEvents["signedIn"]
{ accounts, contractId, methodNames, contracts }: WalletEvents["signedIn"]
) {
const { selectedWalletId } = this.store.getState();
const jsonStorage = new JsonStorage(this.storage, PACKAGE_NAME);
Expand All @@ -183,6 +201,13 @@ export class WalletModules {
if (module.type === "browser") {
await jsonStorage.setItem(PENDING_SELECTED_WALLET_ID, walletId);
await jsonStorage.setItem<ContractState>(PENDING_CONTRACT, contract);

if (contracts) {
await jsonStorage.setItem<MultiContractState>(
PENDING_CONTRACTS,
contracts
);
}
}

return;
Expand All @@ -198,14 +223,21 @@ export class WalletModules {

this.store.dispatch({
type: "WALLET_CONNECTED",
payload: { walletId, contract, accounts, recentlySignedInWallets },
payload: {
walletId,
contract,
accounts,
recentlySignedInWallets,
contracts,
},
});

this.emitter.emit("signedIn", {
walletId,
contractId,
methodNames,
accounts,
contracts,
});
}

Expand Down Expand Up @@ -255,6 +287,7 @@ export class WalletModules {

private decorateWallet(wallet: Wallet): Wallet {
const _signIn = wallet.signIn;
const _signInMulti = wallet.signInMulti;
const _signOut = wallet.signOut;

wallet.signIn = async (params: never) => {
Expand All @@ -265,6 +298,25 @@ export class WalletModules {
accounts,
contractId,
methodNames,
contracts: null,
});

return accounts;
};

wallet.signInMulti = async (params: never) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than having every wallet module be required by convention to implement a signInMulti() method that throw's an error if it isn't supported, can we instead not add a signInMulti() method in the modules that do not support it, and make this decorator check if the method exists and throw the error

throw new Error(
        `The signInMulti method is not supported by ${metadata.name}`
      );

if it hasn't been implemented?

This has two benefits:

  1. If any wallet module doesn't implement the method for whatever reason, callers will always get a consistent error thrown instead of there being a '_signInMulti() is not a function' type of error thrown from our attempt to call it in those cases
  2. We don't need to inform wallet module authors about the need to add a stub, ensure their modules have the stub during code reviews, etc. -- we can treat the lack of implementation as a 'not supported'.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed it in this commit: 8121e5e

const accounts = await _signInMulti(params);

const { permissions } = params as SignInMultiParams;
const contracts: MultiContractState = permissions.map((permission) => ({
contractId: permission.receiverId,
methodNames: permission.methodNames,
}));
await this.onWalletSignedIn(wallet.id, {
accounts,
contractId: contracts[0].contractId,
methodNames: contracts[0].methodNames,
contracts,
});

return accounts;
Expand Down Expand Up @@ -374,8 +426,13 @@ export class WalletModules {

this.modules = modules;

const { accounts, contract, selectedWalletId, recentlySignedInWallets } =
await this.resolveStorageState();
const {
accounts,
contract,
selectedWalletId,
recentlySignedInWallets,
contracts,
} = await this.resolveStorageState();

this.store.dispatch({
type: "SETUP_WALLET_MODULES",
Expand All @@ -385,6 +442,7 @@ export class WalletModules {
contract,
selectedWalletId,
recentlySignedInWallets,
contracts,
},
});

Expand Down
Loading