From 529070a52a95f0be8079b9a3accd5a3aebcb233e Mon Sep 17 00:00:00 2001 From: Sanjay Date: Fri, 14 Jul 2023 22:16:32 -0700 Subject: [PATCH] feat: allow user submitted proofs (#38) * feat: allow user submitted proofs * Fix build --- package.json | 5 +- src/abi/IdRegistry.ts | 458 +++++++++++++++++++++ src/abi/common.ts | 129 ++++++ src/abi/factories/IdRegistry__factory.ts | 481 +++++++++++++++++++++++ src/abi/factories/index.ts | 4 + src/abi/idRegistry.abi | 464 ++++++++++++++++++++++ src/abi/index.ts | 6 + src/app.ts | 5 +- src/env.ts | 22 +- src/ethereum.ts | 9 + src/providerManager.ts | 96 ----- src/transfers.ts | 52 ++- tests/transfers.test.ts | 53 ++- tests/utils.ts | 20 +- yarn.lock | 176 ++++++++- 15 files changed, 1840 insertions(+), 140 deletions(-) create mode 100644 src/abi/IdRegistry.ts create mode 100644 src/abi/common.ts create mode 100644 src/abi/factories/IdRegistry__factory.ts create mode 100644 src/abi/factories/index.ts create mode 100644 src/abi/idRegistry.abi create mode 100644 src/abi/index.ts create mode 100644 src/ethereum.ts delete mode 100644 src/providerManager.ts diff --git a/package.json b/package.json index 447b8e33..9a7f11b5 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,12 @@ "build": "rm -rf ./build && tsc --project ./tsconfig.json", "test": "NODE_OPTIONS='--no-warnings --experimental-vm-modules' ENVIRONMENT=test jest --detectOpenHandles --forceExit", "lint": "eslint . --ext .ts", + "abi": "typechain --node16-modules --target ethers-v6 --out-dir src/abi src/abi/*.abi", "lint:fix": "npm run lint -- --fix" }, "dependencies": { - "@farcaster/hub-nodejs": "^0.8.3", "@chainlink/ccip-read-server": "^0.2.1", + "@farcaster/hub-nodejs": "^0.8.3", "body-parser": "^1.20.2", "dd-trace": "^4.4.0", "dotenv": "^16.3.1", @@ -29,6 +30,7 @@ "postgres": "^3.3.5" }, "devDependencies": { + "@typechain/ethers-v6": "^0.4.0", "@types/express": "^4.17.17", "@types/jest": "^29.5.3", "@types/node": "^20.2.6", @@ -44,6 +46,7 @@ "supertest": "^6.3.3", "ts-jest": "^29.1.1", "ts-node": "^10.9.1", + "typechain": "^8.2.0", "typescript": "^5.1.6" } } diff --git a/src/abi/IdRegistry.ts b/src/abi/IdRegistry.ts new file mode 100644 index 00000000..1a142655 --- /dev/null +++ b/src/abi/IdRegistry.ts @@ -0,0 +1,458 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { + BaseContract, + BigNumberish, + BytesLike, + FunctionFragment, + Result, + Interface, + EventFragment, + AddressLike, + ContractRunner, + ContractMethod, + Listener, +} from 'ethers'; +import type { + TypedContractEvent, + TypedDeferredTopicFilter, + TypedEventLog, + TypedLogDescription, + TypedListener, + TypedContractMethod, +} from './common.js'; + +export interface IdRegistryInterface extends Interface { + getFunction( + nameOrSignature: + | 'cancelRecovery' + | 'changeHome' + | 'changeRecoveryAddress' + | 'changeTrustedCaller' + | 'completeRecovery' + | 'completeTransferOwnership' + | 'disableTrustedOnly' + | 'idOf' + | 'isTrustedForwarder' + | 'owner' + | 'register' + | 'renounceOwnership' + | 'requestRecovery' + | 'requestTransferOwnership' + | 'transfer' + | 'transferOwnership' + | 'trustedRegister' + ): FunctionFragment; + + getEvent( + nameOrSignatureOrTopic: + | 'CancelRecovery' + | 'ChangeHome' + | 'ChangeRecoveryAddress' + | 'ChangeTrustedCaller' + | 'DisableTrustedOnly' + | 'OwnershipTransferred' + | 'Register' + | 'RequestRecovery' + | 'Transfer' + ): EventFragment; + + encodeFunctionData(functionFragment: 'cancelRecovery', values: [AddressLike]): string; + encodeFunctionData(functionFragment: 'changeHome', values: [string]): string; + encodeFunctionData(functionFragment: 'changeRecoveryAddress', values: [AddressLike]): string; + encodeFunctionData(functionFragment: 'changeTrustedCaller', values: [AddressLike]): string; + encodeFunctionData(functionFragment: 'completeRecovery', values: [AddressLike]): string; + encodeFunctionData(functionFragment: 'completeTransferOwnership', values?: undefined): string; + encodeFunctionData(functionFragment: 'disableTrustedOnly', values?: undefined): string; + encodeFunctionData(functionFragment: 'idOf', values: [AddressLike]): string; + encodeFunctionData(functionFragment: 'isTrustedForwarder', values: [AddressLike]): string; + encodeFunctionData(functionFragment: 'owner', values?: undefined): string; + encodeFunctionData(functionFragment: 'register', values: [AddressLike, AddressLike, string]): string; + encodeFunctionData(functionFragment: 'renounceOwnership', values?: undefined): string; + encodeFunctionData(functionFragment: 'requestRecovery', values: [AddressLike, AddressLike]): string; + encodeFunctionData(functionFragment: 'requestTransferOwnership', values: [AddressLike]): string; + encodeFunctionData(functionFragment: 'transfer', values: [AddressLike]): string; + encodeFunctionData(functionFragment: 'transferOwnership', values: [AddressLike]): string; + encodeFunctionData(functionFragment: 'trustedRegister', values: [AddressLike, AddressLike, string]): string; + + decodeFunctionResult(functionFragment: 'cancelRecovery', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'changeHome', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'changeRecoveryAddress', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'changeTrustedCaller', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'completeRecovery', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'completeTransferOwnership', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'disableTrustedOnly', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'idOf', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'isTrustedForwarder', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'owner', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'register', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'renounceOwnership', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'requestRecovery', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'requestTransferOwnership', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'transfer', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'transferOwnership', data: BytesLike): Result; + decodeFunctionResult(functionFragment: 'trustedRegister', data: BytesLike): Result; +} + +export namespace CancelRecoveryEvent { + export type InputTuple = [by: AddressLike, id: BigNumberish]; + export type OutputTuple = [by: string, id: bigint]; + export interface OutputObject { + by: string; + id: bigint; + } + export type Event = TypedContractEvent; + export type Filter = TypedDeferredTopicFilter; + export type Log = TypedEventLog; + export type LogDescription = TypedLogDescription; +} + +export namespace ChangeHomeEvent { + export type InputTuple = [id: BigNumberish, url: string]; + export type OutputTuple = [id: bigint, url: string]; + export interface OutputObject { + id: bigint; + url: string; + } + export type Event = TypedContractEvent; + export type Filter = TypedDeferredTopicFilter; + export type Log = TypedEventLog; + export type LogDescription = TypedLogDescription; +} + +export namespace ChangeRecoveryAddressEvent { + export type InputTuple = [id: BigNumberish, recovery: AddressLike]; + export type OutputTuple = [id: bigint, recovery: string]; + export interface OutputObject { + id: bigint; + recovery: string; + } + export type Event = TypedContractEvent; + export type Filter = TypedDeferredTopicFilter; + export type Log = TypedEventLog; + export type LogDescription = TypedLogDescription; +} + +export namespace ChangeTrustedCallerEvent { + export type InputTuple = [trustedCaller: AddressLike]; + export type OutputTuple = [trustedCaller: string]; + export interface OutputObject { + trustedCaller: string; + } + export type Event = TypedContractEvent; + export type Filter = TypedDeferredTopicFilter; + export type Log = TypedEventLog; + export type LogDescription = TypedLogDescription; +} + +export namespace DisableTrustedOnlyEvent { + export type InputTuple = []; + export type OutputTuple = []; + export interface OutputObject {} + export type Event = TypedContractEvent; + export type Filter = TypedDeferredTopicFilter; + export type Log = TypedEventLog; + export type LogDescription = TypedLogDescription; +} + +export namespace OwnershipTransferredEvent { + export type InputTuple = [previousOwner: AddressLike, newOwner: AddressLike]; + export type OutputTuple = [previousOwner: string, newOwner: string]; + export interface OutputObject { + previousOwner: string; + newOwner: string; + } + export type Event = TypedContractEvent; + export type Filter = TypedDeferredTopicFilter; + export type Log = TypedEventLog; + export type LogDescription = TypedLogDescription; +} + +export namespace RegisterEvent { + export type InputTuple = [to: AddressLike, id: BigNumberish, recovery: AddressLike, url: string]; + export type OutputTuple = [to: string, id: bigint, recovery: string, url: string]; + export interface OutputObject { + to: string; + id: bigint; + recovery: string; + url: string; + } + export type Event = TypedContractEvent; + export type Filter = TypedDeferredTopicFilter; + export type Log = TypedEventLog; + export type LogDescription = TypedLogDescription; +} + +export namespace RequestRecoveryEvent { + export type InputTuple = [from: AddressLike, to: AddressLike, id: BigNumberish]; + export type OutputTuple = [from: string, to: string, id: bigint]; + export interface OutputObject { + from: string; + to: string; + id: bigint; + } + export type Event = TypedContractEvent; + export type Filter = TypedDeferredTopicFilter; + export type Log = TypedEventLog; + export type LogDescription = TypedLogDescription; +} + +export namespace TransferEvent { + export type InputTuple = [from: AddressLike, to: AddressLike, id: BigNumberish]; + export type OutputTuple = [from: string, to: string, id: bigint]; + export interface OutputObject { + from: string; + to: string; + id: bigint; + } + export type Event = TypedContractEvent; + export type Filter = TypedDeferredTopicFilter; + export type Log = TypedEventLog; + export type LogDescription = TypedLogDescription; +} + +export interface IdRegistry extends BaseContract { + connect(runner?: ContractRunner | null): IdRegistry; + waitForDeployment(): Promise; + + interface: IdRegistryInterface; + + queryFilter( + event: TCEvent, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined + ): Promise>>; + queryFilter( + filter: TypedDeferredTopicFilter, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined + ): Promise>>; + + on(event: TCEvent, listener: TypedListener): Promise; + on( + filter: TypedDeferredTopicFilter, + listener: TypedListener + ): Promise; + + once(event: TCEvent, listener: TypedListener): Promise; + once( + filter: TypedDeferredTopicFilter, + listener: TypedListener + ): Promise; + + listeners(event: TCEvent): Promise>>; + listeners(eventName?: string): Promise>; + removeAllListeners(event?: TCEvent): Promise; + + cancelRecovery: TypedContractMethod<[from: AddressLike], [void], 'nonpayable'>; + + changeHome: TypedContractMethod<[url: string], [void], 'nonpayable'>; + + changeRecoveryAddress: TypedContractMethod<[recovery: AddressLike], [void], 'nonpayable'>; + + changeTrustedCaller: TypedContractMethod<[_trustedCaller: AddressLike], [void], 'nonpayable'>; + + completeRecovery: TypedContractMethod<[from: AddressLike], [void], 'nonpayable'>; + + completeTransferOwnership: TypedContractMethod<[], [void], 'nonpayable'>; + + disableTrustedOnly: TypedContractMethod<[], [void], 'nonpayable'>; + + idOf: TypedContractMethod<[arg0: AddressLike], [bigint], 'view'>; + + isTrustedForwarder: TypedContractMethod<[forwarder: AddressLike], [boolean], 'view'>; + + owner: TypedContractMethod<[], [string], 'view'>; + + register: TypedContractMethod<[to: AddressLike, recovery: AddressLike, url: string], [void], 'nonpayable'>; + + renounceOwnership: TypedContractMethod<[], [void], 'nonpayable'>; + + requestRecovery: TypedContractMethod<[from: AddressLike, to: AddressLike], [void], 'nonpayable'>; + + requestTransferOwnership: TypedContractMethod<[newOwner: AddressLike], [void], 'nonpayable'>; + + transfer: TypedContractMethod<[to: AddressLike], [void], 'nonpayable'>; + + transferOwnership: TypedContractMethod<[arg0: AddressLike], [void], 'view'>; + + trustedRegister: TypedContractMethod<[to: AddressLike, recovery: AddressLike, url: string], [void], 'nonpayable'>; + + getFunction(key: string | FunctionFragment): T; + + getFunction(nameOrSignature: 'cancelRecovery'): TypedContractMethod<[from: AddressLike], [void], 'nonpayable'>; + getFunction(nameOrSignature: 'changeHome'): TypedContractMethod<[url: string], [void], 'nonpayable'>; + getFunction( + nameOrSignature: 'changeRecoveryAddress' + ): TypedContractMethod<[recovery: AddressLike], [void], 'nonpayable'>; + getFunction( + nameOrSignature: 'changeTrustedCaller' + ): TypedContractMethod<[_trustedCaller: AddressLike], [void], 'nonpayable'>; + getFunction(nameOrSignature: 'completeRecovery'): TypedContractMethod<[from: AddressLike], [void], 'nonpayable'>; + getFunction(nameOrSignature: 'completeTransferOwnership'): TypedContractMethod<[], [void], 'nonpayable'>; + getFunction(nameOrSignature: 'disableTrustedOnly'): TypedContractMethod<[], [void], 'nonpayable'>; + getFunction(nameOrSignature: 'idOf'): TypedContractMethod<[arg0: AddressLike], [bigint], 'view'>; + getFunction(nameOrSignature: 'isTrustedForwarder'): TypedContractMethod<[forwarder: AddressLike], [boolean], 'view'>; + getFunction(nameOrSignature: 'owner'): TypedContractMethod<[], [string], 'view'>; + getFunction( + nameOrSignature: 'register' + ): TypedContractMethod<[to: AddressLike, recovery: AddressLike, url: string], [void], 'nonpayable'>; + getFunction(nameOrSignature: 'renounceOwnership'): TypedContractMethod<[], [void], 'nonpayable'>; + getFunction( + nameOrSignature: 'requestRecovery' + ): TypedContractMethod<[from: AddressLike, to: AddressLike], [void], 'nonpayable'>; + getFunction( + nameOrSignature: 'requestTransferOwnership' + ): TypedContractMethod<[newOwner: AddressLike], [void], 'nonpayable'>; + getFunction(nameOrSignature: 'transfer'): TypedContractMethod<[to: AddressLike], [void], 'nonpayable'>; + getFunction(nameOrSignature: 'transferOwnership'): TypedContractMethod<[arg0: AddressLike], [void], 'view'>; + getFunction( + nameOrSignature: 'trustedRegister' + ): TypedContractMethod<[to: AddressLike, recovery: AddressLike, url: string], [void], 'nonpayable'>; + + getEvent( + key: 'CancelRecovery' + ): TypedContractEvent< + CancelRecoveryEvent.InputTuple, + CancelRecoveryEvent.OutputTuple, + CancelRecoveryEvent.OutputObject + >; + getEvent( + key: 'ChangeHome' + ): TypedContractEvent; + getEvent( + key: 'ChangeRecoveryAddress' + ): TypedContractEvent< + ChangeRecoveryAddressEvent.InputTuple, + ChangeRecoveryAddressEvent.OutputTuple, + ChangeRecoveryAddressEvent.OutputObject + >; + getEvent( + key: 'ChangeTrustedCaller' + ): TypedContractEvent< + ChangeTrustedCallerEvent.InputTuple, + ChangeTrustedCallerEvent.OutputTuple, + ChangeTrustedCallerEvent.OutputObject + >; + getEvent( + key: 'DisableTrustedOnly' + ): TypedContractEvent< + DisableTrustedOnlyEvent.InputTuple, + DisableTrustedOnlyEvent.OutputTuple, + DisableTrustedOnlyEvent.OutputObject + >; + getEvent( + key: 'OwnershipTransferred' + ): TypedContractEvent< + OwnershipTransferredEvent.InputTuple, + OwnershipTransferredEvent.OutputTuple, + OwnershipTransferredEvent.OutputObject + >; + getEvent( + key: 'Register' + ): TypedContractEvent; + getEvent( + key: 'RequestRecovery' + ): TypedContractEvent< + RequestRecoveryEvent.InputTuple, + RequestRecoveryEvent.OutputTuple, + RequestRecoveryEvent.OutputObject + >; + getEvent( + key: 'Transfer' + ): TypedContractEvent; + + filters: { + 'CancelRecovery(address,uint256)': TypedContractEvent< + CancelRecoveryEvent.InputTuple, + CancelRecoveryEvent.OutputTuple, + CancelRecoveryEvent.OutputObject + >; + CancelRecovery: TypedContractEvent< + CancelRecoveryEvent.InputTuple, + CancelRecoveryEvent.OutputTuple, + CancelRecoveryEvent.OutputObject + >; + + 'ChangeHome(uint256,string)': TypedContractEvent< + ChangeHomeEvent.InputTuple, + ChangeHomeEvent.OutputTuple, + ChangeHomeEvent.OutputObject + >; + ChangeHome: TypedContractEvent< + ChangeHomeEvent.InputTuple, + ChangeHomeEvent.OutputTuple, + ChangeHomeEvent.OutputObject + >; + + 'ChangeRecoveryAddress(uint256,address)': TypedContractEvent< + ChangeRecoveryAddressEvent.InputTuple, + ChangeRecoveryAddressEvent.OutputTuple, + ChangeRecoveryAddressEvent.OutputObject + >; + ChangeRecoveryAddress: TypedContractEvent< + ChangeRecoveryAddressEvent.InputTuple, + ChangeRecoveryAddressEvent.OutputTuple, + ChangeRecoveryAddressEvent.OutputObject + >; + + 'ChangeTrustedCaller(address)': TypedContractEvent< + ChangeTrustedCallerEvent.InputTuple, + ChangeTrustedCallerEvent.OutputTuple, + ChangeTrustedCallerEvent.OutputObject + >; + ChangeTrustedCaller: TypedContractEvent< + ChangeTrustedCallerEvent.InputTuple, + ChangeTrustedCallerEvent.OutputTuple, + ChangeTrustedCallerEvent.OutputObject + >; + + 'DisableTrustedOnly()': TypedContractEvent< + DisableTrustedOnlyEvent.InputTuple, + DisableTrustedOnlyEvent.OutputTuple, + DisableTrustedOnlyEvent.OutputObject + >; + DisableTrustedOnly: TypedContractEvent< + DisableTrustedOnlyEvent.InputTuple, + DisableTrustedOnlyEvent.OutputTuple, + DisableTrustedOnlyEvent.OutputObject + >; + + 'OwnershipTransferred(address,address)': TypedContractEvent< + OwnershipTransferredEvent.InputTuple, + OwnershipTransferredEvent.OutputTuple, + OwnershipTransferredEvent.OutputObject + >; + OwnershipTransferred: TypedContractEvent< + OwnershipTransferredEvent.InputTuple, + OwnershipTransferredEvent.OutputTuple, + OwnershipTransferredEvent.OutputObject + >; + + 'Register(address,uint256,address,string)': TypedContractEvent< + RegisterEvent.InputTuple, + RegisterEvent.OutputTuple, + RegisterEvent.OutputObject + >; + Register: TypedContractEvent; + + 'RequestRecovery(address,address,uint256)': TypedContractEvent< + RequestRecoveryEvent.InputTuple, + RequestRecoveryEvent.OutputTuple, + RequestRecoveryEvent.OutputObject + >; + RequestRecovery: TypedContractEvent< + RequestRecoveryEvent.InputTuple, + RequestRecoveryEvent.OutputTuple, + RequestRecoveryEvent.OutputObject + >; + + 'Transfer(address,address,uint256)': TypedContractEvent< + TransferEvent.InputTuple, + TransferEvent.OutputTuple, + TransferEvent.OutputObject + >; + Transfer: TypedContractEvent; + }; +} diff --git a/src/abi/common.ts b/src/abi/common.ts new file mode 100644 index 00000000..192c895b --- /dev/null +++ b/src/abi/common.ts @@ -0,0 +1,129 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { + FunctionFragment, + Typed, + EventFragment, + ContractTransaction, + ContractTransactionResponse, + DeferredTopicFilter, + EventLog, + TransactionRequest, + LogDescription, +} from "ethers"; + +export interface TypedDeferredTopicFilter<_TCEvent extends TypedContractEvent> + extends DeferredTopicFilter {} + +export interface TypedContractEvent< + InputTuple extends Array = any, + OutputTuple extends Array = any, + OutputObject = any +> { + (...args: Partial): TypedDeferredTopicFilter< + TypedContractEvent + >; + name: string; + fragment: EventFragment; + getFragment(...args: Partial): EventFragment; +} + +type __TypechainAOutputTuple = T extends TypedContractEvent< + infer _U, + infer W +> + ? W + : never; +type __TypechainOutputObject = T extends TypedContractEvent< + infer _U, + infer _W, + infer V +> + ? V + : never; + +export interface TypedEventLog + extends Omit { + args: __TypechainAOutputTuple & __TypechainOutputObject; +} + +export interface TypedLogDescription + extends Omit { + args: __TypechainAOutputTuple & __TypechainOutputObject; +} + +export type TypedListener = ( + ...listenerArg: [ + ...__TypechainAOutputTuple, + TypedEventLog, + ...undefined[] + ] +) => void; + +export type MinEthersFactory = { + deploy(...a: ARGS[]): Promise; +}; + +export type GetContractTypeFromFactory = F extends MinEthersFactory< + infer C, + any +> + ? C + : never; +export type GetARGsTypeFromFactory = F extends MinEthersFactory + ? Parameters + : never; + +export type StateMutability = "nonpayable" | "payable" | "view"; + +export type BaseOverrides = Omit; +export type NonPayableOverrides = Omit< + BaseOverrides, + "value" | "blockTag" | "enableCcipRead" +>; +export type PayableOverrides = Omit< + BaseOverrides, + "blockTag" | "enableCcipRead" +>; +export type ViewOverrides = Omit; +export type Overrides = S extends "nonpayable" + ? NonPayableOverrides + : S extends "payable" + ? PayableOverrides + : ViewOverrides; + +export type PostfixOverrides, S extends StateMutability> = + | A + | [...A, Overrides]; +export type ContractMethodArgs< + A extends Array, + S extends StateMutability +> = PostfixOverrides<{ [I in keyof A]-?: A[I] | Typed }, S>; + +export type DefaultReturnType = R extends Array ? R[0] : R; + +// export interface ContractMethod = Array, R = any, D extends R | ContractTransactionResponse = R | ContractTransactionResponse> { +export interface TypedContractMethod< + A extends Array = Array, + R = any, + S extends StateMutability = "payable" +> { + (...args: ContractMethodArgs): S extends "view" + ? Promise> + : Promise; + + name: string; + + fragment: FunctionFragment; + + getFragment(...args: ContractMethodArgs): FunctionFragment; + + populateTransaction( + ...args: ContractMethodArgs + ): Promise; + staticCall(...args: ContractMethodArgs): Promise>; + send(...args: ContractMethodArgs): Promise; + estimateGas(...args: ContractMethodArgs): Promise; + staticCallResult(...args: ContractMethodArgs): Promise; +} diff --git a/src/abi/factories/IdRegistry__factory.ts b/src/abi/factories/IdRegistry__factory.ts new file mode 100644 index 00000000..0e530d72 --- /dev/null +++ b/src/abi/factories/IdRegistry__factory.ts @@ -0,0 +1,481 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ + +import { Contract, Interface, type ContractRunner } from 'ethers'; +import type { IdRegistry, IdRegistryInterface } from '../IdRegistry.js'; + +const _abi = [ + { + inputs: [ + { + internalType: 'address', + name: '_forwarder', + type: 'address', + }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [], + name: 'Escrow', + type: 'error', + }, + { + inputs: [], + name: 'HasId', + type: 'error', + }, + { + inputs: [], + name: 'HasNoId', + type: 'error', + }, + { + inputs: [], + name: 'Invitable', + type: 'error', + }, + { + inputs: [], + name: 'NoRecovery', + type: 'error', + }, + { + inputs: [], + name: 'Registrable', + type: 'error', + }, + { + inputs: [], + name: 'Unauthorized', + type: 'error', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'by', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + ], + name: 'CancelRecovery', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + { + indexed: false, + internalType: 'string', + name: 'url', + type: 'string', + }, + ], + name: 'ChangeHome', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + { + indexed: true, + internalType: 'address', + name: 'recovery', + type: 'address', + }, + ], + name: 'ChangeRecoveryAddress', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'trustedCaller', + type: 'address', + }, + ], + name: 'ChangeTrustedCaller', + type: 'event', + }, + { + anonymous: false, + inputs: [], + name: 'DisableTrustedOnly', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'previousOwner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'OwnershipTransferred', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + { + indexed: false, + internalType: 'address', + name: 'recovery', + type: 'address', + }, + { + indexed: false, + internalType: 'string', + name: 'url', + type: 'string', + }, + ], + name: 'Register', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + ], + name: 'RequestRecovery', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', + }, + { + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + ], + name: 'cancelRecovery', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'string', + name: 'url', + type: 'string', + }, + ], + name: 'changeHome', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'recovery', + type: 'address', + }, + ], + name: 'changeRecoveryAddress', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_trustedCaller', + type: 'address', + }, + ], + name: 'changeTrustedCaller', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + ], + name: 'completeRecovery', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'completeTransferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'disableTrustedOnly', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + name: 'idOf', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'forwarder', + type: 'address', + }, + ], + name: 'isTrustedForwarder', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'owner', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'address', + name: 'recovery', + type: 'address', + }, + { + internalType: 'string', + name: 'url', + type: 'string', + }, + ], + name: 'register', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'renounceOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + ], + name: 'requestRecovery', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'requestTransferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'to', + type: 'address', + }, + ], + name: 'transfer', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + name: 'transferOwnership', + outputs: [], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'address', + name: 'recovery', + type: 'address', + }, + { + internalType: 'string', + name: 'url', + type: 'string', + }, + ], + name: 'trustedRegister', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const; + +export class IdRegistry__factory { + static readonly abi = _abi; + static createInterface(): IdRegistryInterface { + return new Interface(_abi) as IdRegistryInterface; + } + static connect(address: string, runner?: ContractRunner | null): IdRegistry { + return new Contract(address, _abi, runner) as unknown as IdRegistry; + } +} diff --git a/src/abi/factories/index.ts b/src/abi/factories/index.ts new file mode 100644 index 00000000..352e3845 --- /dev/null +++ b/src/abi/factories/index.ts @@ -0,0 +1,4 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +export { IdRegistry__factory } from './IdRegistry__factory.js'; diff --git a/src/abi/idRegistry.abi b/src/abi/idRegistry.abi new file mode 100644 index 00000000..ab385011 --- /dev/null +++ b/src/abi/idRegistry.abi @@ -0,0 +1,464 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_forwarder", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "Escrow", + "type": "error" + }, + { + "inputs": [], + "name": "HasId", + "type": "error" + }, + { + "inputs": [], + "name": "HasNoId", + "type": "error" + }, + { + "inputs": [], + "name": "Invitable", + "type": "error" + }, + { + "inputs": [], + "name": "NoRecovery", + "type": "error" + }, + { + "inputs": [], + "name": "Registrable", + "type": "error" + }, + { + "inputs": [], + "name": "Unauthorized", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "by", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "CancelRecovery", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "url", + "type": "string" + } + ], + "name": "ChangeHome", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "recovery", + "type": "address" + } + ], + "name": "ChangeRecoveryAddress", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "trustedCaller", + "type": "address" + } + ], + "name": "ChangeTrustedCaller", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "DisableTrustedOnly", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "recovery", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "url", + "type": "string" + } + ], + "name": "Register", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "RequestRecovery", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + } + ], + "name": "cancelRecovery", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "url", + "type": "string" + } + ], + "name": "changeHome", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recovery", + "type": "address" + } + ], + "name": "changeRecoveryAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_trustedCaller", + "type": "address" + } + ], + "name": "changeTrustedCaller", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + } + ], + "name": "completeRecovery", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "completeTransferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "disableTrustedOnly", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "idOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "forwarder", + "type": "address" + } + ], + "name": "isTrustedForwarder", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "address", + "name": "recovery", + "type": "address" + }, + { + "internalType": "string", + "name": "url", + "type": "string" + } + ], + "name": "register", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "requestRecovery", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "requestTransferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "transfer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "address", + "name": "recovery", + "type": "address" + }, + { + "internalType": "string", + "name": "url", + "type": "string" + } + ], + "name": "trustedRegister", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/src/abi/index.ts b/src/abi/index.ts new file mode 100644 index 00000000..5900d8bf --- /dev/null +++ b/src/abi/index.ts @@ -0,0 +1,6 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +export type { IdRegistry } from './IdRegistry.js'; +export * as factories from './factories/index.js'; +export { IdRegistry__factory } from './factories/IdRegistry__factory.js'; diff --git a/src/app.ts b/src/app.ts index 8dec5f04..ae82fd7c 100644 --- a/src/app.ts +++ b/src/app.ts @@ -15,6 +15,7 @@ import { } from './transfers.js'; import { decodeDnsName } from './util.js'; +import { getIdRegistryContract } from './ethereum.js'; export const RESOLVE_ABI = [ 'function resolve(bytes calldata name, bytes calldata data) external view returns(string name, uint256 timestamp, address owner, bytes memory sig)', @@ -22,6 +23,7 @@ export const RESOLVE_ABI = [ const db = getDbClient(); await migrateToLatest(db, log); +const idContract = getIdRegistryContract(); const server = new ccipread.Server(); @@ -75,7 +77,8 @@ app.post('/transfers', async (req, res) => { userSignature: tr.signature, userFid: tr.fid, }, - db + db, + idContract ); if (!result) { log.warn({ name: tr.username }, `Unable to create transfer`); diff --git a/src/env.ts b/src/env.ts index ad3beb96..1ee37641 100644 --- a/src/env.ts +++ b/src/env.ts @@ -10,26 +10,6 @@ if (GOERLI_ALCHEMY_SECRET === '') { throw new Error('GOERLI_ALCHEMY_SECRET missing from .env'); } -export const MAINNET_ALCHEMY_SECRET = process.env['MAINNET_ALCHEMY_SECRET'] || ''; -if (MAINNET_ALCHEMY_SECRET === '') { - throw new Error('MAINNET_ALCHEMY_SECRET missing from .env'); -} - -export const ETHERSCAN_API_SECRET = process.env['ETHERSCAN_API_SECRET'] || ''; -if (ETHERSCAN_API_SECRET === '') { - throw new Error('ETHERSCAN_API_SECRET missing from .env'); -} - -export const INFURA_PROJECT_ID = process.env['INFURA_PROJECT_ID'] || ''; -if (INFURA_PROJECT_ID === '') { - throw new Error('INFURA_PROJECT_ID missing from .env'); -} - -export const INFURA_PROJECT_SECRET = process.env['INFURA_PROJECT_SECRET'] || ''; -if (INFURA_PROJECT_SECRET === '') { - throw new Error('INFURA_PROJECT_SECRET missing from .env'); -} - export const WARPCAST_ADDRESS = process.env['WARPCAST_ADDRESS'] || ''; if (WARPCAST_ADDRESS === '') { throw new Error('WARPCAST_ADDRESS missing from .env'); @@ -40,3 +20,5 @@ export const CCIP_ADDRESS = process.env['CCIP_ADDRESS'] || ''; if (WARPCAST_ADDRESS === '') { throw new Error('CCIP_ADDRESS missing from .env'); } + +export const ID_REGISTRY_ADDRESS = process.env['ID_REGISTRY_ADDRESS'] || '0xDA107A1CAf36d198B12c16c7B6a1d1C795978C42'; diff --git a/src/ethereum.ts b/src/ethereum.ts new file mode 100644 index 00000000..d2f373b0 --- /dev/null +++ b/src/ethereum.ts @@ -0,0 +1,9 @@ +import { AlchemyProvider } from 'ethers'; +import { GOERLI_ALCHEMY_SECRET, ID_REGISTRY_ADDRESS } from './env.js'; +import { IdRegistry } from './abi/IdRegistry.js'; +import { IdRegistry__factory } from './abi/index.js'; + +export function getIdRegistryContract(): IdRegistry { + const provider = new AlchemyProvider('goerli', GOERLI_ALCHEMY_SECRET); + return IdRegistry__factory.connect(ID_REGISTRY_ADDRESS, provider); +} diff --git a/src/providerManager.ts b/src/providerManager.ts deleted file mode 100644 index b7622843..00000000 --- a/src/providerManager.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { FallbackProvider, AlchemyProvider, InfuraProvider, EtherscanProvider } from 'ethers'; -import { - GOERLI_ALCHEMY_SECRET, - MAINNET_ALCHEMY_SECRET, - INFURA_PROJECT_ID, - INFURA_PROJECT_SECRET, - ETHERSCAN_API_SECRET, -} from './env.js'; -import { EthereumChain } from './util.js'; - -let availableProviders: Map; - -// For HTTP providers: How many providers have to agree for us to trust the result? -const QUORUM = 2; - -/** - * Manages creation of web3 provider instances for a given network - * (Goerli, Mainnet, etc.) and type (HTTPS or WebSocket). - * - * The returned providers are designed to be more fault tolerant if one of the - * underlying providers (e.g. Alchemy, Infura, etc.) is down by using a - * FallbackProvider with appropriate quorum. - */ -export class ProviderManager { - static getProvider(chain: EthereumChain): FallbackProvider { - if (!availableProviders) { - this._init(); - } - - const provider = availableProviders.get(chain); - - if (!provider) { - throw Error(`No provider found for chain: ${chain}`); - } - - return provider; - } - - private static _init() { - availableProviders = new Map(); - - availableProviders.set( - EthereumChain.Goerli, - new FallbackProvider( - [ - { - provider: new AlchemyProvider('goerli', GOERLI_ALCHEMY_SECRET), - priority: 1, - stallTimeout: 5000, - weight: 1, - }, - { - provider: new InfuraProvider('goerli', INFURA_PROJECT_ID, INFURA_PROJECT_SECRET), - priority: 2, - stallTimeout: 5000, - weight: 1, - }, - { - provider: new EtherscanProvider('goerli', ETHERSCAN_API_SECRET), - priority: 3, - stallTimeout: 5000, - weight: 1, - }, - ], - QUORUM - ) - ); - - availableProviders.set( - EthereumChain.Mainnet, - new FallbackProvider( - [ - { - provider: new AlchemyProvider('homestead', MAINNET_ALCHEMY_SECRET), - priority: 1, - stallTimeout: 5000, - weight: 1, - }, - { - provider: new InfuraProvider('homestead', INFURA_PROJECT_ID, INFURA_PROJECT_SECRET), - priority: 2, - stallTimeout: 5000, - weight: 1, - }, - { - provider: new EtherscanProvider('homestead', ETHERSCAN_API_SECRET), - priority: 3, - stallTimeout: 5000, - weight: 1, - }, - ], - QUORUM - ) - ); - } -} diff --git a/src/transfers.ts b/src/transfers.ts index 1b6992e6..396da8ad 100644 --- a/src/transfers.ts +++ b/src/transfers.ts @@ -1,9 +1,11 @@ import { Kysely, Selectable } from 'kysely'; import { Database, TransfersTable } from './db.js'; import { ADMIN_KEYS, generateSignature, signer, verifySignature } from './signature.js'; -import { bytesToHex, hexToBytes } from './util.js'; -import { currentTimestamp } from './util.js'; +import { bytesToHex, currentTimestamp, hexToBytes } from './util.js'; import { bytesCompare, validations } from '@farcaster/hub-nodejs'; +import { log } from './log.js'; +import { IdRegistry } from './abi/index.js'; +import { toNumber } from 'ethers'; const PAGE_SIZE = 100; const TIMESTAMP_TOLERANCE = 60; // 1 minute @@ -32,6 +34,7 @@ type ErrorCode = | 'USERNAME_NOT_FOUND' | 'INVALID_SIGNATURE' | 'INVALID_USERNAME' + | 'INVALID_FID_OWNER' | 'INVALID_TIMESTAMP'; export class ValidationError extends Error { public readonly code: ErrorCode; @@ -41,8 +44,8 @@ export class ValidationError extends Error { } } -export async function createTransfer(req: TransferRequest, db: Kysely) { - const existing_matching_transfer_id = await validateTransfer(req, db); +export async function createTransfer(req: TransferRequest, db: Kysely, idContract: IdRegistry) { + const existing_matching_transfer_id = await validateTransfer(req, db, idContract); if (existing_matching_transfer_id) { return { id: existing_matching_transfer_id }; } @@ -56,8 +59,45 @@ export async function createTransfer(req: TransferRequest, db: Kysely) return await db.insertInto('transfers').values(transfer).returning('id').executeTakeFirst(); } -export async function validateTransfer(req: TransferRequest, db: Kysely) { - const verifierAddress = ADMIN_KEYS[req.userFid]; +async function getAndValidateVerifierAddress(req: TransferRequest, idContract: IdRegistry) { + // Admin transfer + if (ADMIN_KEYS[req.userFid]) { + return ADMIN_KEYS[req.userFid]; + } + + // For user transfers, make sure the userFid matches the transfer request if it's present and that the + // owner address actually owns the fid + let userFid = -1; + if (req.from === 0) { + userFid = req.to; + } else if (req.to === 0) { + userFid = req.from; + } + if (req.userFid && userFid !== req.userFid) { + log.warn(`User FID ${req.userFid} does not match FID ${userFid} in transfer request`); + throw new ValidationError('UNAUTHORIZED'); + } + + let ownerFid: bigint; + + try { + ownerFid = await idContract.idOf(req.owner); + } catch (e) { + log.error(e, `Unable to get fid for owner: ${req.owner}`); + throw new ValidationError('INVALID_FID_OWNER'); + } + + if (toNumber(ownerFid) !== userFid) { + log.warn(`Owner for FID ${ownerFid.toString()} does not match owner ${req.owner} in transfer request`); + throw new ValidationError('INVALID_FID_OWNER'); + } + + return req.owner; +} + +export async function validateTransfer(req: TransferRequest, db: Kysely, idContract: IdRegistry) { + const verifierAddress = await getAndValidateVerifierAddress(req, idContract); + if (!verifierAddress) { // Only admin transfers are allowed until we finish migrating throw new ValidationError('UNAUTHORIZED'); diff --git a/tests/transfers.test.ts b/tests/transfers.test.ts index 86c76443..91c85827 100644 --- a/tests/transfers.test.ts +++ b/tests/transfers.test.ts @@ -105,7 +105,7 @@ describe('transfers', () => { ).rejects.toThrow('INVALID_SIGNATURE'); }); - test('only allows admin fids to transfer', async () => { + test('only admins can transfer names owned by other fids', async () => { const now = currentTimestamp(); // FID is not an admin, rejected @@ -129,6 +129,57 @@ describe('transfers', () => { }) ).rejects.toThrow('INVALID_SIGNATURE'); }); + + test('user can transfer name if they own the fid', async () => { + await expect( + createTestTransfer( + db, + { + username: 'anewname', + to: 5, + owner: anotherSigner.address, + userSignature: await generateSignature( + 'anewname', + currentTimestamp(), + anotherSigner.address, + anotherSigner + ), + userFid: 5, + }, + 5 + ) + ).resolves.toBeDefined(); + }); + test('user cannot transfer name if they do not own the fid', async () => { + await expect( + createTestTransfer( + db, + { + username: 'anewname', + to: 5, + owner: anotherSigner.address, + userSignature: await generateSignature( + 'anewname', + currentTimestamp(), + anotherSigner.address, + anotherSigner + ), + userFid: 5, + }, + 1 + ) + ).rejects.toThrow('INVALID_FID_OWNER'); + }); + test('fails if userFid does not match transfer fid', async () => { + await expect( + createTestTransfer(db, { + username: 'anewname', + to: 5, + owner: anotherSigner.address, + userFid: 1, + }) + ).rejects.toThrow('UNAUTHORIZED'); + }); }); describe('getLatestTransfer', () => { diff --git a/tests/utils.ts b/tests/utils.ts index ad34ea89..92fb4c3f 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -1,9 +1,10 @@ -import { generateSignature, signer, signerAddress, signerFid } from '../src/signature'; -import { createTransfer } from '../src/transfers'; -import { currentTimestamp } from '../src/util'; -import { Database } from '../src/db'; +import { generateSignature, signer, signerAddress, signerFid } from '../src/signature.js'; +import { createTransfer } from '../src/transfers.js'; +import { currentTimestamp } from '../src/util.js'; +import { Database } from '../src/db.js'; import { Kysely } from 'kysely'; -import { bytesToHex } from '../src/util'; +import { bytesToHex } from '../src/util.js'; +import { jest } from '@jest/globals'; type TestTransferParams = { username: string; @@ -15,12 +16,14 @@ type TestTransferParams = { userFid?: number; }; -export async function createTestTransfer(db: Kysely, opts: TestTransferParams) { +export async function createTestTransfer(db: Kysely, opts: TestTransferParams, idOfOwner?: number) { opts.timestamp = opts.timestamp ?? currentTimestamp(); opts.from = opts.from ?? 0; opts.owner = opts.owner ?? signerAddress; opts.userSignature = opts.userSignature ?? (await generateSignature(opts.username, opts.timestamp, opts.owner, signer)); + const userFid = opts.userFid ?? signerFid; + const idRegistry = { idOf: jest.fn().mockReturnValue(Promise.resolve(idOfOwner || 0)) } as any; return createTransfer( { timestamp: opts.timestamp, @@ -29,8 +32,9 @@ export async function createTestTransfer(db: Kysely, opts: TestTransfe from: opts.from, to: opts.to, userSignature: bytesToHex(opts.userSignature), - userFid: opts.userFid ?? signerFid, + userFid: userFid, }, - db + db, + idRegistry as any ); } diff --git a/yarn.lock b/yarn.lock index 633154b7..f43e2bf9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1257,6 +1257,14 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== +"@typechain/ethers-v6@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@typechain/ethers-v6/-/ethers-v6-0.4.0.tgz#fb9e9b8eeadc455fd1fc9048b2340309860deaca" + integrity sha512-vD3Agzz63Gf2XlU3ed2/y+8dLWQj+wf+4Eq+0JXsyOio/plyV5F6r0yYe+s3XdGI858U3Sr263pl8mliDrUqbw== + dependencies: + lodash "^4.17.15" + ts-essentials "^7.0.1" + "@types/babel__core@^7.1.14": version "7.20.1" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.1.tgz#916ecea274b0c776fec721e333e55762d3a9614b" @@ -1399,7 +1407,7 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.2.tgz#fa6a90f2600e052a03c18b8cb3fd83dd4e599898" integrity sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw== -"@types/prettier@^2.1.5": +"@types/prettier@^2.1.1", "@types/prettier@^2.1.5": version "2.7.3" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f" integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA== @@ -1723,6 +1731,16 @@ argparse@^2.0.1: resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +array-back@^3.0.1, array-back@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-3.1.0.tgz#b8859d7a508871c9a7b2cf42f99428f65e96bfb0" + integrity sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q== + +array-back@^4.0.1, array-back@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.2.tgz#8004e999a6274586beeb27342168652fdb89fa1e" + integrity sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg== + array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" @@ -1966,7 +1984,7 @@ caniuse-lite@^1.0.30001489: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001495.tgz#64a0ccef1911a9dcff647115b4430f8eff1ef2d9" integrity sha512-F6x5IEuigtUfU5ZMQK2jsy5JqUUlEFRVZq8bO2a+ysq5K7jD6PPc9YXZj78xDNS3uNchesp1Jw47YXEqr+Viyg== -chalk@^2.0.0: +chalk@^2.0.0, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1975,7 +1993,7 @@ chalk@^2.0.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0: +chalk@^4.0.0, chalk@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -2058,6 +2076,26 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" +command-line-args@^5.1.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.2.1.tgz#c44c32e437a57d7c51157696893c5909e9cec42e" + integrity sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg== + dependencies: + array-back "^3.1.0" + find-replace "^3.0.0" + lodash.camelcase "^4.3.0" + typical "^4.0.0" + +command-line-usage@^6.1.0: + version "6.1.3" + resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-6.1.3.tgz#428fa5acde6a838779dfa30e44686f4b6761d957" + integrity sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw== + dependencies: + array-back "^4.0.2" + chalk "^2.4.2" + table-layout "^1.0.2" + typical "^5.2.0" + component-emitter@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" @@ -2181,7 +2219,7 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -2193,6 +2231,11 @@ dedent@^0.7.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== +deep-extend@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -2705,6 +2748,13 @@ finalhandler@1.2.0: statuses "2.0.1" unpipe "~1.0.0" +find-replace@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38" + integrity sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ== + dependencies: + array-back "^3.0.1" + find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -2763,6 +2813,15 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== +fs-extra@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -2822,6 +2881,18 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" +glob@7.1.7: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -2869,7 +2940,7 @@ globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -graceful-fs@^4.2.9: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -3515,7 +3586,7 @@ joycon@^3.1.1: resolved "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03" integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw== -js-sha3@0.8.0: +js-sha3@0.8.0, js-sha3@^0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== @@ -3565,6 +3636,13 @@ json5@^2.2.2, json5@^2.2.3: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== + optionalDependencies: + graceful-fs "^4.1.6" + kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" @@ -3657,6 +3735,11 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== +lodash@^4.17.15: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + long@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" @@ -3794,6 +3877,11 @@ minimist@^1.2.6: resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + module-details-from-path@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.3.tgz#114c949673e2a8a35e9d35788527aa37b679da2b" @@ -4123,7 +4211,7 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier@^2.8.8: +prettier@^2.3.1, prettier@^2.8.8: version "2.8.8" resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== @@ -4277,6 +4365,11 @@ real-require@^0.2.0: resolved "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78" integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== +reduce-flatten@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27" + integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w== + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -4498,6 +4591,11 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== +string-format@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/string-format/-/string-format-2.0.0.tgz#f2df2e7097440d3b65de31b6d40d54c96eaffb9b" + integrity sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA== + string-length@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" @@ -4594,6 +4692,16 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +table-layout@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-1.0.2.tgz#c4038a1853b0136d63365a734b6931cf4fad4a04" + integrity sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A== + dependencies: + array-back "^4.0.1" + deep-extend "~0.6.0" + typical "^5.2.0" + wordwrapjs "^4.0.0" + test-exclude@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" @@ -4642,6 +4750,21 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== +ts-command-line-args@^2.2.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/ts-command-line-args/-/ts-command-line-args-2.5.1.tgz#e64456b580d1d4f6d948824c274cf6fa5f45f7f0" + integrity sha512-H69ZwTw3rFHb5WYpQya40YAX2/w7Ut75uUECbgBIsLmM+BNuYnxsltfyyLMxy6sEeKxgijLTnQtLd0nKd6+IYw== + dependencies: + chalk "^4.1.0" + command-line-args "^5.1.1" + command-line-usage "^6.1.0" + string-format "^2.0.0" + +ts-essentials@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-7.0.3.tgz#686fd155a02133eedcc5362dc8b5056cde3e5a38" + integrity sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ== + ts-jest@^29.1.1: version "29.1.1" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.1.tgz#f58fe62c63caf7bfcc5cc6472082f79180f0815b" @@ -4727,11 +4850,42 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +typechain@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/typechain/-/typechain-8.2.0.tgz#bd4fc8f111d4405e36858bae6f744604617b60f3" + integrity sha512-tZqhqjxJ9xAS/Lh32jccTjMkpx7sTdUVVHAy5Bf0TIer5QFNYXotiX74oCvoVYjyxUKDK3MXHtMFzMyD3kE+jg== + dependencies: + "@types/prettier" "^2.1.1" + debug "^4.3.1" + fs-extra "^7.0.0" + glob "7.1.7" + js-sha3 "^0.8.0" + lodash "^4.17.15" + mkdirp "^1.0.4" + prettier "^2.3.1" + ts-command-line-args "^2.2.0" + ts-essentials "^7.0.1" + typescript@^5.1.6: version "5.1.6" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== +typical@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" + integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw== + +typical@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/typical/-/typical-5.2.0.tgz#4daaac4f2b5315460804f0acf6cb69c52bb93066" + integrity sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg== + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -4810,6 +4964,14 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +wordwrapjs@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-4.0.1.tgz#d9790bccfb110a0fc7836b5ebce0937b37a8b98f" + integrity sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA== + dependencies: + reduce-flatten "^2.0.0" + typical "^5.2.0" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"