UNPKG

0xweb

Version:

Contract package manager and other web3 tools

190 lines (157 loc) 6.27 kB
import di from 'a-di'; import alot from 'alot'; import { Rpc, RpcTypes } from './Rpc'; import { TAbiItem, TAbiOutput } from '@dequanto/types/TAbi'; import { TAddress } from '@dequanto/models/TAddress'; import { $require } from '@dequanto/utils/$require'; import { $abiUtils } from '@dequanto/utils/$abiUtils'; import { $abiCoder } from '@dequanto/abi/$abiCoder'; import { RpcError } from './RpcError'; import { $hex } from '@dequanto/utils/$hex'; import { TEth } from '@dequanto/models/TEth'; import { DataLike } from '@dequanto/utils/types'; import { $web3Abi } from '@dequanto/clients/utils/$web3Abi'; import { BlockDateResolver } from '@dequanto/blocks/BlockDateResolver'; import type { Web3Client } from '@dequanto/clients/Web3Client'; type TRpcContract = { address?: TAddress abi?: TAbiItem[] }; type TRpcContractCallBase = { address?: TAddress abi?: string | TAbiItem | TAbiItem[] method?: string params?: any[] data?: TEth.Hex value?: bigint from?: TAddress blockNumber?: DataLike<RpcTypes.BlockNumberOrTagOrHash> | Date } export type TRpcContractCall = (TRpcContractCallBase & Pick<TRpcContractCallBase, 'method' | 'params'>) | (TRpcContractCallBase & Pick<TRpcContractCallBase, 'data'>) export class RpcContract { constructor(public client: Web3Client, private defaults?: TRpcContract) { } async request (req: TRpcContractCall) { let { methodAbi, methodRequest } = await this.getCallRequestRaw(req); try { let hex = await this.client.request(methodRequest); if ($hex.isEmpty(hex)) { return null; } if (methodAbi == null) { return hex; } return utils.deserializeOutput(hex, methodAbi.outputs); } catch (err) { if (err instanceof RpcError) { err.message = `RpcCall ${req.method} (${JSON.stringify(req.params)}) ${err.message}`; } throw err; } } async batch (req: TRpcContractCall[]) { let requests = await this.getCallRequestsRaw(req); try { let responseArr = await this.client.batch(requests.map(x => x.methodRequest)); return responseArr.map((resp, i) => { let abi = requests[i].methodAbi; if (abi == null) { return resp; } let outputs = abi.outputs; let hex = typeof resp === 'string' ? resp : resp.data; let result = utils.deserializeOutput(hex, outputs); return result; }); } catch (err) { if (err instanceof RpcError) { err.message = `RpcCall ${req.map(x => x.method).join(', ')} ${err.message}`; } throw err; } } private async getCallRequestsRaw (requests: TRpcContractCall[]) { return alot(requests).mapAsync(req => this.getCallRequestRaw(req)).toArrayAsync(); } private async getCallRequestRaw (request: TRpcContractCall) { let address = request.address ?? this.defaults?.address; $require.Address(address); let data = request.data; let { abi, method, params, blockNumber } = request; let abiArr = $web3Abi.ensureAbis(abi ?? this.defaults?.abi); let abiItem = abiArr?.find(x => x.name === method); if (abiItem == null) { abiItem = this.defaults?.abi?.find(x => x.name === method); } if (data == null) { $require.notNull(abiItem, `Method ${method} not found. Available methods: ${abiArr?.map(x => x.name) }`); data = $abiUtils.serializeMethodCallData(abiItem, params ?? []); } if (blockNumber instanceof Date) { let resolver = di.resolve(BlockDateResolver, this.client); blockNumber = await resolver.getBlockNumberFor(blockNumber); } let tx = { from: request.from ?? void 0, to: address, value: $hex.ensure(request.value ?? 0n), data: data, }; let rpc = new Rpc(); return { methodRequest: rpc.req.eth_call(tx, blockNumber ?? 'latest'), methodAbi: abiItem }; } // async submit (contract: TRpcContract, req: TRpcContractCallReq) { // let abis = contract.abi; // let abi = abis.find(x => x.name === req.method); // $require.notNull(abi, `Method ${req.method} not found. Available methods: ${abis.map(x => x.name).join(', ')}`); // let sig = $abiUtils.getMethodSignature(abi); // let data = $abiUtils.encode(abi.inputs, req.params); // let result = await this.rpc.eth_call({ // from: req.from, // to: contract.address, // input: sig + data.substring(2), // value: req.value ?? 0n, // }, req.blockNumber ?? 'latest'); // let outputs = abi.outputs; // let results = Array.isArray(result) === false // ? [ result ] // : (outputs.length === 1 // // decode array as single value // ? [ result ] // : result // ); // return $abiCoder.decode(outputs, results) // } // protected getTransaction () { // } } namespace utils { export function deserializeOutput(hex: string, outputs: TAbiOutput[]) { let abi = outputs; let isDynamic: boolean = null; if (outputs.length > 1) { let isNamedTuple = outputs.every(x => x.name != null && x.name !== ''); if (isNamedTuple) { // will return as object abi = [ { type: 'tuple', components: outputs, name: null } ]; isDynamic = false; } } try { let arr = $abiCoder.decode(abi as any, hex, { dynamic: isDynamic }); let value = abi.length === 1 ? arr[0] : arr; return value; } catch (error) { if (outputs.length === 1 && abi.length === 1) { return $abiCoder.decodeSingle(abi[0], hex); } throw error; } } }