From 0ab576957b3e193a68ae6f3ceb15d0b042d39b14 Mon Sep 17 00:00:00 2001 From: Toni Tabak Date: Fri, 16 Sep 2022 14:33:05 +0200 Subject: [PATCH] feat: rpc options, methods, fetch with prop types, imp. types, clenup responseParser --- src/provider/rpc.ts | 130 ++++++++++++++++--------------- src/types/api/openrpc.ts | 36 ++++----- src/types/api/rpc.ts | 132 +------------------------------- src/types/lib.ts | 2 +- src/types/provider.ts | 5 +- src/utils/responseParser/rpc.ts | 31 ++------ 6 files changed, 100 insertions(+), 236 deletions(-) diff --git a/src/provider/rpc.ts b/src/provider/rpc.ts index 8f2388416..f0aee27e2 100644 --- a/src/provider/rpc.ts +++ b/src/provider/rpc.ts @@ -32,6 +32,7 @@ import { ProviderInterface } from './interface'; import { Block, BlockIdentifier } from './utils'; export type RpcProviderOptions = { nodeUrl: string }; +type Options = { retries: number }; export class RpcProvider implements ProviderInterface { public nodeUrl: string; @@ -41,9 +42,12 @@ export class RpcProvider implements ProviderInterface { private responseParser = new RPCResponseParser(); - constructor(optionsOrProvider: RpcProviderOptions) { + private options: Options; + + constructor(optionsOrProvider: RpcProviderOptions, options: Options) { const { nodeUrl } = optionsOrProvider; this.nodeUrl = nodeUrl; + this.options = options; this.getChainId().then((chainId) => { this.chainId = chainId; @@ -67,13 +71,13 @@ export class RpcProvider implements ProviderInterface { protected async fetchEndpoint( method: T, - request?: RPC.Methods[T]['REQUEST'] - ): Promise { + params?: RPC.Methods[T]['params'] + ): Promise { try { - const rawResult = await this.fetch(method, request); + const rawResult = await this.fetch(method, params); const { error, result } = await rawResult.json(); this.errorHandler(error); - return result as RPC.Methods[T]['RESPONSE']; + return result as RPC.Methods[T]['result']; } catch (error: any) { this.errorHandler(error?.response?.data); throw error; @@ -100,14 +104,14 @@ export class RpcProvider implements ProviderInterface { blockIdentifier: BlockIdentifier = 'pending' ): Promise { const block = new Block(blockIdentifier); - return this.fetchEndpoint('starknet_getBlockWithTxHashes', [block.identifier]); + return this.fetchEndpoint('starknet_getBlockWithTxHashes', { block_id: block.identifier }); } public async getBlockWithTxs( blockIdentifier: BlockIdentifier = 'pending' ): Promise { const block = new Block(blockIdentifier); - return this.fetchEndpoint('starknet_getBlockWithTxs', [block.identifier]); + return this.fetchEndpoint('starknet_getBlockWithTxs', { block_id: block.identifier }); } public async getClassHashAt( @@ -115,11 +119,14 @@ export class RpcProvider implements ProviderInterface { contractAddress: RPC.ContractAddress ): Promise { const block = new Block(blockIdentifier); - return this.fetchEndpoint('starknet_getClassHashAt', [block.identifier, contractAddress]); + return this.fetchEndpoint('starknet_getClassHashAt', { + block_id: block.identifier, + contract_address: contractAddress, + }); } - public async getNonce(contractAddress: string): Promise { - return this.fetchEndpoint('starknet_getNonce', [contractAddress]); + public async getNonce(contractAddress: string): Promise { + return this.fetchEndpoint('starknet_getNonce', { contract_address: contractAddress }); } public async getPendingTransactions(): Promise { @@ -132,7 +139,7 @@ export class RpcProvider implements ProviderInterface { public async getStateUpdate(blockIdentifier: BlockIdentifier): Promise { const block = new Block(blockIdentifier); - return this.fetchEndpoint('starknet_getStateUpdate', [block.identifier]); + return this.fetchEndpoint('starknet_getStateUpdate', { block_id: block.identifier }); } public async getStorageAt( @@ -142,48 +149,50 @@ export class RpcProvider implements ProviderInterface { ): Promise { const parsedKey = toHex(toBN(key)); const block = new Block(blockIdentifier); - return this.fetchEndpoint('starknet_getStorageAt', [ - contractAddress, - parsedKey, - block.identifier, - ]); + return this.fetchEndpoint('starknet_getStorageAt', { + contract_address: contractAddress, + key: parsedKey, + block_id: block.identifier, + }); } // Methods from Interface - public async getTransaction(txHash: BigNumberish): Promise { + public async getTransaction(txHash: string): Promise { return this.getTransactionByHash(txHash).then(this.responseParser.parseGetTransactionResponse); } - public async getTransactionByHash( - txHash: BigNumberish - ): Promise { - return this.fetchEndpoint('starknet_getTransactionByHash', [txHash]); + public async getTransactionByHash(txHash: string): Promise { + const transaction_hash = txHash; + return this.fetchEndpoint('starknet_getTransactionByHash', { transaction_hash }); } public async getTransactionByBlockIdAndIndex( blockIdentifier: BlockIdentifier, index: number ): Promise { - const block = new Block(blockIdentifier); - return this.fetchEndpoint('starknet_getTransactionByBlockIdAndIndex', [ - block.identifier, - index, - ]); + const block_id = new Block(blockIdentifier).identifier; + return this.fetchEndpoint('starknet_getTransactionByBlockIdAndIndex', { block_id, index }); } - public async getTransactionReceipt(txHash: BigNumberish): Promise { - return this.fetchEndpoint('starknet_getTransactionReceipt', [txHash]).then( + public async getTransactionReceipt(txHash: string): Promise { + return this.fetchEndpoint('starknet_getTransactionReceipt', { transaction_hash: txHash }).then( this.responseParser.parseGetTransactionReceiptResponse ); } public async getClass(classHash: RPC.Felt): Promise { - return this.fetchEndpoint('starknet_getClass', [classHash]); + return this.fetchEndpoint('starknet_getClass', { class_hash: classHash }); } - public async getClassAt(contractAddress: string, blockIdentifier: BlockIdentifier): Promise { - const block = new Block(blockIdentifier); - return this.fetchEndpoint('starknet_getClassAt', [block.identifier, contractAddress]); + public async getClassAt( + contractAddress: string, + blockIdentifier: BlockIdentifier + ): Promise { + const block_id = new Block(blockIdentifier).identifier; + return this.fetchEndpoint('starknet_getClassAt', { + block_id, + contract_address: contractAddress, + }); } public async getCode( @@ -199,16 +208,17 @@ export class RpcProvider implements ProviderInterface { invocationDetails: InvocationsDetails = {} ): Promise { const block_id = new Block(blockIdentifier).identifier; - return this.fetchEndpoint('starknet_estimateFee', [ - { + return this.fetchEndpoint('starknet_estimateFee', { + request: { contract_address: invocation.contractAddress, entry_point_selector: getSelectorFromName(invocation.entrypoint), calldata: parseCalldata(invocation.calldata), signature: bigNumberishArrayToHexadecimalStringArray(invocation.signature || []), version: toHex(toBN(invocationDetails?.version || 0)), + max_fee: toHex(toBN(invocationDetails?.maxFee || 0)), }, block_id, - ]).then(this.responseParser.parseFeeEstimateResponse); + }).then(this.responseParser.parseFeeEstimateResponse); } public async declareContract({ @@ -217,14 +227,14 @@ export class RpcProvider implements ProviderInterface { }: DeclareContractPayload): Promise { const contractDefinition = parseContract(contract); - return this.fetchEndpoint('starknet_addDeclareTransaction', [ - { + return this.fetchEndpoint('starknet_addDeclareTransaction', { + contract_class: { program: contractDefinition.program, entry_points_by_type: contractDefinition.entry_points_by_type, abi: contractDefinition.abi, // rpc 2.0 }, - toHex(toBN(version || 0)), - ]).then(this.responseParser.parseDeclareContractResponse); + version: toHex(toBN(version || 0)), + }); } public async deployContract({ @@ -234,31 +244,31 @@ export class RpcProvider implements ProviderInterface { }: DeployContractPayload): Promise { const contractDefinition = parseContract(contract); - return this.fetchEndpoint('starknet_addDeployTransaction', [ - addressSalt ?? randomAddress(), - bigNumberishArrayToHexadecimalStringArray(constructorCalldata ?? []), - { + return this.fetchEndpoint('starknet_addDeployTransaction', { + contract_address_salt: addressSalt ?? randomAddress(), + constructor_calldata: bigNumberishArrayToHexadecimalStringArray(constructorCalldata ?? []), + contract_definition: { program: contractDefinition.program, entry_points_by_type: contractDefinition.entry_points_by_type, abi: contractDefinition.abi, // rpc 2.0 }, - ]).then(this.responseParser.parseDeployContractResponse); + }); } public async invokeFunction( functionInvocation: Invocation, details: InvocationsDetails ): Promise { - return this.fetchEndpoint('starknet_addInvokeTransaction', [ - { + return this.fetchEndpoint('starknet_addInvokeTransaction', { + function_invocation: { contract_address: functionInvocation.contractAddress, entry_point_selector: getSelectorFromName(functionInvocation.entrypoint), calldata: parseCalldata(functionInvocation.calldata), }, - bigNumberishArrayToHexadecimalStringArray(functionInvocation.signature || []), - toHex(toBN(details.maxFee || 0)), - toHex(toBN(details.version || 0)), - ]).then(this.responseParser.parseInvokeFunctionResponse); + signature: bigNumberishArrayToHexadecimalStringArray(functionInvocation.signature || []), + max_fee: toHex(toBN(details.maxFee || 0)), + version: toHex(toBN(details.version || 0)), + }); } // Methods from Interface @@ -267,29 +277,29 @@ export class RpcProvider implements ProviderInterface { blockIdentifier: BlockIdentifier = 'pending' ): Promise { const block_id = new Block(blockIdentifier).identifier; - const result = await this.fetchEndpoint('starknet_call', [ - { + const result = await this.fetchEndpoint('starknet_call', { + request: { contract_address: call.contractAddress, entry_point_selector: getSelectorFromName(call.entrypoint), calldata: parseCalldata(call.calldata), }, block_id, - ]); + }); return this.responseParser.parseCallContractResponse(result); } public async traceTransaction(transactionHash: RPC.TransactionHash): Promise { - return this.fetchEndpoint('starknet_traceTransaction', [transactionHash]); + return this.fetchEndpoint('starknet_traceTransaction', { transaction_hash: transactionHash }); } public async traceBlockTransactions(blockHash: RPC.BlockHash): Promise { - return this.fetchEndpoint('starknet_traceBlockTransactions', [blockHash]); + return this.fetchEndpoint('starknet_traceBlockTransactions', { block_hash: blockHash }); } - public async waitForTransaction(txHash: BigNumberish, retryInterval: number = 8000) { + public async waitForTransaction(txHash: string, retryInterval: number = 8000) { + let retries = this.options?.retries || 200; let onchain = false; - let retries = 200; while (!onchain) { const successStates = ['ACCEPTED_ON_L1', 'ACCEPTED_ON_L2', 'PENDING']; @@ -315,7 +325,7 @@ export class RpcProvider implements ProviderInterface { } if (retries === 0) { - throw error; + throw new Error('waitForTransaction timedout with retries'); } } @@ -336,7 +346,7 @@ export class RpcProvider implements ProviderInterface { blockIdentifier: BlockIdentifier ): Promise { const block = new Block(blockIdentifier); - return this.fetchEndpoint('starknet_getBlockTransactionCount', [block.identifier]); + return this.fetchEndpoint('starknet_getBlockTransactionCount', { block_id: block.identifier }); } /** @@ -366,6 +376,6 @@ export class RpcProvider implements ProviderInterface { * @returns events and the pagination of the events */ public async getEvents(eventFilter: RPC.EventFilter): Promise { - return this.fetchEndpoint('starknet_getEvents', [eventFilter]); + return this.fetchEndpoint('starknet_getEvents', { filter: eventFilter }); } } diff --git a/src/types/api/openrpc.ts b/src/types/api/openrpc.ts index 22d7e1509..6d16c9ef1 100644 --- a/src/types/api/openrpc.ts +++ b/src/types/api/openrpc.ts @@ -8,26 +8,14 @@ * TypeScript Representation of OpenRpc protocol types */ -/** - * "type": "string", - * "title": "Field element", - * "$comment": "A field element, represented as a string of hex digits", - * "description": "A field element. Represented as up to 63 hex digits and leading 4 bits zeroed.", - * "pattern": "^0x0[a-fA-F0-9]{1,63}$" - */ export type FELT = string; export type ADDRESS = FELT; -/** - * "title": "An integer number in hex format (0x...)", - * "pattern": "^0x[a-fA-F0-9]+$" - */ type NUM_AS_HEX = string; type SIGNATURE = Array; type ETH_ADDRESS = string; type BLOCK_NUMBER = number; type BLOCK_HASH = FELT; type TXN_HASH = FELT; -type CHAIN_ID = string; type PROTOCOL_VERSION = string; type TXN_STATUS = 'PENDING' | 'ACCEPTED_ON_L2' | 'ACCEPTED_ON_L1' | 'REJECTED'; type TXN_TYPE = 'DECLARE' | 'DEPLOY' | 'INVOKE' | 'L1_HANDLER'; @@ -68,7 +56,6 @@ type PENDING_COMMON_RECEIPT_PROPERTIES = { actual_fee: FELT; }; type INVOKE_TXN_RECEIPT = COMMON_RECEIPT_PROPERTIES & INVOKE_TXN_RECEIPT_PROPERTIES; -// type L1_HANDLER_TXN_RECEIPT = COMMON_RECEIPT_PROPERTIES; type DECLARE_TXN_RECEIPT = COMMON_RECEIPT_PROPERTIES; type DEPLOY_TXN_RECEIPT = COMMON_RECEIPT_PROPERTIES; type PENDING_INVOKE_TXN_RECEIPT = PENDING_COMMON_RECEIPT_PROPERTIES & INVOKE_TXN_RECEIPT_PROPERTIES; @@ -98,13 +85,14 @@ type PENDING_BLOCK_WITH_TX_HASHES = BLOCK_BODY_WITH_TX_HASHES & { sequencer_address: FELT; parent_hash: BLOCK_HASH; }; +// transaction_hash, nonce, type optional because of pathfinder not implemented type COMMON_TXN_PROPERTIES = { - transaction_hash: TXN_HASH; + transaction_hash?: TXN_HASH; max_fee: FELT; version: NUM_AS_HEX; signature: SIGNATURE; - nonce: FELT; - type: TXN_TYPE; + nonce?: FELT; + type?: TXN_TYPE; }; type FUNCTION_CALL = { contract_address: ADDRESS; @@ -138,6 +126,7 @@ type PENDING_BLOCK_WITH_TXS = BLOCK_BODY_WITH_TXS & { sequencer_address: FELT; parent_hash: BLOCK_HASH; }; + type CONTRACT_CLASS = { program: string; // A base64 representation of the compressed program code entry_points_by_type: { @@ -234,6 +223,7 @@ type TRACE_ROOT = { }; export namespace OPENRPC { + export type Nonce = FELT; export type BlockWithTxHashes = BLOCK_WITH_TX_HASHES | PENDING_BLOCK_WITH_TX_HASHES; export type BlockWithTxs = BLOCK_WITH_TXS | PENDING_BLOCK_WITH_TXS; export type StateUpdate = STATE_UPDATE; @@ -248,7 +238,7 @@ export namespace OPENRPC { block_hash: BLOCK_HASH; block_number: BLOCK_NUMBER; }; - export type ChainId = CHAIN_ID; + export type CHAIN_ID = string; export type PendingTransactions = Array; export type ProtocolVersion = PROTOCOL_VERSION; export type SyncingStatus = false | SYNC_STATUS; @@ -257,7 +247,6 @@ export namespace OPENRPC { page_number: number; is_last_page: boolean; }; - export type Nonce = FELT; export type Trace = TRACE_ROOT; export type Traces = Array<{ transaction_hash: FELT; @@ -265,7 +254,7 @@ export namespace OPENRPC { }>; export type TransactionHash = TXN_HASH; export type BlockHash = BLOCK_HASH; - export type EventFilter = EVENT_FILTER; + export type EventFilter = EVENT_FILTER & RESULT_PAGE_REQUEST; export type InvokedTransaction = { transaction_hash: TXN_HASH }; export type DeclaredTransaction = { transaction_hash: TXN_HASH; class_hash: FELT }; export type DeployedTransaction = { transaction_hash: TXN_HASH; contract_address: FELT }; @@ -349,25 +338,30 @@ export namespace OPENRPC { | Errors.INVALID_BLOCK_ID; }; starknet_blockNumber: { + params: {}; result: BLOCK_NUMBER; errors: Errors.NO_BLOCKS; }; starknet_blockHashAndNumber: { + params: {}; result: BLOCK_HASH & BLOCK_NUMBER; errors: Errors.NO_BLOCKS; }; starknet_chainId: { + params: {}; result: CHAIN_ID; }; starknet_pendingTransactions: { + params: {}; result: PendingTransactions; }; starknet_syncing: { + params: {}; result: SyncingStatus; }; starknet_getEvents: { params: { filter: EVENT_FILTER & RESULT_PAGE_REQUEST }; - result: { events: EMITTED_EVENT; page_number: number; is_last_page: boolean }; + result: Events; errors: Errors.PAGE_SIZE_TOO_BIG; }; starknet_getNonce: { @@ -397,7 +391,7 @@ export namespace OPENRPC { starknet_addDeployTransaction: { params: { contract_address_salt: FELT; - constructor_calldata: FELT; + constructor_calldata: Array; contract_definition: CONTRACT_CLASS; }; result: DeployedTransaction; diff --git a/src/types/api/rpc.ts b/src/types/api/rpc.ts index 4dacb26be..05e575676 100644 --- a/src/types/api/rpc.ts +++ b/src/types/api/rpc.ts @@ -11,9 +11,11 @@ export namespace RPC { }; }; - export type ChainId = OPENRPC.ChainId; + export type ChainId = OPENRPC.CHAIN_ID; + export type CallResponse = OPENRPC.CallResponse; export type ContractAddress = ADDRESS; export type Felt = FELT; + export type Nonce = OPENRPC.Nonce; export type ContractClass = OPENRPC.ContractClass; export type StateUpdate = OPENRPC.StateUpdate; export type Transaction = OPENRPC.Transaction; @@ -40,131 +42,5 @@ export namespace RPC { export type DeclaredTransaction = OPENRPC.DeclaredTransaction; export type DeployedTransaction = OPENRPC.DeployedTransaction; - export type Methods = { - starknet_pendingTransactions: { - QUERY: never; - REQUEST: any[]; - RESPONSE: PendingTransactions; - }; - starknet_blockHashAndNumber: { - QUERY: never; - REQUEST: any[]; - RESPONSE: BlockHashAndNumber; - }; - starknet_getClassHashAt: { - QUERY: never; - REQUEST: any[]; - RESPONSE: Felt; - }; - starknet_getStateUpdate: { - QUERY: never; - REQUEST: any[]; - RESPONSE: OPENRPC.StateUpdate; - }; - starknet_getClass: { - QUERY: never; - REQUEST: any[]; - RESPONSE: OPENRPC.ContractClass; - }; - starknet_getBlockWithTxHashes: { - QUERY: never; - REQUEST: any[]; - RESPONSE: GetBlockWithTxHashesResponse; - }; - starknet_getBlockWithTxs: { - QUERY: never; - REQUEST: any[]; - RESPONSE: GetBlockWithTxs; - }; - starknet_getNonce: { - QUERY: never; - REQUEST: any[]; - RESPONSE: OPENRPC.Nonce; - }; - starknet_getStorageAt: { - QUERY: never; - REQUEST: any[]; - RESPONSE: GetStorageAtResponse; - }; - starknet_getTransactionByHash: { - QUERY: never; - REQUEST: any[]; - RESPONSE: GetTransactionByHashResponse; - }; - starknet_getTransactionByBlockIdAndIndex: { - QUERY: never; - REQUEST: any[]; - RESPONSE: GetTransactionByBlockIdAndIndex; - }; - starknet_getTransactionReceipt: { - QUERY: never; - REQUEST: any[]; - RESPONSE: TransactionReceipt; - }; - starknet_getBlockTransactionCount: { - QUERY: never; - REQUEST: any[]; - RESPONSE: GetTransactionCountResponse; - }; - starknet_call: { - QUERY: never; - REQUEST: any[]; - RESPONSE: string[]; - }; - starknet_estimateFee: { - QUERY: never; - REQUEST: any[]; - RESPONSE: OPENRPC.EstimatedFee; - }; - starknet_blockNumber: { - QUERY: never; - REQUEST: any[]; - RESPONSE: GetBlockNumberResponse; - }; - starknet_chainId: { - QUERY: never; - REQUEST: any[]; - RESPONSE: OPENRPC.ChainId; - }; - starknet_syncing: { - QUERY: never; - REQUEST: any[]; - RESPONSE: GetSyncingStatsResponse; - }; - starknet_getEvents: { - QUERY: never; - REQUEST: any[]; - RESPONSE: GetEventsResponse; - }; - starknet_addInvokeTransaction: { - QUERY: never; - REQUEST: any[]; - RESPONSE: OPENRPC.InvokedTransaction; - }; - starknet_addDeployTransaction: { - QUERY: never; - REQUEST: any[]; - RESPONSE: OPENRPC.DeployedTransaction; - }; - starknet_addDeclareTransaction: { - QUERY: never; - REQUEST: any[]; - RESPONSE: OPENRPC.DeclaredTransaction; - }; - starknet_getClassAt: { - QUERY: never; - REQUEST: any[]; - RESPONSE: any; - }; - starknet_traceTransaction: { - QUERY: never; - REQUEST: any[]; - RESPONSE: any; - }; - starknet_traceBlockTransactions: { - QUERY: never; - REQUEST: any[]; - RESPONSE: any; - }; - }; + export type Methods = OPENRPC.Methods; } diff --git a/src/types/lib.ts b/src/types/lib.ts index c9b4b8c36..947c3f780 100644 --- a/src/types/lib.ts +++ b/src/types/lib.ts @@ -9,7 +9,7 @@ export type RawCalldata = BigNumberish[]; export type DeployContractPayload = { contract: CompiledContract | string; constructorCalldata?: RawCalldata; - addressSalt?: BigNumberish; + addressSalt?: string; }; export type DeclareContractPayload = { diff --git a/src/types/provider.ts b/src/types/provider.ts index 8504c8c62..9722338f7 100644 --- a/src/types/provider.ts +++ b/src/types/provider.ts @@ -4,7 +4,8 @@ */ import BN from 'bn.js'; -import { Abi, CompressedProgram, EntryPointsByType, RawCalldata, Signature, Status } from './lib'; +import { RPC } from './api/rpc'; +import { Abi, CompressedProgram, RawCalldata, Signature, Status } from './lib'; export interface GetBlockResponse { timestamp: number; @@ -44,7 +45,7 @@ export interface ContractEntryPoint { export interface ContractClass { program: CompressedProgram; - entry_points_by_type: EntryPointsByType; + entry_points_by_type: RPC.ContractClass['entry_points_by_type']; abi?: Abi; } diff --git a/src/utils/responseParser/rpc.ts b/src/utils/responseParser/rpc.ts index 13d655006..efcf483e0 100644 --- a/src/utils/responseParser/rpc.ts +++ b/src/utils/responseParser/rpc.ts @@ -4,13 +4,10 @@ */ import { CallContractResponse, - DeclareContractResponse, - DeployContractResponse, EstimateFeeResponse, GetBlockResponse, GetTransactionReceiptResponse, GetTransactionResponse, - InvokeFunctionResponse, } from '../../types'; import { RPC } from '../../types/api'; import { toBN } from '../number'; @@ -28,7 +25,13 @@ type TransactionReceipt = RPC.TransactionReceipt & { [key: string]: any; }; -export class RPCResponseParser extends ResponseParser { +export class RPCResponseParser + implements + Omit< + ResponseParser, + 'parseDeclareContractResponse' | 'parseDeployContractResponse' | 'parseInvokeFunctionResponse' + > +{ public parseGetBlockResponse(res: RpcGetBlockResponse): GetBlockResponse { return { timestamp: res.timestamp, @@ -81,24 +84,4 @@ export class RPCResponseParser extends ResponseParser { result: res, }; } - - public parseInvokeFunctionResponse(res: RPC.InvokedTransaction): InvokeFunctionResponse { - return { - transaction_hash: res.transaction_hash, - }; - } - - public parseDeployContractResponse(res: RPC.DeployedTransaction): DeployContractResponse { - return { - transaction_hash: res.transaction_hash, - contract_address: res.contract_address, - }; - } - - public parseDeclareContractResponse(res: RPC.DeclaredTransaction): DeclareContractResponse { - return { - transaction_hash: res.transaction_hash, - class_hash: res.class_hash, - }; - } }