diff --git a/packages/transaction-request-intent/src/lib/decoders/decode.ts b/packages/transaction-request-intent/src/lib/decoders/decode.ts index fd465047e..9fce92817 100644 --- a/packages/transaction-request-intent/src/lib/decoders/decode.ts +++ b/packages/transaction-request-intent/src/lib/decoders/decode.ts @@ -156,7 +156,7 @@ const decode = ({ input, config = defaultConfig }: { input: DecodeInput; config? payload: input.raw.rawData } default: - throw new Error('Invalid input type') + throw new DecoderError({ message: 'Invalid input type', status: 400 }) } } diff --git a/packages/transaction-request-intent/src/lib/decoders/transaction/interaction/ApproveAllowanceDecoder.ts b/packages/transaction-request-intent/src/lib/decoders/transaction/interaction/ApproveAllowanceDecoder.ts index 482b0454e..aa58dd9fe 100644 --- a/packages/transaction-request-intent/src/lib/decoders/transaction/interaction/ApproveAllowanceDecoder.ts +++ b/packages/transaction-request-intent/src/lib/decoders/transaction/interaction/ApproveAllowanceDecoder.ts @@ -1,4 +1,5 @@ import { ContractCallInput, Intents } from '../../../domain' +import { DecoderError } from '../../../error' import { ApproveAllowanceParams } from '../../../extraction/types' import { ApproveTokenAllowance } from '../../../intent.types' import { MethodsMapping } from '../../../supported-methods' @@ -13,12 +14,12 @@ export const decodeApproveTokenAllowance = ( const { from, to, chainId, data, methodId } = input if (!isSupportedMethodId(methodId)) { - throw new Error('Unsupported methodId') + throw new DecoderError({ message: 'Unsupported methodId', status: 400 }) } const params = extract(supportedMethods, data, methodId) as ApproveAllowanceParams if (!params) { - throw new Error('Params do not match ERC20 transfer methodId') + throw new DecoderError({ message: 'Params do not match ERC20 transfer methodId', status: 400 }) } const { amount, spender } = params diff --git a/packages/transaction-request-intent/src/lib/decoders/transaction/interaction/Erc1155TransferDecoder.ts b/packages/transaction-request-intent/src/lib/decoders/transaction/interaction/Erc1155TransferDecoder.ts index b9f0f06ec..847ec34a7 100644 --- a/packages/transaction-request-intent/src/lib/decoders/transaction/interaction/Erc1155TransferDecoder.ts +++ b/packages/transaction-request-intent/src/lib/decoders/transaction/interaction/Erc1155TransferDecoder.ts @@ -1,6 +1,7 @@ import { AssetType, Hex, toAccountId, toAssetId } from '@narval/authz-shared' import { Address } from 'viem' import { ContractCallInput, Intents } from '../../../domain' +import { DecoderError } from '../../../error' import { Erc1155SafeTransferFromParams, SafeBatchTransferFromParams } from '../../../extraction/types' import { ERC1155Transfer, TransferErc1155 } from '../../../intent.types' import { MethodsMapping, SupportedMethodId } from '../../../supported-methods' @@ -10,7 +11,7 @@ import { extract } from '../../utils' export const decodeERC1155Transfer = (input: ContractCallInput, supportedMethods: MethodsMapping): TransferErc1155 => { const { to: contract, from, data, chainId, methodId } = input if (!isSupportedMethodId(methodId)) { - throw new Error('Unsupported methodId') + throw new DecoderError({ message: 'Unsupported methodId', status: 400 }) } const params = extract(supportedMethods, data, methodId) @@ -31,8 +32,7 @@ export const decodeERC1155Transfer = (input: ContractCallInput, supportedMethods return constructTransferErc1155Intent({ to, from, contract, transfers, chainId }) } - - throw new Error('Params do not match ERC1155 transfer methodId') + throw new DecoderError({ message: 'Params do not match ERC1155 transfer methodId', status: 400 }) } function createERC1155Transfer({ diff --git a/packages/transaction-request-intent/src/lib/decoders/transaction/interaction/Erc20TransferDecoder.ts b/packages/transaction-request-intent/src/lib/decoders/transaction/interaction/Erc20TransferDecoder.ts index 5d341f643..9abb345d6 100644 --- a/packages/transaction-request-intent/src/lib/decoders/transaction/interaction/Erc20TransferDecoder.ts +++ b/packages/transaction-request-intent/src/lib/decoders/transaction/interaction/Erc20TransferDecoder.ts @@ -1,4 +1,5 @@ import { ContractCallInput, Intents } from '../../../domain' +import { DecoderError } from '../../../error' import { TransferParams } from '../../../extraction/types' import { TransferErc20 } from '../../../intent.types' import { MethodsMapping } from '../../../supported-methods' @@ -9,7 +10,7 @@ import { extract } from '../../utils' export const decodeErc20Transfer = (input: ContractCallInput, supportedMethods: MethodsMapping): TransferErc20 => { const { from, to, chainId, data, methodId } = input if (!isSupportedMethodId(methodId)) { - throw new Error('Unsupported methodId') + throw new DecoderError({ message: 'Unsupported methodId', status: 400 }) } const params = extract(supportedMethods, data, methodId) as TransferParams diff --git a/packages/transaction-request-intent/src/lib/decoders/transaction/interaction/Erc721TransferDecoder.ts b/packages/transaction-request-intent/src/lib/decoders/transaction/interaction/Erc721TransferDecoder.ts index fb1f62ed9..fa43c756b 100644 --- a/packages/transaction-request-intent/src/lib/decoders/transaction/interaction/Erc721TransferDecoder.ts +++ b/packages/transaction-request-intent/src/lib/decoders/transaction/interaction/Erc721TransferDecoder.ts @@ -1,5 +1,6 @@ import { AssetType } from '@narval/authz-shared' import { ContractCallInput, Intents } from '../../../domain' +import { DecoderError } from '../../../error' import { Erc721SafeTransferFromParams } from '../../../extraction/types' import { TransferErc721 } from '../../../intent.types' import { MethodsMapping } from '../../../supported-methods' @@ -10,7 +11,7 @@ import { extract } from '../../utils' export const decodeErc721Transfer = (input: ContractCallInput, supportedMethods: MethodsMapping): TransferErc721 => { const { to: contract, from, chainId, data, methodId } = input if (!isSupportedMethodId(methodId)) { - throw new Error('Unsupported methodId') + throw new DecoderError({ message: 'Unsupported methodId', status: 400 }) } const params = extract(supportedMethods, data, methodId) as Erc721SafeTransferFromParams diff --git a/packages/transaction-request-intent/src/lib/decoders/transaction/interaction/UserOperationDecoder.ts b/packages/transaction-request-intent/src/lib/decoders/transaction/interaction/UserOperationDecoder.ts index e44419050..eecdd6e39 100644 --- a/packages/transaction-request-intent/src/lib/decoders/transaction/interaction/UserOperationDecoder.ts +++ b/packages/transaction-request-intent/src/lib/decoders/transaction/interaction/UserOperationDecoder.ts @@ -1,6 +1,7 @@ import { Address } from '@narval/authz-shared' import { Hex, toHex } from 'viem' import { ContractCallInput, InputType, Intents } from '../../../domain' +import { DecoderError } from '../../../error' import { ExecuteAndRevertParams, ExecuteParams, HandleOpsParams } from '../../../extraction/types' import { Intent, UserOperation } from '../../../intent.types' import { MethodsMapping, SupportedMethodId } from '../../../supported-methods' @@ -69,7 +70,7 @@ const decodeExecuteAndRevert = ( export const decodeUserOperation = (input: ContractCallInput, supportedMethods: MethodsMapping): UserOperation => { const { from, chainId, data, to, methodId } = input if (!isSupportedMethodId(methodId)) { - throw new Error('Unsupported methodId') + throw new DecoderError({ message: 'Unsupported methodId', status: 400 }) } const params = extract(supportedMethods, data, methodId) as HandleOpsParams diff --git a/packages/transaction-request-intent/src/lib/decoders/utils.ts b/packages/transaction-request-intent/src/lib/decoders/utils.ts index 3350914f6..200cb7eff 100644 --- a/packages/transaction-request-intent/src/lib/decoders/utils.ts +++ b/packages/transaction-request-intent/src/lib/decoders/utils.ts @@ -1,10 +1,13 @@ import { Hex, decodeAbiParameters } from 'viem' +import { DecoderError } from '../error' import { ExtractedParams } from '../extraction/types' import { MethodsMapping, SupportedMethodId } from '../supported-methods' export const getMethod = (supportedMethods: MethodsMapping, methodId: SupportedMethodId) => { const method = supportedMethods[methodId] - if (!method) throw new Error('Unsupported methodId') + if (!method) { + throw new DecoderError({ message: 'Unsupported methodId', status: 400 }) + } return method } @@ -14,6 +17,6 @@ export const extract = (supportedMethods: MethodsMapping, data: Hex, methodId: S const params = decodeAbiParameters(method.abi, data) return method.transformer(params) } catch (error) { - throw new Error(`Failed to decode abi parameters: ${error}`) + throw new DecoderError({ message: 'Failed to decode abi parameters', status: 400, context: { error } }) } } diff --git a/packages/transaction-request-intent/src/lib/extraction/transformers.ts b/packages/transaction-request-intent/src/lib/extraction/transformers.ts index c5c647858..9ba089c13 100644 --- a/packages/transaction-request-intent/src/lib/extraction/transformers.ts +++ b/packages/transaction-request-intent/src/lib/extraction/transformers.ts @@ -1,3 +1,4 @@ +import { DecoderError } from '../error' import { assertAddress, assertArray, assertBigInt, assertHexString, assertLowerHexString } from '../typeguards' import { ApproveAllowanceParams, @@ -83,7 +84,7 @@ export const ExecuteAndRevertParamsTransform = (params: unknown[]): ExecuteAndRe export const transformUserOperation = (op: unknown[]): UserOp => { if (typeof op !== 'object' || op === null) { - throw new Error('UserOperation is not an object') + throw new DecoderError({ message: 'UserOperation is not an object', status: 400 }) } return { @@ -103,7 +104,7 @@ export const transformUserOperation = (op: unknown[]): UserOp => { export const HandleOpsParamsTransform = (params: unknown[]): HandleOpsParams => { if (!Array.isArray(params[0]) || typeof params[1] !== 'string') { - throw new Error('Invalid input format') + throw new DecoderError({ message: 'Invalid input format', status: 400 }) } return { userOps: params[0].map(transformUserOperation), diff --git a/packages/transaction-request-intent/src/lib/typeguards.ts b/packages/transaction-request-intent/src/lib/typeguards.ts index 412e38460..bc1b28752 100644 --- a/packages/transaction-request-intent/src/lib/typeguards.ts +++ b/packages/transaction-request-intent/src/lib/typeguards.ts @@ -2,6 +2,7 @@ import { Address, AssetType, Hex } from '@narval/authz-shared' // eslint-disable-next-line no-restricted-imports import { isAddress } from 'viem' import { AssetTypeAndUnknown, Misc, Permit2Message, PermitMessage } from './domain' +import { DecoderError } from './error' import { SupportedMethodId } from './supported-methods' export const isString = (value: unknown): value is string => { @@ -16,7 +17,7 @@ export const assertBigInt = (value: unknown): bigint => { if (isBigInt(value)) { return value } - throw new Error('Value is not a bigint') + throw new DecoderError({ message: 'Value is not a bigint', status: 400 }) } export function isHexString(value: unknown): value is Hex { @@ -27,7 +28,7 @@ export const assertHexString = (value: unknown): Hex => { if (isHexString(value)) { return value } - throw new Error('Value is not a hex string') + throw new DecoderError({ message: 'Value is not a hex string', status: 400 }) } export const assertLowerHexString = (value: unknown): Hex => { @@ -38,7 +39,7 @@ export function assertString(value: unknown): string { if (isString(value)) { return value } - throw new Error('Value is not a string') + throw new DecoderError({ message: 'Value is not a string', status: 400 }) } // Checks if a value is a number @@ -50,7 +51,7 @@ export const assertNumber = (value: unknown): number => { if (isNumber(value)) { return value } - throw new Error('Value is not a number') + throw new DecoderError({ message: 'Value is not a number', status: 400 }) } // Checks if a value is a boolean @@ -62,7 +63,7 @@ export const assertBoolean = (value: unknown): boolean => { if (isBoolean(value)) { return value } - throw new Error('Value is not a boolean') + throw new DecoderError({ message: 'Value is not a boolean', status: 400 }) } // Checks if a value is an array @@ -76,7 +77,7 @@ export const isSupportedMethodId = (value: Hex): value is SupportedMethodId => { export const assertAddress = (value: unknown): Address => { if (!isString(value) || !isAddress(value)) { - throw new Error('Value is not an address') + throw new DecoderError({ message: 'Value is not an address', status: 400 }) } return value.toLowerCase() as Address } @@ -92,7 +93,7 @@ export const isAssetType = (value: unknown): value is AssetTypeAndUnknown => { export const assertArray = (value: unknown, type: AssertType): T[] => { if (!Array.isArray(value)) { - throw new Error('Value is not an array') + throw new DecoderError({ message: 'Value is not an array', status: 400 }) } switch (type) { case 'string': { diff --git a/packages/transaction-request-intent/src/lib/utils.ts b/packages/transaction-request-intent/src/lib/utils.ts index 1e7cd1bd6..8f714942e 100644 --- a/packages/transaction-request-intent/src/lib/utils.ts +++ b/packages/transaction-request-intent/src/lib/utils.ts @@ -63,7 +63,7 @@ export const buildContractRegistryEntry = ({ }): { [key: AccountId]: AssetTypeAndUnknown } => { const registry: { [key: AccountId]: AssetTypeAndUnknown } = {} if (!isAddress(contractAddress) || !isAssetType(assetType)) { - throw new Error('Invalid contract registry entry') + throw new DecoderError({ message: 'Invalid contract registry entry', status: 400 }) } const key = buildContractKey(chainId, contractAddress as Address) registry[key] = assetType @@ -79,7 +79,11 @@ export const buildContractRegistry = (input: ContractRegistryInput): ContractReg } if (isString(contract)) { if (!isAccountId(contract)) { - throw new Error(`Contract registry key is not a valid Caip10: ${contract}`) + throw new DecoderError({ + message: 'Contract registry key is not a valid Caip10', + status: 400, + context: { contract, input } + }) } registry.set(contract.toLowerCase(), information) } else { @@ -99,10 +103,18 @@ export const buildContractKey = ( export const checkContractRegistry = (registry: Record) => { Object.keys(registry).forEach((key) => { if (!isAccountId(key)) { - throw new Error(`Invalid contract registry key: ${key}: ${registry[key]}`) + throw new DecoderError({ + message: 'Invalid contract registry key', + status: 400, + context: { key, value: registry[key] } + }) } if (!isAssetType(registry[key])) { - throw new Error(`Invalid contract registry value: ${key}: ${registry[key]}`) + throw new DecoderError({ + message: 'Invalid contract registry value', + status: 400, + context: { key, value: registry[key] } + }) } }) return true @@ -122,7 +134,9 @@ export const contractTypeLookup = ( } export const buildTransactionKey = (txRequest: TransactionRequest): TransactionKey => { - if (!txRequest.nonce) throw new Error('nonce needed to build transaction key') + if (!txRequest.nonce) { + throw new DecoderError({ message: 'nonce needed to build transaction key', status: 400 }) + } const account = toAccountId({ chainId: txRequest.chainId, address: txRequest.from, @@ -152,7 +166,7 @@ export const decodeTypedData = (typedData: TypedData): SignTypedData => ({ export const decodeMessage = (message: MessageInput): SignMessage => { if (!message.payload.startsWith(presignMessagePrefix)) { - throw new Error('Invalid message prefix') + throw new DecoderError({ message: 'Invalid message prefix', status: 400 }) } return { type: Intents.SIGN_MESSAGE, @@ -316,6 +330,8 @@ export const nativeCaip19 = (chainId: number): AssetId => { export const getMethod = (methodId: SupportedMethodId, supportedMethods: MethodsMapping) => { const method = supportedMethods[methodId] - if (!method) throw new Error('Unsupported methodId') + if (!method) { + throw new DecoderError({ message: 'Unsupported methodId', status: 400 }) + } return method }