From 5b2b2997ee7e54144864315ed20175017e4218b4 Mon Sep 17 00:00:00 2001 From: Toni Tabak Date: Sat, 25 Nov 2023 21:04:12 +0100 Subject: [PATCH] feat: rpc 0.6 implementation as superset of 0.5 tx --- __tests__/account.test.ts | 5 +- src/account/default.ts | 2 +- src/channel/index.ts | 1 + src/channel/rpc_0_5.ts | 561 ------------------------ src/channel/rpc_0_6.ts | 215 ++++++--- src/constants.ts | 3 + src/index.ts | 1 + src/provider/interface.ts | 2 +- src/provider/rpc.ts | 2 +- src/types/api/rpc.ts | 4 +- src/types/api/rpcspec_0_6/components.ts | 1 - src/types/lib/index.ts | 20 +- src/types/provider/response.ts | 6 +- src/utils/hash.ts | 8 +- src/utils/provider.ts | 7 + src/utils/responseParser/rpc.ts | 2 +- 16 files changed, 202 insertions(+), 638 deletions(-) create mode 100644 src/channel/index.ts delete mode 100644 src/channel/rpc_0_5.ts diff --git a/__tests__/account.test.ts b/__tests__/account.test.ts index d72581cbc..04995e38b 100644 --- a/__tests__/account.test.ts +++ b/__tests__/account.test.ts @@ -3,7 +3,6 @@ import { Account, Contract, DeclareDeployUDCResponse, - DeployTransactionReceiptResponse, Provider, TransactionType, cairo, @@ -559,7 +558,7 @@ describe('deploy and test Wallet', () => { // check pre-calculated address const txReceipt = await provider.waitForTransaction(deployment.transaction_hash); - const udcEvent = parseUDCEvent(txReceipt as DeployTransactionReceiptResponse); + const udcEvent = parseUDCEvent(txReceipt as any); // todo: when time fix types expect(cleanHex(deployment.contract_address[0])).toBe(cleanHex(udcEvent.contract_address)); }); @@ -580,7 +579,7 @@ describe('deploy and test Wallet', () => { // check pre-calculated address const txReceipt = await provider.waitForTransaction(deployment.transaction_hash); - const udcEvent = parseUDCEvent(txReceipt as DeployTransactionReceiptResponse); + const udcEvent = parseUDCEvent(txReceipt as any); // todo: when time fix types expect(cleanHex(deployment.contract_address[0])).toBe(cleanHex(udcEvent.contract_address)); }); diff --git a/src/account/default.ts b/src/account/default.ts index 48d668c84..d4bb0fa66 100644 --- a/src/account/default.ts +++ b/src/account/default.ts @@ -409,7 +409,7 @@ export class Account extends Provider implements AccountInterface { ): Promise { const deployTx = await this.deploy(payload, details); const txReceipt = await this.waitForTransaction(deployTx.transaction_hash); - return parseUDCEvent(txReceipt as DeployTransactionReceiptResponse); + return parseUDCEvent(txReceipt as unknown as DeployTransactionReceiptResponse); } public async declareAndDeploy( diff --git a/src/channel/index.ts b/src/channel/index.ts new file mode 100644 index 000000000..4e0bb013b --- /dev/null +++ b/src/channel/index.ts @@ -0,0 +1 @@ +export * from './rpc_0_6'; diff --git a/src/channel/rpc_0_5.ts b/src/channel/rpc_0_5.ts deleted file mode 100644 index a1c9ad427..000000000 --- a/src/channel/rpc_0_5.ts +++ /dev/null @@ -1,561 +0,0 @@ -import { - HEX_STR_TRANSACTION_VERSION_1, - HEX_STR_TRANSACTION_VERSION_2, - NetworkName, - StarknetChainId, -} from '../constants'; -import { LibraryError } from '../provider/errors'; -import { - AccountInvocationItem, - AccountInvocations, - BigNumberish, - BlockIdentifier, - BlockTag, - Call, - DeclareContractTransaction, - DeployAccountContractTransaction, - Invocation, - InvocationsDetailsWithNonce, - RPC, - RpcProviderOptions, - TransactionType, - getEstimateFeeBulkOptions, - getSimulateTransactionOptions, - waitForTransactionOptions, -} from '../types'; -import { CallData } from '../utils/calldata'; -import { isSierra } from '../utils/contract'; -import fetch from '../utils/fetchPonyfill'; -import { getSelector, getSelectorFromName, getVersionsByType } from '../utils/hash'; -import { stringify } from '../utils/json'; -import { getHexStringArray, toHex, toStorageKey } from '../utils/num'; -import { Block, getDefaultNodeUrl, wait } from '../utils/provider'; -import { decompressProgram, signatureToHexArray } from '../utils/stark'; - -const defaultOptions = { - headers: { 'Content-Type': 'application/json' }, - blockIdentifier: BlockTag.pending, - retries: 200, -}; - -export class RpcChannel { - public nodeUrl: string; - - public headers: object; - - readonly retries: number; - - readonly blockIdentifier: BlockIdentifier; - - private chainId?: StarknetChainId; - - readonly waitMode: Boolean; // behave like web2 rpc and return when tx is processed - - readonly rpcSpec = '0_5'; - - 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; - } - - public fetch(method: string, params?: object, id: string | number = 0) { - const rpcRequestBody: RPC.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?: RPC.JRPC.Error, otherError?: any) { - if (rpcError) { - const { code, message, data } = rpcError; - throw new LibraryError( - `RPC: ${method} with params ${stringify(params)}\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); - 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 getSpecVersion() { - return this.fetchEndpoint('starknet_specVersion'); - } - - 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 = false, - skipFeeCharge = true, - }: getSimulateTransactionOptions = {} - ) { - const block_id = new Block(blockIdentifier).identifier; - const simulationFlags = []; - 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, - 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 (successStates.includes(executionStatus) || successStates.includes(finalityStatus)) { - onchain = true; - } else 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; - } - } 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.TransactionReceipt; - } - - 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 getEstimateFee( - invocations: AccountInvocations, - { blockIdentifier = this.blockIdentifier }: getEstimateFeeBulkOptions - ) { - const block_id = new Block(blockIdentifier).identifier; - return this.fetchEndpoint('starknet_estimateFee', { - request: invocations.map((it) => this.buildTransaction(it, 'fee')), - block_id, - }); - } - - public async invoke(functionInvocation: Invocation, details: InvocationsDetailsWithNonce) { - const 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: '0x1', - signature: signatureToHexArray(functionInvocation.signature), - nonce: toHex(details.nonce), - }, - }); - - 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)) { - 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: HEX_STR_TRANSACTION_VERSION_1, - max_fee: toHex(details.maxFee || 0), - signature: signatureToHexArray(signature), - sender_address: senderAddress, - nonce: toHex(details.nonce), - }, - }); - } else { - 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: HEX_STR_TRANSACTION_VERSION_2, - max_fee: toHex(details.maxFee || 0), - signature: signatureToHexArray(signature), - sender_address: senderAddress, - nonce: toHex(details.nonce), - }, - }); - } - - return this.waitMode ? this.waitForTransaction((await promise).transaction_hash) : promise; - } - - public async deployAccount( - { classHash, constructorCalldata, addressSalt, signature }: DeployAccountContractTransaction, - details: InvocationsDetailsWithNonce - ) { - const 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: toHex(details.version || 0), - signature: signatureToHexArray(signature), - nonce: toHex(details.nonce), - }, - }); - - 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); - const details = { - signature: signatureToHexArray(invocation.signature), - nonce: toHex(invocation.nonce), - max_fee: toHex(invocation.maxFee || 0), - }; - - if (invocation.type === TransactionType.INVOKE) { - return { - type: RPC.ETransactionType.INVOKE, // Diff between sequencer and rpc invoke type - sender_address: invocation.contractAddress, - calldata: CallData.toHex(invocation.calldata), - version: toHex(invocation.version || defaultVersions.v1), - ...details, - }; - } - if (invocation.type === TransactionType.DECLARE) { - if (!isSierra(invocation.contract)) { - return { - type: invocation.type, - contract_class: invocation.contract, - sender_address: invocation.senderAddress, - version: toHex(invocation.version || defaultVersions.v1), - ...details, - }; - } - return { - // compiled_class_hash - 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.v2), - ...details, - }; - } - if (invocation.type === TransactionType.DEPLOY_ACCOUNT) { - 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.v1), - ...details, - }; - } - throw Error('RPC buildTransaction received unknown TransactionType'); - } -} diff --git a/src/channel/rpc_0_6.ts b/src/channel/rpc_0_6.ts index 6163f65b4..c1aa4ab4e 100644 --- a/src/channel/rpc_0_6.ts +++ b/src/channel/rpc_0_6.ts @@ -1,6 +1,7 @@ import { HEX_STR_TRANSACTION_VERSION_1, HEX_STR_TRANSACTION_VERSION_2, + HEX_STR_TRANSACTION_VERSION_3, NetworkName, StarknetChainId, } from '../constants'; @@ -29,7 +30,7 @@ import fetch from '../utils/fetchPonyfill'; import { getSelector, getSelectorFromName, getVersionsByType } from '../utils/hash'; import { stringify } from '../utils/json'; import { getHexStringArray, toHex, toStorageKey } from '../utils/num'; -import { Block, getDefaultNodeUrl, wait } from '../utils/provider'; +import { Block, getDefaultNodeUrl, isV3Tx, wait } from '../utils/provider'; import { decompressProgram, signatureToHexArray } from '../utils/stark'; const defaultOptions = { @@ -51,7 +52,7 @@ export class RpcChannel { readonly waitMode: Boolean; // behave like web2 rpc and return when tx is processed - readonly rpcSpec = '0_6'; + readonly version = 'v0_6'; constructor(optionsOrProvider?: RpcProviderOptions) { const { nodeUrl, retries, headers, blockIdentifier, chainId, waitMode } = @@ -99,15 +100,15 @@ export class RpcChannel { } } - protected async fetchEndpoint( + protected async fetchEndpoint( method: T, - params?: RPC.Methods[T]['params'] - ): Promise { + params?: RPC.V0_6.Methods[T]['params'] + ): Promise { try { const rawResult = await this.fetch(method, params); const { error, result } = await rawResult.json(); this.errorHandler(method, params, error); - return result as RPC.Methods[T]['result']; + return result as RPC.V0_6.Methods[T]['result']; } catch (error: any) { this.errorHandler(method, params, error?.response?.data, error); throw error; @@ -223,8 +224,8 @@ export class RpcChannel { ) { const block_id = new Block(blockIdentifier).identifier; const simulationFlags = []; - if (skipValidate) simulationFlags.push(RPC.ESimulationFlag.SKIP_VALIDATE); - if (skipFeeCharge) simulationFlags.push(RPC.ESimulationFlag.SKIP_FEE_CHARGE); + if (skipValidate) simulationFlags.push(RPC.V0_6.ESimulationFlag.SKIP_VALIDATE); + if (skipFeeCharge) simulationFlags.push(RPC.V0_6.ESimulationFlag.SKIP_FEE_CHARGE); return this.fetchEndpoint('starknet_simulateTransactions', { block_id, @@ -240,16 +241,16 @@ export class RpcChannel { let isErrorState = false; const retryInterval = options?.retryInterval ?? 5000; const errorStates: any = options?.errorStates ?? [ - RPC.ETransactionStatus.REJECTED, - RPC.ETransactionExecutionStatus.REVERTED, + RPC.V0_6.ETransactionStatus.REJECTED, + RPC.V0_6.ETransactionExecutionStatus.REVERTED, ]; const successStates: any = options?.successStates ?? [ - RPC.ETransactionExecutionStatus.SUCCEEDED, - RPC.ETransactionStatus.ACCEPTED_ON_L2, - RPC.ETransactionStatus.ACCEPTED_ON_L1, + RPC.V0_6.ETransactionExecutionStatus.SUCCEEDED, + RPC.V0_6.ETransactionStatus.ACCEPTED_ON_L2, + RPC.V0_6.ETransactionStatus.ACCEPTED_ON_L1, ]; - let txStatus: RPC.TransactionStatus; + let txStatus: RPC.V0_6.TransactionStatus; while (!onchain) { // eslint-disable-next-line no-await-in-loop await wait(retryInterval); @@ -271,7 +272,7 @@ export class RpcChannel { onchain = true; } else if (errorStates.includes(executionStatus) || errorStates.includes(finalityStatus)) { const message = `${executionStatus}: ${finalityStatus}`; - const error = new Error(message) as Error & { response: RPC.TransactionStatus }; + const error = new Error(message) as Error & { response: RPC.V0_6.TransactionStatus }; error.response = txStatus; isErrorState = true; throw error; @@ -307,7 +308,7 @@ export class RpcChannel { // eslint-disable-next-line no-await-in-loop await wait(retryInterval); } - return txReceipt as RPC.TransactionReceipt; + return txReceipt as RPC.V0_6.SPEC.TXN_RECEIPT; } public getStorageAt( @@ -373,17 +374,39 @@ export class RpcChannel { } public async invoke(functionInvocation: Invocation, details: InvocationsDetailsWithNonce) { - const 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: '0x1', - signature: signatureToHexArray(functionInvocation.signature), - nonce: toHex(details.nonce), - }, - }); + let promise; + if (!isV3Tx(details)) { + // V1 + promise = this.fetchEndpoint('starknet_addInvokeTransaction', { + invoke_transaction: { + sender_address: functionInvocation.contractAddress, + calldata: CallData.toHex(functionInvocation.calldata), + type: RPC.V0_6.ETransactionType.INVOKE, + max_fee: toHex(details.maxFee || 0), + version: HEX_STR_TRANSACTION_VERSION_1, + signature: signatureToHexArray(functionInvocation.signature), + nonce: toHex(details.nonce), + }, + }); + } else { + // V3 + promise = this.fetchEndpoint('starknet_addInvokeTransaction', { + invoke_transaction: { + type: RPC.V0_6.ETransactionType.INVOKE, + sender_address: functionInvocation.contractAddress, + calldata: CallData.toHex(functionInvocation.calldata), + version: HEX_STR_TRANSACTION_VERSION_3, + 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; } @@ -393,10 +416,11 @@ export class RpcChannel { details: InvocationsDetailsWithNonce ) { let promise; - if (!isSierra(contract)) { + if (!isSierra(contract) && !isV3Tx(details)) { + // V1 Cairo 0 promise = this.fetchEndpoint('starknet_addDeclareTransaction', { declare_transaction: { - type: RPC.ETransactionType.DECLARE, + type: RPC.V0_6.ETransactionType.DECLARE, contract_class: { program: contract.program, entry_points_by_type: contract.entry_points_by_type, @@ -409,10 +433,11 @@ export class RpcChannel { nonce: toHex(details.nonce), }, }); - } else { + } else if (isSierra(contract) && !isV3Tx(details)) { + // V2 Cairo1 promise = this.fetchEndpoint('starknet_addDeclareTransaction', { declare_transaction: { - type: RPC.ETransactionType.DECLARE, + type: RPC.V0_6.ETransactionType.DECLARE, contract_class: { sierra_program: decompressProgram(contract.sierra_program), contract_class_version: contract.contract_class_version, @@ -427,6 +452,32 @@ export class RpcChannel { nonce: toHex(details.nonce), }, }); + } else if (isSierra(contract) && isV3Tx(details)) { + // V3 Cairo1 + promise = this.fetchEndpoint('starknet_addDeclareTransaction', { + declare_transaction: { + type: RPC.V0_6.ETransactionType.DECLARE, + sender_address: senderAddress, + compiled_class_hash: compiledClassHash || '', + version: HEX_STR_TRANSACTION_VERSION_3, + 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; @@ -436,18 +487,40 @@ export class RpcChannel { { classHash, constructorCalldata, addressSalt, signature }: DeployAccountContractTransaction, details: InvocationsDetailsWithNonce ) { - const 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: toHex(details.version || 0), - signature: signatureToHexArray(signature), - nonce: toHex(details.nonce), - }, - }); + const version = details.version ? toHex(details.version) : '0x3'; + let promise; + if (!isV3Tx(details)) { + 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.V0_6.ETransactionType.DEPLOY_ACCOUNT, + max_fee: toHex(details.maxFee || 0), + version: version as RPC.V0_6.SPEC.DEPLOY_ACCOUNT_TXN_V1['version'], // todo: rethink + signature: signatureToHexArray(signature), + nonce: toHex(details.nonce), + }, + }); + } else { + promise = this.fetchEndpoint('starknet_addDeployAccountTransaction', { + deploy_account_transaction: { + type: RPC.V0_6.ETransactionType.DEPLOY_ACCOUNT, + version: version as RPC.V0_6.SPEC.DEPLOY_ACCOUNT_TXN_V3['version'], // todo: rethink + 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)), + 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; } @@ -469,7 +542,7 @@ export class RpcChannel { * @param message Message From L1 */ public estimateMessageFee( - message: RPC.L1Message, + message: RPC.V0_6.L1Message, blockIdentifier: BlockIdentifier = this.blockIdentifier ) { const { from_address, to_address, entry_point_selector, payload } = message; @@ -499,42 +572,61 @@ export class RpcChannel { * Returns all events matching the given filter * @returns events and the pagination of the events */ - public getEvents(eventFilter: RPC.EventFilter) { + public getEvents(eventFilter: RPC.V0_6.EventFilter) { return this.fetchEndpoint('starknet_getEvents', { filter: eventFilter }); } public buildTransaction( invocation: AccountInvocationItem, versionType?: 'fee' | 'transaction' - ): RPC.BaseTransaction { + ): RPC.V0_6.BaseTransaction { const defaultVersions = getVersionsByType(versionType); - const details = { - signature: signatureToHexArray(invocation.signature), - nonce: toHex(invocation.nonce), - max_fee: toHex(invocation.maxFee || 0), - }; + 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)), + account_deployment_data: invocation.accountDeploymentData.map((it) => toHex(it)), + nonce_data_availability_mode: invocation.nonceDataAvailabilityMode, + fee_data_availability_mode: invocation.feeDataAvailabilityMode, + }; + } if (invocation.type === TransactionType.INVOKE) { return { - type: RPC.ETransactionType.INVOKE, // Diff between sequencer and rpc invoke type + // v0 v1 v3 + type: RPC.V0_6.ETransactionType.INVOKE, // Diff between sequencer and rpc invoke type sender_address: invocation.contractAddress, calldata: CallData.toHex(invocation.calldata), - version: toHex(invocation.version || defaultVersions.v1), + version: toHex(invocation.version || defaultVersions.v3), ...details, - }; + } as RPC.V0_6.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.V0_6.SPEC.BROADCASTED_DECLARE_TXN_V1; } return { - // compiled_class_hash + // Cairo 1 - v2 v3 type: invocation.type, contract_class: { ...invocation.contract, @@ -542,19 +634,22 @@ export class RpcChannel { }, compiled_class_hash: invocation.compiledClassHash || '', sender_address: invocation.senderAddress, - version: toHex(invocation.version || defaultVersions.v2), + version: toHex(invocation.version || defaultVersions.v3), ...details, - }; + } as RPC.V0_6.SPEC.BROADCASTED_DECLARE_TXN; } if (invocation.type === TransactionType.DEPLOY_ACCOUNT) { + // 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.v1), + version: toHex( + invocation.version || defaultVersions.v3 + ) as RPC.V0_6.SPEC.INVOKE_TXN['version'], ...details, - }; + } as RPC.V0_6.SPEC.BROADCASTED_DEPLOY_ACCOUNT_TXN; } throw Error('RPC buildTransaction received unknown TransactionType'); } diff --git a/src/constants.ts b/src/constants.ts index 60a9303b1..5e51cf0af 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -7,10 +7,13 @@ export const TEXT_TO_FELT_MAX_LEN = 31; export const HEX_STR_TRANSACTION_VERSION_1 = '0x1'; export const HEX_STR_TRANSACTION_VERSION_2 = '0x2'; +export const HEX_STR_TRANSACTION_VERSION_3 = '0x3'; export const BN_TRANSACTION_VERSION_1 = 1n; export const BN_TRANSACTION_VERSION_2 = 2n; +export const BN_TRANSACTION_VERSION_3 = 3n; export const BN_FEE_TRANSACTION_VERSION_1 = 2n ** 128n + BN_TRANSACTION_VERSION_1; export const BN_FEE_TRANSACTION_VERSION_2 = 2n ** 128n + BN_TRANSACTION_VERSION_2; +export const BN_FEE_TRANSACTION_VERSION_3 = 2n ** 128n + BN_TRANSACTION_VERSION_3; export const ZERO = 0n; export const MASK_250 = 2n ** 250n - 1n; // 2 ** 250 - 1 diff --git a/src/index.ts b/src/index.ts index 195cfbc16..ed7d7e3c0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ export * from './account'; export * from './contract'; export * from './provider'; export * from './signer'; +export * from './channel'; // TODO: decide on final export style export * from './types'; diff --git a/src/provider/interface.ts b/src/provider/interface.ts index 0333b9a70..3093255aa 100644 --- a/src/provider/interface.ts +++ b/src/provider/interface.ts @@ -1,4 +1,4 @@ -import { RpcChannel } from '../channel/rpc_0_5'; +import { RpcChannel } from '../channel/rpc_0_6'; import { StarknetChainId } from '../constants'; import type { AccountInvocations, diff --git a/src/provider/rpc.ts b/src/provider/rpc.ts index 53ac3eafc..56f95ace5 100644 --- a/src/provider/rpc.ts +++ b/src/provider/rpc.ts @@ -1,4 +1,4 @@ -import { RpcChannel } from '../channel/rpc_0_5'; +import { RpcChannel } from '../channel/rpc_0_6'; import { AccountInvocations, BigNumberish, diff --git a/src/types/api/rpc.ts b/src/types/api/rpc.ts index 49f4cdf89..5e88b2eef 100644 --- a/src/types/api/rpc.ts +++ b/src/types/api/rpc.ts @@ -1,2 +1,4 @@ export * as JRPC from './jsonrpc'; -export * from './rpcspec_0_5'; +export * from './rpcspec_0_6'; // exported as default +export * as V0_5 from './rpcspec_0_5'; +export * as V0_6 from './rpcspec_0_6'; diff --git a/src/types/api/rpcspec_0_6/components.ts b/src/types/api/rpcspec_0_6/components.ts index 3d08a538c..62d38f473 100644 --- a/src/types/api/rpcspec_0_6/components.ts +++ b/src/types/api/rpcspec_0_6/components.ts @@ -320,7 +320,6 @@ export type DEPLOY_ACCOUNT_TXN_V1 = { export type DEPLOY_ACCOUNT_TXN_V3 = { type: 'DEPLOY_ACCOUNT'; - max_fee: FELT; version: '0x3' | '0x100000000000000000000000000000003'; signature: SIGNATURE; nonce: FELT; diff --git a/src/types/lib/index.ts b/src/types/lib/index.ts index 92fcb45e3..70d5df5b9 100644 --- a/src/types/lib/index.ts +++ b/src/types/lib/index.ts @@ -1,5 +1,6 @@ import { StarknetChainId } from '../../constants'; import { weierstrass } from '../../utils/ec'; +import { V0_6 } from '../api/rpc'; import { CairoEnum } from '../cairoEnum'; import { CompiledContract, CompiledSierraCasm, ContractClass } from './contract'; @@ -114,6 +115,17 @@ export type InvocationsDetails = { version?: BigNumberish; }; +export type V3InvocationDetails = { + nonce: BigNumberish; + version: BigNumberish; + resourceBounds: V0_6.SPEC.RESOURCE_BOUNDS_MAPPING; + tip: BigNumberish; + paymasterData: BigNumberish[]; + accountDeploymentData: BigNumberish[]; + nonceDataAvailabilityMode: V0_6.SPEC.DA_MODE; + feeDataAvailabilityMode: V0_6.SPEC.DA_MODE; +}; + /** * Contain all additional details params */ @@ -124,9 +136,11 @@ export type Details = { chainId: StarknetChainId; }; -export type InvocationsDetailsWithNonce = InvocationsDetails & { - nonce: BigNumberish; -}; +export type InvocationsDetailsWithNonce = + | (InvocationsDetails & { + nonce: BigNumberish; + }) + | V3InvocationDetails; export enum TransactionType { DECLARE = 'DECLARE', diff --git a/src/types/provider/response.ts b/src/types/provider/response.ts index 67db579e6..133ead74a 100644 --- a/src/types/provider/response.ts +++ b/src/types/provider/response.ts @@ -4,7 +4,7 @@ */ import * as RPC from '../api/rpc'; -import { BlockHash } from '../api/rpc'; +import { BlockHash, V0_6 } from '../api/rpc'; import * as Sequencer from '../api/sequencer'; import { AllowArray, @@ -113,7 +113,7 @@ export interface InvokeTransactionReceiptResponse { execution_status: TransactionExecutionStatus; finality_status: TransactionFinalityStatus; status?: `${TransactionStatus}`; // SEQ only - actual_fee: string; + actual_fee: string | V0_6.SPEC.FEE_ESTIMATE; block_hash: BlockHash; block_number: BlockNumber; transaction_hash: string; @@ -158,7 +158,7 @@ export type RevertedTransactionReceiptResponse = { execution_status: TransactionExecutionStatus.REVERTED | any; // any due to RPC Spec issue finality_status: TransactionFinalityStatus | any; status?: TransactionStatus; // SEQ only - actual_fee: string; + actual_fee: string | V0_6.SPEC.FEE_PAYMENT; block_hash?: string; // ?~ optional due to RPC spec issue block_number?: BlockNumber; // ?~ optional due to RCP spec issue transaction_hash: string; diff --git a/src/utils/hash.ts b/src/utils/hash.ts index 205a37599..cef399b2b 100644 --- a/src/utils/hash.ts +++ b/src/utils/hash.ts @@ -6,8 +6,10 @@ import { API_VERSION, BN_FEE_TRANSACTION_VERSION_1, BN_FEE_TRANSACTION_VERSION_2, + BN_FEE_TRANSACTION_VERSION_3, BN_TRANSACTION_VERSION_1, BN_TRANSACTION_VERSION_2, + BN_TRANSACTION_VERSION_3, StarknetChainId, TransactionHashPrefix, } from '../constants'; @@ -37,16 +39,18 @@ export * from './selector'; // Preserve legacy export structure export const transactionVersion = BN_TRANSACTION_VERSION_1; export const transactionVersion_2 = BN_TRANSACTION_VERSION_2; +export const transactionVersion_3 = BN_TRANSACTION_VERSION_3; export const feeTransactionVersion = BN_FEE_TRANSACTION_VERSION_1; export const feeTransactionVersion_2 = BN_FEE_TRANSACTION_VERSION_2; +export const feeTransactionVersion_3 = BN_FEE_TRANSACTION_VERSION_3; /** * Return transaction versions based on version type, default version type is 'transaction' */ export function getVersionsByType(versionType?: 'fee' | 'transaction') { return versionType === 'fee' - ? { v1: feeTransactionVersion, v2: feeTransactionVersion_2 } - : { v1: transactionVersion, v2: transactionVersion_2 }; + ? { v1: feeTransactionVersion, v2: feeTransactionVersion_2, v3: feeTransactionVersion_3 } + : { v1: transactionVersion, v2: transactionVersion_2, v3: transactionVersion_3 }; } /** diff --git a/src/utils/provider.ts b/src/utils/provider.ts index c23fb9ba6..6c993d363 100644 --- a/src/utils/provider.ts +++ b/src/utils/provider.ts @@ -7,10 +7,12 @@ import { CompiledContract, CompiledSierra, ContractClass, + InvocationsDetailsWithNonce, LegacyContractClass, RPC, SequencerIdentifier, SierraContractClass, + V3InvocationDetails, } from '../types'; import { isSierra } from './contract'; import { formatSpaces } from './hash'; @@ -177,3 +179,8 @@ export function defStateUpdate( } return pending(state); } + +export function isV3Tx(details: InvocationsDetailsWithNonce): details is V3InvocationDetails { + const version = details.version ? toHex(details.version) : '0x3'; + return version === '0x3' || version === '0x100000000000000000000000000000003'; +} diff --git a/src/utils/responseParser/rpc.ts b/src/utils/responseParser/rpc.ts index 3e5b942b5..292ad4824 100644 --- a/src/utils/responseParser/rpc.ts +++ b/src/utils/responseParser/rpc.ts @@ -18,7 +18,7 @@ import { FeeEstimate, SimulateTransactionResponse as RPCSimulateTransactionResponse, TransactionWithHash, -} from '../../types/api/rpcspec_0_5'; +} from '../../types/api/rpcspec_0_6'; import { toBigInt } from '../num'; import { estimatedFeeToMaxFee } from '../stark'; import { ResponseParser } from '.';