Skip to content

Commit

Permalink
Adding signed typed data (#286)
Browse files Browse the repository at this point in the history
* feat: adding signer typed data with connex

* feat: expose signedTypedData

* feat: revert changes to dappKit Driver

* test: sign typed data

* fix: linting

* refactor: simplify signTypedData method in certificate-wallet and wc-wallet
  • Loading branch information
Valazan authored Oct 9, 2024
1 parent d9b3bd2 commit acde52d
Show file tree
Hide file tree
Showing 20 changed files with 224 additions and 76 deletions.
1 change: 1 addition & 0 deletions packages/dapp-kit-react/src/DAppKitProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ export const DAppKitProvider: React.FC<DAppKitProviderOptions> = ({
account,
source,
connectionCertificate,
signTypedData: connex.wallet.signTypedData,
},
modal: {
open: openModal,
Expand Down
7 changes: 6 additions & 1 deletion packages/dapp-kit-react/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
/// <reference types="@vechain/connex" />
import type React from 'react';
import type { ConnectResponse, WalletSource } from '@vechain/dapp-kit';
import type {
ConnectResponse,
WalletManager,
WalletSource,
} from '@vechain/dapp-kit';
import { type DAppKitUIOptions } from '@vechain/dapp-kit-ui';
import { type Certificate } from '@vechain/sdk-core';

Expand Down Expand Up @@ -39,6 +43,7 @@ export interface DAppKitContext {
account: string | null;
source: WalletSource | null;
connectionCertificate: Certificate | null;
signTypedData: WalletManager['signTypedData'];
};
modal: {
open: () => void;
Expand Down
1 change: 1 addition & 0 deletions packages/dapp-kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@walletconnect/modal": "2.6.2",
"@walletconnect/sign-client": "2.10.2",
"@walletconnect/utils": "2.10.2",
"ethers": "^6.13.3",
"events": "^3.3.0",
"valtio": "1.11.2"
},
Expand Down
10 changes: 10 additions & 0 deletions packages/dapp-kit/src/classes/certificate-wallet.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { certificate } from '@vechain/sdk-core';
import type { BaseWallet, ConnectResponse, ConnexWallet } from '../types';
import { DEFAULT_CONNECT_CERT_MESSAGE } from '../constants';
import { ethers } from 'ethers';
import { SignTypedDataOptions } from '../types/types';

/**
* A `ConnexWallet` for wallet's that use a certificate connection
Expand Down Expand Up @@ -60,6 +62,14 @@ class CertificateBasedWallet implements ConnexWallet {
options: Connex.Signer.TxOptions,
): Promise<Connex.Vendor.TxResponse> => this.wallet.signTx(msg, options);

signTypedData = (
_domain: ethers.TypedDataDomain,
_types: Record<string, ethers.TypedDataField[]>,
_value: Record<string, unknown>,
_options?: SignTypedDataOptions,
): Promise<string> =>
this.wallet.signTypedData(_domain, _types, _value, _options);

disconnect = async (): Promise<void> => this.wallet.disconnect?.();
}

Expand Down
13 changes: 13 additions & 0 deletions packages/dapp-kit/src/classes/wallet-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import type {
import { DAppKitLogger, Storage, createWallet } from '../utils';
import { DEFAULT_CONNECT_CERT_MESSAGE, WalletSources } from '../constants';
import { certificate } from '@vechain/sdk-core';
import { ethers } from 'ethers';
import { SignTypedDataOptions } from '../types/types';

class WalletManager {
public readonly state: WalletManagerState;
Expand Down Expand Up @@ -194,6 +196,17 @@ class WalletManager {
throw e;
});

signTypedData = (
domain: ethers.TypedDataDomain,
types: Record<string, ethers.TypedDataField[]>,
value: Record<string, unknown>,
options?: SignTypedDataOptions,
): Promise<string> =>
this.wallet.signTypedData(domain, types, value, options).catch((e) => {
DAppKitLogger.error('WalletManager', 'signTypedData', e);
throw e;
});

setSource = (src: WalletSource): void => {
if (this.state.source === src) {
return;
Expand Down
10 changes: 10 additions & 0 deletions packages/dapp-kit/src/classes/wc-wallet.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { ethers } from 'ethers';
import type { ConnectResponse, ConnexWallet, WCSigner } from '../types';
import { SignTypedDataOptions } from '../types/types';

class WCWallet implements ConnexWallet {
constructor(private readonly signer: WCSigner) {}
Expand All @@ -23,6 +25,14 @@ class WCWallet implements ConnexWallet {
options: Connex.Signer.TxOptions,
): Promise<Connex.Vendor.TxResponse> => this.signer.signTx(msg, options);

signTypedData = async (
_domain: ethers.TypedDataDomain,
_types: Record<string, ethers.TypedDataField[]>,
_value: Record<string, unknown>,
_options?: SignTypedDataOptions,
): Promise<string> =>
this.signer.signTypedData(_domain, _types, _value, _options);

disconnect = (): Promise<void> => this.signer.disconnect();
}

Expand Down
1 change: 1 addition & 0 deletions packages/dapp-kit/src/constants/wallet-connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
export enum DefaultMethods {
RequestTransaction = 'thor_sendTransaction',
SignCertificate = 'thor_signCertificate',
SignTypedData = 'thor_signTypedData',
}
20 changes: 18 additions & 2 deletions packages/dapp-kit/src/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,22 @@ import type { LogLevel } from '../utils';
declare global {
interface Window {
vechain?: {
newConnexSigner: (genesisId: string) => Connex.Signer;
newConnexSigner: (genesisId: string) => ExpandedConnexSigner;
isInAppBrowser?: boolean;
};
connex?: unknown;
}
}

interface ExpandedConnexSigner extends Connex.Signer {
signTypedData: (
_domain: ethers.TypedDataDomain,
_types: Record<string, ethers.TypedDataField[]>,
_value: Record<string, unknown>,
_options?: SignTypedDataOptions,
) => Promise<string>;
}

type WalletSource = 'wallet-connect' | 'veworld' | 'sync2' | 'sync';

interface WalletConfig {
Expand Down Expand Up @@ -44,7 +53,7 @@ interface DAppKitOptions {
};
}

type BaseWallet = Connex.Signer & {
type BaseWallet = ExpandedConnexSigner & {
disconnect?: () => Promise<void> | void;
};

Expand All @@ -68,6 +77,10 @@ interface WalletManagerState {
connectionCertificate: Certificate | null;
}

interface SignTypedDataOptions {
signer?: string;
}

export type {
BaseWallet,
DAppKitOptions,
Expand All @@ -77,4 +90,7 @@ export type {
WalletManagerState,
ConnectResponse,
Genesis,
SignTypedDataOptions,
ExpandedConnexSigner,
DriverSignedTypedData,
};
3 changes: 2 additions & 1 deletion packages/dapp-kit/src/types/wc-types.d.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import type { SignClientTypes } from '@walletconnect/types';
import type { SignClient } from '@walletconnect/sign-client';
import { ExpandedConnexSigner } from './types';

export type ResolvedSignClient = Awaited<ReturnType<typeof SignClient.init>>;

/**
* WCSigner is a {@link Connex.Signer} with an additional disconnect method
*
*/
export type WCSigner = Connex.Signer & {
export type WCSigner = ExpandedConnexSigner & {
/**
* Disconnects and cleans up the WalletConnect session
*/
Expand Down
8 changes: 7 additions & 1 deletion packages/dapp-kit/src/utils/convert-vendor-to-signer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { ExpandedConnexSigner } from '../types/types';
import { DAppKitLogger } from './logger';

export const convertVendorToSigner = (vendor: Connex.Vendor): Connex.Signer => {
export const convertVendorToSigner = (
vendor: Connex.Vendor,
): ExpandedConnexSigner => {
return {
signTx: (msg, options): Promise<Connex.Vendor.TxResponse> => {
const service = vendor.sign('tx', msg);
Expand Down Expand Up @@ -66,5 +69,8 @@ export const convertVendorToSigner = (vendor: Connex.Vendor): Connex.Signer => {

return service.request();
},
signTypedData(_domain, _types, _value, _options) {
throw new Error('Sign typed data it is not available with sync2');
},
};
};
15 changes: 15 additions & 0 deletions packages/dapp-kit/src/utils/create-wc-signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import type { SignClient } from '@walletconnect/sign-client/dist/types/client';
import type { WCSigner, WCSignerOptions } from '../types';
import { DefaultMethods } from '../constants';
import { DAppKitLogger } from './logger';
import { ethers } from 'ethers';
import { SignTypedDataOptions } from '../types/types';

interface SessionAccount {
networkIdentifier: string;
Expand Down Expand Up @@ -208,6 +210,18 @@ export const createWcSigner = ({
});
};

const signTypedData = async (
domain: ethers.TypedDataDomain,
types: Record<string, ethers.TypedDataField[]>,
value: Record<string, unknown>,
options?: SignTypedDataOptions,
): Promise<string> => {
return makeRequest<string>({
method: DefaultMethods.SignTypedData,
params: [{ domain, types, value, options }],
});
};

const disconnect = async (): Promise<void> => {
if (!session) return;

Expand Down Expand Up @@ -252,6 +266,7 @@ export const createWcSigner = ({
return {
signTx,
signCert,
signTypedData,
disconnect,
genesisId,
connect: connectAccount,
Expand Down
3 changes: 2 additions & 1 deletion packages/dapp-kit/test/create-wallet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
WalletConnectOptions,
WalletSource,
} from '../src';
import { ExpandedConnexSigner } from '../src/types/types';

type ICreateWallet = DAppKitOptions & {
source: WalletSource;
Expand Down Expand Up @@ -56,7 +57,7 @@ describe('createWallet', () => {

it('is installed', () => {
window.vechain = {
newConnexSigner: () => ({} as Connex.Signer),
newConnexSigner: () => ({} as ExpandedConnexSigner),
};

const wallet = createWallet(createOptions('veworld'));
Expand Down
38 changes: 38 additions & 0 deletions packages/dapp-kit/test/fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const domain = {
name: 'Ether Mail',
version: '1',
chainId: 1,
verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
};

// The named list of all type definitions
const types = {
Person: [
{ name: 'name', type: 'string' },
{ name: 'wallet', type: 'address' },
],
Mail: [
{ name: 'from', type: 'Person' },
{ name: 'to', type: 'Person' },
{ name: 'contents', type: 'string' },
],
};

// The data to sign
const value = {
from: {
name: 'Cow',
wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
},
to: {
name: 'Bob',
wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
},
contents: 'Hello, Bob!',
};

export const typedData = {
domain,
types,
value,
};
10 changes: 9 additions & 1 deletion packages/dapp-kit/test/helpers/mocked-sign-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const defaultMockConnectHandler = (): ReturnType<IEngine['connect']> => {

const defaultMockRequestHandler = (
params: EngineTypes.RequestParams,
): Promise<Connex.Vendor.CertResponse | Connex.Vendor.TxResponse> => {
): Promise<Connex.Vendor.CertResponse | Connex.Vendor.TxResponse | string> => {
if (params.request.method === DefaultMethods.RequestTransaction) {
return Promise.resolve({
txid: '0x123',
Expand All @@ -38,6 +38,14 @@ const defaultMockRequestHandler = (
params.request.params[0].options,
),
);
} else if (params.request.method === DefaultMethods.SignTypedData) {
return Promise.resolve(
mockedConnexSigner.signTypedData(
params.request.params[0].domain,
params.request.params[0].types,
params.request.params[0].value,
),
);
}
throw new Error('Invalid method');
};
Expand Down
7 changes: 6 additions & 1 deletion packages/dapp-kit/test/helpers/mocked-signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Hex0x,
secp256k1,
} from '@vechain/sdk-core';
import { ExpandedConnexSigner } from '../../src/types/types';

const mnemonicWords =
'denial kitchen pet squirrel other broom bar gas better priority spoil cross';
Expand All @@ -19,7 +20,7 @@ const firstAccount = hdNode.deriveChild(0);
const privateKey = firstAccount.privateKey!;
const address = addressUtils.fromPrivateKey(privateKey);

const mockedConnexSigner: Connex.Signer = {
const mockedConnexSigner: ExpandedConnexSigner = {
signTx() {
return Promise.resolve({ txid: '0x1234', signer: address });
},
Expand Down Expand Up @@ -51,6 +52,10 @@ const mockedConnexSigner: Connex.Signer = {
signature: Hex0x.of(signatureCore),
});
},

signTypedData() {
return Promise.resolve('0x1234');
},
};

export { mockedConnexSigner, hdNode, mnemonicWords, privateKey, address };
13 changes: 13 additions & 0 deletions packages/dapp-kit/test/utils/signer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { createWcClient, createWcSigner } from '../../src';
import { mockedSignClient } from '../helpers/mocked-sign-client';
import { normalizeGenesisId } from '../../src';
import { address } from '../helpers/mocked-signer';
import { typedData } from '../fixture';

vi.spyOn(SignClient, 'init').mockResolvedValue(mockedSignClient);

Expand Down Expand Up @@ -66,6 +67,18 @@ describe('createWcSigner', () => {
expect(certRes).toBeDefined();
});

it('can sign typed data', async () => {
const signer = createNewSignClient();

const signedData = await signer.signTypedData(
typedData.domain,
typedData.types,
typedData.value,
);

expect(signedData).toBeDefined();
});

it('can disconnect', async () => {
const signer = createNewSignClient();

Expand Down
Loading

0 comments on commit acde52d

Please sign in to comment.