diff --git a/__tests__/WebSocketChannel.test.ts b/__tests__/WebSocketChannel.test.ts index 29fe01ae7..50f98f1ec 100644 --- a/__tests__/WebSocketChannel.test.ts +++ b/__tests__/WebSocketChannel.test.ts @@ -1,7 +1,7 @@ import { WebSocket } from 'isows'; import { Provider, WSSubscriptions, WebSocketChannel } from '../src'; -import { StarknetChainId } from '../src/constants'; +import { StarknetChainId } from '../src/global/constants'; import { getTestAccount, getTestProvider } from './config/fixtures'; const nodeUrl = 'wss://sepolia-pathfinder-rpc.spaceshard.io/rpc/v0_8'; diff --git a/__tests__/cairo1.test.ts b/__tests__/cairo1.test.ts index af0b8e089..980297599 100644 --- a/__tests__/cairo1.test.ts +++ b/__tests__/cairo1.test.ts @@ -143,9 +143,11 @@ describeIfDevnet('Cairo 1 Devnet', () => { ); await account.waitForTransaction(tx.transaction_hash); - const balance = await cairo1Contract.get_balance({ - parseResponse: false, - }); + const balance = await cairo1Contract + .withOptions({ + parseResponse: false, + }) + .get_balance(); expect(num.toBigInt(balance[0])).toBe(100n); }); @@ -291,9 +293,11 @@ describeIfDevnet('Cairo 1 Devnet', () => { test('Cairo 1 more complex structs', async () => { const tx = await cairo1Contract.set_bet(); await account.waitForTransaction(tx.transaction_hash); - const status = await cairo1Contract.get_bet(1, { - formatResponse: { name: 'string', description: 'string' }, - }); + const status = await cairo1Contract + .withOptions({ + formatResponse: { name: 'string', description: 'string' }, + }) + .get_bet(1); const expected = { name: 'test', diff --git a/__tests__/cairo1v2.test.ts b/__tests__/cairo1v2.test.ts index ce1992364..28640e5ce 100644 --- a/__tests__/cairo1v2.test.ts +++ b/__tests__/cairo1v2.test.ts @@ -137,9 +137,11 @@ describe('Cairo 1', () => { ); await account.waitForTransaction(tx.transaction_hash); - const balance = await cairo1Contract.get_balance({ - parseResponse: false, - }); + const balance = await cairo1Contract + .withOptions({ + parseResponse: false, + }) + .get_balance(); expect(num.toBigInt(balance[0])).toBe(100n); }); @@ -335,9 +337,11 @@ describe('Cairo 1', () => { test('Cairo 1 more complex structs', async () => { const tx = await cairo1Contract.set_bet(); await account.waitForTransaction(tx.transaction_hash); - const status = await cairo1Contract.get_bet(1, { - formatResponse: { name: 'string', description: 'string' }, - }); + const status = await cairo1Contract + .withOptions({ + formatResponse: { name: 'string', description: 'string' }, + }) + .get_bet(1); const expected = { name: 'test', diff --git a/__tests__/cairo1v2_typed.test.ts b/__tests__/cairo1v2_typed.test.ts index 251557959..ab5777cc5 100644 --- a/__tests__/cairo1v2_typed.test.ts +++ b/__tests__/cairo1v2_typed.test.ts @@ -143,9 +143,11 @@ describe('Cairo 1', () => { ); await account.waitForTransaction(tx.transaction_hash); - const balance = await cairo1Contract.get_balance({ - parseResponse: false, - }); + const balance = await cairo1Contract + .withOptions({ + parseResponse: false, + }) + .get_balance(); // TODO: handle parseResponse correctly, get_balance should return a list here !? expect(num.toBigInt(balance)).toBe(100n); @@ -348,9 +350,11 @@ describe('Cairo 1', () => { test('Cairo 1 more complex structs', async () => { const tx = await cairo1Contract.set_bet(); await account.waitForTransaction(tx.transaction_hash); - const status = await cairo1Contract.get_bet(1, { - formatResponse: { name: 'string', description: 'string' }, - }); + const status = await cairo1Contract + .withOptions({ + formatResponse: { name: 'string', description: 'string' }, + }) + .get_bet(1); const expected = { name: 'test', diff --git a/__tests__/contract.test.ts b/__tests__/contract.test.ts index 019ffea56..09d30c44c 100644 --- a/__tests__/contract.test.ts +++ b/__tests__/contract.test.ts @@ -83,9 +83,11 @@ describe('contract module', () => { }); test('read initial balance of that account', async () => { - const { balance } = await erc20Contract.balanceOf(wallet, { - formatResponse: { balance: uint256ToBN }, - }); + const { balance } = await erc20Contract + .withOptions({ + formatResponse: { balance: uint256ToBN }, + }) + .balanceOf(wallet); expect(balance).toStrictEqual(BigInt(1000)); }); @@ -573,18 +575,24 @@ describe('Complex interaction', () => { const calldata = CallData.compile(request); const args = Object.values(request); - const result = await erc20Echo20Contract.echo(calldata, { - parseRequest: true, - parseResponse: true, - formatResponse, - }); + const result = await erc20Echo20Contract + .withOptions({ + parseRequest: true, + parseResponse: true, + formatResponse, + }) + .echo(calldata); + + const result2 = await erc20Echo20Contract + .withOptions({ + formatResponse, + }) + .echo(...args); - const result2 = await erc20Echo20Contract.echo(...args, { - formatResponse, - }); const result3 = await erc20Echo20Contract.call('echo', calldata, { formatResponse, }); + const result4 = await erc20Echo20Contract.call('echo', args, { formatResponse, }); @@ -851,26 +859,27 @@ describe('Complex interaction', () => { describe('speedup live tests', () => { test('call parameterized data', async () => { - const result = await erc20Echo20Contract.echo( - request.t1, - request.n1, - request.tl2, - request.k1, - request.k2, - request.u1, - request.s1, - request.s2, - request.af1, - request.au1, - request.as1, - request.atmk, - request.atmku, - { + const result = await erc20Echo20Contract + .withOptions({ parseRequest: true, parseResponse: true, formatResponse, - } - ); + }) + .echo( + request.t1, + request.n1, + request.tl2, + request.k1, + request.k2, + request.u1, + request.s1, + request.s2, + request.af1, + request.au1, + request.as1, + request.atmk, + request.atmku + ); // Convert request uint256 to match response const compareRequest = { @@ -883,22 +892,25 @@ describe('Complex interaction', () => { }); test('invoke parameterized data', async () => { - const result = await erc20Echo20Contract.iecho( - request.t1, - request.n1, - request.tl2, - request.k1, - request.k2, - request.u1, - request.s1, - request.s2, - request.af1, - request.au1, - request.as1, - request.atmk, - request.atmku, - { formatResponse } - ); + const result = await erc20Echo20Contract + .withOptions({ + formatResponse, + }) + .iecho( + request.t1, + request.n1, + request.tl2, + request.k1, + request.k2, + request.u1, + request.s1, + request.s2, + request.af1, + request.au1, + request.as1, + request.atmk, + request.atmku + ); const transaction = await provider.waitForTransaction(result.transaction_hash); expect( (transaction as unknown as SuccessfulTransactionReceiptResponse).execution_status @@ -930,7 +942,11 @@ describe('Complex interaction', () => { // mark data as compiled (it can be also done manually check defineProperty compiled in CallData.compile) const compiledCallData = CallData.compile(populated4.calldata); - const result = await erc20Echo20Contract.echo(compiledCallData, { formatResponse }); + const result = await erc20Echo20Contract + .withOptions({ + formatResponse, + }) + .echo(compiledCallData); // Convert request uint256 to match response const compareRequest = { diff --git a/__tests__/utils/ethSigner.test.ts b/__tests__/utils/ethSigner.test.ts index c7d28a46b..f1ad79ca9 100644 --- a/__tests__/utils/ethSigner.test.ts +++ b/__tests__/utils/ethSigner.test.ts @@ -169,11 +169,9 @@ describe('Ethereum signer', () => { test('ETH account transaction V2', async () => { const ethContract2 = new Contract(contracts.Erc20.abi, devnetETHtokenAddress, ethAccount); - const respTransfer = await ethContract2.transfer( - account.address, - cairo.uint256(1 * 10 ** 4), - { maxFee: 1 * 10 ** 16 } - ); + const respTransfer = await ethContract2 + .withOptions({ maxFee: 1 * 10 ** 16 }) + .transfer(account.address, cairo.uint256(1 * 10 ** 4)); const txR = await provider.waitForTransaction(respTransfer.transaction_hash); if (txR.isSuccess()) { // TODO: @PhilippeR26 Why this is not working, fix 'as any' hotfix diff --git a/src/contract/contractFactory.ts b/src/contract/contractFactory.ts index 96ae758cb..def650e20 100644 --- a/src/contract/contractFactory.ts +++ b/src/contract/contractFactory.ts @@ -2,14 +2,15 @@ import { AccountInterface } from '../account'; import { logger } from '../global/logger'; import { Abi, - ArgsOrCalldataWithOptions, + ArgsOrCalldata, CairoAssembly, CompiledContract, + ContractOptions, ValidateType, } from '../types'; import assert from '../utils/assert'; import { CallData } from '../utils/calldata'; -import { Contract, getCalldata, splitArgsAndOptions } from './default'; +import { Contract, getCalldata } from './default'; export type ContractFactoryParams = { compiledContract: CompiledContract; @@ -18,6 +19,7 @@ export type ContractFactoryParams = { classHash?: string; compiledClassHash?: string; abi?: Abi; + contractOptions?: ContractOptions; }; export class ContractFactory { @@ -35,6 +37,8 @@ export class ContractFactory { private CallData: CallData; + public contractOptions?: ContractOptions; + /** * @param params CFParams * - compiledContract: CompiledContract; @@ -52,6 +56,7 @@ export class ContractFactory { this.classHash = params.classHash; this.compiledClassHash = params.compiledClassHash; this.CallData = new CallData(this.abi); + this.contractOptions = params.contractOptions; } /** @@ -59,16 +64,16 @@ export class ContractFactory { * * If contract is not declared it will first declare it, and then deploy */ - public async deploy(...args: ArgsOrCalldataWithOptions): Promise { - const { args: param, options = { parseRequest: true } } = splitArgsAndOptions(args); + public async deploy(...args: ArgsOrCalldata): Promise { + // const { args: param, options = { parseRequest: true } } = args; // splitArgsAndOptions(args); - const constructorCalldata = getCalldata(param, () => { - if (options.parseRequest) { - this.CallData.validate(ValidateType.DEPLOY, 'constructor', param); - return this.CallData.compile('constructor', param); + const constructorCalldata = getCalldata(args, () => { + if (this.contractOptions?.parseRequest) { + this.CallData.validate(ValidateType.DEPLOY, 'constructor', args); + return this.CallData.compile('constructor', args); } logger.warn('Call skipped parsing but provided rawArgs, possible malfunction request'); - return param; + return args; }); const { @@ -79,7 +84,7 @@ export class ContractFactory { classHash: this.classHash, compiledClassHash: this.compiledClassHash, constructorCalldata, - salt: options.addressSalt, + salt: this.contractOptions?.addressSalt, }); assert(Boolean(contract_address), 'Deployment of the contract failed'); diff --git a/src/contract/default.ts b/src/contract/default.ts index cbd5edd34..9910530c8 100644 --- a/src/contract/default.ts +++ b/src/contract/default.ts @@ -5,8 +5,8 @@ import { ProviderInterface, defaultProvider } from '../provider'; import { Abi, AbiEvents, + AbiStruct, ArgsOrCalldata, - ArgsOrCalldataWithOptions, AsyncContractFunction, Call, CallOptions, @@ -21,7 +21,6 @@ import { ParsedEvents, RawArgs, Result, - AbiStruct, ValidateType, type SuccessfulTransactionReceiptResponse, } from '../types'; @@ -35,34 +34,18 @@ import { logger } from '../global/logger'; export type TypedContractV2 = AbiWanTypedContract & Contract; -export const splitArgsAndOptions = (args: ArgsOrCalldataWithOptions) => { - const options = [ - 'blockIdentifier', - 'parseRequest', - 'parseResponse', - 'formatResponse', - 'maxFee', - 'nonce', - 'signature', - 'addressSalt', - ]; - const lastArg = args[args.length - 1]; - if (typeof lastArg === 'object' && options.some((x) => x in lastArg)) { - return { args: args as ArgsOrCalldata, options: args.pop() as ContractOptions }; - } - return { args: args as ArgsOrCalldata }; -}; - /** * Adds call methods to the contract */ function buildCall(contract: Contract, functionAbi: FunctionAbi): AsyncContractFunction { - return async function (...args: ArgsOrCalldataWithOptions): Promise { - const params = splitArgsAndOptions(args); - return contract.call(functionAbi.name, params.args, { + return async function (...args: ArgsOrCalldata): Promise { + const options = { ...contract.contractOptions }; + // eslint-disable-next-line no-param-reassign + contract.contractOptions = undefined; + return contract.call(functionAbi.name, args, { parseRequest: true, parseResponse: true, - ...params.options, + ...options, }); }; } @@ -71,11 +54,13 @@ function buildCall(contract: Contract, functionAbi: FunctionAbi): AsyncContractF * Adds invoke methods to the contract */ function buildInvoke(contract: Contract, functionAbi: FunctionAbi): AsyncContractFunction { - return async function (...args: Array): Promise { - const params = splitArgsAndOptions(args); - return contract.invoke(functionAbi.name, params.args, { + return async function (...args: ArgsOrCalldata): Promise { + const options = { ...contract.contractOptions }; + // eslint-disable-next-line no-param-reassign + contract.contractOptions = undefined; + return contract.invoke(functionAbi.name, args, { parseRequest: true, - ...params.options, + ...options, }); }; } @@ -141,6 +126,8 @@ export class Contract implements ContractInterface { private callData: CallData; + public contractOptions?: ContractOptions; + /** * Contract class to handle contract methods * @@ -204,6 +191,11 @@ export class Contract implements ContractInterface { }); } + public withOptions(options: ContractOptions) { + this.contractOptions = options; + return this; + } + public attach(address: string): void { this.address = address; } diff --git a/src/types/contract.ts b/src/types/contract.ts index 94389c01e..6737d83be 100644 --- a/src/types/contract.ts +++ b/src/types/contract.ts @@ -23,12 +23,61 @@ export type Result = | boolean | CairoEnum; -export type ArgsOrCalldata = RawArgsArray | [Calldata] | Calldata; -export type ArgsOrCalldataWithOptions = ArgsOrCalldata & ContractOptions; +// export type ArgsOrCalldata = RawArgsArray | [Calldata] | Calldata; +// export type ArgsOrCalldataWithOptions = ArgsOrCalldata & ContractOptions; + +// RawParamsOrCalldata as args +export type ArgsOrCalldata = + // params like (va,vb,vc,vd...) as args is [va,vb,vc,vd...] + // params like (x) where x = {a:va,b:vb,c:vc...} as args is [x] + // params like (x) where x = [va,vb,vc...] as args is [[x]] + | RawArgsArray // recursive definition cover all this cases + // [calldata] is [['0x','0x'...]] + | [Calldata] + // calldata is ['0x','0x'...] + | Calldata; + +// RawParamsOrCalldata where each can have an option +export type ArgsOrCalldataWithOptions = + // params like (va,vb,vc,vd..., option) as args is [va,vb,vc,vd..., option] + // params like (x, option) where x = {a:va,b:vb,c:vc...} as args is [x, option] + // params like (x, option) where x = [va,vb,vc...] as args is [[x], option] + // recursive definition cover all this cases + | [...RawArgsArray] + | [...RawArgsArray, ContractOptions] + // used when called compile that return array of calldata + // (calldata, options) as args is [['0x','0x'...], options] + | [Calldata] + | [Calldata, ContractOptions] + // used when separate params compilations + // (c,a,l,l,d,a,t,a, options) as args is ['0x','0x'..., options] + | [...Calldata] + | [...Calldata, ContractOptions]; + export type ContractOptions = { blockIdentifier?: BlockIdentifier; + /** + * compile and validate arguments + */ parseRequest?: boolean; + /** + * Parse elements of the response array and structuring them into response object + */ parseResponse?: boolean; + /** + * Advance formatting used to get js types data as result + * @description https://starknetjs.com/docs/guides/define_call_message/#formatresponse + * @example + * ```typescript + * // assign custom or existing method to resulting data + * formatResponse: { balance: uint256ToBN }, + * ``` + * @example + * ```typescript + * // define resulting data js types + * const formatAnswer = { id: 'number', description: 'string' }; + * ``` + */ formatResponse?: { [key: string]: any }; maxFee?: BigNumberish; nonce?: BigNumberish;