From 87c85b03ee74caa5152b5efc961c785a7fd2b50b Mon Sep 17 00:00:00 2001 From: Pierre Troger Date: Mon, 29 Jan 2024 11:17:36 -0400 Subject: [PATCH] TypedData supported and tested --- .../src/lib/__test__/unit/decoders.spec.ts | 98 ++++++++++++++++--- .../src/lib/decoders/Decoder.ts | 19 +++- .../src/lib/decoders/DecoderStrategy.ts | 1 - .../src/lib/domain.ts | 15 ++- .../src/lib/intent.types.ts | 6 +- .../src/lib/supported-methods.ts | 38 +++++++ .../src/lib/utils.ts | 24 +++++ 7 files changed, 177 insertions(+), 24 deletions(-) diff --git a/packages/transaction-request-intent/src/lib/__test__/unit/decoders.spec.ts b/packages/transaction-request-intent/src/lib/__test__/unit/decoders.spec.ts index 96931968c..eddbe6e7c 100644 --- a/packages/transaction-request-intent/src/lib/__test__/unit/decoders.spec.ts +++ b/packages/transaction-request-intent/src/lib/__test__/unit/decoders.spec.ts @@ -239,18 +239,88 @@ describe('decode', () => { }) }) }) - // describe('message and typed data input', () => { - // it('decodes message', () => { - // }); - // it('decodes typed data', () => { - // }); - // it('decodes raw message', () => { - // }); - // it('decodes permit', () => { - // }); - // it('decodes permit2', () => { - // }); - // it('defaults to raw payload', () => { - // }); - // }); + describe('message and typed data input', () => { + it('decodes message', () => {}) + it('decodes typed data', () => { + const decoded = decoder.decode({ + type: InputType.TYPED_DATA, + typedData: { + chainId: 137, + from: '0xEd123cf8e3bA51c6C15DA1eAc74B2b5DEEA31448', + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' } + ], + DoStuff: [ + { name: 'do', type: 'function' }, + { name: 'stuff', type: 'address' } + ] + }, + primaryType: 'DoStuff', + domain: { + name: 'Unicorn Milk Token', + version: '0.1.0', + chainId: 137, + verifyingContract: '0x64060aB139Feaae7f06Ca4E63189D86aDEb51691' + }, + message: { + do: 'doingStuff(address stuff)', + stuff: '0x1234567890123456789012345678901234567890' + } + } + }) + expect(decoded).toEqual({ + from: 'eip155:137/0xed123cf8e3ba51c6c15da1eac74b2b5deea31448', + type: Intents.SIGN_TYPED_DATA, + domain: { + version: '0.1.0', + name: 'Unicorn Milk Token', + chainId: 137, + verifyingContract: '0x64060aB139Feaae7f06Ca4E63189D86aDEb51691' + } + }) + }) + it('decodes raw message', () => {}) + it('decodes permit', () => { + // const permit = { + // } + }) + it('decodes permit2', () => { + // const permit2 = { + // types: { + // EIP712Domain: [ + // {name: 'name', 'type': 'string'}, + // {name: 'version', 'type': 'string'}, + // {name: 'chainId', 'type': 'uint256'}, + // {name: 'verifyingContract', 'type': 'address'} + // ], + // Permit2: [ + // {name: 'holder', 'type': 'address'}, + // {name: 'spender', 'type': 'address'}, + // {name: 'nonce', 'type': 'uint256'}, + // {name: 'expiry', 'type': 'uint256'}, + // {name: 'value', 'type': 'uint256'} + // ] + // }, + // primaryType: 'Permit2', + // domain: { + // name: 'UNI Token', + // version: '1', + // chainId: 1, + // verifyingContract: '0x000000000022D473030F116dDEE9F6B43aC78BA3' + // }, + // message: { + // holder: '0xAliceAddress', + // spender: '0xSpenderAddress', + // nonce: 0, + // expiry: 1714521600, // Example UNIX timestamp (e.g., 1st May 2022) + // value: 10000000000000000000 // Example value in wei + // } + // } + }) + it('defaults to raw payload', () => {}) + }) }) diff --git a/packages/transaction-request-intent/src/lib/decoders/Decoder.ts b/packages/transaction-request-intent/src/lib/decoders/Decoder.ts index bdd4a8a51..2b3cf1758 100644 --- a/packages/transaction-request-intent/src/lib/decoders/Decoder.ts +++ b/packages/transaction-request-intent/src/lib/decoders/Decoder.ts @@ -8,12 +8,13 @@ import { TransactionCategory, TransactionInput, TransactionRegistry, - TransactionStatus + TransactionStatus, + TypedDataInput } from '../domain' import { TransactionRequestIntentError } from '../error' -import { Intent } from '../intent.types' +import { Intent, TypedDataIntent } from '../intent.types' import { isSupportedMethodId } from '../typeguards' -import { getCategory, getMethodId, getTransactionIntentType, transactionLookup } from '../utils' +import { decodeTypedData, getCategory, getMethodId, getTransactionIntentType, transactionLookup } from '../utils' import { validateContractDeploymentInput, validateContractInteractionInput, @@ -106,6 +107,15 @@ export default class Decoder { }) } + decodeTypedData(input: TypedDataInput): TypedDataIntent { + const { typedData } = input + const { primaryType } = typedData + switch (primaryType) { + default: + return decodeTypedData(typedData) + } + } + public decode(input: DecodeInput): Intent { switch (input.type) { case InputType.TRANSACTION_REQUEST: { @@ -113,6 +123,9 @@ export default class Decoder { const decoded = strategy.decode() return this.#wrapTransactionManagementIntents(decoded, input) } + case InputType.TYPED_DATA: { + return this.decodeTypedData(input) + } default: throw new Error('Invalid input type') } diff --git a/packages/transaction-request-intent/src/lib/decoders/DecoderStrategy.ts b/packages/transaction-request-intent/src/lib/decoders/DecoderStrategy.ts index 27e430af9..c39ca8a05 100644 --- a/packages/transaction-request-intent/src/lib/decoders/DecoderStrategy.ts +++ b/packages/transaction-request-intent/src/lib/decoders/DecoderStrategy.ts @@ -21,7 +21,6 @@ export default abstract class DecoderStrategy { const method = this.getMethod(methodId) try { const params = decodeAbiParameters(method.abi, data) - console.log('params', params) return method.transformer(params) } catch (error) { throw new Error(`Failed to decode abi parameters: ${error}`) diff --git a/packages/transaction-request-intent/src/lib/domain.ts b/packages/transaction-request-intent/src/lib/domain.ts index 57b04f576..4673c9b2a 100644 --- a/packages/transaction-request-intent/src/lib/domain.ts +++ b/packages/transaction-request-intent/src/lib/domain.ts @@ -1,5 +1,5 @@ import { AccountId, Address, Alg, AssetType, Hex, TransactionRequest } from '@narval/authz-shared' -import { TypedDataDomain, TypedData as TypedDataParams } from 'viem' +import { TypedData as TypedDataParams } from 'viem' import { Intent } from './intent.types' export type Message = { @@ -8,17 +8,24 @@ export type Message = { from: Address } +export type Eip712Domain = { + version: string + chainId: number + name: string + verifyingContract: Address +} + export type Raw = { rawData: string algorithm: Alg } export type TypedData = { - chainId: string from: Address + chainId: number types: TypedDataParams primaryType: string - domain: TypedDataDomain + domain: Eip712Domain message: Record } @@ -167,5 +174,5 @@ export enum Slip44SupportedAddresses { ETH = 60, MATIC = 966 } -export const permit2Address = '0x000000000022d473030f116ddee9f6b43ac78ba3' +export const PERMIT2_ADDRESS = '0x000000000022d473030f116ddee9f6b43ac78ba3' export const NULL_METHOD_ID = '0x00000000' diff --git a/packages/transaction-request-intent/src/lib/intent.types.ts b/packages/transaction-request-intent/src/lib/intent.types.ts index 3f70fd8e1..4cccd61b5 100644 --- a/packages/transaction-request-intent/src/lib/intent.types.ts +++ b/packages/transaction-request-intent/src/lib/intent.types.ts @@ -1,5 +1,5 @@ import { AccountId, Alg, AssetId, Hex } from '@narval/authz-shared' -import { Address, TypedData } from 'viem' +import { Address } from 'viem' import { Intents } from './domain' export type TransferNative = { @@ -69,7 +69,7 @@ export type SignRawPayload = { export type SignTypedData = { type: Intents.SIGN_TYPED_DATA from: AccountId - typedData: TypedData + domain: Eip712Domain } export type DeployContract = { @@ -131,6 +131,8 @@ export type UserOperation = { beneficiary: Address } +export type TypedDataIntent = SignTypedData | Permit | Permit2 + export type Intent = | TransferNative | TransferErc20 diff --git a/packages/transaction-request-intent/src/lib/supported-methods.ts b/packages/transaction-request-intent/src/lib/supported-methods.ts index 80ffca33c..b826f1317 100644 --- a/packages/transaction-request-intent/src/lib/supported-methods.ts +++ b/packages/transaction-request-intent/src/lib/supported-methods.ts @@ -315,3 +315,41 @@ export const SUPPORTED_METHODS: MethodsMapping = { intent: Intents.TRANSFER_NATIVE } } + +export const permitTypedData = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' } + ], + Permit: [ + { name: 'owner', type: 'address' }, + { name: 'spender', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint256' } + ] + }, + primaryType: 'Permit' as const +} + +export const permit2TypedData = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' } + ], + Permit: [ + { name: 'owner', type: 'address' }, + { name: 'spender', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint256' } + ] + }, + primaryType: 'Permit' as const +} diff --git a/packages/transaction-request-intent/src/lib/utils.ts b/packages/transaction-request-intent/src/lib/utils.ts index cf039a2ac..c22f2dee1 100644 --- a/packages/transaction-request-intent/src/lib/utils.ts +++ b/packages/transaction-request-intent/src/lib/utils.ts @@ -27,8 +27,10 @@ import { TransactionKey, TransactionRegistry, TransactionStatus, + TypedData, WalletType } from './domain' +import { SignTypedData } from './intent.types' import { SUPPORTED_METHODS, SupportedMethodId } from './supported-methods' import { assertLowerHexString, isAssetType, isString, isSupportedMethodId } from './typeguards' @@ -137,6 +139,28 @@ export const buildTransactionRegistry = (input: TransactionRegistryInput): Trans return registry } +export const decodeTypedData = (typedData: TypedData): SignTypedData => ({ + type: Intents.SIGN_TYPED_DATA, + domain: typedData.domain, + from: toAccountId({ + chainId: typedData.domain.chainId, + address: typedData.from.toLowerCase() as Address + }) +}) + +export const decodePermit = (typedData: TypedData): SignTypedData => { + const { domain } = typedData + // const { spender, value, nonce, deadline } = message + return { + type: Intents.SIGN_TYPED_DATA, + domain, + from: toAccountId({ + chainId: domain.chainId, + address: typedData.from + }) + } +} + export const transactionLookup = ( txRequest: TransactionRequest, transactionRegistry?: TransactionRegistry