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

SQC-153: Support for verifyOwner #320

Closed
wants to merge 39 commits into from
Closed
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
f7dffdd
Added types for signMessage.
lewis-sqa Jun 13, 2022
737321e
Added signMessage for Math Wallet.
lewis-sqa Jun 13, 2022
cde5488
Added signMessage for Ledger.
lewis-sqa Jun 13, 2022
2ae941f
Added account method.
lewis-sqa Jun 13, 2022
f873dca
Added signMessage for Sender.
lewis-sqa Jun 13, 2022
0862c56
Added signMessage for (My) NEAR Wallet.
lewis-sqa Jun 13, 2022
fda1434
Added signMessage for WalletConnect.
lewis-sqa Jun 13, 2022
b126b34
Merge remote-tracking branch 'origin/dev' into SQAC-167/sign-message
lewis-sqa Jun 13, 2022
55b8fec
Added demo for sign message functionality.
lewis-sqa Jun 13, 2022
775ca89
Added note around weird behaviour with Sender.
lewis-sqa Jun 13, 2022
02c7187
Added note around weird behaviour with Math Wallet.
lewis-sqa Jun 13, 2022
3fdc892
Merge remote-tracking branch 'origin/dev' into SQAC-167/sign-message
lewis-sqa Jul 29, 2022
3e7d6b6
Aligned with React example.
lewis-sqa Jul 29, 2022
c902f81
Updated interfaces.
lewis-sqa Jul 29, 2022
5e5fa6a
Simplified params.
lewis-sqa Jul 29, 2022
ae28958
Switched to verifyOwner.
lewis-sqa Jul 29, 2022
7476005
Switched to verifyOwner.
lewis-sqa Jul 29, 2022
8b85581
Added verifyOwner method.
lewis-sqa Jul 29, 2022
b0cb0e9
Added logs.
lewis-sqa Jul 29, 2022
e75ff19
Switched to verifyOwner.
lewis-sqa Jul 29, 2022
4d7e1ed
Fixed log.
lewis-sqa Jul 29, 2022
0861b90
Switched to verifyOwner.
lewis-sqa Jul 29, 2022
5a9e8f7
Added verifyOwner method.
lewis-sqa Jul 29, 2022
cd06612
Switched to verifyOwner.
lewis-sqa Jul 29, 2022
0a80b76
Added log.
lewis-sqa Jul 29, 2022
d5d29a3
Switched to verifyOwner.
lewis-sqa Jul 29, 2022
8d0389a
Corrected getPublicKey call.
lewis-sqa Jul 29, 2022
0a8786d
Updated examples.
lewis-sqa Jul 29, 2022
aa38a1f
Connect to ledger device, avoid error Device not connected error
kujtimprenkuSQA Aug 2, 2022
de59834
Fixed typo
kujtimprenkuSQA Aug 2, 2022
7c1c8b8
Redirect to MyNearWallet
kujtimprenkuSQA Aug 5, 2022
648c168
Throw for ledger since it can only sign transactions at the moment
kujtimprenkuSQA Aug 5, 2022
14c1e62
Catch errors in examples.
kujtimprenkuSQA Aug 5, 2022
1bb4598
Updated docs.
kujtimprenkuSQA Aug 8, 2022
d49c461
Throw when callbackUrl can not be constructed, avoid SSR error of win…
kujtimprenkuSQA Aug 8, 2022
1a6e10f
Merged with dev and fixed conflicts
kujtimprenkuSQA Aug 10, 2022
e994a8a
Fixed linting
kujtimprenkuSQA Aug 10, 2022
214ae8e
Merge branch 'dev' into SQAC-167/sign-message-2
kujtimprenkuSQA Aug 11, 2022
d0f4e2d
Merge branch 'SQAC-167/sign-message-2' into SQAC-167/sign-message
kujtimprenkuSQA Aug 11, 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
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<div>
<button (click)="signOut()">Log out</button>
<button (click)="switchWallet()">Switch Wallet</button>
<button (click)="onVerifyOwner()">Verify Owner</button>
<button *ngIf="accounts.length > 1" (click)="switchAccount()">
Switch Account
</button>
Expand Down
15 changes: 15 additions & 0 deletions examples/angular/src/app/components/content/content.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,21 @@ export class ContentComponent implements OnInit, OnDestroy {
alert("Switched account to " + nextAccountId);
}

async onVerifyOwner() {
const wallet = await this.selector.wallet();
try {
const signature = await wallet.verifyOwner();

if (signature) {
alert(`Signature for verification: ${signature.signature.toString()}`);
}
} catch (err) {
const message =
err instanceof Error ? err.message : "Something went wrong";
alert(message);
}
}

subscribeToEvents() {
this.subscription = this.selector.store.observable
.pipe(
Expand Down
16 changes: 16 additions & 0 deletions examples/react/components/Content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,21 @@ const Content: React.FC = () => {
[selector, accountId]
);

const handleVerifyOwner = async () => {
const wallet = await selector.wallet();
try {
const signature = await wallet.verifyOwner();

if (signature) {
alert(`Signature for verification: ${signature.signature.toString()}`);
}
} catch (err) {
const message =
err instanceof Error ? err.message : "Something went wrong";
alert(message);
}
};

const handleSubmit = useCallback(
async (e: SubmitEvent) => {
e.preventDefault();
Expand Down Expand Up @@ -220,6 +235,7 @@ const Content: React.FC = () => {
<div>
<button onClick={handleSignOut}>Log out</button>
<button onClick={handleSwitchWallet}>Switch Wallet</button>
<button onClick={handleVerifyOwner}>Verify Owner</button>
{accounts.length > 1 && (
<button onClick={handleSwitchAccount}>Switch Account</button>
)}
Expand Down
31 changes: 31 additions & 0 deletions packages/core/docs/api/wallet.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,37 @@ Returns one or more accounts when signed in. This method can be useful for walle
})();
```

### `.verifyOwner(params)`

**Parameters**
- `params` (`object`)
- `message` (`string?`): The message requested sign. Defaults to `verify owner` string.
- `signerId` (`string?`): Account ID used to sign the message. Defaults to the first account.
- `publicKey` (`PublicKey?`): Public key used to sign the message. Defaults to the public key of the signed in account.
- `callbackUrl` (`string?`): Applicable to browser wallets (e.g. MyNearWallet). This is the callback url once the signing is approved. Defaults to `window.location.href`.
- `meta` (`string?`): Applicable to browser wallets (e.g. MyNearWallet) extra data that will be passed to the callback url once the signing is approved.

**Returns**
- `Promise<void | utils.key_pair.Signature>`: Browser wallets won't return the signing outcome as they may need to redirect for signing. For MyNearWallet the outcome is passed to the callback url.

**Description**

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.

**Example**

```ts
// MyNearWallet
(async () => {
const wallet = await selector.wallet("my-near-wallet");
await wallet.verifyOwner({
message: "Test message",
});
})();
```

### `.signAndSendTransaction(params)`

**Parameters**
Expand Down
14 changes: 13 additions & 1 deletion packages/core/src/lib/wallet/wallet.types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { providers } from "near-api-js";
import { providers, utils } from "near-api-js";

import {
EventEmitterService,
Expand All @@ -10,6 +10,7 @@ import type { Options } from "../options.types";
import type { ReadOnlyStore } from "../store.types";
import type { Transaction, Action } from "./transactions.types";
import type { Modify, Optional } from "../utils.types";
import { PublicKey } from "near-api-js/lib/utils";
import type { FinalExecutionOutcome } from "near-api-js/lib/providers";

interface BaseWalletMetadata {
Expand All @@ -29,6 +30,14 @@ export interface SignInParams {
methodNames?: Array<string>;
}

export interface VerifyOwnerParams {
message?: string;
signerId?: string;
publicKey?: PublicKey;
callbackUrl?: string;
meta?: string;
}

export interface SignAndSendTransactionParams {
signerId?: string;
receiverId?: string;
Expand All @@ -43,6 +52,9 @@ interface BaseWalletBehaviour {
signIn(params: SignInParams): Promise<Array<Account>>;
signOut(): Promise<void>;
getAccounts(): Promise<Array<Account>>;
verifyOwner(
params?: VerifyOwnerParams
): Promise<utils.key_pair.Signature | void>;
signAndSendTransaction(
params: SignAndSendTransactionParams
): Promise<providers.FinalExecutionOutcome>;
Expand Down
36 changes: 36 additions & 0 deletions packages/ledger/src/lib/ledger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const Ledger: WalletBehaviourFactory<HardwareWallet> = async ({
provider,
logger,
storage,
metadata,
}) => {
const _state = await setupLedgerState(storage);

Expand Down Expand Up @@ -219,6 +220,41 @@ const Ledger: WalletBehaviourFactory<HardwareWallet> = async ({
return getAccounts();
},

async verifyOwner({ message = "verify owner", signerId, publicKey } = {}) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Currently the NEAR Ledger app can only sign valid transactions; it will not be able to sign any payload that is not a transaction -- so this is not implemented currently and should be throwing a 'Method not Supported' type error.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thank you, for the feedback. I will make the changes tomorrow when I start working.

logger.log("Ledger:verifyOwner", { message, signerId, publicKey });

const account = getActiveAccount(store.getState());

if (!account) {
throw new Error("No active account");
}

// Note: Connection must be triggered by user interaction.
await connectLedgerDevice();

const networkId = options.network.networkId;
const accountId = signerId || account.accountId;
const pubKey =
publicKey || (await signer.getPublicKey(accountId, networkId));
const block = await provider.block({ finality: "final" });

const msg = JSON.stringify({
accountId,
message,
blockId: block.header.hash,
publicKey: Buffer.from(pubKey.data).toString("base64"),
keyType: pubKey.keyType,
});

throw new Error(`Method not supported by ${metadata.name}`);

return signer.signMessage(
new Uint8Array(Buffer.from(msg)),
accountId,
networkId
);
},

async signAndSendTransaction({ signerId, receiverId, actions }) {
logger.log("signAndSendTransaction", { signerId, receiverId, actions });

Expand Down
33 changes: 33 additions & 0 deletions packages/math-wallet/src/lib/math-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const setupMathWalletState = (): MathWalletState => {
};

const MathWallet: WalletBehaviourFactory<InjectedWallet> = async ({
metadata,
options,
store,
provider,
Expand Down Expand Up @@ -104,6 +105,38 @@ const MathWallet: WalletBehaviourFactory<InjectedWallet> = async ({
return getAccounts();
},

async verifyOwner({ message = "verify owner", signerId, publicKey } = {}) {
logger.log("MathWallet:verifyOwner", { message, signerId, publicKey });

const account = getActiveAccount(store.getState());

if (!account) {
throw new Error("No active account");
}

const accountId = signerId || account.accountId;
const pubKey =
publicKey || (await _state.wallet.signer.getPublicKey(accountId));
const block = await provider.block({ finality: "final" });

const msg = JSON.stringify({
accountId,
message,
blockId: block.header.hash,
publicKey: Buffer.from(pubKey.data).toString("base64"),
keyType: pubKey.keyType,
});

// Note: Math Wallet currently hangs when calling signMessage.
throw new Error(`Method not supported by ${metadata.name}`);

return _state.wallet.signer.signMessage(
new Uint8Array(Buffer.from(msg)),
accountId,
options.network.networkId
);
},

async signAndSendTransaction({ signerId, receiverId, actions }) {
logger.log("signAndSendTransaction", { signerId, receiverId, actions });
const signedTransactions = await signTransactions(
Expand Down
8 changes: 7 additions & 1 deletion packages/meteor-wallet/src/lib/meteor-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const setupWalletState = async (
const createMeteorWalletInjected: WalletBehaviourFactory<
InjectedWallet,
{ params: MeteorWalletParams_Injected }
> = async ({ options, logger, store, params }) => {
> = async ({ metadata, options, logger, store, params }) => {
const _state = await setupWalletState(params, options.network);

const cleanup = () => {
Expand Down Expand Up @@ -155,6 +155,12 @@ const createMeteorWalletInjected: WalletBehaviourFactory<
return getAccounts();
},

async verifyOwner({ message = "verify owner", signerId, publicKey } = {}) {
logger.log("MeteorWallet:verifyOwner", { message, signerId, publicKey });

throw new Error(`Method not supported by ${metadata.name}`);
},

async signAndSendTransaction({ signerId, receiverId, actions }) {
logger.log("MeteorWallet:signAndSendTransaction", {
signerId,
Expand Down
35 changes: 34 additions & 1 deletion packages/my-near-wallet/src/lib/my-near-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const setupWalletState = async (
const MyNearWallet: WalletBehaviourFactory<
BrowserWallet,
{ params: MyNearWalletExtraOptions }
> = async ({ options, store, params, logger }) => {
> = async ({ metadata, options, store, params, logger }) => {
const _state = await setupWalletState(params, options.network);

const cleanup = () => {
Expand Down Expand Up @@ -155,6 +155,39 @@ const MyNearWallet: WalletBehaviourFactory<
return getAccounts();
},

async verifyOwner({
message = "verify owner",
signerId,
publicKey,
callbackUrl,
meta,
} = {}) {
logger.log("verifyOwner", { message, signerId, publicKey });

const account = _state.wallet.account();

if (!account) {
throw new Error("Wallet not signed in");
}
const locationUrl =
typeof window !== "undefined" ? window.location.href : "";

const url = callbackUrl || locationUrl;

if (!url) {
throw new Error(`The callbackUrl is missing for ${metadata.name}`);
}

const encodedUrl = encodeURIComponent(url);
const extraMeta = meta ? `&meta=${meta}` : "";

window.location.replace(
`${params.walletUrl}/verify-owner?message=${message}&callbackUrl=${encodedUrl}${extraMeta}`
);

return;
},

async signAndSendTransaction({
signerId,
receiverId,
Expand Down
12 changes: 11 additions & 1 deletion packages/nightly-connect/src/lib/nightly-connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const setupNightlyConnectState = (): NightlyConnectState => {
const NightlyConnect: WalletBehaviourFactory<
BridgeWallet,
{ params: NightlyConnectParams }
> = async ({ store, params, logger, options, provider, emitter }) => {
> = async ({ metadata, store, params, logger, options, provider, emitter }) => {
const _state = setupNightlyConnectState();

const getAccounts = () => {
Expand Down Expand Up @@ -156,6 +156,16 @@ const NightlyConnect: WalletBehaviourFactory<
return getAccounts().map(({ accountId }) => ({ accountId }));
},

async verifyOwner({ message = "verify owner", signerId, publicKey } = {}) {
logger.log("NightlyConnect:verifyOwner", {
message,
signerId,
publicKey,
});

throw new Error(`Method not supported by ${metadata.name}`);
},

async signAndSendTransaction({ signerId, receiverId, actions }) {
logger.log("signAndSendTransaction", { signerId, receiverId, actions });

Expand Down
7 changes: 7 additions & 0 deletions packages/nightly/src/lib/nightly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const isInstalled = () => {
return waitFor(() => !!window.nightly!.near!).catch(() => false);
};
const Nightly: WalletBehaviourFactory<InjectedWallet> = async ({
metadata,
options,
store,
logger,
Expand Down Expand Up @@ -141,6 +142,12 @@ const Nightly: WalletBehaviourFactory<InjectedWallet> = async ({
return getAccounts().map(({ accountId }) => ({ accountId }));
},

async verifyOwner({ message = "verify owner", signerId, publicKey } = {}) {
logger.log("Nightly:verifyOwner", { message, signerId, publicKey });

throw new Error(`Method not supported by ${metadata.name}`);
},

async signAndSendTransaction({ signerId, receiverId, actions }) {
logger.log("signAndSendTransaction", { signerId, receiverId, actions });

Expand Down
3 changes: 2 additions & 1 deletion packages/sender/src/lib/injected-sender.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// 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";
import { Account, providers } from "near-api-js";

interface AccessKey {
publicKey: {
Expand Down Expand Up @@ -113,6 +113,7 @@ export interface InjectedSender {
isSender: boolean;
callbacks: Record<keyof SenderEvents, unknown>;
getAccountId: () => string | null;
account(): Account | null;
getRpc: () => Promise<GetRpcResponse>;
requestSignIn: (
params: RequestSignInParams
Expand Down
Loading