From de329361d838157b4fbe2b37bb043c3235b97a14 Mon Sep 17 00:00:00 2001 From: Petar Penovic Date: Mon, 19 Feb 2024 09:45:08 +0100 Subject: [PATCH] feat: implement RPC v0.7.0-rc2 specification --- __tests__/utils/stark.test.ts | 3 +- __tests__/utils/transactionHash.test.ts | 2 +- src/channel/index.ts | 4 +- src/channel/rpc_0_6.ts | 21 +- src/channel/rpc_0_7.ts | 669 +++++++++++++++++++++++ src/provider/interface.ts | 4 +- src/provider/rpc.ts | 4 +- src/types/api/index.ts | 5 +- src/types/api/rpcspec_0_7/components.ts | 674 ++++++++++++++++++++++++ src/types/api/rpcspec_0_7/contract.ts | 1 + src/types/api/rpcspec_0_7/errors.ts | 1 + src/types/api/rpcspec_0_7/index.ts | 9 + src/types/api/rpcspec_0_7/methods.ts | 330 ++++++++++++ src/types/api/rpcspec_0_7/nonspec.ts | 189 +++++++ src/types/provider/response.ts | 91 ++-- src/types/provider/spec.ts | 102 ++++ src/utils/provider.ts | 2 +- src/utils/responseParser/rpc.ts | 37 +- src/utils/stark.ts | 9 +- 19 files changed, 2082 insertions(+), 75 deletions(-) create mode 100644 src/channel/rpc_0_7.ts create mode 100644 src/types/api/rpcspec_0_7/components.ts create mode 100644 src/types/api/rpcspec_0_7/contract.ts create mode 100644 src/types/api/rpcspec_0_7/errors.ts create mode 100644 src/types/api/rpcspec_0_7/index.ts create mode 100644 src/types/api/rpcspec_0_7/methods.ts create mode 100644 src/types/api/rpcspec_0_7/nonspec.ts create mode 100644 src/types/provider/spec.ts diff --git a/__tests__/utils/stark.test.ts b/__tests__/utils/stark.test.ts index f2247e551..66257c698 100644 --- a/__tests__/utils/stark.test.ts +++ b/__tests__/utils/stark.test.ts @@ -1,5 +1,6 @@ import { CallData, RawArgs, UniversalDetails, json, stark } from '../../src'; -import { EDataAvailabilityMode, FeeEstimate } from '../../src/types/api'; +import { EDataAvailabilityMode } from '../../src/types/api'; +import { FeeEstimate } from '../../src/types/provider'; import { toBigInt, toHex } from '../../src/utils/num'; import { compiledOpenZeppelinAccount } from '../config/fixtures'; diff --git a/__tests__/utils/transactionHash.test.ts b/__tests__/utils/transactionHash.test.ts index 40df46bd1..9d2cd5c99 100644 --- a/__tests__/utils/transactionHash.test.ts +++ b/__tests__/utils/transactionHash.test.ts @@ -1,5 +1,5 @@ import { constants, hash, shortString, types, v2hash, v3hash } from '../../src'; -import { ResourceBounds } from '../../src/types/api/rpcspec_0_6'; +import { ResourceBounds } from '../../src/types/api'; describe('TxV2 Hash Tests', () => { describe('calculateTransactionHashCommon()', () => { diff --git a/src/channel/index.ts b/src/channel/index.ts index 4e0bb013b..f210bfe3e 100644 --- a/src/channel/index.ts +++ b/src/channel/index.ts @@ -1 +1,3 @@ -export * from './rpc_0_6'; +export * as RPC06 from './rpc_0_6'; +export * as RPC07 from './rpc_0_7'; +export * from './rpc_0_7'; diff --git a/src/channel/rpc_0_6.ts b/src/channel/rpc_0_6.ts index eea7a9c13..ec2eb24c1 100644 --- a/src/channel/rpc_0_6.ts +++ b/src/channel/rpc_0_6.ts @@ -11,14 +11,13 @@ import { DeployAccountContractTransaction, Invocation, InvocationsDetailsWithNonce, - RPC, RpcProviderOptions, TransactionType, getEstimateFeeBulkOptions, getSimulateTransactionOptions, waitForTransactionOptions, } from '../types'; -import { ETransactionVersion } from '../types/api'; +import { JRPC, RPCSPEC06 as RPC } from '../types/api'; import { CallData } from '../utils/calldata'; import { isSierra } from '../utils/contract'; import fetch from '../utils/fetchPonyfill'; @@ -72,7 +71,7 @@ export class RpcChannel { } public fetch(method: string, params?: object, id: string | number = 0) { - const rpcRequestBody: RPC.JRPC.RequestBody = { + const rpcRequestBody: JRPC.RequestBody = { id, jsonrpc: '2.0', method, @@ -85,7 +84,7 @@ export class RpcChannel { }); } - protected errorHandler(method: string, params: any, rpcError?: RPC.JRPC.Error, otherError?: any) { + protected errorHandler(method: string, params: any, rpcError?: JRPC.Error, otherError?: any) { if (rpcError) { const { code, message, data } = rpcError; throw new LibraryError( @@ -398,7 +397,7 @@ export class RpcChannel { calldata: CallData.toHex(functionInvocation.calldata), type: RPC.ETransactionType.INVOKE, max_fee: toHex(details.maxFee || 0), - version: ETransactionVersion.V1, + version: RPC.ETransactionVersion.V1, signature: signatureToHexArray(functionInvocation.signature), nonce: toHex(details.nonce), }, @@ -410,7 +409,7 @@ export class RpcChannel { type: RPC.ETransactionType.INVOKE, sender_address: functionInvocation.contractAddress, calldata: CallData.toHex(functionInvocation.calldata), - version: ETransactionVersion.V3, + version: RPC.ETransactionVersion.V3, signature: signatureToHexArray(functionInvocation.signature), nonce: toHex(details.nonce), resource_bounds: details.resourceBounds, @@ -441,7 +440,7 @@ export class RpcChannel { entry_points_by_type: contract.entry_points_by_type, abi: contract.abi, }, - version: ETransactionVersion.V1, + version: RPC.ETransactionVersion.V1, max_fee: toHex(details.maxFee || 0), signature: signatureToHexArray(signature), sender_address: senderAddress, @@ -460,7 +459,7 @@ export class RpcChannel { abi: contract.abi, }, compiled_class_hash: compiledClassHash || '', - version: ETransactionVersion.V2, + version: RPC.ETransactionVersion.V2, max_fee: toHex(details.maxFee || 0), signature: signatureToHexArray(signature), sender_address: senderAddress, @@ -474,7 +473,7 @@ export class RpcChannel { type: RPC.ETransactionType.DECLARE, sender_address: senderAddress, compiled_class_hash: compiledClassHash || '', - version: ETransactionVersion.V3, + version: RPC.ETransactionVersion.V3, signature: signatureToHexArray(signature), nonce: toHex(details.nonce), contract_class: { @@ -512,7 +511,7 @@ export class RpcChannel { contract_address_salt: toHex(addressSalt || 0), type: RPC.ETransactionType.DEPLOY_ACCOUNT, max_fee: toHex(details.maxFee || 0), - version: ETransactionVersion.V1, + version: RPC.ETransactionVersion.V1, signature: signatureToHexArray(signature), nonce: toHex(details.nonce), }, @@ -522,7 +521,7 @@ export class RpcChannel { promise = this.fetchEndpoint('starknet_addDeployAccountTransaction', { deploy_account_transaction: { type: RPC.ETransactionType.DEPLOY_ACCOUNT, - version: ETransactionVersion.V3, + version: RPC.ETransactionVersion.V3, signature: signatureToHexArray(signature), nonce: toHex(details.nonce), contract_address_salt: toHex(addressSalt || 0), diff --git a/src/channel/rpc_0_7.ts b/src/channel/rpc_0_7.ts new file mode 100644 index 000000000..51146ad18 --- /dev/null +++ b/src/channel/rpc_0_7.ts @@ -0,0 +1,669 @@ +import { NetworkName, StarknetChainId } from '../constants'; +import { LibraryError } from '../provider/errors'; +import { + AccountInvocationItem, + AccountInvocations, + BigNumberish, + BlockIdentifier, + BlockTag, + Call, + DeclareContractTransaction, + DeployAccountContractTransaction, + Invocation, + InvocationsDetailsWithNonce, + RpcProviderOptions, + TransactionType, + getEstimateFeeBulkOptions, + getSimulateTransactionOptions, + waitForTransactionOptions, +} from '../types'; +import { JRPC, RPCSPEC07 as RPC } from '../types/api'; +import { CallData } from '../utils/calldata'; +import { isSierra } from '../utils/contract'; +import fetch from '../utils/fetchPonyfill'; +import { getSelector, getSelectorFromName } from '../utils/hash'; +import { stringify } from '../utils/json'; +import { getHexStringArray, toHex, toStorageKey } from '../utils/num'; +import { Block, getDefaultNodeUrl, isV3Tx, isVersion, wait } from '../utils/provider'; +import { decompressProgram, signatureToHexArray } from '../utils/stark'; +import { getVersionsByType } from '../utils/transaction'; + +const defaultOptions = { + headers: { 'Content-Type': 'application/json' }, + blockIdentifier: BlockTag.pending, + retries: 200, +}; + +export class RpcChannel { + public nodeUrl: string; + + public headers: object; + + readonly retries: number; + + public requestId: number; + + readonly blockIdentifier: BlockIdentifier; + + private chainId?: StarknetChainId; + + private speckVersion?: string; + + readonly waitMode: Boolean; // behave like web2 rpc and return when tx is processed + + constructor(optionsOrProvider?: RpcProviderOptions) { + const { nodeUrl, retries, headers, blockIdentifier, chainId, waitMode } = + optionsOrProvider || {}; + if (Object.values(NetworkName).includes(nodeUrl as NetworkName)) { + this.nodeUrl = getDefaultNodeUrl(nodeUrl as NetworkName, optionsOrProvider?.default); + } else if (nodeUrl) { + this.nodeUrl = nodeUrl; + } else { + this.nodeUrl = getDefaultNodeUrl(undefined, optionsOrProvider?.default); + } + this.retries = retries || defaultOptions.retries; + this.headers = { ...defaultOptions.headers, ...headers }; + this.blockIdentifier = blockIdentifier || defaultOptions.blockIdentifier; + this.chainId = chainId; + this.waitMode = waitMode || false; + this.requestId = 0; + } + + public fetch(method: string, params?: object, id: string | number = 0) { + const rpcRequestBody: JRPC.RequestBody = { + id, + jsonrpc: '2.0', + method, + ...(params && { params }), + }; + return fetch(this.nodeUrl, { + method: 'POST', + body: stringify(rpcRequestBody), + headers: this.headers as Record, + }); + } + + protected errorHandler(method: string, params: any, rpcError?: JRPC.Error, otherError?: any) { + if (rpcError) { + const { code, message, data } = rpcError; + throw new LibraryError( + `RPC: ${method} with params ${stringify(params, null, 2)}\n + ${code}: ${message}: ${stringify(data)}` + ); + } + if (otherError instanceof LibraryError) { + throw otherError; + } + if (otherError) { + throw Error(otherError.message); + } + } + + protected async fetchEndpoint( + method: T, + params?: RPC.Methods[T]['params'] + ): Promise { + try { + const rawResult = await this.fetch(method, params, (this.requestId += 1)); + const { error, result } = await rawResult.json(); + this.errorHandler(method, params, error); + return result as RPC.Methods[T]['result']; + } catch (error: any) { + this.errorHandler(method, params, error?.response?.data, error); + throw error; + } + } + + public async getChainId() { + this.chainId ??= (await this.fetchEndpoint('starknet_chainId')) as StarknetChainId; + return this.chainId; + } + + public async getSpecVersion() { + this.speckVersion ??= (await this.fetchEndpoint('starknet_specVersion')) as StarknetChainId; + return this.speckVersion; + } + + public getNonceForAddress( + contractAddress: BigNumberish, + blockIdentifier: BlockIdentifier = this.blockIdentifier + ) { + const contract_address = toHex(contractAddress); + const block_id = new Block(blockIdentifier).identifier; + return this.fetchEndpoint('starknet_getNonce', { + contract_address, + block_id, + }); + } + + /** + * Get the most recent accepted block hash and number + */ + public getBlockLatestAccepted() { + return this.fetchEndpoint('starknet_blockHashAndNumber'); + } + + /** + * Get the most recent accepted block number + * redundant use getBlockLatestAccepted(); + * @returns Number of the latest block + */ + public getBlockNumber() { + return this.fetchEndpoint('starknet_blockNumber'); + } + + public getBlockWithTxHashes(blockIdentifier: BlockIdentifier = this.blockIdentifier) { + const block_id = new Block(blockIdentifier).identifier; + return this.fetchEndpoint('starknet_getBlockWithTxHashes', { block_id }); + } + + public getBlockWithTxs(blockIdentifier: BlockIdentifier = this.blockIdentifier) { + const block_id = new Block(blockIdentifier).identifier; + return this.fetchEndpoint('starknet_getBlockWithTxs', { block_id }); + } + + public getBlockStateUpdate(blockIdentifier: BlockIdentifier = this.blockIdentifier) { + const block_id = new Block(blockIdentifier).identifier; + return this.fetchEndpoint('starknet_getStateUpdate', { block_id }); + } + + public getBlockTransactionsTraces(blockIdentifier: BlockIdentifier = this.blockIdentifier) { + const block_id = new Block(blockIdentifier).identifier; + return this.fetchEndpoint('starknet_traceBlockTransactions', { block_id }); + } + + public getBlockTransactionCount(blockIdentifier: BlockIdentifier = this.blockIdentifier) { + const block_id = new Block(blockIdentifier).identifier; + return this.fetchEndpoint('starknet_getBlockTransactionCount', { block_id }); + } + + public getTransactionByHash(txHash: BigNumberish) { + const transaction_hash = toHex(txHash); + return this.fetchEndpoint('starknet_getTransactionByHash', { + transaction_hash, + }); + } + + public getTransactionByBlockIdAndIndex(blockIdentifier: BlockIdentifier, index: number) { + const block_id = new Block(blockIdentifier).identifier; + return this.fetchEndpoint('starknet_getTransactionByBlockIdAndIndex', { block_id, index }); + } + + public getTransactionReceipt(txHash: BigNumberish) { + const transaction_hash = toHex(txHash); + return this.fetchEndpoint('starknet_getTransactionReceipt', { transaction_hash }); + } + + public getTransactionTrace(txHash: BigNumberish) { + const transaction_hash = toHex(txHash); + return this.fetchEndpoint('starknet_traceTransaction', { transaction_hash }); + } + + /** + * Get the status of a transaction + */ + public getTransactionStatus(transactionHash: BigNumberish) { + const transaction_hash = toHex(transactionHash); + return this.fetchEndpoint('starknet_getTransactionStatus', { transaction_hash }); + } + + /** + * @param invocations AccountInvocations + * @param simulateTransactionOptions blockIdentifier and flags to skip validation and fee charge
+ * - blockIdentifier
+ * - skipValidate (default false)
+ * - skipFeeCharge (default true)
+ */ + public simulateTransaction( + invocations: AccountInvocations, + { + blockIdentifier = this.blockIdentifier, + skipValidate = true, + skipFeeCharge = true, + }: getSimulateTransactionOptions = {} + ) { + const block_id = new Block(blockIdentifier).identifier; + const simulationFlags: RPC.ESimulationFlag[] = []; + if (skipValidate) simulationFlags.push(RPC.ESimulationFlag.SKIP_VALIDATE); + if (skipFeeCharge) simulationFlags.push(RPC.ESimulationFlag.SKIP_FEE_CHARGE); + + return this.fetchEndpoint('starknet_simulateTransactions', { + block_id, + transactions: invocations.map((it) => this.buildTransaction(it)), + simulation_flags: simulationFlags, + }); + } + + public async waitForTransaction(txHash: BigNumberish, options?: waitForTransactionOptions) { + const transactionHash = toHex(txHash); + let { retries } = this; + let onchain = false; + let isErrorState = false; + const retryInterval = options?.retryInterval ?? 5000; + const errorStates: any = options?.errorStates ?? [ + RPC.ETransactionStatus.REJECTED, + // TODO: commented out to preserve the long-standing behavior of "reverted" not being treated as an error by default + // should decide which behavior to keep in the future + // RPC.ETransactionExecutionStatus.REVERTED, + ]; + const successStates: any = options?.successStates ?? [ + RPC.ETransactionExecutionStatus.SUCCEEDED, + RPC.ETransactionStatus.ACCEPTED_ON_L2, + RPC.ETransactionStatus.ACCEPTED_ON_L1, + ]; + + let txStatus: RPC.TransactionStatus; + while (!onchain) { + // eslint-disable-next-line no-await-in-loop + await wait(retryInterval); + try { + // eslint-disable-next-line no-await-in-loop + txStatus = await this.getTransactionStatus(transactionHash); + + const executionStatus = txStatus.execution_status; + const finalityStatus = txStatus.finality_status; + + if (!finalityStatus) { + // Transaction is potentially NOT_RECEIVED or RPC not Synced yet + // so we will retry '{ retries }' times + const error = new Error('waiting for transaction status'); + throw error; + } + + if (errorStates.includes(executionStatus) || errorStates.includes(finalityStatus)) { + const message = `${executionStatus}: ${finalityStatus}`; + const error = new Error(message) as Error & { response: RPC.TransactionStatus }; + error.response = txStatus; + isErrorState = true; + throw error; + } else if ( + successStates.includes(executionStatus) || + successStates.includes(finalityStatus) + ) { + onchain = true; + } + } catch (error) { + if (error instanceof Error && isErrorState) { + throw error; + } + + if (retries <= 0) { + throw new Error(`waitForTransaction timed-out with retries ${this.retries}`); + } + } + + retries -= 1; + } + + /** + * For some nodes even though the transaction has executionStatus SUCCEEDED finalityStatus ACCEPTED_ON_L2, getTransactionReceipt returns "Transaction hash not found" + * Retry until rpc is actually ready to work with txHash + */ + let txReceipt = null; + while (txReceipt === null) { + try { + // eslint-disable-next-line no-await-in-loop + txReceipt = await this.getTransactionReceipt(transactionHash); + } catch (error) { + if (retries <= 0) { + throw new Error(`waitForTransaction timed-out with retries ${this.retries}`); + } + } + retries -= 1; + // eslint-disable-next-line no-await-in-loop + await wait(retryInterval); + } + return txReceipt as RPC.SPEC.TXN_RECEIPT; + } + + public getStorageAt( + contractAddress: BigNumberish, + key: BigNumberish, + blockIdentifier: BlockIdentifier = this.blockIdentifier + ) { + const contract_address = toHex(contractAddress); + const parsedKey = toStorageKey(key); + const block_id = new Block(blockIdentifier).identifier; + return this.fetchEndpoint('starknet_getStorageAt', { + contract_address, + key: parsedKey, + block_id, + }); + } + + public getClassHashAt( + contractAddress: BigNumberish, + blockIdentifier: BlockIdentifier = this.blockIdentifier + ) { + const contract_address = toHex(contractAddress); + const block_id = new Block(blockIdentifier).identifier; + return this.fetchEndpoint('starknet_getClassHashAt', { + block_id, + contract_address, + }); + } + + public getClass( + classHash: BigNumberish, + blockIdentifier: BlockIdentifier = this.blockIdentifier + ) { + const class_hash = toHex(classHash); + const block_id = new Block(blockIdentifier).identifier; + return this.fetchEndpoint('starknet_getClass', { + class_hash, + block_id, + }); + } + + public getClassAt( + contractAddress: BigNumberish, + blockIdentifier: BlockIdentifier = this.blockIdentifier + ) { + const contract_address = toHex(contractAddress); + const block_id = new Block(blockIdentifier).identifier; + return this.fetchEndpoint('starknet_getClassAt', { + block_id, + contract_address, + }); + } + + public async getEstimateFee( + invocations: AccountInvocations, + { blockIdentifier = this.blockIdentifier, skipValidate = true }: getEstimateFeeBulkOptions + ) { + const block_id = new Block(blockIdentifier).identifier; + let flags = {}; + if (!isVersion('0.5', await this.getSpecVersion())) { + flags = { + simulation_flags: skipValidate ? [RPC.ESimulationFlag.SKIP_VALIDATE] : [], + }; + } // else v(0.5) no flags + + return this.fetchEndpoint('starknet_estimateFee', { + request: invocations.map((it) => this.buildTransaction(it, 'fee')), + block_id, + ...flags, + }); + } + + public async invoke(functionInvocation: Invocation, details: InvocationsDetailsWithNonce) { + let promise; + if (!isV3Tx(details)) { + // V1 + promise = this.fetchEndpoint('starknet_addInvokeTransaction', { + invoke_transaction: { + sender_address: functionInvocation.contractAddress, + calldata: CallData.toHex(functionInvocation.calldata), + type: RPC.ETransactionType.INVOKE, + max_fee: toHex(details.maxFee || 0), + version: RPC.ETransactionVersion.V1, + signature: signatureToHexArray(functionInvocation.signature), + nonce: toHex(details.nonce), + }, + }); + } else { + // V3 + promise = this.fetchEndpoint('starknet_addInvokeTransaction', { + invoke_transaction: { + type: RPC.ETransactionType.INVOKE, + sender_address: functionInvocation.contractAddress, + calldata: CallData.toHex(functionInvocation.calldata), + version: RPC.ETransactionVersion.V3, + signature: signatureToHexArray(functionInvocation.signature), + nonce: toHex(details.nonce), + resource_bounds: details.resourceBounds, + tip: toHex(details.tip), + paymaster_data: details.paymasterData.map((it) => toHex(it)), + account_deployment_data: details.accountDeploymentData.map((it) => toHex(it)), + nonce_data_availability_mode: details.nonceDataAvailabilityMode, + fee_data_availability_mode: details.feeDataAvailabilityMode, + }, + }); + } + + return this.waitMode ? this.waitForTransaction((await promise).transaction_hash) : promise; + } + + public async declare( + { contract, signature, senderAddress, compiledClassHash }: DeclareContractTransaction, + details: InvocationsDetailsWithNonce + ) { + let promise; + if (!isSierra(contract) && !isV3Tx(details)) { + // V1 Cairo 0 + promise = this.fetchEndpoint('starknet_addDeclareTransaction', { + declare_transaction: { + type: RPC.ETransactionType.DECLARE, + contract_class: { + program: contract.program, + entry_points_by_type: contract.entry_points_by_type, + abi: contract.abi, + }, + version: RPC.ETransactionVersion.V1, + max_fee: toHex(details.maxFee || 0), + signature: signatureToHexArray(signature), + sender_address: senderAddress, + nonce: toHex(details.nonce), + }, + }); + } else if (isSierra(contract) && !isV3Tx(details)) { + // V2 Cairo1 + promise = this.fetchEndpoint('starknet_addDeclareTransaction', { + declare_transaction: { + type: RPC.ETransactionType.DECLARE, + contract_class: { + sierra_program: decompressProgram(contract.sierra_program), + contract_class_version: contract.contract_class_version, + entry_points_by_type: contract.entry_points_by_type, + abi: contract.abi, + }, + compiled_class_hash: compiledClassHash || '', + version: RPC.ETransactionVersion.V2, + max_fee: toHex(details.maxFee || 0), + signature: signatureToHexArray(signature), + sender_address: senderAddress, + nonce: toHex(details.nonce), + }, + }); + } else if (isSierra(contract) && isV3Tx(details)) { + // V3 Cairo1 + promise = this.fetchEndpoint('starknet_addDeclareTransaction', { + declare_transaction: { + type: RPC.ETransactionType.DECLARE, + sender_address: senderAddress, + compiled_class_hash: compiledClassHash || '', + version: RPC.ETransactionVersion.V3, + signature: signatureToHexArray(signature), + nonce: toHex(details.nonce), + contract_class: { + sierra_program: decompressProgram(contract.sierra_program), + contract_class_version: contract.contract_class_version, + entry_points_by_type: contract.entry_points_by_type, + abi: contract.abi, + }, + resource_bounds: details.resourceBounds, + tip: toHex(details.tip), + paymaster_data: details.paymasterData.map((it) => toHex(it)), + account_deployment_data: details.accountDeploymentData.map((it) => toHex(it)), + nonce_data_availability_mode: details.nonceDataAvailabilityMode, + fee_data_availability_mode: details.feeDataAvailabilityMode, + }, + }); + } else { + throw Error('declare unspotted parameters'); + } + + return this.waitMode ? this.waitForTransaction((await promise).transaction_hash) : promise; + } + + public async deployAccount( + { classHash, constructorCalldata, addressSalt, signature }: DeployAccountContractTransaction, + details: InvocationsDetailsWithNonce + ) { + let promise; + if (!isV3Tx(details)) { + // v1 + promise = this.fetchEndpoint('starknet_addDeployAccountTransaction', { + deploy_account_transaction: { + constructor_calldata: CallData.toHex(constructorCalldata || []), + class_hash: toHex(classHash), + contract_address_salt: toHex(addressSalt || 0), + type: RPC.ETransactionType.DEPLOY_ACCOUNT, + max_fee: toHex(details.maxFee || 0), + version: RPC.ETransactionVersion.V1, + signature: signatureToHexArray(signature), + nonce: toHex(details.nonce), + }, + }); + } else { + // v3 + promise = this.fetchEndpoint('starknet_addDeployAccountTransaction', { + deploy_account_transaction: { + type: RPC.ETransactionType.DEPLOY_ACCOUNT, + version: RPC.ETransactionVersion.V3, + signature: signatureToHexArray(signature), + nonce: toHex(details.nonce), + contract_address_salt: toHex(addressSalt || 0), + constructor_calldata: CallData.toHex(constructorCalldata || []), + class_hash: toHex(classHash), + resource_bounds: details.resourceBounds, + tip: toHex(details.tip), + paymaster_data: details.paymasterData.map((it) => toHex(it)), + nonce_data_availability_mode: details.nonceDataAvailabilityMode, + fee_data_availability_mode: details.feeDataAvailabilityMode, + }, + }); + } + + return this.waitMode ? this.waitForTransaction((await promise).transaction_hash) : promise; + } + + public callContract(call: Call, blockIdentifier: BlockIdentifier = this.blockIdentifier) { + const block_id = new Block(blockIdentifier).identifier; + return this.fetchEndpoint('starknet_call', { + request: { + contract_address: call.contractAddress, + entry_point_selector: getSelectorFromName(call.entrypoint), + calldata: CallData.toHex(call.calldata), + }, + block_id, + }); + } + + /** + * NEW: Estimate the fee for a message from L1 + * @param message Message From L1 + */ + public estimateMessageFee( + message: RPC.L1Message, + blockIdentifier: BlockIdentifier = this.blockIdentifier + ) { + const { from_address, to_address, entry_point_selector, payload } = message; + const formattedMessage = { + from_address: toHex(from_address), + to_address: toHex(to_address), + entry_point_selector: getSelector(entry_point_selector), + payload: getHexStringArray(payload), + }; + + const block_id = new Block(blockIdentifier).identifier; + return this.fetchEndpoint('starknet_estimateMessageFee', { + message: formattedMessage, + block_id, + }); + } + + /** + * Returns an object about the sync status, or false if the node is not synching + * @returns Object with the stats data + */ + public getSyncingStats() { + return this.fetchEndpoint('starknet_syncing'); + } + + /** + * Returns all events matching the given filter + * @returns events and the pagination of the events + */ + public getEvents(eventFilter: RPC.EventFilter) { + return this.fetchEndpoint('starknet_getEvents', { filter: eventFilter }); + } + + public buildTransaction( + invocation: AccountInvocationItem, + versionType?: 'fee' | 'transaction' + ): RPC.BaseTransaction { + const defaultVersions = getVersionsByType(versionType); + let details; + + if (!isV3Tx(invocation)) { + // V0,V1,V2 + details = { + signature: signatureToHexArray(invocation.signature), + nonce: toHex(invocation.nonce), + max_fee: toHex(invocation.maxFee || 0), + }; + } else { + // V3 + details = { + signature: signatureToHexArray(invocation.signature), + nonce: toHex(invocation.nonce), + resource_bounds: invocation.resourceBounds, + tip: toHex(invocation.tip), + paymaster_data: invocation.paymasterData.map((it) => toHex(it)), + nonce_data_availability_mode: invocation.nonceDataAvailabilityMode, + fee_data_availability_mode: invocation.feeDataAvailabilityMode, + account_deployment_data: invocation.accountDeploymentData.map((it) => toHex(it)), + }; + } + + if (invocation.type === TransactionType.INVOKE) { + return { + // v0 v1 v3 + type: RPC.ETransactionType.INVOKE, // TODO: Diff between sequencer and rpc invoke type + sender_address: invocation.contractAddress, + calldata: CallData.toHex(invocation.calldata), + version: toHex(invocation.version || defaultVersions.v3), + ...details, + } as RPC.SPEC.BROADCASTED_INVOKE_TXN; + } + if (invocation.type === TransactionType.DECLARE) { + if (!isSierra(invocation.contract)) { + // Cairo 0 - v1 + return { + type: invocation.type, + contract_class: invocation.contract, + sender_address: invocation.senderAddress, + version: toHex(invocation.version || defaultVersions.v1), + ...details, + } as RPC.SPEC.BROADCASTED_DECLARE_TXN_V1; + } + return { + // Cairo 1 - v2 v3 + type: invocation.type, + contract_class: { + ...invocation.contract, + sierra_program: decompressProgram(invocation.contract.sierra_program), + }, + compiled_class_hash: invocation.compiledClassHash || '', + sender_address: invocation.senderAddress, + version: toHex(invocation.version || defaultVersions.v3), + ...details, + } as RPC.SPEC.BROADCASTED_DECLARE_TXN; + } + if (invocation.type === TransactionType.DEPLOY_ACCOUNT) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { account_deployment_data, ...restDetails } = details; + // v1 v3 + return { + type: invocation.type, + constructor_calldata: CallData.toHex(invocation.constructorCalldata || []), + class_hash: toHex(invocation.classHash), + contract_address_salt: toHex(invocation.addressSalt || 0), + version: toHex(invocation.version || defaultVersions.v3) as RPC.SPEC.INVOKE_TXN['version'], + ...restDetails, + } as RPC.SPEC.BROADCASTED_DEPLOY_ACCOUNT_TXN; + } + throw Error('RPC buildTransaction received unknown TransactionType'); + } +} diff --git a/src/provider/interface.ts b/src/provider/interface.ts index e046fe7c3..b9026ab91 100644 --- a/src/provider/interface.ts +++ b/src/provider/interface.ts @@ -1,4 +1,4 @@ -import { RpcChannel } from '../channel/rpc_0_6'; +import { RPC06, RPC07 } from '../channel'; import { StarknetChainId } from '../constants'; import type { AccountInvocations, @@ -34,7 +34,7 @@ import type { } from '../types'; export abstract class ProviderInterface { - public abstract channel: RpcChannel; + public abstract channel: RPC07.RpcChannel | RPC06.RpcChannel; /** * Gets the Starknet chain Id diff --git a/src/provider/rpc.ts b/src/provider/rpc.ts index f71ee8f48..e5e0a4c36 100644 --- a/src/provider/rpc.ts +++ b/src/provider/rpc.ts @@ -1,4 +1,4 @@ -import { RpcChannel } from '../channel/rpc_0_6'; +import { RpcChannel, RPC06, RPC07 } from '../channel'; import { AccountInvocations, BigNumberish, @@ -32,7 +32,7 @@ import { ProviderInterface } from './interface'; export class RpcProvider implements ProviderInterface { private responseParser = new RPCResponseParser(); - public channel: RpcChannel; + public channel: RPC07.RpcChannel | RPC06.RpcChannel; constructor(optionsOrProvider?: RpcProviderOptions | ProviderInterface | RpcProvider) { if (optionsOrProvider && 'channel' in optionsOrProvider) { diff --git a/src/types/api/index.ts b/src/types/api/index.ts index 9fd21dd55..56fb63876 100644 --- a/src/types/api/index.ts +++ b/src/types/api/index.ts @@ -1,2 +1,5 @@ export * as JRPC from './jsonrpc'; -export * from './rpcspec_0_6'; + +export * as RPCSPEC06 from './rpcspec_0_6'; +export * as RPCSPEC07 from './rpcspec_0_7'; +export * from './rpcspec_0_7'; diff --git a/src/types/api/rpcspec_0_7/components.ts b/src/types/api/rpcspec_0_7/components.ts new file mode 100644 index 000000000..7dcf7d33b --- /dev/null +++ b/src/types/api/rpcspec_0_7/components.ts @@ -0,0 +1,674 @@ +/** + * PRIMITIVES + */ + +/** + * A field element. represented by at most 63 hex digits + * @pattern ^0x(0|[a-fA-F1-9]{1}[a-fA-F0-9]{0,62})$ + */ +export type FELT = string; +/** + * an ethereum address represented as 40 hex digits + * @pattern ^0x[a-fA-F0-9]{40}$ + */ +export type ETH_ADDRESS = string; +/** + * A storage key. Represented as up to 62 hex digits, 3 bits, and 5 leading zeroes. + * @pattern ^0x0[0-7]{1}[a-fA-F0-9]{0,62}$ + */ +export type STORAGE_KEY = string; +export type ADDRESS = FELT; +export type NUM_AS_HEX = string; +/** + * 64 bit integers, represented by hex string of length at most 16 + * "pattern": "^0x(0|[a-fA-F1-9]{1}[a-fA-F0-9]{0,15})$" + */ +export type u64 = string; +/** + * 64 bit integers, represented by hex string of length at most 32 + * "pattern": "^0x(0|[a-fA-F1-9]{1}[a-fA-F0-9]{0,31})$" + */ +export type u128 = string; +export type SIGNATURE = Array; +export type BLOCK_NUMBER = number; +export type BLOCK_HASH = FELT; +export type TXN_HASH = FELT; +export type CHAIN_ID = NUM_AS_HEX; +export type STRUCT_ABI_TYPE = 'struct'; +export type EVENT_ABI_TYPE = 'event'; +export type FUNCTION_ABI_TYPE = 'function' | 'l1_handler' | 'constructor'; +// Represents the type of an entry point. +export type ENTRY_POINT_TYPE = 'EXTERNAL' | 'L1_HANDLER' | 'CONSTRUCTOR'; +// Represents the type of a function call. +export type CALL_TYPE = 'DELEGATE' | 'LIBRARY_CALL' | 'CALL'; +// Represents the status of the transaction +export type TXN_STATUS = 'RECEIVED' | 'REJECTED' | 'ACCEPTED_ON_L2' | 'ACCEPTED_ON_L1'; +// Flags that indicate how to simulate a given transaction. By default, the sequencer behavior is replicated locally (enough funds are expected to be in the account, and the fee will be deducted from the balance before the simulation of the next transaction). To skip the fee charge, use the SKIP_FEE_CHARGE flag. +export type SIMULATION_FLAG = 'SKIP_VALIDATE' | 'SKIP_FEE_CHARGE'; +// Data availability mode +export type DA_MODE = 'L1' | 'L2'; +export type TXN_TYPE = 'DECLARE' | 'DEPLOY' | 'DEPLOY_ACCOUNT' | 'INVOKE' | 'L1_HANDLER'; +export type TXN_FINALITY_STATUS = 'ACCEPTED_ON_L2' | 'ACCEPTED_ON_L1'; +export type TXN_EXECUTION_STATUS = 'SUCCEEDED' | 'REVERTED'; +export type BLOCK_STATUS = 'PENDING' | 'ACCEPTED_ON_L2' | 'ACCEPTED_ON_L1' | 'REJECTED'; +export type BLOCK_TAG = 'latest' | 'pending'; + +/** + * READ API + */ + +export type EVENTS_CHUNK = { + // Returns matching events + events: EMITTED_EVENT[]; + // Use this token in a subsequent query to obtain the next page. Should not appear if there are no more pages. + continuation_token?: string; +}; + +export type RESULT_PAGE_REQUEST = { + // The token returned from the previous query. If no token is provided the first page is returned. + continuation_token?: string; + // Chunk size + chunk_size: number; +}; + +export type EMITTED_EVENT = EVENT & { + block_hash: BLOCK_HASH; + block_number: BLOCK_NUMBER; + transaction_hash: TXN_HASH; +}; + +export type EVENT = { + from_address: ADDRESS; +} & EVENT_CONTENT; + +export type EVENT_CONTENT = { + keys: FELT[]; + data: FELT[]; +}; + +export type EVENT_FILTER = { + from_block?: BLOCK_ID; + to_block?: BLOCK_ID; + address?: ADDRESS; + keys?: FELT[][]; +}; + +export type BLOCK_ID = + | { + block_hash?: BLOCK_HASH; + block_number?: BLOCK_NUMBER; + } + | BLOCK_TAG; + +export type SYNC_STATUS = { + starting_block_hash: BLOCK_HASH; + starting_block_num: BLOCK_NUMBER; + current_block_hash: BLOCK_HASH; + current_block_num: BLOCK_NUMBER; + highest_block_hash: BLOCK_HASH; + highest_block_num: BLOCK_NUMBER; +}; + +export type NEW_CLASSES = { + class_hash: FELT; + compiled_class_hash: FELT; +}; + +export type REPLACED_CLASS = { + class_hash: FELT; + contract_address: FELT; +}; + +export type NONCE_UPDATE = { + contract_address: ADDRESS; + nonce: FELT; +}; + +export type STATE_DIFF = { + storage_diffs: CONTRACT_STORAGE_DIFF_ITEM[]; + deprecated_declared_classes: FELT[]; + declared_classes: NEW_CLASSES[]; + deployed_contracts: DEPLOYED_CONTRACT_ITEM[]; + replaced_classes: REPLACED_CLASS[]; + nonces: NONCE_UPDATE[]; +}; + +export type PENDING_STATE_UPDATE = { + old_root: FELT; + state_diff: STATE_DIFF; + block_hash: never; // diverge: this makes it distinct +}; + +export type STATE_UPDATE = { + block_hash: BLOCK_HASH; + old_root: FELT; + new_root: FELT; + state_diff: STATE_DIFF; +}; + +export type BLOCK_BODY_WITH_TX_HASHES = { + transactions: TXN_HASH[]; +}; + +export type BLOCK_BODY_WITH_TXS = { + transactions: { + transaction: TXN; + transaction_hash: TXN_HASH; + }[]; +}; + +export type BLOCK_HEADER = { + block_hash: BLOCK_HASH; + parent_hash: BLOCK_HASH; + block_number: BLOCK_NUMBER; + new_root: FELT; + timestamp: number; + sequencer_address: FELT; + l1_gas_price: RESOURCE_PRICE; + starknet_version: string; +}; + +export type PENDING_BLOCK_HEADER = { + parent_hash: BLOCK_HASH; + timestamp: number; + sequencer_address: FELT; + l1_gas_price: RESOURCE_PRICE; + starknet_version: string; +}; + +export type BLOCK_WITH_TX_HASHES = { status: BLOCK_STATUS } & BLOCK_HEADER & + BLOCK_BODY_WITH_TX_HASHES; + +export type BLOCK_WITH_TXS = { status: BLOCK_STATUS } & BLOCK_HEADER & BLOCK_BODY_WITH_TXS; + +export type PENDING_BLOCK_WITH_TX_HASHES = BLOCK_BODY_WITH_TX_HASHES & PENDING_BLOCK_HEADER; + +export type PENDING_BLOCK_WITH_TXS = BLOCK_BODY_WITH_TXS & PENDING_BLOCK_HEADER; + +export type DEPLOYED_CONTRACT_ITEM = { + address: FELT; + class_hash: FELT; +}; + +export type CONTRACT_STORAGE_DIFF_ITEM = { + // The contract address for which the storage changed (in FELT format) + address: string; + // The changes in the storage of the contract + storage_entries: StorageDiffItem[]; +}; + +export type StorageDiffItem = { + // The key of the changed value (in FELT format) + key: string; + // The new value applied to the given address (in FELT format) + value: string; +}; + +export type TXN = INVOKE_TXN | L1_HANDLER_TXN | DECLARE_TXN | DEPLOY_TXN | DEPLOY_ACCOUNT_TXN; + +export type DECLARE_TXN = DECLARE_TXN_V0 | DECLARE_TXN_V1 | DECLARE_TXN_V2 | DECLARE_TXN_V3; + +export type DECLARE_TXN_V0 = { + type: 'DECLARE'; + sender_address: ADDRESS; + max_fee: FELT; + version: '0x0' | '0x100000000000000000000000000000000'; + signature: SIGNATURE; + class_hash: FELT; +}; + +export type DECLARE_TXN_V1 = { + type: 'DECLARE'; + sender_address: ADDRESS; + max_fee: FELT; + version: '0x1' | '0x100000000000000000000000000000001'; + signature: SIGNATURE; + nonce: FELT; + class_hash: FELT; +}; + +export type DECLARE_TXN_V2 = { + type: 'DECLARE'; + sender_address: ADDRESS; + compiled_class_hash: FELT; + max_fee: FELT; + version: '0x2' | '0x100000000000000000000000000000002'; + signature: SIGNATURE; + nonce: FELT; + class_hash: FELT; +}; + +export type DECLARE_TXN_V3 = { + type: 'DECLARE'; + sender_address: ADDRESS; + compiled_class_hash: FELT; + version: '0x3' | '0x100000000000000000000000000000003'; + signature: SIGNATURE; + nonce: FELT; + class_hash: FELT; + // new... + resource_bounds: RESOURCE_BOUNDS_MAPPING; + tip: u64; + paymaster_data: FELT[]; + account_deployment_data: FELT[]; + nonce_data_availability_mode: DA_MODE; + fee_data_availability_mode: DA_MODE; +}; + +export type BROADCASTED_TXN = + | BROADCASTED_INVOKE_TXN + | BROADCASTED_DECLARE_TXN + | BROADCASTED_DEPLOY_ACCOUNT_TXN; + +export type BROADCASTED_INVOKE_TXN = INVOKE_TXN; + +export type BROADCASTED_DEPLOY_ACCOUNT_TXN = DEPLOY_ACCOUNT_TXN; + +export type BROADCASTED_DECLARE_TXN = + | BROADCASTED_DECLARE_TXN_V1 + | BROADCASTED_DECLARE_TXN_V2 + | BROADCASTED_DECLARE_TXN_V3; + +export type BROADCASTED_DECLARE_TXN_V1 = { + type: 'DECLARE'; + sender_address: ADDRESS; + max_fee: FELT; + // todo: check if working, prev i fixed it with NUM_AS_HEX + version: '0x1' | '0x100000000000000000000000000000001'; + signature: SIGNATURE; + nonce: FELT; + contract_class: DEPRECATED_CONTRACT_CLASS; +}; + +export type BROADCASTED_DECLARE_TXN_V2 = { + type: 'DECLARE'; + sender_address: ADDRESS; + compiled_class_hash: FELT; + max_fee: FELT; + version: '0x2' | '0x100000000000000000000000000000002'; + signature: SIGNATURE; + nonce: FELT; + contract_class: CONTRACT_CLASS; +}; + +export type BROADCASTED_DECLARE_TXN_V3 = { + type: 'DECLARE'; + sender_address: ADDRESS; + compiled_class_hash: FELT; + version: '0x3' | '0x100000000000000000000000000000003'; + signature: SIGNATURE; + nonce: FELT; + contract_class: CONTRACT_CLASS; + // new... + resource_bounds: RESOURCE_BOUNDS_MAPPING; + tip: u64; + paymaster_data: FELT[]; + account_deployment_data: FELT[]; + nonce_data_availability_mode: DA_MODE; + fee_data_availability_mode: DA_MODE; +}; + +export type DEPLOY_ACCOUNT_TXN = DEPLOY_ACCOUNT_TXN_V1 | DEPLOY_ACCOUNT_TXN_V3; + +export type DEPLOY_ACCOUNT_TXN_V1 = { + type: 'DEPLOY_ACCOUNT'; + max_fee: FELT; + version: '0x1' | '0x100000000000000000000000000000001'; + signature: SIGNATURE; + nonce: FELT; + contract_address_salt: FELT; + constructor_calldata: FELT[]; + class_hash: FELT; +}; + +export type DEPLOY_ACCOUNT_TXN_V3 = { + type: 'DEPLOY_ACCOUNT'; + version: '0x3' | '0x100000000000000000000000000000003'; + signature: SIGNATURE; + nonce: FELT; + contract_address_salt: FELT; + constructor_calldata: FELT[]; + class_hash: FELT; + resource_bounds: RESOURCE_BOUNDS_MAPPING; + tip: u64; + paymaster_data: FELT[]; + nonce_data_availability_mode: DA_MODE; + fee_data_availability_mode: DA_MODE; +}; + +export type DEPLOY_TXN = { + type: 'DEPLOY'; + version: FELT; + contract_address_salt: FELT; + constructor_calldata: FELT[]; + class_hash: FELT; +}; + +export type INVOKE_TXN = INVOKE_TXN_V0 | INVOKE_TXN_V1 | INVOKE_TXN_V3; + +export type INVOKE_TXN_V0 = { + type: 'INVOKE'; + max_fee: FELT; + version: '0x0' | '0x100000000000000000000000000000000'; + signature: SIGNATURE; + contract_address: ADDRESS; + entry_point_selector: FELT; + calldata: FELT[]; +}; + +export type INVOKE_TXN_V1 = { + type: 'INVOKE'; + sender_address: ADDRESS; + calldata: FELT[]; + max_fee: FELT; + version: '0x1' | '0x100000000000000000000000000000001'; + signature: SIGNATURE; + nonce: FELT; +}; + +export type INVOKE_TXN_V3 = { + type: 'INVOKE'; + sender_address: ADDRESS; + calldata: FELT[]; + version: '0x3' | '0x100000000000000000000000000000003'; + signature: SIGNATURE; + nonce: FELT; + resource_bounds: RESOURCE_BOUNDS_MAPPING; + tip: u64; + paymaster_data: FELT[]; + account_deployment_data: FELT[]; + nonce_data_availability_mode: DA_MODE; + fee_data_availability_mode: DA_MODE; +}; + +export type L1_HANDLER_TXN = { + version: FELT; + type: 'L1_HANDLER'; + nonce: NUM_AS_HEX; +} & FUNCTION_CALL; + +export type COMMON_RECEIPT_PROPERTIES = { + transaction_hash: TXN_HASH; + actual_fee: FEE_PAYMENT; + execution_status: TXN_EXECUTION_STATUS; + finality_status: TXN_FINALITY_STATUS; + block_hash: BLOCK_HASH; + block_number: BLOCK_NUMBER; + messages_sent: MSG_TO_L1[]; + revert_reason?: string; + events: EVENT[]; + execution_resources: EXECUTION_RESOURCES; +}; + +export type PENDING_COMMON_RECEIPT_PROPERTIES = { + transaction_hash: TXN_HASH; + actual_fee: FEE_PAYMENT; + messages_sent: MSG_TO_L1[]; + events: EVENT[]; + revert_reason?: string; + finality_status: 'ACCEPTED_ON_L2'; + execution_status: TXN_EXECUTION_STATUS; + execution_resources: EXECUTION_RESOURCES; +}; + +export type INVOKE_TXN_RECEIPT = { + type: 'INVOKE'; +} & COMMON_RECEIPT_PROPERTIES; + +export type PENDING_INVOKE_TXN_RECEIPT = { + type: 'INVOKE'; +} & PENDING_COMMON_RECEIPT_PROPERTIES; + +export type DECLARE_TXN_RECEIPT = { + type: 'DECLARE'; +} & COMMON_RECEIPT_PROPERTIES; + +export type PENDING_DECLARE_TXN_RECEIPT = { + type: 'DECLARE'; +} & PENDING_COMMON_RECEIPT_PROPERTIES; + +export type DEPLOY_ACCOUNT_TXN_RECEIPT = { + type: 'DEPLOY_ACCOUNT'; + contract_address: FELT; +} & COMMON_RECEIPT_PROPERTIES; + +export type PENDING_DEPLOY_ACCOUNT_TXN_RECEIPT = { + type: 'DEPLOY_ACCOUNT'; + contract_address: FELT; +} & PENDING_COMMON_RECEIPT_PROPERTIES; + +export type DEPLOY_TXN_RECEIPT = { + type: 'DEPLOY'; + contract_address: FELT; +} & COMMON_RECEIPT_PROPERTIES; + +export type L1_HANDLER_TXN_RECEIPT = { + type: 'L1_HANDLER'; + message_hash: NUM_AS_HEX; +} & COMMON_RECEIPT_PROPERTIES; + +export type PENDING_L1_HANDLER_TXN_RECEIPT = { + type: 'L1_HANDLER'; + message_hash: NUM_AS_HEX; +} & PENDING_COMMON_RECEIPT_PROPERTIES; + +export type TXN_RECEIPT = + | INVOKE_TXN_RECEIPT + | L1_HANDLER_TXN_RECEIPT + | DECLARE_TXN_RECEIPT + | DEPLOY_TXN_RECEIPT + | DEPLOY_ACCOUNT_TXN_RECEIPT; + +export type PENDING_TXN_RECEIPT = + | PENDING_INVOKE_TXN_RECEIPT + | PENDING_L1_HANDLER_TXN_RECEIPT + | PENDING_DECLARE_TXN_RECEIPT + | PENDING_DEPLOY_ACCOUNT_TXN_RECEIPT; + +export type MSG_TO_L1 = { + from_address: FELT; + to_address: FELT; + payload: FELT[]; +}; + +export type MSG_FROM_L1 = { + from_address: ETH_ADDRESS; + to_address: ADDRESS; + entry_point_selector: FELT; + payload: FELT[]; +}; + +export type FUNCTION_CALL = { + contract_address: ADDRESS; + entry_point_selector: FELT; + calldata: FELT[]; +}; + +export type CONTRACT_CLASS = { + sierra_program: FELT[]; + contract_class_version: string; + entry_points_by_type: { + CONSTRUCTOR: SIERRA_ENTRY_POINT[]; + EXTERNAL: SIERRA_ENTRY_POINT[]; + L1_HANDLER: SIERRA_ENTRY_POINT[]; + }; + abi: string; +}; + +export type DEPRECATED_CONTRACT_CLASS = { + program: string; + entry_points_by_type: { + CONSTRUCTOR: DEPRECATED_CAIRO_ENTRY_POINT[]; + EXTERNAL: DEPRECATED_CAIRO_ENTRY_POINT[]; + L1_HANDLER: DEPRECATED_CAIRO_ENTRY_POINT[]; + }; + abi: CONTRACT_ABI; +}; + +export type DEPRECATED_CAIRO_ENTRY_POINT = { + offset: NUM_AS_HEX | number; + selector: FELT; +}; + +export type SIERRA_ENTRY_POINT = { + selector: FELT; + function_idx: number; +}; + +export type CONTRACT_ABI = readonly CONTRACT_ABI_ENTRY[]; + +export type CONTRACT_ABI_ENTRY = { + selector: FELT; + input: string; + output: string; +}; + +export type STRUCT_ABI_ENTRY = { + type: STRUCT_ABI_TYPE; + name: string; + size: number; + members: STRUCT_MEMBER[]; +}; + +export type STRUCT_MEMBER = TYPED_PARAMETER & { + offset: number; +}; + +export type EVENT_ABI_ENTRY = { + type: EVENT_ABI_TYPE; + name: string; + keys: TYPED_PARAMETER[]; + data: TYPED_PARAMETER[]; +}; + +export type FUNCTION_STATE_MUTABILITY = 'view'; + +export type FUNCTION_ABI_ENTRY = { + type: FUNCTION_ABI_TYPE; + name: string; + inputs: TYPED_PARAMETER[]; + outputs: TYPED_PARAMETER[]; + stateMutability: FUNCTION_STATE_MUTABILITY; +}; + +export type TYPED_PARAMETER = { + name: string; + type: string; +}; + +export type SIMULATION_FLAG_FOR_ESTIMATE_FEE = 'SKIP_VALIDATE'; +export type PRICE_UNIT = 'WEI' | 'FRI'; + +export type FEE_ESTIMATE = { + gas_consumed: FELT; + gas_price: FELT; + overall_fee: FELT; + unit: PRICE_UNIT; +}; + +export type FEE_PAYMENT = { + amount: FELT; + unit: PRICE_UNIT; +}; + +export type RESOURCE_BOUNDS_MAPPING = { + l1_gas: RESOURCE_BOUNDS; + l2_gas: RESOURCE_BOUNDS; +}; + +export type RESOURCE_BOUNDS = { + max_amount: u64; + max_price_per_unit: u128; +}; + +export type RESOURCE_PRICE = { + price_in_fri: FELT; + price_in_wei: FELT; +}; + +export type EXECUTION_RESOURCES = { + steps: number; + memory_holes?: number; + range_check_builtin_applications?: number; + pedersen_builtin_applications?: number; + poseidon_builtin_applications?: number; + ec_op_builtin_applications?: number; + ecdsa_builtin_applications?: number; + bitwise_builtin_applications?: number; + keccak_builtin_applications?: number; + segment_arena_builtin?: number; +}; + +/** + * TRACE API + */ + +// Represents a transaction trace including the execution details. +export type TRANSACTION_TRACE = { + invoke_tx_trace?: INVOKE_TXN_TRACE; + declare_tx_trace?: DECLARE_TXN_TRACE; + deploy_account_tx_trace?: DEPLOY_ACCOUNT_TXN_TRACE; + l1_handler_tx_trace?: L1_HANDLER_TXN_TRACE; +}; + +// Represents a transaction trace for an invoke transaction. +export type INVOKE_TXN_TRACE = { + type: 'INVOKE'; + execute_invocation: FUNCTION_INVOCATION | { revert_reason: string }; + validate_invocation?: FUNCTION_INVOCATION; + fee_transfer_invocation?: FUNCTION_INVOCATION; + state_diff?: STATE_DIFF; +}; + +// Represents a transaction trace for a declare transaction. +export type DECLARE_TXN_TRACE = { + type: 'DECLARE'; + validate_invocation?: FUNCTION_INVOCATION; + fee_transfer_invocation?: FUNCTION_INVOCATION; + state_diff?: STATE_DIFF; +}; + +// Represents a transaction trace for a deploy account transaction. +export type DEPLOY_ACCOUNT_TXN_TRACE = { + type: 'DEPLOY_ACCOUNT'; + constructor_invocation: FUNCTION_INVOCATION; + validate_invocation?: FUNCTION_INVOCATION; + fee_transfer_invocation?: FUNCTION_INVOCATION; + state_diff?: STATE_DIFF; +}; + +// Represents a transaction trace for an L1 handler transaction. +export type L1_HANDLER_TXN_TRACE = { + type: 'L1_HANDLER'; + function_invocation: FUNCTION_INVOCATION; + state_diff?: STATE_DIFF; +}; + +// Represents a nested function call. +export type NESTED_CALL = FUNCTION_INVOCATION; + +// Represents a function invocation along with its execution details. +export type FUNCTION_INVOCATION = { + function_call: FUNCTION_CALL; + caller_address: string; + class_hash: string; + entry_point_type: ENTRY_POINT_TYPE; + call_type: CALL_TYPE; + result: string[]; + calls: NESTED_CALL[]; + events: ORDERED_EVENT[]; + messages: ORDERED_MESSAGE[]; + execution_resources: EXECUTION_RESOURCES; +}; + +// Represents an ordered event alongside its order within the transaction. +export type ORDERED_EVENT = { + order: number; + event: EVENT; +}; + +// Represents an ordered message alongside its order within the transaction. +export type ORDERED_MESSAGE = { + order: number; + message: MSG_TO_L1; +}; diff --git a/src/types/api/rpcspec_0_7/contract.ts b/src/types/api/rpcspec_0_7/contract.ts new file mode 100644 index 000000000..4b07cf29b --- /dev/null +++ b/src/types/api/rpcspec_0_7/contract.ts @@ -0,0 +1 @@ +export * from '../rpcspec_0_6/contract'; diff --git a/src/types/api/rpcspec_0_7/errors.ts b/src/types/api/rpcspec_0_7/errors.ts new file mode 100644 index 000000000..c94051dc6 --- /dev/null +++ b/src/types/api/rpcspec_0_7/errors.ts @@ -0,0 +1 @@ +export * from '../rpcspec_0_6/errors'; diff --git a/src/types/api/rpcspec_0_7/index.ts b/src/types/api/rpcspec_0_7/index.ts new file mode 100644 index 000000000..921fcf095 --- /dev/null +++ b/src/types/api/rpcspec_0_7/index.ts @@ -0,0 +1,9 @@ +/** + * version 0.7.0 + */ + +export { Methods } from './methods'; +export { ABI } from './contract'; +export * as Errors from './errors'; +export * as SPEC from './components'; +export * from './nonspec'; diff --git a/src/types/api/rpcspec_0_7/methods.ts b/src/types/api/rpcspec_0_7/methods.ts new file mode 100644 index 000000000..0350b0360 --- /dev/null +++ b/src/types/api/rpcspec_0_7/methods.ts @@ -0,0 +1,330 @@ +import { + ADDRESS, + BLOCK_ID, + BLOCK_NUMBER, + BROADCASTED_DECLARE_TXN, + BROADCASTED_DEPLOY_ACCOUNT_TXN, + BROADCASTED_INVOKE_TXN, + BROADCASTED_TXN, + CHAIN_ID, + EVENT_FILTER, + FELT, + FUNCTION_CALL, + MSG_FROM_L1, + RESULT_PAGE_REQUEST, + SIMULATION_FLAG, + SIMULATION_FLAG_FOR_ESTIMATE_FEE, + STORAGE_KEY, + TXN_HASH, +} from './components'; +import * as Errors from './errors'; +import { + BlockHashAndNumber, + BlockTransactionsTraces, + BlockWithTxHashes, + BlockWithTxs, + ContractClass, + DeclaredTransaction, + DeployedAccountTransaction, + Events, + FeeEstimate, + InvokedTransaction, + Nonce, + SimulateTransactionResponse, + StateUpdate, + Syncing, + TransactionReceipt, + TransactionStatus, + TransactionTrace, + TransactionWithHash, +} from './nonspec'; + +export type Methods = ReadMethods & WriteMethods & TraceMethods; + +type ReadMethods = { + // Returns the version of the Starknet JSON-RPC specification being used + starknet_specVersion: { + params: []; + result: string; + }; + + // Get block information with transaction hashes given the block id + starknet_getBlockWithTxHashes: { + params: { + block_id: BLOCK_ID; + }; + result: BlockWithTxHashes; + errors: Errors.BLOCK_NOT_FOUND; + }; + + // Get block information with full transactions given the block id + starknet_getBlockWithTxs: { + params: { + block_id: BLOCK_ID; + }; + result: BlockWithTxs; + errors: Errors.BLOCK_NOT_FOUND; + }; + + // Get the information about the result of executing the requested block + starknet_getStateUpdate: { + params: { + block_id: BLOCK_ID; + }; + result: StateUpdate; + errors: Errors.BLOCK_NOT_FOUND; + }; + + // Get the value of the storage at the given address and key + starknet_getStorageAt: { + params: { + contract_address: ADDRESS; + key: STORAGE_KEY; + block_id: BLOCK_ID; + }; + result: FELT; + errors: Errors.CONTRACT_NOT_FOUND | Errors.BLOCK_NOT_FOUND; + }; + + // Gets the transaction status (possibly reflecting that the tx is still in the mempool, or dropped from it) + starknet_getTransactionStatus: { + params: { + transaction_hash: TXN_HASH; + }; + result: TransactionStatus; + errors: Errors.TXN_HASH_NOT_FOUND; + }; + + // Get the details and status of a submitted transaction + starknet_getTransactionByHash: { + params: { + transaction_hash: TXN_HASH; + }; + result: TransactionWithHash; + errors: Errors.TXN_HASH_NOT_FOUND; + }; + + // Get the details of a transaction by a given block id and index + starknet_getTransactionByBlockIdAndIndex: { + params: { + block_id: BLOCK_ID; + index: number; + }; + result: TransactionWithHash; + errors: Errors.BLOCK_NOT_FOUND | Errors.INVALID_TXN_INDEX; + }; + + // Get the transaction receipt by the transaction hash + starknet_getTransactionReceipt: { + params: { + transaction_hash: TXN_HASH; + }; + result: TransactionReceipt; + errors: Errors.TXN_HASH_NOT_FOUND; + }; + + // Get the contract class definition in the given block associated with the given hash + starknet_getClass: { + params: { + block_id: BLOCK_ID; + class_hash: FELT; + }; + result: ContractClass; + errors: Errors.BLOCK_NOT_FOUND | Errors.CLASS_HASH_NOT_FOUND; + }; + + // Get the contract class hash in the given block for the contract deployed at the given address + starknet_getClassHashAt: { + params: { + block_id: BLOCK_ID; + contract_address: ADDRESS; + }; + result: FELT; + errors: Errors.BLOCK_NOT_FOUND | Errors.CONTRACT_NOT_FOUND; + }; + + // Get the contract class definition in the given block at the given address + starknet_getClassAt: { + params: { + block_id: BLOCK_ID; + contract_address: ADDRESS; + }; + result: ContractClass; + errors: Errors.BLOCK_NOT_FOUND | Errors.CONTRACT_NOT_FOUND; + }; + + // Get the number of transactions in a block given a block id + starknet_getBlockTransactionCount: { + params: { + block_id: BLOCK_ID; + }; + result: number; + errors: Errors.BLOCK_NOT_FOUND; + }; + + // Call a StarkNet function without creating a StarkNet transaction + starknet_call: { + params: { + request: FUNCTION_CALL; + block_id: BLOCK_ID; + }; + result: FELT[]; + errors: Errors.CONTRACT_NOT_FOUND | Errors.CONTRACT_ERROR | Errors.BLOCK_NOT_FOUND; + }; + + // Estimate the fee for StarkNet transactions + starknet_estimateFee: { + params: { + request: BROADCASTED_TXN[]; + simulation_flags?: [SIMULATION_FLAG_FOR_ESTIMATE_FEE] | []; // Diverged from spec (0.5 can't be, 0.6 must be) + block_id: BLOCK_ID; + }; + result: FeeEstimate[]; + errors: Errors.TRANSACTION_EXECUTION_ERROR | Errors.BLOCK_NOT_FOUND; + }; + + // Estimate the L2 fee of a message sent on L1 + starknet_estimateMessageFee: { + params: { + message: MSG_FROM_L1; + block_id: BLOCK_ID; + }; + result: FeeEstimate; + errors: Errors.CONTRACT_ERROR | Errors.BLOCK_NOT_FOUND; + }; + + // Get the most recent accepted block number + starknet_blockNumber: { + params: []; + result: BLOCK_NUMBER; + errors: Errors.NO_BLOCKS; + }; + + // Get the most recent accepted block hash and number + starknet_blockHashAndNumber: { + params: []; + result: BlockHashAndNumber; + errors: Errors.NO_BLOCKS; + }; + + // Return the currently configured StarkNet chain id + starknet_chainId: { + params: []; + result: CHAIN_ID; + }; + + // Returns an object about the sync status, or false if the node is not syncing + starknet_syncing: { + params: []; + result: Syncing; + }; + + // Returns all events matching the given filter + starknet_getEvents: { + params: { + filter: EVENT_FILTER & RESULT_PAGE_REQUEST; + }; + result: Events; + errors: + | Errors.PAGE_SIZE_TOO_BIG + | Errors.INVALID_CONTINUATION_TOKEN + | Errors.BLOCK_NOT_FOUND + | Errors.TOO_MANY_KEYS_IN_FILTER; + }; + + // Get the nonce associated with the given address in the given block + starknet_getNonce: { + params: { + block_id: BLOCK_ID; + contract_address: ADDRESS; + }; + result: Nonce; + errors: Errors.BLOCK_NOT_FOUND | Errors.CONTRACT_NOT_FOUND; + }; +}; + +type WriteMethods = { + // Submit a new transaction to be added to the chain + starknet_addInvokeTransaction: { + params: { + invoke_transaction: BROADCASTED_INVOKE_TXN; + }; + result: InvokedTransaction; + errors: + | Errors.INSUFFICIENT_ACCOUNT_BALANCE + | Errors.INSUFFICIENT_MAX_FEE + | Errors.INVALID_TRANSACTION_NONCE + | Errors.VALIDATION_FAILURE + | Errors.NON_ACCOUNT + | Errors.DUPLICATE_TX + | Errors.UNSUPPORTED_TX_VERSION + | Errors.UNEXPECTED_ERROR; + }; + + // Submit a new class declaration transaction + starknet_addDeclareTransaction: { + params: { + declare_transaction: BROADCASTED_DECLARE_TXN; + }; + result: DeclaredTransaction; + errors: + | Errors.CLASS_ALREADY_DECLARED + | Errors.COMPILATION_FAILED + | Errors.COMPILED_CLASS_HASH_MISMATCH + | Errors.INSUFFICIENT_ACCOUNT_BALANCE + | Errors.INSUFFICIENT_MAX_FEE + | Errors.INVALID_TRANSACTION_NONCE + | Errors.VALIDATION_FAILURE + | Errors.NON_ACCOUNT + | Errors.DUPLICATE_TX + | Errors.CONTRACT_CLASS_SIZE_IS_TOO_LARGE + | Errors.UNSUPPORTED_TX_VERSION + | Errors.UNSUPPORTED_CONTRACT_CLASS_VERSION + | Errors.UNEXPECTED_ERROR; + }; + + // Submit a new deploy account transaction + starknet_addDeployAccountTransaction: { + params: { + deploy_account_transaction: BROADCASTED_DEPLOY_ACCOUNT_TXN; + }; + result: DeployedAccountTransaction; + errors: + | Errors.INSUFFICIENT_ACCOUNT_BALANCE + | Errors.INSUFFICIENT_MAX_FEE + | Errors.INVALID_TRANSACTION_NONCE + | Errors.VALIDATION_FAILURE + | Errors.NON_ACCOUNT + | Errors.CLASS_HASH_NOT_FOUND + | Errors.DUPLICATE_TX + | Errors.UNSUPPORTED_TX_VERSION + | Errors.UNEXPECTED_ERROR; + }; +}; + +type TraceMethods = { + // For a given executed transaction, return the trace of its execution, including internal calls + starknet_traceTransaction: { + params: { transaction_hash: TXN_HASH }; + result: TransactionTrace; + errors: Errors.TXN_HASH_NOT_FOUND | Errors.NO_TRACE_AVAILABLE; + }; + + // Returns the execution traces of all transactions included in the given block + starknet_traceBlockTransactions: { + params: { block_id: BLOCK_ID }; + result: BlockTransactionsTraces; + errors: Errors.BLOCK_NOT_FOUND; + }; + + // Simulate a given sequence of transactions on the requested state, and generate the execution traces. If one of the transactions is reverted, raises CONTRACT_ERROR + starknet_simulateTransactions: { + params: { + block_id: BLOCK_ID; + transactions: Array; + simulation_flags: Array; + }; + result: SimulateTransactionResponse; + errors: Errors.BLOCK_NOT_FOUND | Errors.TRANSACTION_EXECUTION_ERROR; + }; +}; diff --git a/src/types/api/rpcspec_0_7/nonspec.ts b/src/types/api/rpcspec_0_7/nonspec.ts new file mode 100644 index 000000000..f3453b43b --- /dev/null +++ b/src/types/api/rpcspec_0_7/nonspec.ts @@ -0,0 +1,189 @@ +/** + * Types that are not in spec but required for UX + */ +import { + ADDRESS, + BLOCK_HASH, + BLOCK_NUMBER, + BLOCK_WITH_TXS, + BLOCK_WITH_TX_HASHES, + BROADCASTED_TXN, + CHAIN_ID, + CONTRACT_CLASS, + CONTRACT_STORAGE_DIFF_ITEM, + DEPRECATED_CONTRACT_CLASS, + EMITTED_EVENT, + EVENT, + EVENTS_CHUNK, + EVENT_FILTER, + FEE_ESTIMATE, + FEE_PAYMENT, + FELT, + MSG_FROM_L1, + NONCE_UPDATE, + PENDING_BLOCK_WITH_TXS, + PENDING_BLOCK_WITH_TX_HASHES, + PENDING_STATE_UPDATE, + PENDING_TXN_RECEIPT, + PRICE_UNIT, + REPLACED_CLASS, + RESOURCE_BOUNDS_MAPPING, + RESULT_PAGE_REQUEST, + SIMULATION_FLAG, + STATE_UPDATE, + SYNC_STATUS, + TRANSACTION_TRACE, + TXN, + TXN_EXECUTION_STATUS, + TXN_HASH, + TXN_RECEIPT, + TXN_STATUS, +} from './components'; + +// METHOD RESPONSES +// response starknet_getClass +export type ContractClass = CONTRACT_CLASS | DEPRECATED_CONTRACT_CLASS; +// response starknet_simulateTransactions +export type SimulateTransaction = { + transaction_trace: TRANSACTION_TRACE; + fee_estimation: FEE_ESTIMATE; +}; +export type SimulateTransactionResponse = SimulateTransaction[]; +// response starknet_estimateFee +export type FeeEstimate = FEE_ESTIMATE; +// response starknet_getTransactionByHash, starknet_getTransactionByBlockIdAndIndex +export type TransactionWithHash = TXN & { transaction_hash: TXN_HASH }; +// response starknet_blockHashAndNumber +export type BlockHashAndNumber = { block_hash: BLOCK_HASH; block_number: BLOCK_NUMBER }; +// response starknet_getBlockWithTxs +export type BlockWithTxs = BLOCK_WITH_TXS | PENDING_BLOCK_WITH_TXS; +// response starknet_getBlockWithTxHashes +export type BlockWithTxHashes = BLOCK_WITH_TX_HASHES | PENDING_BLOCK_WITH_TX_HASHES; +// response starknet_getStateUpdate +export type StateUpdate = STATE_UPDATE | PENDING_STATE_UPDATE; +// response starknet_traceBlockTransactions +export type BlockTransactionsTraces = { transaction_hash: FELT; trace_root: TRANSACTION_TRACE }[]; +// response starknet_syncing +export type Syncing = false | SYNC_STATUS; +// response starknet_getEvents +export type Events = EVENTS_CHUNK; +export type EmittedEvent = EMITTED_EVENT; +export type Event = EVENT; +// response starknet_addInvokeTransaction +export type InvokedTransaction = { transaction_hash: TXN_HASH }; +// response starknet_addDeclareTransaction +export type DeclaredTransaction = { transaction_hash: TXN_HASH; class_hash: FELT }; +// response starknet_addDeployAccountTransaction +export type DeployedAccountTransaction = { transaction_hash: TXN_HASH; contract_address: FELT }; + +// Nice Components names +export type ContractAddress = ADDRESS; +export type Felt = FELT; +export type Nonce = FELT; +export type TransactionHash = TXN_HASH; +export type TransactionTrace = TRANSACTION_TRACE; +export type BlockHash = BLOCK_HASH; +export type TransactionReceipt = TXN_RECEIPT | PENDING_TXN_RECEIPT; +export type Receipt = TXN_RECEIPT; +export type PendingReceipt = PENDING_TXN_RECEIPT; +export type EventFilter = EVENT_FILTER & RESULT_PAGE_REQUEST; +export type SimulationFlags = Array; +export type L1Message = MSG_FROM_L1; +export type BaseTransaction = BROADCASTED_TXN; +export type ChainId = CHAIN_ID; +export type Transaction = TXN; +export type TransactionStatus = { + finality_status: TXN_STATUS; + execution_status?: TXN_EXECUTION_STATUS; +}; +export type ResourceBounds = RESOURCE_BOUNDS_MAPPING; +export type FeePayment = FEE_PAYMENT; +export type PriceUnit = PRICE_UNIT; + +// Diff Than Seq +export type StorageDiffs = Array; +export type DeprecatedDeclaredClasses = Array; +export type NonceUpdates = NONCE_UPDATE[]; +export type ReplacedClasses = REPLACED_CLASS[]; + +// Enums Derived From Spec Types (require manual check for changes) +export enum ETransactionType { + DECLARE = 'DECLARE', + DEPLOY = 'DEPLOY', + DEPLOY_ACCOUNT = 'DEPLOY_ACCOUNT', + INVOKE = 'INVOKE', + L1_HANDLER = 'L1_HANDLER', +} + +export enum ESimulationFlag { + SKIP_VALIDATE = 'SKIP_VALIDATE', + SKIP_FEE_CHARGE = 'SKIP_FEE_CHARGE', +} + +export enum ETransactionStatus { + RECEIVED = 'RECEIVED', + REJECTED = 'REJECTED', + ACCEPTED_ON_L2 = 'ACCEPTED_ON_L2', + ACCEPTED_ON_L1 = 'ACCEPTED_ON_L1', +} + +export enum ETransactionFinalityStatus { + ACCEPTED_ON_L2 = 'ACCEPTED_ON_L2', + ACCEPTED_ON_L1 = 'ACCEPTED_ON_L1', +} +export enum ETransactionExecutionStatus { + SUCCEEDED = 'SUCCEEDED', + REVERTED = 'REVERTED', +} + +export enum EBlockTag { + LATEST = 'latest', + PENDING = 'pending', +} + +// 'L1' | 'L2' +export enum EDataAvailabilityMode { + L1 = 'L1', + L2 = 'L2', +} + +// 0 | 1 +export enum EDAMode { + L1, + L2, +} + +/** + * V_ Transaction versions HexString + * F_ Fee Transaction Versions HexString (2 ** 128 + TRANSACTION_VERSION) + */ +export enum ETransactionVersion { + V0 = '0x0', + V1 = '0x1', + V2 = '0x2', + V3 = '0x3', + F0 = '0x100000000000000000000000000000000', + F1 = '0x100000000000000000000000000000001', + F2 = '0x100000000000000000000000000000002', + F3 = '0x100000000000000000000000000000003', +} + +/** + * Old Transaction Versions + */ +export enum ETransactionVersion2 { + V0 = '0x0', + V1 = '0x1', + V2 = '0x2', + F0 = '0x100000000000000000000000000000000', + F1 = '0x100000000000000000000000000000001', + F2 = '0x100000000000000000000000000000002', +} + +/** + * V3 Transaction Versions + */ +export enum ETransactionVersion3 { + V3 = '0x3', + F3 = '0x100000000000000000000000000000003', +} diff --git a/src/types/provider/response.ts b/src/types/provider/response.ts index 2ff3eded7..6be225614 100644 --- a/src/types/provider/response.ts +++ b/src/types/provider/response.ts @@ -3,85 +3,108 @@ * Intersection (sequencer response ∩ (∪ rpc responses)) */ -import * as RPC from '../api'; import { CompiledSierra, LegacyContractClass } from '../lib'; +import { + BLOCK_HASH, + BLOCK_NUMBER, + DECLARE_TXN_RECEIPT, + DEPLOY_ACCOUNT_TXN_RECEIPT, + FELT, + INVOKE_TXN_RECEIPT, + L1_HANDLER_TXN_RECEIPT, + PENDING_DECLARE_TXN_RECEIPT, + PENDING_DEPLOY_ACCOUNT_TXN_RECEIPT, + PENDING_INVOKE_TXN_RECEIPT, + PENDING_L1_HANDLER_TXN_RECEIPT, + PENDING_STATE_UPDATE, + PRICE_UNIT, + RESOURCE_PRICE, + SIMULATION_FLAG, + STATE_UPDATE, + TXN_HASH, + DeclaredTransaction, + InvokedTransaction, + PendingReceipt, + Receipt, + ResourceBounds, + SimulateTransaction, + TransactionWithHash, +} from './spec'; + +export { BlockWithTxHashes, ContractClassPayload, FeeEstimate, TransactionReceipt } from './spec'; export type GetBlockResponse = PendingBlock | Block; export type PendingBlock = { status: 'PENDING'; - parent_hash: RPC.SPEC.BLOCK_HASH; + parent_hash: BLOCK_HASH; timestamp: number; - sequencer_address: RPC.Felt; - l1_gas_price: RPC.SPEC.RESOURCE_PRICE; + sequencer_address: FELT; + l1_gas_price: RESOURCE_PRICE; starknet_version: string; - transactions: RPC.SPEC.TXN_HASH[]; + transactions: TXN_HASH[]; }; export type Block = { status: 'ACCEPTED_ON_L2' | 'ACCEPTED_ON_L1' | 'REJECTED'; - block_hash: RPC.SPEC.BLOCK_HASH; - parent_hash: RPC.SPEC.BLOCK_HASH; - block_number: RPC.SPEC.BLOCK_NUMBER; - new_root: RPC.SPEC.FELT; + block_hash: BLOCK_HASH; + parent_hash: BLOCK_HASH; + block_number: BLOCK_NUMBER; + new_root: FELT; timestamp: number; - sequencer_address: RPC.SPEC.FELT; - l1_gas_price: RPC.SPEC.RESOURCE_PRICE; + sequencer_address: FELT; + l1_gas_price: RESOURCE_PRICE; starknet_version: string; - transactions: RPC.SPEC.TXN_HASH[]; + transactions: TXN_HASH[]; }; -export type GetTransactionResponse = RPC.TransactionWithHash; +export type GetTransactionResponse = TransactionWithHash; -export type GetTransactionReceiptResponse = RPC.Receipt | RPC.PendingReceipt; +export type GetTransactionReceiptResponse = Receipt | PendingReceipt; // Spread individual types for usage convenience -export type InvokeTransactionReceiptResponse = - | RPC.SPEC.INVOKE_TXN_RECEIPT - | RPC.SPEC.PENDING_INVOKE_TXN_RECEIPT; -export type DeclareTransactionReceiptResponse = - | RPC.SPEC.DECLARE_TXN_RECEIPT - | RPC.SPEC.PENDING_DECLARE_TXN_RECEIPT; +export type InvokeTransactionReceiptResponse = INVOKE_TXN_RECEIPT | PENDING_INVOKE_TXN_RECEIPT; +export type DeclareTransactionReceiptResponse = DECLARE_TXN_RECEIPT | PENDING_DECLARE_TXN_RECEIPT; export type DeployTransactionReceiptResponse = InvokeTransactionReceiptResponse; export type DeployAccountTransactionReceiptResponse = - | RPC.SPEC.DEPLOY_ACCOUNT_TXN_RECEIPT - | RPC.SPEC.PENDING_DEPLOY_ACCOUNT_TXN_RECEIPT; + | DEPLOY_ACCOUNT_TXN_RECEIPT + | PENDING_DEPLOY_ACCOUNT_TXN_RECEIPT; export type L1HandlerTransactionReceiptResponse = - | RPC.SPEC.L1_HANDLER_TXN_RECEIPT - | RPC.SPEC.PENDING_L1_HANDLER_TXN_RECEIPT; + | L1_HANDLER_TXN_RECEIPT + | PENDING_L1_HANDLER_TXN_RECEIPT; export interface EstimateFeeResponse { gas_consumed: bigint; overall_fee: bigint; gas_price: bigint; - unit: RPC.PriceUnit; + unit: PRICE_UNIT; suggestedMaxFee: bigint; - resourceBounds: RPC.ResourceBounds; + resourceBounds: ResourceBounds; } export type EstimateFeeResponseBulk = Array; -export type InvokeFunctionResponse = RPC.InvokedTransaction; +export type InvokeFunctionResponse = InvokedTransaction; -export type DeclareContractResponse = RPC.DeclaredTransaction; +export type DeclareContractResponse = DeclaredTransaction; export type CallContractResponse = string[]; -export type Storage = RPC.Felt; +export type Storage = FELT; export type Nonce = string; -export type SimulationFlags = RPC.SimulationFlags; +export type SimulationFlags = Array; -export type SimulatedTransaction = RPC.SimulateTransaction & { +export type SimulatedTransaction = SimulateTransaction & { suggestedMaxFee: bigint; - resourceBounds: RPC.ResourceBounds; + resourceBounds: ResourceBounds; }; export type SimulateTransactionResponse = SimulatedTransaction[]; export type StateUpdateResponse = StateUpdate | PendingStateUpdate; -export type StateUpdate = RPC.SPEC.STATE_UPDATE; -export type PendingStateUpdate = RPC.SPEC.PENDING_STATE_UPDATE; +export type StateUpdate = STATE_UPDATE; +export type PendingStateUpdate = PENDING_STATE_UPDATE; /** * Standardized type diff --git a/src/types/provider/spec.ts b/src/types/provider/spec.ts new file mode 100644 index 000000000..49635feb8 --- /dev/null +++ b/src/types/provider/spec.ts @@ -0,0 +1,102 @@ +// this file aims to unify the RPC specification types used by the common Provider class + +import * as RPC06 from '../api/rpcspec_0_6'; +import * as RPC07 from '../api/rpcspec_0_7'; +import { SPEC as SPEC06 } from '../api/rpcspec_0_6'; +import { SPEC as SPEC07 } from '../api/rpcspec_0_7'; + +// taken from type-fest +type Simplify = { [K in keyof T]: T[K] } & {}; + +// taken from type-fest +export type RequiredKeysOf = Exclude< + { + [K in keyof T]: T extends Record ? K : never; + }[keyof T], + undefined +>; + +type ArrayElement = T extends Array ? U : never; + +type MergeProperties, T2 extends Record> = { + [K in RequiredKeysOf & RequiredKeysOf]: Merge; +} & { + [K in keyof T1 & keyof T2]?: Merge; +} & { + [K in Exclude]?: T1[K]; +} & { + [K in Exclude]?: T2[K]; +}; + +// type a = { w: bigint[]; x: bigint; y: string }; +// type b = { w: number[]; x: number; z: string }; +// type c = Merge; // { w: (bigint | number)[] x: bigint | number; y?: string; z?: string; } +// +// NOTE: handling for ambiguous overlaps, such as a shared property being an array or object, +// is simplified to resolve to only one type since there shouldn't be such occurences in the +// currently supported RPC specifications +type Merge = Simplify< + T1 extends Array + ? T2 extends Array + ? Array, ArrayElement>> + : T1 + : T2 extends Array + ? T2 + : T1 extends object + ? T2 extends object + ? MergeProperties + : T1 + : T2 extends object + ? T2 + : T1 | T2 +>; + +export type BLOCK_HASH = Merge; +export type BLOCK_NUMBER = Merge; +export type FELT = Merge; +export type TXN_HASH = Merge; + +export type PRICE_UNIT = Merge; +export type RESOURCE_PRICE = Merge; +export type SIMULATION_FLAG = Merge; + +export type INVOKE_TXN_RECEIPT = Merge; +export type PENDING_INVOKE_TXN_RECEIPT = Merge< + SPEC06.PENDING_INVOKE_TXN_RECEIPT, + SPEC07.PENDING_INVOKE_TXN_RECEIPT +>; +export type DECLARE_TXN_RECEIPT = Merge; +export type PENDING_DECLARE_TXN_RECEIPT = Merge< + SPEC06.PENDING_DECLARE_TXN_RECEIPT, + SPEC07.PENDING_DECLARE_TXN_RECEIPT +>; +export type DEPLOY_ACCOUNT_TXN_RECEIPT = Merge< + SPEC06.DEPLOY_ACCOUNT_TXN_RECEIPT, + SPEC07.DEPLOY_ACCOUNT_TXN_RECEIPT +>; +export type PENDING_DEPLOY_ACCOUNT_TXN_RECEIPT = Merge< + SPEC06.PENDING_DEPLOY_ACCOUNT_TXN_RECEIPT, + SPEC07.PENDING_DEPLOY_ACCOUNT_TXN_RECEIPT +>; +export type L1_HANDLER_TXN_RECEIPT = Merge< + SPEC06.L1_HANDLER_TXN_RECEIPT, + SPEC07.L1_HANDLER_TXN_RECEIPT +>; +export type PENDING_L1_HANDLER_TXN_RECEIPT = Merge< + SPEC06.PENDING_L1_HANDLER_TXN_RECEIPT, + SPEC07.PENDING_L1_HANDLER_TXN_RECEIPT +>; +export type STATE_UPDATE = Merge; +export type PENDING_STATE_UPDATE = Merge; + +export type BlockWithTxHashes = Merge; +export type ContractClassPayload = Merge; +export type DeclaredTransaction = Merge; +export type FeeEstimate = Merge; +export type InvokedTransaction = Merge; +export type PendingReceipt = Merge; +export type Receipt = Merge; +export type ResourceBounds = Merge; +export type SimulateTransaction = Merge; +export type TransactionReceipt = Merge; +export type TransactionWithHash = Merge; diff --git a/src/utils/provider.ts b/src/utils/provider.ts index 7e5f52ec3..bd125b8df 100644 --- a/src/utils/provider.ts +++ b/src/utils/provider.ts @@ -178,7 +178,7 @@ export function isV3Tx(details: InvocationsDetailsWithNonce): details is V3Trans return version === ETransactionVersion.V3 || version === ETransactionVersion.F3; } -export function isVersion(version: '0.5' | '0.6', response: string) { +export function isVersion(version: '0.5' | '0.6' | '0.7', response: string) { const [majorS, minorS] = version.split('.'); const [majorR, minorR] = response.split('.'); diff --git a/src/utils/responseParser/rpc.ts b/src/utils/responseParser/rpc.ts index 7d5ccce70..64badba28 100644 --- a/src/utils/responseParser/rpc.ts +++ b/src/utils/responseParser/rpc.ts @@ -3,14 +3,18 @@ * Intersection (sequencer response ∩ (∪ rpc responses)) */ import { + BlockWithTxHashes, + ContractClassPayload, ContractClassResponse, + TransactionReceipt, EstimateFeeResponse, EstimateFeeResponseBulk, GetBlockResponse, GetTransactionReceiptResponse, - RPC, + FeeEstimate, SimulateTransactionResponse, -} from '../../types'; + SimulatedTransaction, +} from '../../types/provider'; import { toBigInt } from '../num'; import { estimateFeeToBounds, estimatedFeeToMaxFee } from '../stark'; import { ResponseParser } from '.'; @@ -27,27 +31,27 @@ export class RPCResponseParser | 'parseCallContractResponse' > { - public parseGetBlockResponse(res: RPC.BlockWithTxHashes): GetBlockResponse { - return { status: 'PENDING', ...res }; + public parseGetBlockResponse(res: BlockWithTxHashes): GetBlockResponse { + return { status: 'PENDING', ...res } as GetBlockResponse; } - public parseTransactionReceipt(res: RPC.TransactionReceipt): GetTransactionReceiptResponse { + public parseTransactionReceipt(res: TransactionReceipt): GetTransactionReceiptResponse { // HOTFIX RPC 0.5 to align with RPC 0.6 // This case is RPC 0.5. It can be only v2 thx with FRI units if ('actual_fee' in res && typeof res.actual_fee === 'string') { return { - ...res, + ...(res as GetTransactionReceiptResponse), actual_fee: { amount: res.actual_fee, - unit: 'FRI' as RPC.PriceUnit, + unit: 'FRI', }, }; } - return res; + return res as GetTransactionReceiptResponse; } - public parseFeeEstimateResponse(res: RPC.FeeEstimate[]): EstimateFeeResponse { + public parseFeeEstimateResponse(res: FeeEstimate[]): EstimateFeeResponse { const val = res[0]; return { overall_fee: toBigInt(val.overall_fee), @@ -59,7 +63,7 @@ export class RPCResponseParser }; } - public parseFeeEstimateBulkResponse(res: RPC.FeeEstimate[]): EstimateFeeResponseBulk { + public parseFeeEstimateBulkResponse(res: FeeEstimate[]): EstimateFeeResponseBulk { return res.map((val) => ({ overall_fee: toBigInt(val.overall_fee), gas_consumed: toBigInt(val.gas_consumed), @@ -71,9 +75,14 @@ export class RPCResponseParser } public parseSimulateTransactionResponse( - res: RPC.SimulateTransactionResponse + // TODO: revisit + // set as 'any' to avoid a mapped type circular recursion error stemming from + // merging src/types/api/rpcspec*/components/FUNCTION_INVOCATION.calls + // + // res: SimulateTransactionResponse + res: any ): SimulateTransactionResponse { - return res.map((it) => { + return res.map((it: SimulatedTransaction) => { return { ...it, suggestedMaxFee: estimatedFeeToMaxFee(BigInt(it.fee_estimation.overall_fee)), @@ -82,9 +91,9 @@ export class RPCResponseParser }); } - public parseContractClassResponse(res: RPC.ContractClass): ContractClassResponse { + public parseContractClassResponse(res: ContractClassPayload): ContractClassResponse { return { - ...res, + ...(res as ContractClassResponse), abi: typeof res.abi === 'string' ? JSON.parse(res.abi) : res.abi, }; } diff --git a/src/utils/stark.ts b/src/utils/stark.ts index 50877560d..374ce91a4 100644 --- a/src/utils/stark.ts +++ b/src/utils/stark.ts @@ -10,13 +10,8 @@ import { Signature, UniversalDetails, } from '../types'; -import { - EDAMode, - EDataAvailabilityMode, - ETransactionVersion, - FeeEstimate, - ResourceBounds, -} from '../types/api'; +import { EDAMode, EDataAvailabilityMode, ETransactionVersion, ResourceBounds } from '../types/api'; +import { FeeEstimate } from '../types/provider'; import { addHexPrefix, arrayBufferToString, atobUniversal, btoaUniversal } from './encode'; import { parse, stringify } from './json'; import {