From d488ab2513b88fdeb4e27b6ec2b7226ffd359445 Mon Sep 17 00:00:00 2001 From: Toni Tabak Date: Mon, 12 Sep 2022 16:04:51 +0200 Subject: [PATCH] feat: read, write and trace api --- src/provider/rpc.ts | 46 +++++- src/types/api/openrpc.ts | 333 ++++++++++++++++++++++++++++----------- src/types/api/rpc.ts | 88 +++++++---- 3 files changed, 339 insertions(+), 128 deletions(-) diff --git a/src/provider/rpc.ts b/src/provider/rpc.ts index 8a4c69a7b..88cb9e24a 100644 --- a/src/provider/rpc.ts +++ b/src/provider/rpc.ts @@ -90,6 +90,10 @@ export class RpcProvider implements ProviderInterface { ); } + public async getBlockHashAndNumber(): Promise { + return this.fetchEndpoint('starknet_blockHashAndNumber'); + } + public async getBlockWithTxHashes( blockIdentifier: BlockIdentifier = 'pending' ): Promise { @@ -104,10 +108,29 @@ export class RpcProvider implements ProviderInterface { return this.fetchEndpoint('starknet_getBlockWithTxs', [block.identifier]); } + public async getClassHashAt( + blockIdentifier: BlockIdentifier, + contractAddress: RPC.ContractAddress + ): Promise { + return this.fetchEndpoint('starknet_getClassHashAt', [blockIdentifier, contractAddress]); + } + public async getNonce(contractAddress: string): Promise { return this.fetchEndpoint('starknet_getNonce', [contractAddress]); } + public async getPendingTransactions(): Promise { + return this.fetchEndpoint('starknet_pendingTransactions'); + } + + public async getProtocolVersion(): Promise { + throw new Error('Pathfinder does not implement this rpc 0.1.0 method'); + } + + public async getStateUpdate(blockIdentifier: BlockIdentifier): Promise { + return this.fetchEndpoint('starknet_getStateUpdate', [blockIdentifier]); + } + public async getStorageAt( contractAddress: string, key: BigNumberish, @@ -146,11 +169,23 @@ export class RpcProvider implements ProviderInterface { ); } + // TODO: Check, changed response from common interface to rpc + public async getClass(classHash: RPC.Felt): Promise { + return this.fetchEndpoint('starknet_getClass', [classHash]); + } + public async getClassAt(contractAddress: string, blockIdentifier: BlockIdentifier): Promise { const block = new Block(blockIdentifier); return this.fetchEndpoint('starknet_getClassAt', [block.identifier, contractAddress]); } + public async getCode( + _contractAddress: string, + _blockIdentifier?: BlockIdentifier + ): Promise { + throw new Error('RPC 0.1.0 does not implement getCode function'); + } + public async getEstimateFee( invocation: Invocation, blockIdentifier: BlockIdentifier = 'pending', @@ -234,11 +269,12 @@ export class RpcProvider implements ProviderInterface { return this.responseParser.parseCallContractResponse(result); } - public async getCode( - _contractAddress: string, - _blockIdentifier?: BlockIdentifier - ): Promise { - throw new Error('RPC 0.1.0 does not implement getCode function'); + public async traceTransaction(transactionHash: RPC.TransactionHash): Promise { + return this.fetchEndpoint('starknet_traceTransaction', [transactionHash]); + } + + public async traceBlockTransactions(blockHash: RPC.BlockHash): Promise { + return this.fetchEndpoint('starknet_traceBlockTransactions', [blockHash]); } public async waitForTransaction(txHash: BigNumberish, retryInterval: number = 8000) { diff --git a/src/types/api/openrpc.ts b/src/types/api/openrpc.ts index ed257367d..2bd38b180 100644 --- a/src/types/api/openrpc.ts +++ b/src/types/api/openrpc.ts @@ -1,9 +1,11 @@ /** * Starknet RPC version 0.1.0 - * starknet_api_openrpc version 0.31.0 * - * TypeScript Representation of OpenRpc protocol types | results - * errors are not implemented here only results + * StarkNet Node API 0.31.0 + * StarkNet Node Write API 0.3.0 + * StarkNet Trace API 0.3.0 + * + * TypeScript Representation of OpenRpc protocol types */ /** @@ -13,7 +15,8 @@ * "description": "A field element. Represented as up to 63 hex digits and leading 4 bits zeroed.", * "pattern": "^0x0[a-fA-F0-9]{1,63}$" */ -type FELT = string; +export type FELT = string; +export type ADDRESS = FELT; /** * "title": "An integer number in hex format (0x...)", * "pattern": "^0x[a-fA-F0-9]+$" @@ -24,9 +27,16 @@ 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'; type BLOCK_STATUS = 'PENDING' | 'ACCEPTED_ON_L2' | 'ACCEPTED_ON_L1' | 'REJECTED'; +enum BLOCK_TAG { + 'latest', + 'pending', +} +type BLOCK_ID = { block_hash: BLOCK_HASH } | { block_number: BLOCK_NUMBER } | BLOCK_TAG; type MSG_TO_L1 = { to_address: FELT; payload: Array; @@ -96,7 +106,6 @@ type COMMON_TXN_PROPERTIES = { nonce: FELT; type: TXN_TYPE; }; -type ADDRESS = FELT; type FUNCTION_CALL = { contract_address: ADDRESS; entry_point_selector: FELT; @@ -136,6 +145,7 @@ type CONTRACT_CLASS = { EXTERNAL: CONTRACT_ENTRY_POINT_LIST; L1_HANDLER: CONTRACT_ENTRY_POINT_LIST; }; + abi?: any; // Pathfinder exception from rpc 0.1.0 }; type CONTRACT_ENTRY_POINT_LIST = Array; type CONTRACT_ENTRY_POINT = { @@ -168,188 +178,323 @@ type STATE_UPDATE = { }>; }; }; -enum BLOCK_TAG { - 'latest', - 'pending', -} -type BLOCK_ID = - | { - block_hash: BLOCK_HASH; - } - | { - block_number: BLOCK_NUMBER; - } - | BLOCK_TAG; type STORAGE_KEY = string; +type EVENT_FILTER = { + from_block?: BLOCK_ID; + to_block?: BLOCK_ID; + address?: ADDRESS; + keys?: Array; +}; +type RESULT_PAGE_REQUEST = { + page_size: number; + page_number: number; +}; +type EMITTED_EVENT = EVENT & { + block_hash: BLOCK_HASH; + block_number: BLOCK_NUMBER; + transaction_hash: TXN_HASH; +}; +type SYNC_STATUS = { + starting_block_hash: BLOCK_HASH; + starting_block_num: NUM_AS_HEX; + current_block_hash: BLOCK_HASH; + current_block_num: NUM_AS_HEX; + highest_block_hash: BLOCK_HASH; + highest_block_num: NUM_AS_HEX; +}; +type FEE_ESTIMATE = { + gas_consumed: NUM_AS_HEX; + gas_price: NUM_AS_HEX; + overall_fee: NUM_AS_HEX; +}; + +enum CALL_TYPE { + 'DELEGATE', + 'CALL', +} +enum ENTRY_POINT_TYPE { + 'EXTERNAL', + 'L1_HANDLER', + 'CONSTRUCTOR', +} +type FUNCTION_INVOCATION = FUNCTION_CALL & { + caller_address: FELT; + code_address: FELT; + entry_point_type: ENTRY_POINT_TYPE; + call_type: CALL_TYPE; + result: FELT; + calls: FUNCTION_INVOCATION; + events: EVENT; + messages: MSG_TO_L1; +}; +type TRACE_ROOT = { + nonce: FELT; + signature: FELT; + function_invocation: FUNCTION_INVOCATION; +}; export namespace OPENRPC { 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; // notimplemented + export type StateUpdate = STATE_UPDATE; export type Storage = FELT; export type Transaction = TXN; export type TransactionReceipt = TXN_RECEIPT; export type ContractClass = CONTRACT_CLASS; - - export type CallResponse = Array; // new - export type EstimateFeeResponse = FEE_ESTIMATE; // new - export type BlockNumberResponse = BLOCK_NUMBER; // new - export type BlockHashAndNumberResponse = { + export type CallResponse = Array; + export type EstimateFee = FEE_ESTIMATE; + export type BlockNumber = BLOCK_NUMBER; + export type BlockHashAndNumber = { block_hash: BLOCK_HASH; block_number: BLOCK_NUMBER; - }; // new - export type ChainIdResponse = CHAIN_ID; // new - export type PendingTransactionsResponse = Array; // new - export type ProtocolVersionResponse = PROTOCOL_VERSION; // new - export type SyncingResponse = boolean | SYNC_STATUS; // new - export type GetEventsResponse = { + }; + export type ChainId = CHAIN_ID; + export type PendingTransactions = Array; + export type ProtocolVersion = PROTOCOL_VERSION; + export type Syncing = boolean | SYNC_STATUS; + export type Events = { events: Array; page_number: number; is_last_page: boolean; - }; // new - export type getNonce = FELT; // new + }; + export type Nonce = FELT; + export type Trace = TRACE_ROOT; + export type Traces = Array<{ + transaction_hash: FELT; + trace_root: TRACE_ROOT; + }>; + export type TransactionHash = TXN_HASH; + export type BlockHash = BLOCK_HASH; + export type addInvokeTransactionResponse = { transaction_hash: TXN_HASH }; + export type addDeclareTransactionResponse = { transaction_hash: TXN_HASH; class_hash: FELT }; + export type addDeployTransactionResponse = { + transaction_hash: TXN_HASH; + contract_address: FELT; + }; // Final Methods export type Methods = { + // Read API starknet_getBlockWithTxHashes: { params: { block_id: BLOCK_ID }; result: BlockWithTxHashes; - errors: errors.INVALIDBLOCKID; + errors: errors.INVALID_BLOCK_ID; }; starknet_getBlockWithTxs: { params: { block_id: BLOCK_ID }; result: BlockWithTxs; - errors: errors.INVALIDBLOCKID; + errors: errors.INVALID_BLOCK_ID; }; starknet_getStateUpdate: { params: { block_id: BLOCK_ID }; result: StateUpdate; - errors: errors.INVALIDBLOCKID; + errors: errors.INVALID_BLOCK_ID; }; starknet_getStorageAt: { params: { contract_address: ADDRESS; key: STORAGE_KEY; block_id: BLOCK_ID }; result: Storage; - errors: errors.CONTRACTNOTFOUND | errors.INVALIDBLOCKID; + errors: errors.CONTRACT_NOT_FOUND | errors.INVALID_BLOCK_ID; }; starknet_getTransactionByHash: { params: { transaction_hash: TXN_HASH }; result: Transaction; - errors: errors.INVALIDTXNHASH; + errors: errors.INVALID_TXN_HASH; }; starknet_getTransactionByBlockIdAndIndex: { params: { block_id: BLOCK_ID; index: number }; result: Transaction; - errors: errors.INVALIDBLOCKID | errors.INVALIDTXNINDEX; + errors: errors.INVALID_BLOCK_ID | errors.INVALID_TXN_INDEX; }; starknet_getTransactionReceipt: { params: { transaction_hash: TXN_HASH }; result: TransactionReceipt; - errors: errors.INVALIDTXNHASH; + errors: errors.INVALID_TXN_HASH; }; starknet_getClass: { params: { class_hash: FELT }; result: ContractClass; - errors: errors.INVALIDCONTRACTCLASSHASH; + errors: errors.INVALID_CONTRACT_CLASS_HASH; }; starknet_getClassHashAt: { params: { block_id: BLOCK_ID; contract_address: ADDRESS }; result: FELT; - errors: errors.INVALIDBLOCKID | errors.CONTRACTNOTFOUND; + errors: errors.INVALID_BLOCK_ID | errors.CONTRACT_NOT_FOUND; }; starknet_getClassAt: { params: { block_id: BLOCK_ID; contract_address: ADDRESS }; result: ContractClass; - errors: errors.INVALIDBLOCKID | errors.CONTRACTNOTFOUND; + errors: errors.INVALID_BLOCK_ID | errors.CONTRACT_NOT_FOUND; }; starknet_getBlockTransactionCount: { params: { block_id: BLOCK_ID }; result: number; - errors: errors.INVALIDBLOCKID; + errors: errors.INVALID_BLOCK_ID; }; starknet_call: { params: { request: FUNCTION_CALL; block_id: BLOCK_ID }; result: Array; errors: - | errors.CONTRACTNOTFOUND - | errors.INVALIDMESSAGESELECTOR - | errors.INVALIDCALLDATA - | errors.CONTRACTERROR - | errors.INVALIDBLOCKID; + | errors.CONTRACT_NOT_FOUND + | errors.INVALID_MESSAGE_SELECTOR + | errors.INVALID_CALL_DATA + | errors.CONTRACT_ERROR + | errors.INVALID_BLOCK_ID; + }; + starknet_estimateFee: { + params: { request: INVOKE_TXN; block_id: BLOCK_ID }; + result: FEE_ESTIMATE; + errors: + | errors.CONTRACT_NOT_FOUND + | errors.INVALID_MESSAGE_SELECTOR + | errors.INVALID_CALL_DATA + | errors.CONTRACT_ERROR + | errors.INVALID_BLOCK_ID; + }; + starknet_blockNumber: { + result: BLOCK_NUMBER; + errors: errors.NO_BLOCKS; + }; + starknet_blockHashAndNumber: { + result: BLOCK_HASH & BLOCK_NUMBER; + errors: errors.NO_BLOCKS; + }; + starknet_chainId: { + result: CHAIN_ID; + }; + starknet_pendingTransactions: { + result: Array; + }; + starknet_syncing: { + result: false | SYNC_STATUS; + }; + starknet_getEvents: { + params: { filter: EVENT_FILTER & RESULT_PAGE_REQUEST }; + result: { events: EMITTED_EVENT; page_number: number; is_last_page: boolean }; + errors: errors.PAGE_SIZE_TOO_BIG; + }; + starknet_getNonce: { + params: { contract_address: ADDRESS }; + result: FELT; + errors: errors.CONTRACT_NOT_FOUND; + }; + + // Write API + starknet_addInvokeTransaction: { + params: { + function_invocation: FUNCTION_CALL; + signature: SIGNATURE; + max_fee: NUM_AS_HEX; + version: NUM_AS_HEX; + }; + result: addInvokeTransactionResponse; + }; + starknet_addDeclareTransaction: { + params: { + contract_class: CONTRACT_CLASS; + version: NUM_AS_HEX; + }; + result: addDeclareTransactionResponse; + errors: errors.INVALID_CONTRACT_CLASS; + }; + starknet_addDeployTransaction: { + params: { + contract_address_salt: FELT; + constructor_calldata: FELT; + contract_definition: CONTRACT_CLASS; + }; + result: addDeployTransactionResponse; + errors: errors.INVALID_CONTRACT_CLASS; + }; + + // Trace API + starknet_traceTransaction: { + params: { transaction_hash: TXN_HASH }; + result: { trace: Trace }; + errors: + | errors.INVALID_TXN_HASH + | errors.NO_TRACE_AVAILABLE + | errors.INVALID_BLOCK_HASH + | errors.INVALID_TXN_HASH; + }; + starknet_traceBlockTransactions: { + params: { block_hash: BLOCK_HASH }; + result: Traces; + errors: errors.INVALID_BLOCK_HASH; }; }; } namespace errors { - export interface FAILEDTORECEIVETXN { - code: number; - message: string; + export interface FAILED_TO_RECEIVE_TXN { + code: 1; + message: 'Failed to write transaction'; } - export interface CONTRACTNOTFOUND { - code: number; - message: string; + export interface CONTRACT_NOT_FOUND { + code: 20; + message: 'Contract not found'; } - export interface INVALIDMESSAGESELECTOR { - code: number; - message: string; + export interface INVALID_MESSAGE_SELECTOR { + code: 21; + message: 'Invalid message selector'; } - export interface INVALIDCALLDATA { - code: number; - message: string; + export interface INVALID_CALL_DATA { + code: 22; + message: 'Invalid call data'; } - export interface INVALIDBLOCKID { - code: number; - message: string; + export interface INVALID_BLOCK_ID { + code: 24; + message: 'Invalid block id'; } - export interface INVALIDTXNHASH { - code: number; - message: string; + export interface INVALID_TXN_INDEX { + code: 27; + message: 'Invalid transaction index in a block'; } - export interface INVALIDTXNINDEX { - code: number; - message: string; + export interface INVALID_CONTRACT_CLASS_HASH { + code: 28; + message: 'The supplied contract class hash is invalid or unknown'; } - export interface INVALIDCONTRACTCLASSHASH { - code: number; - message: string; + export interface PAGE_SIZE_TOO_BIG { + code: 31; + message: 'Requested page size is too big'; } - export interface PAGESIZETOOBIG { - code: number; - message: string; + export interface NO_BLOCKS { + code: 32; + message: 'There are no blocks'; } - export interface NOBLOCKS { - code: number; - message: string; + export interface CONTRACT_ERROR { + code: 40; + message: 'Contract error'; } - export interface CONTRACTERROR { - code: number; - message: string; + export interface INVALID_CONTRACT_CLASS { + code: 50; + message: 'Invalid contract class'; + } + + export interface NO_TRACE_AVAILABLE { + code: 10; + message: 'No trace available for transaction'; + data: { + status: 'RECEIVED' | 'REJECTED'; + }; } - export interface Errors { - FAILED_TO_RECEIVE_TXN: FAILEDTORECEIVETXN; - CONTRACT_NOT_FOUND: CONTRACTNOTFOUND; - INVALID_MESSAGE_SELECTOR: INVALIDMESSAGESELECTOR; - INVALID_CALL_DATA: INVALIDCALLDATA; - INVALID_BLOCK_ID: INVALIDBLOCKID; - INVALID_TXN_HASH: INVALIDTXNHASH; - INVALID_TXN_INDEX: INVALIDTXNINDEX; - INVALID_CONTRACT_CLASS_HASH: INVALIDCONTRACTCLASSHASH; - PAGE_SIZE_TOO_BIG: PAGESIZETOOBIG; - NO_BLOCKS: NOBLOCKS; - CONTRACT_ERROR: CONTRACTERROR; + export interface INVALID_BLOCK_HASH { + code: 24; + message: 'Invalid block hash'; } - export interface RootObject { - errors: Errors; + export interface INVALID_TXN_HASH { + code: 25; + message: 'Invalid transaction hash'; } } diff --git a/src/types/api/rpc.ts b/src/types/api/rpc.ts index d3e0d2095..ff85ffb3c 100644 --- a/src/types/api/rpc.ts +++ b/src/types/api/rpc.ts @@ -1,40 +1,46 @@ import { StarknetChainId } from '../../constants'; import { BlockIdentifier } from '../../provider/utils'; -import { OPENRPC } from './openrpc'; +import { ADDRESS, FELT, OPENRPC } from './openrpc'; export namespace RPC { export type Response = { id: number; - result: any; jsonrpc: string; + result?: any; error?: { code: string; message: string; }; }; + export type ContractAddress = ADDRESS; + export type Felt = FELT; + export type ContractClass = OPENRPC.ContractClass; + export type StateUpdate = OPENRPC.StateUpdate; + export type Transaction = OPENRPC.Transaction; + export type TransactionHash = OPENRPC.TransactionHash; + export type Trace = OPENRPC.Trace; + export type Traces = OPENRPC.Traces; + export type BlockHash = OPENRPC.BlockHash; + export type BlockHashAndNumber = OPENRPC.BlockHashAndNumber; + export type AddTransactionResponse = { transaction_hash: string; }; - export type GetClassResponse = OPENRPC.GetClassResponse; + export type GetClassResponse = OPENRPC.ContractClass; export type DeclareResponse = { transaction_hash: string; class_hash: string; }; - export type EstimateFeeResponse = { - overall_fee: number; - gas_consumed: number; - gas_price: number; - }; - - export type GetBlockWithTxHashesResponse = OPENRPC.GetBlockWithTxHashesResponse; - export type GetBlockWithTxs = OPENRPC.GetBlockWithTxsResponse; - export type GetStorageAtResponse = OPENRPC.GetStorageAtResponse; + export type EstimateFeeResponse = OPENRPC.EstimateFee; - export type GetTransactionReceiptResponse = OPENRPC.GetTransactionReceiptResponse; + export type GetBlockWithTxHashesResponse = OPENRPC.BlockWithTxHashes; + export type GetBlockWithTxs = OPENRPC.BlockWithTxs; + export type GetStorageAtResponse = OPENRPC.Storage; + export type GetTransactionReceiptResponse = OPENRPC.TransactionReceipt; interface CommonTransactionProperties { txn_hash: string; @@ -55,12 +61,12 @@ export namespace RPC { sender_address?: string; } - export type GetTransactionByHashResponse = OPENRPC.GetTransactionByHashResponse; - export type GetTransactionByBlockIdAndIndex = OPENRPC.GetTransactionByBlockIdAndIndex; + export type GetTransactionByHashResponse = OPENRPC.Transaction; + export type GetTransactionByBlockIdAndIndex = OPENRPC.Transaction; export type GetTransactionCountResponse = number; - export type GetBlockNumberResponse = number; + export type GetBlockNumberResponse = OPENRPC.BlockNumber; export type GetSyncingStatsResponse = | { @@ -82,11 +88,7 @@ export namespace RPC { page_number: number; }; - export type GetEventsResponse = { - events: StarknetEmittedEvent[]; - page_number: number; - is_last_page: number; - }; + export type GetEventsResponse = OPENRPC.Events; export type DeployContractResponse = { transaction_hash: string; @@ -100,13 +102,6 @@ export namespace RPC { data: string[]; }; - export type StarknetEmittedEvent = { - event: StarknetEvent; - block_hash: string; - block_number: number; - transaction_hash: string; - }; - export type MessageToL1 = { to_address: string; payload: string[]; @@ -118,6 +113,31 @@ export namespace RPC { }; export type Methods = { + starknet_pendingTransactions: { + QUERY: never; + REQUEST: any[]; + RESPONSE: Transaction; + }; + 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[]; @@ -131,7 +151,7 @@ export namespace RPC { starknet_getNonce: { QUERY: never; REQUEST: any[]; - RESPONSE: string; + RESPONSE: OPENRPC.Nonce; }; starknet_getStorageAt: { QUERY: never; @@ -208,5 +228,15 @@ export namespace RPC { REQUEST: any[]; RESPONSE: any; }; + starknet_traceTransaction: { + QUERY: never; + REQUEST: any[]; + RESPONSE: any; + }; + starknet_traceBlockTransactions: { + QUERY: never; + REQUEST: any[]; + RESPONSE: any; + }; }; }