diff --git a/examples/angular/src/app/components/content/content.component.html b/examples/angular/src/app/components/content/content.component.html
index 88ca7ffb4..43e57a47c 100644
--- a/examples/angular/src/app/components/content/content.component.html
+++ b/examples/angular/src/app/components/content/content.component.html
@@ -2,6 +2,7 @@
+
diff --git a/examples/angular/src/app/components/content/content.component.ts b/examples/angular/src/app/components/content/content.component.ts
index 62ed199bf..3f4ee7367 100644
--- a/examples/angular/src/app/components/content/content.component.ts
+++ b/examples/angular/src/app/components/content/content.component.ts
@@ -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(
diff --git a/examples/react/components/Content.tsx b/examples/react/components/Content.tsx
index 9b14c73a9..ca24c5257 100644
--- a/examples/react/components/Content.tsx
+++ b/examples/react/components/Content.tsx
@@ -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();
@@ -220,6 +237,7 @@ const Content: React.FC = () => {
+
{accounts.length > 1 && (
)}
diff --git a/packages/core/docs/api/wallet.md b/packages/core/docs/api/wallet.md
index 6291dc1dd..549a77b1e 100644
--- a/packages/core/docs/api/wallet.md
+++ b/packages/core/docs/api/wallet.md
@@ -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
`: 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**
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 5f94c63a7..6b6584a6c 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -45,6 +45,8 @@ export type {
BridgeWalletMetadata,
BridgeWalletBehaviour,
BridgeWallet,
+ VerifiedOwner,
+ VerifyOwnerParams,
Account,
Transaction,
Action,
diff --git a/packages/core/src/lib/wallet/wallet.types.ts b/packages/core/src/lib/wallet/wallet.types.ts
index 435409d31..df06fa38f 100644
--- a/packages/core/src/lib/wallet/wallet.types.ts
+++ b/packages/core/src/lib/wallet/wallet.types.ts
@@ -1,4 +1,4 @@
-import { providers } from "near-api-js";
+import { providers, utils } from "near-api-js";
import {
EventEmitterService,
@@ -29,6 +29,21 @@ export interface SignInParams {
methodNames?: Array;
}
+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;
@@ -43,6 +58,7 @@ interface BaseWalletBehaviour {
signIn(params: SignInParams): Promise>;
signOut(): Promise;
getAccounts(): Promise>;
+ verifyOwner(params: VerifyOwnerParams): Promise;
signAndSendTransaction(
params: SignAndSendTransactionParams
): Promise;
diff --git a/packages/ledger/src/lib/ledger.ts b/packages/ledger/src/lib/ledger.ts
index 101dfd88b..c967c32c1 100644
--- a/packages/ledger/src/lib/ledger.ts
+++ b/packages/ledger/src/lib/ledger.ts
@@ -60,6 +60,7 @@ const Ledger: WalletBehaviourFactory = async ({
provider,
logger,
storage,
+ metadata,
}) => {
const _state = await setupLedgerState(storage);
@@ -219,6 +220,12 @@ const Ledger: WalletBehaviourFactory = 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 });
diff --git a/packages/math-wallet/src/lib/math-wallet.ts b/packages/math-wallet/src/lib/math-wallet.ts
index ce0a4a11f..c90d90622 100644
--- a/packages/math-wallet/src/lib/math-wallet.ts
+++ b/packages/math-wallet/src/lib/math-wallet.ts
@@ -41,6 +41,7 @@ const setupMathWalletState = (): MathWalletState => {
};
const MathWallet: WalletBehaviourFactory = async ({
+ metadata,
options,
store,
provider,
@@ -104,6 +105,44 @@ const MathWallet: WalletBehaviourFactory = 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(
diff --git a/packages/meteor-wallet/src/lib/meteor-wallet.ts b/packages/meteor-wallet/src/lib/meteor-wallet.ts
index c01f57f24..27d642dbe 100644
--- a/packages/meteor-wallet/src/lib/meteor-wallet.ts
+++ b/packages/meteor-wallet/src/lib/meteor-wallet.ts
@@ -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 = () => {
@@ -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,
diff --git a/packages/my-near-wallet/src/lib/my-near-wallet.ts b/packages/my-near-wallet/src/lib/my-near-wallet.ts
index d723dd15e..87811d480 100644
--- a/packages/my-near-wallet/src/lib/my-near-wallet.ts
+++ b/packages/my-near-wallet/src/lib/my-near-wallet.ts
@@ -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 = () => {
@@ -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,
diff --git a/packages/nightly-connect/src/lib/nightly-connect.ts b/packages/nightly-connect/src/lib/nightly-connect.ts
index 5cf380ecc..c79545d8a 100644
--- a/packages/nightly-connect/src/lib/nightly-connect.ts
+++ b/packages/nightly-connect/src/lib/nightly-connect.ts
@@ -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 = () => {
@@ -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 });
diff --git a/packages/nightly/src/lib/nightly.ts b/packages/nightly/src/lib/nightly.ts
index 67703d8ed..0bb7e92ff 100644
--- a/packages/nightly/src/lib/nightly.ts
+++ b/packages/nightly/src/lib/nightly.ts
@@ -40,6 +40,7 @@ const isInstalled = () => {
return waitFor(() => !!window.nightly!.near!).catch(() => false);
};
const Nightly: WalletBehaviourFactory = async ({
+ metadata,
options,
store,
logger,
@@ -141,6 +142,12 @@ const Nightly: WalletBehaviourFactory = 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 });
diff --git a/packages/sender/src/lib/injected-sender.ts b/packages/sender/src/lib/injected-sender.ts
index 6ad3aa201..6a04c153f 100644
--- a/packages/sender/src/lib/injected-sender.ts
+++ b/packages/sender/src/lib/injected-sender.ts
@@ -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: {
@@ -114,6 +114,7 @@ export interface InjectedSender {
callbacks: Record;
getAccountId: () => string | null;
getRpc: () => Promise;
+ account(): Account | null;
requestSignIn: (
params: RequestSignInParams
) => Promise;
diff --git a/packages/sender/src/lib/sender.ts b/packages/sender/src/lib/sender.ts
index a64b0fd31..7160d5e02 100644
--- a/packages/sender/src/lib/sender.ts
+++ b/packages/sender/src/lib/sender.ts
@@ -42,6 +42,7 @@ const Sender: WalletBehaviourFactory = async ({
options,
metadata,
store,
+ provider,
emitter,
logger,
}) => {
@@ -174,6 +175,52 @@ const Sender: WalletBehaviourFactory = async ({
return getAccounts();
},
+ async verifyOwner({ message }) {
+ logger.log("Sender:verifyOwner", { message });
+
+ const account = _state.wallet.account();
+
+ if (!account) {
+ throw new Error("Wallet not signed in");
+ }
+
+ // Note: When the wallet is locked, Sender returns an empty Signer interface.
+ // Even after unlocking the wallet, the user will need to refresh to gain
+ // access to these methods.
+ if (!account.connection.signer.signMessage) {
+ throw new Error("Wallet is locked");
+ }
+
+ const networkId = options.network.networkId;
+ const accountId = account.accountId;
+ const pubKey = await account.connection.signer.getPublicKey(
+ accountId,
+ networkId
+ );
+ 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);
+
+ const signed = await account.connection.signer.signMessage(
+ new Uint8Array(Buffer.from(encoded)),
+ accountId,
+ 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 });
diff --git a/packages/wallet-connect/src/lib/wallet-connect.ts b/packages/wallet-connect/src/lib/wallet-connect.ts
index b12a33094..229abd7c7 100644
--- a/packages/wallet-connect/src/lib/wallet-connect.ts
+++ b/packages/wallet-connect/src/lib/wallet-connect.ts
@@ -66,7 +66,7 @@ const setupWalletConnectState = async (
const WalletConnect: WalletBehaviourFactory<
BridgeWallet,
{ params: WalletConnectExtraOptions }
-> = async ({ options, store, params, emitter, logger }) => {
+> = async ({ metadata, options, store, params, emitter, logger }) => {
const _state = await setupWalletConnectState(params);
const getChainId = () => {
@@ -177,6 +177,12 @@ const WalletConnect: WalletBehaviourFactory<
return getAccounts();
},
+ async verifyOwner({ message }) {
+ logger.log("WalletConnect:verifyOwner", { message });
+
+ throw new Error(`Method not supported by ${metadata.name}`);
+ },
+
async signAndSendTransaction({ signerId, receiverId, actions }) {
logger.log("signAndSendTransaction", { signerId, receiverId, actions });