Skip to content

Commit

Permalink
feat(wallet connect): sign payload using wc in test-dapp
Browse files Browse the repository at this point in the history
Added support to sign payload in the test-dapp using wallet connect, fix getPk and sign in the
wallet connect package
  • Loading branch information
roxaneletourneau committed Nov 1, 2024
1 parent 303376c commit c1580f5
Show file tree
Hide file tree
Showing 8 changed files with 282 additions and 236 deletions.
8 changes: 8 additions & 0 deletions apps/taquito-test-dapp/src/lib/TestContainer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@
title: "Confirmations through observable",
body: result.confirmationObsOutput,
};
} else if (test.id === "show-public-key") {
testResult = {
id: test.id,
title: "Public Key",
body: {
output: result.output
}
}
}
} else {
error = result.error;
Expand Down
9 changes: 6 additions & 3 deletions apps/taquito-test-dapp/src/lib/Wallet.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,18 @@
});
wallet.signClient.on("session_ping", ({ id, topic }) => {
console.log("session_ping in test dapp", id, topic);
store.addEvent("session_ping");
});
wallet.signClient.on("session_delete", ({ topic }) => {
console.log("EVEN: session_delete", topic);
console.log("EVENT: session_delete", topic);
store.addEvent("session_delete");
if (!wallet.isActiveSession()) {
resetApp();
}
});
wallet.signClient.on("session_update", async ({ topic }) => {
console.log("EVEN: session_update", topic);
console.log("EVENT: session_update", topic);
store.addEvent("session_update");
const allAccounts = wallet.getAccounts();
await updateStore(wallet, allAccounts);
});
Expand All @@ -75,7 +78,7 @@
permissionScope: {
networks: [$store.networkType as NetworkTypeWc2],
events: [],
methods: [PermissionScopeMethods.TEZOS_SEND, PermissionScopeMethods.TEZOS_SIGN],
methods: [PermissionScopeMethods.TEZOS_SEND, PermissionScopeMethods.TEZOS_SIGN, PermissionScopeMethods.TEZOS_GET_ACCOUNTS],
},
pairingTopic,
registryUrl: "https://www.tezos.help/wcdata/"
Expand Down
99 changes: 72 additions & 27 deletions apps/taquito-test-dapp/src/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
UnitValue
} from "@taquito/taquito";
import type { ContractProvider } from "@taquito/taquito";
import type { BeaconWallet } from "@taquito/beacon-wallet";
import { BeaconWallet } from "@taquito/beacon-wallet";
import { stringToBytes, verifySignature } from "@taquito/utils";
import { SigningType, type RequestSignPayloadInput } from "@airgap/beacon-types";
import { get } from "svelte/store";
Expand Down Expand Up @@ -56,6 +56,16 @@ const sendTezToEtherlink = async (
}
};

const showPublicKey = async (Tezos: TezosToolkit): Promise<TestResult> => {
try {
const publicKey = await Tezos.wallet.pk();
return { success: true, opHash: "", output: publicKey };
} catch (error) {
console.log(error);
return { success: false, opHash: "", output: JSON.stringify(error) };
}
}

const sendTez = async (Tezos: TezosToolkit): Promise<TestResult> => {
let opHash = "";
try {
Expand Down Expand Up @@ -348,49 +358,73 @@ const batchApiContractCallsTest = async (

const signPayload = async (
input: string,
wallet: BeaconWallet
wallet: BeaconWallet | WalletConnect2
): Promise<TestResult> => {
const userAddress = await wallet.getPKH();
const { payload, formattedInput } = preparePayloadToSign(input, userAddress);
try {
const signedPayload = await wallet.client.requestSignPayload(payload);
return {
success: true,
opHash: "",
output: signedPayload.signature,
sigDetails: { input, formattedInput, bytes: payload.payload }
};
} catch (error) {
if (wallet instanceof BeaconWallet) {
const signedPayload = await wallet.client.requestSignPayload(payload);
return {
success: true,
opHash: "",
output: signedPayload.signature,
sigDetails: { input, formattedInput, bytes: payload.payload }
};
} else {
const signature = await wallet.sign(payload.payload);
return {
success: true,
opHash: "",
output: signature,
sigDetails: { input, formattedInput, bytes: payload.payload }
};
}
}
catch (error) {
console.log(error);
return { success: false, opHash: "", output: JSON.stringify(error) };
}
};

const signPayloadAndSend = async (
input: string,
wallet: BeaconWallet,
contract: ContractAbstraction<Wallet> | ContractAbstraction<ContractProvider>
wallet: BeaconWallet | WalletConnect2,
contract: ContractAbstraction<Wallet>
): Promise<TestResult> => {
if (!input) throw "No input provided";

const userAddress = await wallet.getPKH();
const { payload, formattedInput } = preparePayloadToSign(input, userAddress);
try {
const signedPayload = await wallet.client.requestSignPayload(payload);
// gets user's public key
const activeAccount = await wallet.client.getActiveAccount();
const publicKey = activeAccount.publicKey;
// sends transaction to contract
const op = await contract.methodsObject
.check_signature({ 0: publicKey, 1: signedPayload.signature, 2: payload.payload })
.send();
await op.confirmation();
return {
success: true,
opHash: op.hasOwnProperty("opHash") ? op["opHash"] : op["hash"],
output: signedPayload.signature,
sigDetails: { input, formattedInput, bytes: payload.payload }
};
if (wallet instanceof BeaconWallet) {
const signedPayload = await wallet.client.requestSignPayload(payload);
// gets user's public key
const activeAccount = await wallet.client.getActiveAccount();
const publicKey = activeAccount.publicKey;
// sends transaction to contract
const op = await contract.methodsObject
.check_signature({ 0: publicKey, 1: signedPayload.signature, 2: payload.payload })
.send();
await op.confirmation();
return {
success: true,
opHash: op.opHash,
output: signedPayload.signature,
sigDetails: { input, formattedInput, bytes: payload.payload }
};
} else {
const signature = await wallet.sign(payload.payload);
const publicKey = await wallet.getPK();
const op = await contract.methodsObject.check_signature({ 0: publicKey, 1: signature, 2: payload.payload }).send();
await op.confirmation();
return {
success: true,
opHash: op.opHash,
output: signature,
sigDetails: { input, formattedInput, bytes: payload.payload }
}
}
} catch (error) {
return { success: false, opHash: "", output: JSON.stringify(error) };
}
Expand Down Expand Up @@ -656,6 +690,17 @@ export const init = (
contract: ContractAbstraction<Wallet> | ContractAbstraction<ContractProvider>,
wallet: BeaconWallet | undefined
): TestSettings[] => [
{
id: "show-public-key",
name: "Show public key",
description: "This test shows your public key.",
documentation: '',
keyword: 'publicKey',
run: () => showPublicKey(Tezos),
showExecutionTime: false,
inputRequired: false,
lastResult: { option: "none", val: false }
},
{
id: "send-tez",
name: "Send tez",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"^@taquito/tzip12$": "<rootDir>/packages/taquito-tzip12/src/taquito-tzip12.ts",
"^@taquito/tzip16$": "<rootDir>/packages/taquito-tzip16/src/taquito-tzip16.ts",
"^@taquito/utils$": "<rootDir>/packages/taquito-utils/src/taquito-utils.ts",
"^@taquito/wallet-connect-2$": "<rootDir>/packages/taquito-wallet-connect-2/src/wallet-connect-2.ts"
"^@taquito/wallet-connect-2$": "<rootDir>/packages/taquito-wallet-connect-2/src/taquito-wallet-connect-2.ts"
},
"coveragePathIgnorePatterns": [
"/node_modules/",
Expand Down
8 changes: 8 additions & 0 deletions packages/taquito-wallet-connect-2/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,11 @@ export class InvalidSession extends Error {
super(message);
}
}

export class PublicKeyRetrievalError extends Error {
name = 'PublicKeyRetrievalError';

constructor() {
super(`Unable to retrieve public key`);
}
}
77 changes: 31 additions & 46 deletions packages/taquito-wallet-connect-2/src/taquito-wallet-connect-2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
OperationParams,
PermissionScopeMethods,
PermissionScopeParam,
SigningType,
TezosAccount,
} from './types';
import {
ActiveAccountUnspecified,
Expand All @@ -49,6 +49,7 @@ import {
InvalidSessionKey,
MissingRequiredScope,
NotConnected,
PublicKeyRetrievalError,
} from './errors';

export { SignClientTypes, PairingTypes };
Expand Down Expand Up @@ -219,7 +220,7 @@ export class WalletConnect2 implements WalletProvider {
const network = this.getActiveNetwork();
const account = await this.getPKH();
this.validateNetworkAndAccount(network, account);
const { transactionHash } = await this.signClient.request<{ transactionHash: string }>({
const { operationHash } = await this.signClient.request<{ operationHash: string }>({
topic: session.topic,
chainId: `${TEZOS_PLACEHOLDER}:${network}`,
request: {
Expand All @@ -230,12 +231,14 @@ export class WalletConnect2 implements WalletProvider {
},
},
});
return transactionHash;
return operationHash;
}

// TODO need unit and test-dapp test
/**
* @description Once the session is establish, send payload to be signed by the wallet.
* @error MissingRequiredScope is thrown if permission to sign payload was not granted
*/
async sign(bytes: string, watermark?: Uint8Array): Promise<string> {
console.log(bytes, watermark);
const session = this.getSession();
if (!this.getPermittedMethods().includes(PermissionScopeMethods.TEZOS_SIGN)) {
throw new MissingRequiredScope(PermissionScopeMethods.TEZOS_SIGN);
Expand All @@ -248,52 +251,20 @@ export class WalletConnect2 implements WalletProvider {
expression =
Array.from(watermark, (byte) => byte.toString(16).padStart(2, '0')).join('') + expression;
}
const signature = await this.signClient.request<string>({
const { signature } = await this.signClient.request<{ signature: string }>({
topic: session.topic,
chainId: `${TEZOS_PLACEHOLDER}:${network}`,
request: {
method: PermissionScopeMethods.TEZOS_SIGN,
params: {
expression,
signingType: SigningType.RAW,
account: await this.getPKH(),
payload: expression,
},
},
});
return signature;
}

/**
* @description Once the session is establish, send payload to be approved and signed by the wallet.
* @error MissingRequiredScope is thrown if permission to sign payload was not granted
*/
async signPayload(params: {
signingType?: SigningType;
payload: string;
sourceAddress?: string;
}) {
const session = this.getSession();
if (!this.getPermittedMethods().includes(PermissionScopeMethods.TEZOS_SIGN)) {
throw new MissingRequiredScope(PermissionScopeMethods.TEZOS_SIGN);
}
const network = this.getActiveNetwork();
const account = await this.getPKH();
this.validateNetworkAndAccount(network, account);
const signature = await this.signClient.request<string>({
topic: session.topic,
chainId: `${TEZOS_PLACEHOLDER}:${network}`,
request: {
method: PermissionScopeMethods.TEZOS_SIGN,
params: {
account: params.sourceAddress ?? account,
expression: params.payload,
signingType: params.signingType,
},
},
});
console.log(signature);
return signature;
}

/**
* @description Return all connected accounts from the active session
* @error NotConnected if no active session
Expand Down Expand Up @@ -331,16 +302,30 @@ export class WalletConnect2 implements WalletProvider {
/**
* @description Access the public key of the active account
* @error ActiveAccountUnspecified thrown when there are multiple Tezos account in the session and none is set as the active one
* @error MissingRequiredScope is thrown if permission to get accounts was not granted
* @error PublicKeyRetrievalError is thrown if the public key is not found
*/
async getPK() {
if (!this.activeAccount) {
this.getSession();
throw new ActiveAccountUnspecified();
const session = this.getSession();
if (!this.getPermittedMethods().includes(PermissionScopeMethods.TEZOS_GET_ACCOUNTS)) {
throw new MissingRequiredScope(PermissionScopeMethods.TEZOS_GET_ACCOUNTS);
}
if (!this.getSession().sessionProperties) {
throw new InvalidSession('Session properties are not defined');
const network = this.getActiveNetwork();
const account = await this.getPKH();
const accounts = await this.signClient.request<TezosAccount[]>({
topic: session.topic,
chainId: `${TEZOS_PLACEHOLDER}:${network}`,
request: {
method: PermissionScopeMethods.TEZOS_GET_ACCOUNTS,
params: {},
},
});

const selectedAccount = accounts.find((acc) => acc.address === account);
if (!selectedAccount) {
throw new PublicKeyRetrievalError();
}
return this.getSession().sessionProperties!.publicKey;
return selectedAccount.pubkey;
}

/**
Expand Down
6 changes: 6 additions & 0 deletions packages/taquito-wallet-connect-2/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ export enum SigningType {
MICHELINE = 'micheline',
}

export interface TezosAccount {
algo: string;
address: string;
pubkey: string;
}

type WalletDefinedFields = 'source' | 'gas_limit' | 'storage_limit' | 'fee';
interface WalletOptionalFields {
gas_limit?: string;
Expand Down
Loading

0 comments on commit c1580f5

Please sign in to comment.