diff --git a/src/account/default.ts b/src/account/default.ts index 92a8ff1a5..0d91542ca 100644 --- a/src/account/default.ts +++ b/src/account/default.ts @@ -45,7 +45,7 @@ import { UniversalDeployerContractPayload, UniversalDetails, } from '../types'; -import { ETransactionVersion, ETransactionVersion3, type ResourceBounds } from '../types/api'; +import { ETransactionVersion, ETransactionVersion3 } from '../types/api'; import { OutsideExecutionVersion, type OutsideExecution, @@ -77,6 +77,7 @@ import { buildUDCCall, getExecuteCalldata } from '../utils/transaction'; import { getMessageHash } from '../utils/typedData'; import { AccountInterface } from './interface'; import { config } from '../global/config'; +import { ResourceBounds } from '../provider/types/spec.type'; export class Account extends Provider implements AccountInterface { public signer: SignerInterface; diff --git a/src/channel/rpc_0_8.ts b/src/channel/rpc_0_8.ts index da3320be6..994d9d0f2 100644 --- a/src/channel/rpc_0_8.ts +++ b/src/channel/rpc_0_8.ts @@ -36,6 +36,8 @@ import { Block, getDefaultNodeUrl, isV3Tx, wait } from '../utils/provider'; import { decompressProgram, signatureToHexArray } from '../utils/stark'; import { getVersionsByType } from '../utils/transaction'; import { logger } from '../global/logger'; +import { isRPC08_ResourceBounds } from '../provider/types/spec.type'; +// TODO: check if we can filet type before entering to this method, as so to specify here only RPC 0.8 types const defaultOptions = { headers: { 'Content-Type': 'application/json' }, @@ -485,23 +487,25 @@ export class RpcChannel { public async invoke(functionInvocation: Invocation, details: InvocationsDetailsWithNonce) { let promise; if (isV3Tx(details)) { - // 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, - }, - }); + if (isRPC08_ResourceBounds(details.resourceBounds)) { + // 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, + }, + }); + } else throw Error(SYSTEM_MESSAGES.SWOldV3); } else throw Error(SYSTEM_MESSAGES.legacyTxRPC08Message); return this.waitMode ? this.waitForTransaction((await promise).transaction_hash) : promise; @@ -513,29 +517,31 @@ export class RpcChannel { ) { let promise; 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, + if (isRPC08_ResourceBounds(details.resourceBounds)) { + // 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, }, - 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(SYSTEM_MESSAGES.SWOldV3); } else throw Error(SYSTEM_MESSAGES.legacyTxRPC08Message); return this.waitMode ? this.waitForTransaction((await promise).transaction_hash) : promise; @@ -547,23 +553,25 @@ export class RpcChannel { ) { let promise; if (isV3Tx(details)) { + if (isRPC08_ResourceBounds(details.resourceBounds)) { + 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, + }, + }); + } else throw Error(SYSTEM_MESSAGES.SWOldV3); // 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, - }, - }); } else throw Error(SYSTEM_MESSAGES.legacyTxRPC08Message); return this.waitMode ? this.waitForTransaction((await promise).transaction_hash) : promise; diff --git a/src/global/constants.ts b/src/global/constants.ts index 486b41fb2..325060fa5 100644 --- a/src/global/constants.ts +++ b/src/global/constants.ts @@ -57,6 +57,10 @@ export enum TransactionHashPrefix { export const enum FeeMarginPercentage { L1_BOUND_MAX_AMOUNT = 50, L1_BOUND_MAX_PRICE_PER_UNIT = 50, + L1_DATA_BOUND_MAX_AMOUNT = 50, + L1_DATA_BOUND_MAX_PRICE_PER_UNIT = 50, + L2_BOUND_MAX_AMOUNT = 50, + L2_BOUND_MAX_PRICE_PER_UNIT = 50, MAX_FEE = 50, } @@ -113,4 +117,5 @@ export const SYSTEM_MESSAGES = { legacyTxWarningMessage: 'You are using a deprecated transaction version (V0,V1,V2)!\nUpdate to the latest V3 transactions!', legacyTxRPC08Message: 'RPC 0.8 do not support legacy transactions', + SWOldV3: 'RPC 0.7 V3 tx (improper resource bounds) not supported in RPC 0.8', }; diff --git a/src/provider/rpc.ts b/src/provider/rpc.ts index 631c844a2..920e8515c 100644 --- a/src/provider/rpc.ts +++ b/src/provider/rpc.ts @@ -1,6 +1,6 @@ import type { SPEC } from 'starknet-types-07'; -import { RPC08, RPC07, RpcChannel } from '../channel'; +import { RPC08, RPC07 } from '../channel'; import { AccountInvocations, BigNumberish, @@ -61,7 +61,7 @@ export class RpcProvider implements ProviderInterface { ? optionsOrProvider.responseParser : new RPCResponseParser(); } else { - this.channel = new RpcChannel({ ...optionsOrProvider, waitMode: false }); + this.channel = new RPC08.RpcChannel({ ...optionsOrProvider, waitMode: false }); this.responseParser = new RPCResponseParser(optionsOrProvider?.feeMarginPercentage); } } diff --git a/src/provider/types/configuration.type.ts b/src/provider/types/configuration.type.ts index 0e0d0d386..a19cfd3f4 100644 --- a/src/provider/types/configuration.type.ts +++ b/src/provider/types/configuration.type.ts @@ -1,5 +1,6 @@ import { NetworkName, StarknetChainId } from '../../global/constants'; import { BlockIdentifier } from '../../types/lib'; +import { ResourceBoundsOverhead } from './spec.type'; export interface ProviderOptions extends RpcProviderOptions {} @@ -15,9 +16,8 @@ export type RpcProviderOptions = { waitMode?: boolean; baseFetch?: WindowOrWorkerGlobalScope['fetch']; feeMarginPercentage?: { - l1BoundMaxAmount: number; - l1BoundMaxPricePerUnit: number; - maxFee: number; + bounds: ResourceBoundsOverhead; // V3 tx + maxFee: number; // V legacy tx }; batch?: false | number; }; diff --git a/src/provider/types/spec.type.ts b/src/provider/types/spec.type.ts index 3448579bf..379f03cc7 100644 --- a/src/provider/types/spec.type.ts +++ b/src/provider/types/spec.type.ts @@ -1,6 +1,7 @@ // this file aims to unify the RPC specification types used by the common Provider class import { IsPending, IsType, RPCSPEC07, RPCSPEC08 } from '../../types/api'; +import { SimpleOneOf } from '../../types/helpers'; // TODO: Check can we remove this ? @@ -27,13 +28,15 @@ type MergeProperties, T2 extends Record> = [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 occurrences in the -// currently supported RPC specifications +/** + * 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 occurrences in the + currently supported RPC specifications + */ type Merge = Simplify< T1 extends Array ? T2 extends Array @@ -91,18 +94,62 @@ export type DeclaredTransaction = Merge< RPCSPEC08.DeclaredTransaction, RPCSPEC07.DeclaredTransaction >; -export type FeeEstimate = Merge; export type InvokedTransaction = Merge; export type PendingReceipt = Merge< RPCSPEC08.TransactionReceiptPendingBlock, RPCSPEC07.PendingReceipt >; export type Receipt = Merge; -export type ResourceBounds = Merge; // TODO: only l1_gas:val, l2_gas:0 OR l1_gas:val, l1_data_gas: val, l2_gas:val ARE VALID COMBO, waiting slack response -export type SimulateTransaction = Merge< - RPCSPEC08.SimulateTransaction, - RPCSPEC07.SimulateTransaction + +// One of +export type FeeEstimate = SimpleOneOf; + +export function isRPC08_FeeEstimate(entry: FeeEstimate): entry is RPCSPEC08.FEE_ESTIMATE { + return 'l1_data_gas_consumed' in entry; +} + +// One of +export type ResourceBounds = Simplify< + SimpleOneOf >; + +export function isRPC08_ResourceBounds(entry: ResourceBounds): entry is RPCSPEC08.ResourceBounds { + return 'l1_data_gas' in entry; +} + +/** + * overhead percentage on estimate fee + */ +export type ResourceBoundsOverhead = ResourceBoundsOverheadRPC08 | ResourceBoundsOverheadRPC07; + +/** + * percentage overhead on estimated fee + */ +export type ResourceBoundsOverheadRPC08 = { + l1_gas: { + max_amount: number; + max_price_per_unit: number; + }; + l2_gas: { + max_amount: number; + max_price_per_unit: number; + }; + l1_data_gas: { + max_amount: number; + max_price_per_unit: number; + }; +}; + +export type ResourceBoundsOverheadRPC07 = { + l1_gas: { + max_amount: number; + max_price_per_unit: number; + }; +}; + +// TODO: ja mislin da types-js rpc 0.7 ima krivu definiciju za transaction trace +export type SimulateTransaction = RPCSPEC08.SimulateTransaction; + export type TransactionWithHash = Merge< RPCSPEC08.TransactionWithHash, RPCSPEC07.TransactionWithHash diff --git a/src/types/account.ts b/src/types/account.ts index fb367cb6d..38efe308b 100644 --- a/src/types/account.ts +++ b/src/types/account.ts @@ -1,4 +1,4 @@ -import { EDataAvailabilityMode, ETransactionVersion, ResourceBounds } from './api'; +import { EDataAvailabilityMode, ETransactionVersion } from './api'; import { AllowArray, BigNumberish, @@ -14,6 +14,7 @@ import { DeclareTransactionReceiptResponse, EstimateFeeResponse, } from '../provider/types/index.type'; +import { ResourceBounds } from '../provider/types/spec.type'; export interface EstimateFee extends EstimateFeeResponse {} diff --git a/src/types/lib/index.ts b/src/types/lib/index.ts index 7f0c89c99..6d4d8be78 100644 --- a/src/types/lib/index.ts +++ b/src/types/lib/index.ts @@ -1,9 +1,10 @@ import { StarknetChainId } from '../../global/constants'; import { weierstrass } from '../../utils/ec'; -import { EDataAvailabilityMode, ResourceBounds } from '../api'; +import { EDataAvailabilityMode } from '../api'; import { CairoEnum } from '../cairoEnum'; import { CompiledContract, CompiledSierraCasm, ContractClass } from './contract'; import { ValuesType } from '../helpers/valuesType'; +import { ResourceBounds } from '../../provider/types/spec.type'; export type WeierstrassSignatureType = weierstrass.SignatureType; export type ArraySignatureType = string[]; diff --git a/src/utils/hash/transactionHash/index.ts b/src/utils/hash/transactionHash/index.ts index 8929208ba..28e14a04c 100644 --- a/src/utils/hash/transactionHash/index.ts +++ b/src/utils/hash/transactionHash/index.ts @@ -3,13 +3,13 @@ */ import { StarknetChainId } from '../../../global/constants'; +import { ResourceBounds } from '../../../provider/types/spec.type'; import { BigNumberish, Calldata } from '../../../types'; import { EDAMode, ETransactionVersion, ETransactionVersion2, ETransactionVersion3, - ResourceBounds, } from '../../../types/api'; import { calculateDeclareTransactionHash as v2calculateDeclareTransactionHash, diff --git a/src/utils/hash/transactionHash/v3.ts b/src/utils/hash/transactionHash/v3.ts index 9902a0b33..5f97a1cff 100644 --- a/src/utils/hash/transactionHash/v3.ts +++ b/src/utils/hash/transactionHash/v3.ts @@ -6,9 +6,10 @@ import { poseidonHashMany } from '@scure/starknet'; import { StarknetChainId, TransactionHashPrefix } from '../../../global/constants'; import { BigNumberish, Calldata } from '../../../types'; -import { EDAMode, ResourceBounds } from '../../../types/api'; +import { EDAMode, RPCSPEC07, RPCSPEC08 } from '../../../types/api'; import { toHex } from '../../num'; import { encodeShortString } from '../../shortString'; +import { isRPC08_ResourceBounds, type ResourceBounds } from '../../../provider/types/spec.type'; const AToBI = (array: BigNumberish[]) => array.map((it: BigNumberish) => BigInt(it)); @@ -19,6 +20,7 @@ const MAX_PRICE_PER_UNIT_BITS = 128n; const RESOURCE_VALUE_OFFSET = MAX_AMOUNT_BITS + MAX_PRICE_PER_UNIT_BITS; const L1_GAS_NAME = BigInt(encodeShortString('L1_GAS')); const L2_GAS_NAME = BigInt(encodeShortString('L2_GAS')); +const L1_DATA_GAS_NAME = BigInt(encodeShortString('L1_DATA')); export function hashDAMode(nonceDAMode: BigNumberish, feeDAMode: BigNumberish) { return (BigInt(nonceDAMode) << DATA_AVAILABILITY_MODE_BITS) + BigInt(feeDAMode); @@ -45,7 +47,7 @@ export function encodeResourceBoundsL1(bounds: ResourceBounds): bigint { } * @returns {bigint} encoded data */ -export function encodeResourceBoundsL2(bounds: ResourceBounds): bigint { +export function encodeResourceBoundsL2(bounds: RPCSPEC07.ResourceBounds): bigint { return ( (L2_GAS_NAME << RESOURCE_VALUE_OFFSET) + (BigInt(bounds.l2_gas.max_amount) << MAX_PRICE_PER_UNIT_BITS) + @@ -53,12 +55,33 @@ export function encodeResourceBoundsL2(bounds: ResourceBounds): bigint { ); } -export function hashFeeField(tip: BigNumberish, bounds: ResourceBounds) { +export function encodeDataResourceBoundsL1(bounds: RPCSPEC08.ResourceBounds): bigint { + return ( + (L1_DATA_GAS_NAME << RESOURCE_VALUE_OFFSET) + + (BigInt(bounds.l1_data_gas.max_amount) << MAX_PRICE_PER_UNIT_BITS) + + BigInt(bounds.l1_data_gas.max_price_per_unit) + ); +} + +/** + * hash tip and resource bounds (2 bound parameters) V3 RPC 0.7 + */ +export function hashFeeField(tip: BigNumberish, bounds: RPCSPEC07.ResourceBounds) { const L1Bound = encodeResourceBoundsL1(bounds); const L2Bound = encodeResourceBoundsL2(bounds); return poseidonHashMany([BigInt(tip), L1Bound, L2Bound]); } +/** + * hash tip and resource bounds (3 bounds params) V3 RPC 0.8 + */ +export function hashFeeFieldV3B3(tip: BigNumberish, bounds: RPCSPEC08.ResourceBounds) { + const L1Bound = encodeResourceBoundsL1(bounds); + const L2Bound = encodeResourceBoundsL2(bounds); + const L1Data = encodeDataResourceBoundsL1(bounds); + return poseidonHashMany([BigInt(tip), L1Bound, L2Bound, L1Data]); +} + export function calculateTransactionHashCommon( txHashPrefix: TransactionHashPrefix, version: BigNumberish, @@ -72,7 +95,9 @@ export function calculateTransactionHashCommon( resourceBounds: ResourceBounds, additionalData: BigNumberish[] = [] ): string { - const feeFieldHash = hashFeeField(tip, resourceBounds); + const feeFieldHash = isRPC08_ResourceBounds(resourceBounds) + ? hashFeeFieldV3B3(tip, resourceBounds) + : hashFeeField(tip, resourceBounds); const dAModeHash = hashDAMode(nonceDataAvailabilityMode, feeDataAvailabilityMode); const dataToHash = AToBI([ txHashPrefix, diff --git a/src/utils/responseParser/rpc.ts b/src/utils/responseParser/rpc.ts index 1b1437ac5..eba3e26cd 100644 --- a/src/utils/responseParser/rpc.ts +++ b/src/utils/responseParser/rpc.ts @@ -13,13 +13,12 @@ import type { GetTxReceiptResponseWithoutHelper, RpcProviderOptions, SimulateTransactionResponse, - SimulatedTransaction, } from '../../provider/types/index.type'; import { toBigInt } from '../num'; import { isString } from '../typed'; import { estimateFeeToBounds, estimatedFeeToMaxFee } from '../stark'; import { ResponseParser } from './interface'; -import { TransactionReceipt } from '../../provider/types/spec.type'; +import { SimulateTransaction, TransactionReceipt } from '../../provider/types/spec.type'; // import { TransactionReceipt } from '../../types/api/merge'; export class RPCResponseParser @@ -44,12 +43,9 @@ export class RPCResponseParser return estimatedFeeToMaxFee(estimatedFee, this.margin?.maxFee); } + // TODO: Check how to recaltulate bounds and what use this ? private estimateFeeToBounds(estimate: Parameters[0]) { - return estimateFeeToBounds( - estimate, - this.margin?.l1BoundMaxAmount, - this.margin?.l1BoundMaxPricePerUnit - ); + return estimateFeeToBounds(estimate, this.margin?.bounds); } public parseGetBlockResponse(res: BlockWithTxHashes): GetBlockResponse { @@ -76,8 +72,8 @@ export class RPCResponseParser const val = res[0]; return { overall_fee: toBigInt(val.overall_fee), - gas_consumed: toBigInt(val.gas_consumed), - gas_price: toBigInt(val.gas_price), + gas_consumed: toBigInt(0), // TODO: vidit sta sa ovim + gas_price: toBigInt(0), // TODO: vidit sta sa ovim unit: val.unit, suggestedMaxFee: this.estimatedFeeToMaxFee(val.overall_fee), resourceBounds: this.estimateFeeToBounds(val), @@ -89,8 +85,9 @@ export class RPCResponseParser public parseFeeEstimateBulkResponse(res: FeeEstimate[]): EstimateFeeResponseBulk { return res.map((val) => ({ overall_fee: toBigInt(val.overall_fee), - gas_consumed: toBigInt(val.gas_consumed), - gas_price: toBigInt(val.gas_price), + // TODO: vidit sta sa ovim + gas_consumed: toBigInt(0), // TODO: vidit sta sa ovim + gas_price: toBigInt(0), // TODO: vidit sta sa ovim unit: val.unit, suggestedMaxFee: this.estimatedFeeToMaxFee(val.overall_fee), resourceBounds: this.estimateFeeToBounds(val), @@ -107,7 +104,7 @@ export class RPCResponseParser // res: SimulateTransactionResponse res: any ): SimulateTransactionResponse { - return res.map((it: SimulatedTransaction) => { + return res.map((it: SimulateTransaction) => { return { ...it, suggestedMaxFee: this.estimatedFeeToMaxFee(it.fee_estimation.overall_fee), diff --git a/src/utils/stark.ts b/src/utils/stark.ts deleted file mode 100644 index 8fd5a7b99..000000000 --- a/src/utils/stark.ts +++ /dev/null @@ -1,381 +0,0 @@ -import { getPublicKey, getStarkKey, utils } from '@scure/starknet'; -import { gzip, ungzip } from 'pako'; - -import { ZERO, FeeMarginPercentage } from '../global/constants'; -import { - ArraySignatureType, - BigNumberish, - CompressedProgram, - Program, - Signature, - UniversalDetails, -} from '../types'; -import { EDAMode, EDataAvailabilityMode, ETransactionVersion, ResourceBounds } from '../types/api'; -import { FeeEstimate } from '../provider/types/index.type'; -import { addHexPrefix, arrayBufferToString, atobUniversal, btoaUniversal, buf2hex } from './encode'; -import { parse, stringify } from './json'; -import { - addPercent, - bigNumberishArrayToDecimalStringArray, - bigNumberishArrayToHexadecimalStringArray, - toHex, -} from './num'; -import { isUndefined, isString, isBigInt } from './typed'; - -type V3Details = Required< - Pick< - UniversalDetails, - | 'tip' - | 'paymasterData' - | 'accountDeploymentData' - | 'nonceDataAvailabilityMode' - | 'feeDataAvailabilityMode' - | 'resourceBounds' - > ->; - -/** - * Compress compiled Cairo 0 program - * - * [Reference](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/services/api/gateway/transaction.py#L54-L58) - * @param {Program | string} jsonProgram Representing the compiled Cairo 0 program - * @return {CompressedProgram} Compressed Cairo 0 program - * @example - * ```typescript - * const contractCairo0 = json.parse(fs.readFileSync("./cairo0contract.json").toString("ascii")); - * const result = stark.compressProgram(contractCairo0); - * // result = "H4sIAAAAAAAAA+1dC4/bOJL+K4aBu01me7r5EEUyixzQk/TuB..." - * ``` - */ -export function compressProgram(jsonProgram: Program | string): CompressedProgram { - const stringified = isString(jsonProgram) ? jsonProgram : stringify(jsonProgram); - const compressedProgram = gzip(stringified); - return btoaUniversal(compressedProgram); -} - -/** - * Decompress compressed compiled Cairo 0 program - * @param {CompressedProgram | CompressedProgram[]} base64 Compressed Cairo 0 program - * @returns Parsed decompressed compiled Cairo 0 program - * @example - * ```typescript - * const contractCairo0 = json.parse(fs.readFileSync("./cairo0contract.json").toString("ascii")); - * const compressedCairo0 = stark.compressProgram(contractCairo0); - * const result = stark.decompressProgram(compressedCairo0); - * // result = { - * // abi: [ - * // { - * // inputs: [Array], - * // name: 'increase_balance', - * // outputs: [], - * // type: 'function' - * // } - * // ], - * // entry_points_by_type: { CONSTRUCTOR: [], EXTERNAL: [ [Object], [Object] ], L1_HANDLER: [] }, - * // program: { - * // attributes: [], - * // builtins: [ 'pedersen', 'range_check' ], - * // compiler_version: '0.10.2', - * // data: [ - * // '0x480680017fff8000', - * // ... - * ``` - */ -export function decompressProgram(base64: CompressedProgram | CompressedProgram[]) { - if (Array.isArray(base64)) return base64; - const decompressed = arrayBufferToString(ungzip(atobUniversal(base64))); - return parse(decompressed); -} - -/** - * Random Address based on random keyPair - * @returns {string} an hex string of a random Starknet address - * @example - * ```typescript - * const result = stark.randomAddress(); - * // result = "0x51fc8126a13cd5ddb29a71ca399cb1e814f086f5af1b502d7151c14929554f" - * ``` - */ -export function randomAddress(): string { - const randomKeyPair = utils.randomPrivateKey(); - return getStarkKey(randomKeyPair); -} - -/** - * Lowercase and hex prefix string - * - * @deprecated Not used internally, naming is confusing based on functionality - */ -export function makeAddress(input: string): string { - return addHexPrefix(input).toLowerCase(); -} - -/** - * Format Signature to standard type (hex array) - * @param {Signature} [sig] - * @returns {ArraySignatureType} Custom hex string array - * @throws {Error} if sig not defined, or wrong format - * @example - * ```typescript - * const signature = ec.starkCurve.sign("0x12de34", "0x3487123eac"); - * const result = stark.formatSignature(signature); - * // result = ['0xba8eecee2d69c417e8c6a20cf331c821f716b58ba9e47166c7476afdb38997', - * // '0x69ef7438c94104839a6e2aa2385482a77399d2f46e894ae4f50ab6d69239d1c'] - * ``` - */ -export function formatSignature(sig?: Signature): ArraySignatureType { - if (!sig) throw Error('formatSignature: provided signature is undefined'); - if (Array.isArray(sig)) { - return sig.map((it) => toHex(it)); - } - try { - const { r, s } = sig; - return [toHex(r), toHex(s)]; - } catch (e) { - throw new Error('Signature need to be weierstrass.SignatureType or an array for custom'); - } -} - -/** - * Format Signature to decimal string array - * @param {Signature} [sig] - * @returns {ArraySignatureType} Custom hex string array - * @throws {Error} if sig not defined, or wrong format - * @example - * ```typescript - * const signature = ec.starkCurve.sign("0x12de34", "0x3487123eac"); - * const result = stark.signatureToDecimalArray(signature); - * // result = ['329619989660444495690615805546674399714973829707166906185976654753023887767', - * // '2994745480203297689255012826403147585778741462125743754529207781488706428188'] - * ``` - */ -export function signatureToDecimalArray(sig?: Signature): ArraySignatureType { - return bigNumberishArrayToDecimalStringArray(formatSignature(sig)); -} - -/** - * Format Signature to hex string array - * @param {Signature} [sig] - * @returns {ArraySignatureType} Custom hex string array - * @throws {Error} if sig not defined, or wrong format - * @example - * ```typescript - * const signature = ec.starkCurve.sign("0x12de34", "0x3487123eac"); - * const result = stark.signatureToHexArray(signature); - * // result = ['0xba8eecee2d69c417e8c6a20cf331c821f716b58ba9e47166c7476afdb38997', - * // '0x69ef7438c94104839a6e2aa2385482a77399d2f46e894ae4f50ab6d69239d1c'] - * ``` - */ -export function signatureToHexArray(sig?: Signature): ArraySignatureType { - return bigNumberishArrayToHexadecimalStringArray(formatSignature(sig)); -} - -/** - * Convert estimated fee to max fee including a margin - * @param {BigNumberish} estimatedFee - The estimated fee - * @param {number} [overhead = feeMarginPercentage.MAX_FEE] - The overhead added to the gas - * @returns {bigint} The maximum fee with the margin - * @example - * ```typescript - * const result = stark.estimatedFeeToMaxFee("8982300000000", 50); - * // result = "13473450000000n" - * ``` - */ -export function estimatedFeeToMaxFee( - estimatedFee: BigNumberish, - overhead: number = FeeMarginPercentage.MAX_FEE -): bigint { - return addPercent(estimatedFee, overhead); -} - -/** - * Calculates the maximum resource bounds for fee estimation. - * - * @param {FeeEstimate | 0n} estimate The estimate for the fee. If a BigInt is provided, the returned bounds will be set to '0x0'. - * @param {number} [amountOverhead = feeMarginPercentage.L1_BOUND_MAX_AMOUNT] - The percentage overhead added to the gas consumed or overall fee amount. - * @param {number} [priceOverhead = feeMarginPercentage.L1_BOUND_MAX_PRICE_PER_UNIT] The percentage overhead added to the gas price per unit. - * @returns {ResourceBounds} The maximum resource bounds for fee estimation. - * @throws {Error} If the estimate object is undefined or does not have the required properties. - * @example - * ```typescript - * const feeEstimated: FeeEstimate = { - gas_consumed: "0x3456a", - gas_price: "0xa45567567567ae4", - overall_fee: "0x2198F463A77A899A5668", - unit: "WEI" -}; -const result = stark.estimateFeeToBounds(feeEstimated, 70, 50); - * // result = { - * // l2_gas: { max_amount: '0x0', max_price_per_unit: '0x0' }, - * // l1_gas: { max_amount: '0x58f9a', max_price_per_unit: '0xf6801b01b01b856' } - * // } - * ``` - */ -export function estimateFeeToBounds( - estimate: FeeEstimate | 0n, - amountOverhead: number = FeeMarginPercentage.L1_BOUND_MAX_AMOUNT, - priceOverhead: number = FeeMarginPercentage.L1_BOUND_MAX_PRICE_PER_UNIT -): ResourceBounds { - if (isBigInt(estimate)) { - return { - l2_gas: { max_amount: '0x0', max_price_per_unit: '0x0' }, - l1_data_gas: { max_amount: '0x0', max_price_per_unit: '0x0' }, - l1_gas: { max_amount: '0x0', max_price_per_unit: '0x0' }, - }; - } - - if (isUndefined(estimate.gas_consumed) || isUndefined(estimate.gas_price)) { - throw Error('estimateFeeToBounds: estimate is undefined'); - } - - const maxUnits = - estimate.data_gas_consumed !== undefined && estimate.data_gas_price !== undefined // RPC v0.7 - ? toHex(addPercent(BigInt(estimate.overall_fee) / BigInt(estimate.gas_price), amountOverhead)) - : toHex(addPercent(estimate.gas_consumed, amountOverhead)); - const maxUnitPrice = toHex(addPercent(estimate.gas_price, priceOverhead)); - return { - l2_gas: { max_amount: '0x0', max_price_per_unit: '0x0' }, - l1_data_gas: { max_amount: '0x0', max_price_per_unit: '0x0' }, // TODO: check what to fill here - l1_gas: { max_amount: maxUnits, max_price_per_unit: maxUnitPrice }, - }; -} - -/** - * Converts the data availability mode from EDataAvailabilityMode to EDAMode. - * - * @param {EDataAvailabilityMode} dam The data availability mode to be converted. - * @return {EDAMode} The converted data availability mode. - * @throws {Error} If the data availability mode is not a valid value. - * @example - * ```typescript - * const result = stark.intDAM(RPC.EDataAvailabilityMode.L1); - * // result = 0 - * ``` - */ -export function intDAM(dam: EDataAvailabilityMode): EDAMode { - if (dam === EDataAvailabilityMode.L1) return EDAMode.L1; - if (dam === EDataAvailabilityMode.L2) return EDAMode.L2; - throw Error('EDAM conversion'); -} - -/** - * Convert to ETransactionVersion or throw an error. - * Return providedVersion is specified else return defaultVersion - * @param {BigNumberish} defaultVersion default estimate transaction version - * @param {BigNumberish} [providedVersion] estimate transaction version - * @returns {ETransactionVersion} if providedVersion is not provided, returns the default estimate version, else return the provided version - * @throws {Error} if estimate transaction version or default estimate transaction version is unknown - * @example - * ```typescript - * const result = stark.toTransactionVersion("0x100000000000000000000000000000003", stark.toFeeVersion(2)); - * // result = "0x100000000000000000000000000000002" - * ``` - */ -export function toTransactionVersion( - defaultVersion: BigNumberish, - providedVersion?: BigNumberish -): ETransactionVersion { - const providedVersion0xs = providedVersion ? toHex(providedVersion) : undefined; - const defaultVersion0xs = toHex(defaultVersion); - - if (providedVersion && !Object.values(ETransactionVersion).includes(providedVersion0xs as any)) { - throw Error(`providedVersion ${providedVersion} is not ETransactionVersion`); - } - if (!Object.values(ETransactionVersion).includes(defaultVersion0xs as any)) { - throw Error(`defaultVersion ${defaultVersion} is not ETransactionVersion`); - } - - return (providedVersion ? providedVersion0xs : defaultVersion0xs) as ETransactionVersion; -} - -/** - * Convert Transaction version to Fee version or throw an error - * @param {BigNumberish} [providedVersion] 0..3 number representing the transaction version - * @returns {ETransactionVersion | undefined} the fee estimation version corresponding to the transaction version provided - * @throws {Error} if the transaction version is unknown - * @example - * ```typescript - * const result = stark.toFeeVersion(2); - * // result = "0x100000000000000000000000000000002" - * ``` - */ -export function toFeeVersion(providedVersion?: BigNumberish): ETransactionVersion | undefined { - if (!providedVersion) return undefined; - const version = toHex(providedVersion); - - if (version === ETransactionVersion.V0) return ETransactionVersion.F0; - if (version === ETransactionVersion.V1) return ETransactionVersion.F1; - if (version === ETransactionVersion.V2) return ETransactionVersion.F2; - if (version === ETransactionVersion.V3) return ETransactionVersion.F3; - - throw Error(`toFeeVersion: ${version} is not supported`); -} - -/** - * Return provided or default v3 tx details - * @param {UniversalDetails} details details of the transaction - * @return {V3Details} an object including the V3 transaction details. - * @example - * ```typescript - * const detail: UniversalDetails = { tip: 3456n }; - * const result = stark.v3Details(detail); - * // result = { - * // tip: 3456n, - * // paymasterData: [], - * // accountDeploymentData: [], - * // nonceDataAvailabilityMode: 'L1', - * // feeDataAvailabilityMode: 'L1', - * // resourceBounds: { - * // l2_gas: { max_amount: '0x0', max_price_per_unit: '0x0' }, - * // l1_gas: { max_amount: '0x0', max_price_per_unit: '0x0' } - * // } - * // } - * ``` - */ - -export function v3Details(details: UniversalDetails): V3Details { - return { - tip: details.tip || 0, - paymasterData: details.paymasterData || [], - accountDeploymentData: details.accountDeploymentData || [], - nonceDataAvailabilityMode: details.nonceDataAvailabilityMode || EDataAvailabilityMode.L1, - feeDataAvailabilityMode: details.feeDataAvailabilityMode || EDataAvailabilityMode.L1, - resourceBounds: details.resourceBounds ?? estimateFeeToBounds(ZERO), - }; -} - -/** - * It will reduce V2 to V1, else (V3) stay the same - * F2 -> F1 - * V2 -> V1 - * F3 -> F3 - * V3 -> V3 - * @param {ETransactionVersion} providedVersion - * @returns {ETransactionVersion} if v2 then returns v1. if v3 then return v3 - * @example - * ```typescript - * const result = stark.reduceV2(constants.TRANSACTION_VERSION.V2); - * // result = "0x1" - * ``` - */ -export function reduceV2(providedVersion: ETransactionVersion): ETransactionVersion { - if (providedVersion === ETransactionVersion.F2) return ETransactionVersion.F1; - if (providedVersion === ETransactionVersion.V2) return ETransactionVersion.V1; - return providedVersion; -} - -/** - * get the hex string of the full public key related to a Starknet private key. - * @param {BigNumberish} privateKey a 252 bits private key. - * @returns {string} an hex string of a 520 bit number, representing the full public key related to `privateKey`. - * @example - * ```typescript - * const result = ec.getFullPublicKey("0x43b7240d227aa2fb8434350b3321c40ac1b88c7067982549e7609870621b535"); - * // result = "0x0400b730bd22358612b5a67f8ad52ce80f9e8e893639ade263537e6ef35852e5d3057795f6b090f7c6985ee143f798608a53b3659222c06693c630857a10a92acf" - * ``` - */ -export function getFullPublicKey(privateKey: BigNumberish): string { - const privKey = toHex(privateKey); - const fullPrivKey = addHexPrefix(buf2hex(getPublicKey(privKey, false))); - return fullPrivKey; -}