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 Support for verifyOwner #391

Merged
merged 6 commits into from
Aug 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 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,23 @@ export class ContentComponent implements OnInit, OnDestroy {
alert("Switched account to " + nextAccountId);
}

async onVerifyOwner() {
const wallet = await this.selector.wallet();
try {
const owner = await wallet.verifyOwner({
message: "test message for verification",
});

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

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

const handleVerifyOwner = async () => {
const wallet = await selector.wallet();
try {
const owner = await wallet.verifyOwner({
message: "test message for verification",
});

if (owner) {
alert(`Signature for verification: ${JSON.stringify(owner)}`);
}
} 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 +237,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
29 changes: 29 additions & 0 deletions packages/core/docs/api/wallet.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,35 @@ 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.
- `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 | VerifiedOwner>`: 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
2 changes: 2 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ export type {
BridgeWalletMetadata,
BridgeWalletBehaviour,
BridgeWallet,
VerifiedOwner,
VerifyOwnerParams,
Account,
Transaction,
Action,
Expand Down
18 changes: 17 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 Down Expand Up @@ -29,6 +29,21 @@ export interface SignInParams {
methodNames?: Array<string>;
}

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

export interface VerifiedOwner {
accountId: string;
message: string;
blockId: string;
publicKey: string;
signature: string;
keyType: utils.key_pair.KeyType;
}

export interface SignAndSendTransactionParams {
signerId?: string;
receiverId?: string;
Expand All @@ -43,6 +58,7 @@ interface BaseWalletBehaviour {
signIn(params: SignInParams): Promise<Array<Account>>;
signOut(): Promise<void>;
getAccounts(): Promise<Array<Account>>;
verifyOwner(params: VerifyOwnerParams): Promise<VerifiedOwner | void>;
signAndSendTransaction(
params: SignAndSendTransactionParams
): Promise<providers.FinalExecutionOutcome>;
Expand Down
7 changes: 7 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,12 @@ const Ledger: WalletBehaviourFactory<HardwareWallet> = async ({
return getAccounts();
},

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

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

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

Expand Down
39 changes: 39 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,44 @@ const MathWallet: WalletBehaviourFactory<InjectedWallet> = async ({
return getAccounts();
},

async verifyOwner({ message }) {
logger.log("MathWallet:verifyOwner", { message });

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

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

const accountId = account.accountId;
const pubKey = await _state.wallet.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);

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

const signed = await _state.wallet.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(
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 }) {
logger.log("MeteorWallet:verifyOwner", { message });

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

async signAndSendTransaction({ signerId, receiverId, actions }) {
logger.log("MeteorWallet:signAndSendTransaction", {
signerId,
Expand Down
29 changes: 28 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,33 @@ const MyNearWallet: WalletBehaviourFactory<
return getAccounts();
},

async verifyOwner({ message, callbackUrl, meta }) {
logger.log("verifyOwner", { message });

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
8 changes: 7 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,12 @@ const NightlyConnect: WalletBehaviourFactory<
return getAccounts().map(({ accountId }) => ({ accountId }));
},

async verifyOwner({ message }) {
logger.log("NightlyConnect:verifyOwner", { message });

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 }) {
logger.log("Nightly:verifyOwner", { message });

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 @@ -114,6 +114,7 @@ export interface InjectedSender {
callbacks: Record<keyof SenderEvents, unknown>;
getAccountId: () => string | null;
getRpc: () => Promise<GetRpcResponse>;
account(): Account | null;
requestSignIn: (
params: RequestSignInParams
) => Promise<RequestSignInResponse>;
Expand Down
Loading