Skip to content

Commit

Permalink
added error handling, safe and unsafe flows
Browse files Browse the repository at this point in the history
  • Loading branch information
Ptroger committed Jan 12, 2024
1 parent e347505 commit cdcb0e0
Show file tree
Hide file tree
Showing 14 changed files with 184 additions and 50 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { IntentRequest } from '../../../shared/types'
import { AssetTypeEnum, Intents } from '../../../utils/domain'
import { Erc20Methods } from '../../../utils/standard-functions/methodId'
import { decodeIntent } from '../../decoders'
import { AssetTypeEnum, Intents } from '../../domain'
import { Erc20Methods } from '../../methodId'
import { IntentRequest } from '../../types'

jest.mock('viem', () => ({
decodeAbiParameters: jest
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { extractErc20TransferAmount } from '../../../utils/standard-functions/param-extractors'
import { extractErc20TransferAmount } from '../../param-extractors'

jest.mock('viem', () => ({
decodeAbiParameters: jest
Expand Down
19 changes: 13 additions & 6 deletions packages/transaction-request-intent/src/lib/decoders.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { IntentRequest } from '../shared/types'
import { encodeEoaAccountId, encodeEoaAssetId } from '../utils/caip'
import { AssetTypeEnum, EipStandardEnum, Intents } from '../utils/domain'
import { Intent, TransferErc20, TransferErc721 } from '../utils/intent.types'
import { extractErc20Amount, extractErc721AssetId } from '../utils/standard-functions/param-extractors'
import { encodeEoaAccountId, encodeEoaAssetId } from './caip'
import { AssetTypeEnum, EipStandardEnum, Intents } from './domain'
import { TransactionRequestIntentError } from './error'
import { Intent, TransferErc20, TransferErc721 } from './intent.types'
import { extractErc20Amount, extractErc721AssetId } from './param-extractors'
import { IntentRequest } from './types'

export const decodeErc721 = ({
data,
Expand Down Expand Up @@ -77,6 +78,12 @@ export const decodeIntent = (request: IntentRequest): Intent => {
methodId
})
default:
throw new Error('Unsupported intent')
throw new TransactionRequestIntentError({
message: 'Unsupported intent',
status: 400,
context: {
request
}
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export enum Intents {
CALL_CONTRACT = 'callContract'
}

export const NATIVE_TRANSFER = 'nativeTransfer'
export const NULL_METHOD_ID = '0x00000000'

// TODO: Move below in a folder shared with other apps, these should be shared within the whole project
export enum AssetTypeEnum {
Expand Down
12 changes: 12 additions & 0 deletions packages/transaction-request-intent/src/lib/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export class TransactionRequestIntentError extends Error {
readonly context?: any

readonly status: number

constructor({ context, message, status }: { context?: any; message: string; status: number }) {
super(message)

this.status = status
this.context = context
}
}
122 changes: 104 additions & 18 deletions packages/transaction-request-intent/src/lib/intent.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { IntentRequest } from '../shared/types'
import { AssetTypeEnum, Intents, NATIVE_TRANSFER } from '../utils/domain'
import { Intent } from '../utils/intent.types'
import { AmbiguousAbi, Erc20TransferAbi, Erc721TransferAbi } from '../utils/standard-functions/abis'
import { TransactionRequest } from '../utils/transaction.type'
import { AmbiguousAbi, Erc20TransferAbi, Erc721TransferAbi } from './abis'
import { decodeIntent } from './decoders'
import { AssetTypeEnum, Intents, NULL_METHOD_ID } from './domain'
import { TransactionRequestIntentError } from './error'
import { Intent } from './intent.types'
import { TransactionRequest } from './transaction.type'
import { Decode, IntentRequest } from './types'

const methodIdToAssetTypeMap: { [key: string]: AssetTypeEnum } = {
...Object.entries(Erc20TransferAbi).reduce((acc, [key]) => ({ ...acc, [key]: AssetTypeEnum.ERC20 }), {}),
...Object.entries(Erc721TransferAbi).reduce((acc, [key]) => ({ ...acc, [key]: AssetTypeEnum.ERC721 }), {}),
...Object.entries(AmbiguousAbi).reduce((acc, [key]) => ({ ...acc, [key]: AssetTypeEnum.AMBIGUOUS }), {}),
[NATIVE_TRANSFER]: AssetTypeEnum.NATIVE
...Object.entries(AmbiguousAbi).reduce((acc, [key]) => ({ ...acc, [key]: AssetTypeEnum.AMBIGUOUS }), {})
}

export const determineType = (methodId: string): AssetTypeEnum => {
export const determineType = (methodId: string, value?: string): AssetTypeEnum => {
if (methodId === NULL_METHOD_ID && value) {
return AssetTypeEnum.NATIVE
}
return methodIdToAssetTypeMap[methodId] || AssetTypeEnum.UNKNOWN
}

Expand All @@ -31,43 +34,126 @@ export const getIntentType = (assetType: AssetTypeEnum): Intents => {
}
}

export const getMethodId = (data?: string): string => (data ? data.slice(0, 10) : NATIVE_TRANSFER)
export const getMethodId = (data?: string): string => (data ? data.slice(0, 10) : NULL_METHOD_ID)

export const validateErc20Intent = (txRequest: TransactionRequest) => {
export const validateTransferIntent = (txRequest: TransactionRequest) => {
const { data, to, chainId } = txRequest
if (!data || !to || !chainId) {
throw new Error('Malformed Erc20 transaction request')
throw new TransactionRequestIntentError({
message: 'Malformed transfer transaction request: missing data || chainId || to',
status: 400,
context: {
chainId,
data,
to,
txRequest
}
})
}
return { data, to, chainId }
}

export const validateNativeTransferIntent = (txRequest: TransactionRequest) => {
const { value, chainId } = txRequest
if (!value || !chainId) {
throw new TransactionRequestIntentError({
message: 'Malformed native transfer transaction request: missing value or chainId',
status: 400,
context: {
value,
chainId,
txRequest
}
})
}
return { value, chainId }
}

export const validateIntent = (txRequest: TransactionRequest): IntentRequest => {
const { from, value, data, chainId } = txRequest
if (!from || !chainId) {
throw new Error('Malformed transaction request: missing from or chainId')
throw new TransactionRequestIntentError({
message: 'Malformed transaction request: missing from OR chainId',
status: 400,
context: {
txRequest
}
})
}
if (!value && !data) {
throw new Error('Malformed transaction request: missing value and data')
throw new TransactionRequestIntentError({
message: 'Malformed transaction request: missing value AND data',
status: 400,
context: {
txRequest
}
})
}

const methodId = getMethodId(data)
const assetType = determineType(methodId)
const assetType = determineType(methodId, value)
const type = getIntentType(assetType)

switch (type) {
case Intents.TRANSFER_ERC20:
case Intents.TRANSFER_ERC20 || Intents.TRANSFER_ERC721 || Intents.TRANSFER_ERC1155:
return {
type,
assetType,
methodId,
validatedFields: validateTransferIntent(txRequest)
}
case Intents.TRANSFER_NATIVE:
return {
type,
assetType,
methodId,
validatedFields: validateErc20Intent(txRequest)
validatedFields: validateTransferIntent(txRequest)
}
default:
throw new Error('Unsupported intent')
throw new TransactionRequestIntentError({
message: 'Unsupported intent',
status: 400,
context: {
type,
assetType,
methodId,
txRequest
}
})
}
}

export const decodeTransaction = (txRequest: TransactionRequest): Intent => {
export const decode = (txRequest: TransactionRequest): Intent => {
const request = validateIntent(txRequest)
return decodeIntent(request)
}

export const safeDecode = (txRequest: TransactionRequest): Decode => {
const request = validateIntent(txRequest)
try {
const intent = decodeIntent(request)
return {
success: true,
intent
}
} catch (error: any) {
if (error instanceof TransactionRequestIntentError) {
return {
success: false,
error: {
message: error.message,
status: error.status,
context: error.context
}
}
}
return {
success: false,
error: {
message: 'Unknown error',
status: 500,
context: error
}
}
}
}
50 changes: 50 additions & 0 deletions packages/transaction-request-intent/src/lib/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { AssetTypeEnum, Intents } from './domain'
import { Intent } from './intent.types'

export type TransferIntent = {
data: `0x${string}`
to: `0x${string}`
chainId: string
}

export type IntentRequest = {
methodId: string
assetType: AssetTypeEnum
} & (
| {
type: Intents.TRANSFER_ERC20
validatedFields: TransferIntent
}
| {
type: Intents.TRANSFER_ERC721
validatedFields: TransferIntent
}
| {
type: Intents.TRANSFER_ERC1155
validatedFields: TransferIntent
}
| {
type: Intents.TRANSFER_NATIVE
validatedFields: TransferIntent
}
| {
type: Intents.CALL_CONTRACT
validatedFields: TransferIntent
}
)

type DecodeSuccess = {
success: true
intent: Intent
}

type DecodeError = {
success: false
error: {
message: string
status: number
context: any
}
}

export type Decode = DecodeSuccess | DecodeError
21 changes: 0 additions & 21 deletions packages/transaction-request-intent/src/shared/types.ts

This file was deleted.

0 comments on commit cdcb0e0

Please sign in to comment.