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

Adding signed typed data #286

Merged
merged 6 commits into from
Oct 9, 2024
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
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 @@ -10,7 +10,7 @@
import { DAppKitUI } from '@vechain/dapp-kit-ui';
import { subscribeKey } from 'valtio/vanilla/utils';
import type { DAppKitProviderOptions, DAppKitContext } from './types';
import { type Certificate } from '@vechain/sdk-core';

Check warning on line 13 in packages/dapp-kit-react/src/DAppKitProvider.tsx

View workflow job for this annotation

GitHub Actions / Lint, Build & Test

`@vechain/sdk-core` import should occur before type import of `./types`

/**
* Context
Expand Down Expand Up @@ -125,6 +125,7 @@
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
Loading