From 27e10e2a0a2276129d254474597b6b35e3892798 Mon Sep 17 00:00:00 2001 From: lgalende Date: Thu, 16 Apr 2026 12:28:45 -0300 Subject: [PATCH 1/7] lib: add dynamic call operation --- .changeset/spicy-areas-switch.md | 7 + .../014-swap-and-dynamic-call/expected.log | 1 + .../014-swap-and-dynamic-call/manifest.yaml | 9 + .../tests/014-swap-and-dynamic-call/mock.json | 12 + .../014-swap-and-dynamic-call/src/function.ts | 30 ++ packages/lib-ts/constants.js | 2 +- .../lib-ts/src/intents/Call/EvmDynamicCall.ts | 343 ++++++++++++++++++ packages/lib-ts/src/intents/Call/index.ts | 1 + packages/lib-ts/src/intents/Intent.ts | 26 ++ packages/lib-ts/src/intents/Operation.ts | 1 + .../tests/intents/EvmDynamicCall.spec.ts | 164 +++++++++ packages/lib-ts/tests/intents/Intent.spec.ts | 32 +- packages/lib-ts/tests/intents/SvmCall.spec.ts | 6 +- packages/test-ts/src/types.ts | 6 +- yarn.lock | 82 ++++- 15 files changed, 709 insertions(+), 13 deletions(-) create mode 100644 .changeset/spicy-areas-switch.md create mode 100644 packages/integration/tests/014-swap-and-dynamic-call/expected.log create mode 100644 packages/integration/tests/014-swap-and-dynamic-call/manifest.yaml create mode 100644 packages/integration/tests/014-swap-and-dynamic-call/mock.json create mode 100644 packages/integration/tests/014-swap-and-dynamic-call/src/function.ts create mode 100644 packages/lib-ts/src/intents/Call/EvmDynamicCall.ts create mode 100644 packages/lib-ts/tests/intents/EvmDynamicCall.spec.ts diff --git a/.changeset/spicy-areas-switch.md b/.changeset/spicy-areas-switch.md new file mode 100644 index 00000000..a2743298 --- /dev/null +++ b/.changeset/spicy-areas-switch.md @@ -0,0 +1,7 @@ +--- +"@mimicprotocol/test-ts": patch +"@mimicprotocol/lib-ts": patch +"@mimicprotocol/cli": patch +--- + +Add dynamic call operation diff --git a/packages/integration/tests/014-swap-and-dynamic-call/expected.log b/packages/integration/tests/014-swap-and-dynamic-call/expected.log new file mode 100644 index 00000000..06cfc875 --- /dev/null +++ b/packages/integration/tests/014-swap-and-dynamic-call/expected.log @@ -0,0 +1 @@ +_sendIntent: {"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0xa000000000000000000000000000000000000002","amount":"10"}],"operations":[{"opType":0,"chainId":1,"user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","events":[],"sourceChain":1,"tokensIn":[{"token":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","amount":"100000000"}],"tokensOut":[{"token":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","minAmount":"95000000","recipient":"0xa000000000000000000000000000000000000001"}],"destinationChain":1},{"opType":4,"chainId":1,"user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","events":[],"calls":[{"target":"0xa000000000000000000000000000000000000001","value":"0","selector":"0x12345678","arguments":[{"kind":0,"data":"0x"},{"kind":1,"data":"0x"},{"kind":2,"data":"0x"}]}]}]} diff --git a/packages/integration/tests/014-swap-and-dynamic-call/manifest.yaml b/packages/integration/tests/014-swap-and-dynamic-call/manifest.yaml new file mode 100644 index 00000000..d3531edf --- /dev/null +++ b/packages/integration/tests/014-swap-and-dynamic-call/manifest.yaml @@ -0,0 +1,9 @@ +version: 1.0.0 +name: Example Function +description: Autogenerated Example Function +inputs: + - chainId: int32 + - target: address + - selector: bytes + - maxFeeToken: address + - maxFeeAmount: uint256 diff --git a/packages/integration/tests/014-swap-and-dynamic-call/mock.json b/packages/integration/tests/014-swap-and-dynamic-call/mock.json new file mode 100644 index 00000000..d63f39ad --- /dev/null +++ b/packages/integration/tests/014-swap-and-dynamic-call/mock.json @@ -0,0 +1,12 @@ +{ + "environment": { + "_getContext": "{ \"timestamp\": 1438223173000, \"consensusThreshold\": 1, \"user\": \"0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0\", \"settlers\": [{\"address\": \"0x6b175474e89094c44da98b954eedeac495271d0f\", \"chainId\": 1}], \"triggerSig\": \"682ec8210b1ce912da4d2952\"}" + }, + "inputs": { + "chainId": 1, + "target": "0xA000000000000000000000000000000000000001", + "selector": "0x12345678", + "maxFeeToken": "0xA000000000000000000000000000000000000002", + "maxFeeAmount": "10" + } +} diff --git a/packages/integration/tests/014-swap-and-dynamic-call/src/function.ts b/packages/integration/tests/014-swap-and-dynamic-call/src/function.ts new file mode 100644 index 00000000..c8145d9a --- /dev/null +++ b/packages/integration/tests/014-swap-and-dynamic-call/src/function.ts @@ -0,0 +1,30 @@ +import { + BigInt, + ERC20Token, + EvmDynamicArg, + EvmDynamicCallBuilder, + EvmEncodeParam, + IntentBuilder, + SwapBuilder, + TokenAmount, +} from '@mimicprotocol/lib-ts' + +import { inputs } from './types' + +export default function main(): void { + const chainId = inputs.chainId + const USDC = ERC20Token.fromString('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', chainId, 6, 'USDC') + const maxFee = TokenAmount.fromBigInt(ERC20Token.fromAddress(inputs.maxFeeToken, chainId), inputs.maxFeeAmount) + + const swap = SwapBuilder.forChains(chainId, chainId) + .addTokenInFromTokenAmount(TokenAmount.fromI32(USDC, 100)) + .addTokenOutFromTokenAmount(TokenAmount.fromI32(USDC, 95), inputs.target) + + const call = EvmDynamicCallBuilder.forChain(chainId).addCall(inputs.target, inputs.selector, [ + EvmDynamicArg.literal([EvmEncodeParam.fromValue('uint256', BigInt.fromI32(123))]), + EvmDynamicArg.variable(0, 0), + EvmDynamicArg.staticCall(inputs.target, inputs.selector), + ]) + + new IntentBuilder().addMaxFee(maxFee).addOperationsBuilders([swap, call]).send() +} diff --git a/packages/lib-ts/constants.js b/packages/lib-ts/constants.js index 0500e6e1..4da6230d 100644 --- a/packages/lib-ts/constants.js +++ b/packages/lib-ts/constants.js @@ -1 +1 @@ -export const RUNNER_TARGET_VERSION = '0.0.2' +export const RUNNER_TARGET_VERSION = '0.0.3' diff --git a/packages/lib-ts/src/intents/Call/EvmDynamicCall.ts b/packages/lib-ts/src/intents/Call/EvmDynamicCall.ts new file mode 100644 index 00000000..40c2ed8d --- /dev/null +++ b/packages/lib-ts/src/intents/Call/EvmDynamicCall.ts @@ -0,0 +1,343 @@ +import { environment } from '../../environment' +import { evm } from '../../evm' +import { TokenAmount } from '../../tokens' +import { Address, BigInt, Bytes, ChainId, EvmEncodeParam } from '../../types' +import { IntentBuilder } from '../Intent' +import { Operation, OperationBuilder, OperationEvent, OperationType } from '../Operation' + +export enum EvmDynamicArgKind { + Literal = 0, + Variable = 1, + StaticCall = 2, +} + +function validateSelector(selector: Bytes): void { + if (selector.length != 4) throw new Error('Selector must be 4 bytes') +} + +function cloneArguments(arguments_: EvmDynamicArg[]): EvmDynamicArg[] { + const cloned = new Array(arguments_.length) + for (let i = 0; i < arguments_.length; i++) { + const argument = arguments_[i] + cloned[i] = new EvmDynamicArg(argument.kind, Bytes.fromHexString(argument.data)) + } + return cloned +} + +/** + * Builder for creating EVM dynamic call operations. + */ +export class EvmDynamicCallBuilder extends OperationBuilder { + protected chainId: ChainId + protected calls: EvmDynamicCallData[] = [] + + /** + * Creates an EvmDynamicCallBuilder for the specified EVM blockchain network. + * @param chainId - The blockchain network identifier + * @returns A new EvmDynamicCallBuilder instance + */ + static forChain(chainId: ChainId): EvmDynamicCallBuilder { + return new EvmDynamicCallBuilder(chainId) + } + + /** + * Creates a new EvmDynamicCallBuilder instance. + * @param chainId - The EVM blockchain network identifier + */ + private constructor(chainId: ChainId) { + super() + this.chainId = chainId + } + + /** + * Adds a dynamic contract call to the operation. + * @param target - The contract address to call + * @param selector - The function selector to call + * @param arguments_ - The dynamic call arguments + * @param value - The native token value to send + * @returns This EvmDynamicCallBuilder instance for method chaining + */ + addCall( + target: Address, + selector: Bytes, + arguments_: EvmDynamicArg[] = [], + value: BigInt = BigInt.zero() + ): EvmDynamicCallBuilder { + this.calls.push(new EvmDynamicCallData(target, selector, arguments_, value)) + return this + } + + /** + * Adds multiple dynamic contract calls to the operation. + * @param calls - The contract calls to add + * @returns This EvmDynamicCallBuilder instance for method chaining + */ + addCalls(calls: EvmDynamicCallData[]): EvmDynamicCallBuilder { + for (let i = 0; i < calls.length; i++) { + this.addCall( + Address.fromString(calls[i].target), + Bytes.fromHexString(calls[i].selector), + cloneArguments(calls[i].arguments), + BigInt.fromString(calls[i].value) + ) + } + return this + } + + /** + * Adds the calls from another EvmDynamicCallBuilder to this EvmDynamicCallBuilder. + * @param builder - The EvmDynamicCallBuilder to add the calls from + * @returns This EvmDynamicCallBuilder instance for method chaining + */ + addCallsFromBuilder(builder: EvmDynamicCallBuilder): EvmDynamicCallBuilder { + return this.addCalls(builder.getCalls()) + } + + /** + * Adds the calls from multiple EvmDynamicCallBuilders to this EvmDynamicCallBuilder. + * @param builders - The EvmDynamicCallBuilders to add the calls from + * @returns This EvmDynamicCallBuilder instance for method chaining + */ + addCallsFromBuilders(builders: EvmDynamicCallBuilder[]): EvmDynamicCallBuilder { + for (let i = 0; i < builders.length; i++) this.addCallsFromBuilder(builders[i]) + return this + } + + /** + * Returns a copy of the calls array. + * @returns A copy of the calls array + */ + getCalls(): EvmDynamicCallData[] { + return this.calls.slice(0) + } + + /** + * Sets the user address for this operation. + * @param user - The user address + * @returns This EvmDynamicCallBuilder instance for method chaining + */ + addUser(user: Address): EvmDynamicCallBuilder { + return changetype(super.addUser(user)) + } + + /** + * Sets the user address from a string. + * @param user - The user address as a hex string + * @returns This EvmDynamicCallBuilder instance for method chaining + */ + addUserAsString(user: string): EvmDynamicCallBuilder { + return changetype(super.addUserAsString(user)) + } + + /** + * Sets an event for the operation. + * @param topic - The topic to be indexed in the event + * @param data - The event data + * @returns This EvmDynamicCallBuilder instance for method chaining + */ + addEvent(topic: Bytes, data: Bytes): EvmDynamicCallBuilder { + return changetype(super.addEvent(topic, data)) + } + + /** + * Sets multiple events for the operation. + * @param events - The list of events to be added + * @returns This EvmDynamicCallBuilder instance for method chaining + */ + addEvents(events: OperationEvent[]): EvmDynamicCallBuilder { + return changetype(super.addEvents(events)) + } + + /** + * Builds and returns the final EvmDynamicCall operation. + * @returns A new EvmDynamicCall instance with all configured parameters + */ + build(): EvmDynamicCall { + return new EvmDynamicCall(this.chainId, this.calls, this.user, this.events) + } + + /** + * Builds this operation and sends it inside an intent with the provided fee data. + * @param maxFee - The max fee to pay for the intent + * @param feePayer - The fee payer for the intent (optional) + */ + send(maxFee: TokenAmount, feePayer: Address | null = null): void { + this.build().send(maxFee, feePayer) + } +} + +/** + * Represents a static call argument specification for a dynamic argument. + */ +@json +export class EvmDynamicStaticCallArg { + public target: string + public selector: string + public arguments: EvmDynamicArg[] + + /** + * Creates a new EvmDynamicStaticCallArg instance. + * @param target - The contract address to call + * @param selector - The function selector to call + * @param arguments_ - The dynamic arguments to pass to the static call + */ + constructor(target: Address, selector: Bytes, arguments_: EvmDynamicArg[] = []) { + validateSelector(selector) + this.target = target.toString() + this.selector = selector.toHexString() + this.arguments = cloneArguments(arguments_) + } + + /** + * Converts this static call specification into an ABI tuple parameter. + * @returns The ABI tuple parameter representation + */ + toEvmEncodeParam(): EvmEncodeParam { + return EvmEncodeParam.fromValues('()', [ + EvmEncodeParam.fromValue('address', Address.fromString(this.target)), + EvmEncodeParam.fromValue('bytes4', Bytes.fromHexString(this.selector)), + EvmEncodeParam.fromValues( + '()[]', + this.arguments.map((argument: EvmDynamicArg) => argument.toEvmEncodeParam()) + ), + ]) + } +} + +/** + * Represents a single dynamic argument in a dynamic call. + */ +@json +export class EvmDynamicArg { + public kind: EvmDynamicArgKind + public data: string + + /** + * Creates a literal dynamic argument from ABI-encoded parameters. + * @param parameters - The ABI parameters to encode as a literal argument + * @returns A new literal dynamic argument + */ + static literal(parameters: EvmEncodeParam[]): EvmDynamicArg { + const encodedParameters = new Array(parameters.length + 1) + encodedParameters[0] = EvmEncodeParam.fromValue('string', Bytes.fromUTF8('')) + for (let i = 0; i < parameters.length; i++) encodedParameters[i + 1] = parameters[i] + return new EvmDynamicArg(EvmDynamicArgKind.Literal, Bytes.fromHexString(evm.encode(encodedParameters))) + } + + /** + * Creates a variable reference dynamic argument. + * @param opIndex - The referenced operation index + * @param subIndex - The referenced output index within the operation + * @returns A new variable dynamic argument + */ + static variable(opIndex: u32, subIndex: u32): EvmDynamicArg { + return new EvmDynamicArg( + EvmDynamicArgKind.Variable, + Bytes.fromHexString( + evm.encode([ + EvmEncodeParam.fromValue('uint256', BigInt.fromU32(opIndex)), + EvmEncodeParam.fromValue('uint256', BigInt.fromU32(subIndex)), + ]) + ) + ) + } + + /** + * Creates a static-call dynamic argument. + * @param target - The contract address to call + * @param selector - The function selector to call + * @param arguments_ - The dynamic arguments to pass to the static call + * @returns A new static-call dynamic argument + */ + static staticCall(target: Address, selector: Bytes, arguments_: EvmDynamicArg[] = []): EvmDynamicArg { + const staticCallArg = new EvmDynamicStaticCallArg(target, selector, arguments_) + return new EvmDynamicArg( + EvmDynamicArgKind.StaticCall, + Bytes.fromHexString(evm.encode([staticCallArg.toEvmEncodeParam()])) + ) + } + + /** + * Creates a new EvmDynamicArg instance. + * @param kind - The argument resolution strategy + * @param data - The ABI-encoded argument data + */ + constructor(kind: EvmDynamicArgKind, data: Bytes) { + this.kind = kind + this.data = data.toHexString() + } + + /** + * Converts this dynamic argument into an ABI tuple parameter. + * @returns The ABI tuple parameter representation + */ + toEvmEncodeParam(): EvmEncodeParam { + return EvmEncodeParam.fromValues('()', [ + EvmEncodeParam.fromValue('uint8', BigInt.fromI32(this.kind as i32)), + EvmEncodeParam.fromValue('bytes', Bytes.fromHexString(this.data)), + ]) + } +} + +/** + * Represents data for a single dynamic contract call within an EVM dynamic call operation. + */ +@json +export class EvmDynamicCallData { + public target: string + public value: string + public selector: string + public arguments: EvmDynamicArg[] + + /** + * Creates a new EvmDynamicCallData instance. + * @param target - The contract address to call + * @param selector - The function selector to call + * @param arguments_ - The dynamic arguments for the call + * @param value - The native token value to send + */ + constructor(target: Address, selector: Bytes, arguments_: EvmDynamicArg[] = [], value: BigInt = BigInt.zero()) { + validateSelector(selector) + this.target = target.toString() + this.value = value.toString() + this.selector = selector.toHexString() + this.arguments = cloneArguments(arguments_) + } +} + +/** + * Represents an EVM dynamic call operation containing one or more dynamic contract calls. + */ +@json +export class EvmDynamicCall extends Operation { + public calls: EvmDynamicCallData[] + + /** + * Creates a new EvmDynamicCall operation. + * @param chainId - The blockchain network identifier + * @param calls - Array of dynamic contract calls to execute + * @param user - The user address + * @param events - The operation events to emit + */ + constructor( + chainId: ChainId, + calls: EvmDynamicCallData[], + user: Address | null = null, + events: OperationEvent[] | null = null + ) { + super(OperationType.EvmDynamicCall, chainId, user, events) + if (calls.length === 0) throw new Error('Call list cannot be empty') + this.calls = calls + } + + /** + * Sends this EvmDynamicCall operation wrapped in an intent. + * @param maxFee - The max fee to pay for the intent + * @param feePayer - The fee payer for the intent (optional) + */ + public send(maxFee: TokenAmount, feePayer: Address | null = null): void { + const intentBuilder = new IntentBuilder().addMaxFee(maxFee).addOperation(this) + if (feePayer) intentBuilder.addFeePayer(feePayer) + environment.sendIntent(intentBuilder.build()) + } +} diff --git a/packages/lib-ts/src/intents/Call/index.ts b/packages/lib-ts/src/intents/Call/index.ts index 2c576fec..62d2dc2a 100644 --- a/packages/lib-ts/src/intents/Call/index.ts +++ b/packages/lib-ts/src/intents/Call/index.ts @@ -1,2 +1,3 @@ export * from './EvmCall' +export * from './EvmDynamicCall' export * from './SvmCall' diff --git a/packages/lib-ts/src/intents/Intent.ts b/packages/lib-ts/src/intents/Intent.ts index b6aff772..863f8bee 100644 --- a/packages/lib-ts/src/intents/Intent.ts +++ b/packages/lib-ts/src/intents/Intent.ts @@ -6,6 +6,7 @@ import { Address, BigInt, Bytes, ChainId } from '../types' import { SvmAccountMeta } from '../types/svm/SvmAccountMeta' import { EvmCall, EvmCallData } from './Call/EvmCall' +import { EvmDynamicArg, EvmDynamicCall, EvmDynamicCallData } from './Call/EvmDynamicCall' import { SvmCall, SvmInstruction } from './Call/SvmCall' import { Operation, OperationBuilder, OperationEvent, OperationType } from './Operation' import { Swap, SwapTokenIn, SwapTokenOut } from './Swap' @@ -84,6 +85,31 @@ export class IntentBuilder { return this.addOperation(new EvmCall(chainId, [new EvmCallData(target, data, value)], user, events)) } + /** + * Adds a single EVM dynamic call operation to this intent from raw parameters. + * @param chainId - The blockchain network identifier + * @param target - The contract address to call + * @param selector - The function selector to call + * @param arguments_ - The dynamic arguments to resolve at execution time + * @param value - The native token value to send + * @param user - The user that should execute the operation + * @param events - The operation events to emit + * @returns This IntentBuilder instance for method chaining + */ + addEvmDynamicCallOperation( + chainId: ChainId, + target: Address, + selector: Bytes, + arguments_: EvmDynamicArg[] = [], + value: BigInt = BigInt.zero(), + user: Address | null = null, + events: OperationEvent[] | null = null + ): IntentBuilder { + return this.addOperation( + new EvmDynamicCall(chainId, [new EvmDynamicCallData(target, selector, arguments_, value)], user, events) + ) + } + /** * Adds a single swap operation to this intent from raw parameters. * @param sourceChain - The source blockchain network identifier diff --git a/packages/lib-ts/src/intents/Operation.ts b/packages/lib-ts/src/intents/Operation.ts index cf93f7cc..7ebe5178 100644 --- a/packages/lib-ts/src/intents/Operation.ts +++ b/packages/lib-ts/src/intents/Operation.ts @@ -7,6 +7,7 @@ export enum OperationType { Transfer, EvmCall, CrossChainSwap, + EvmDynamicCall, SvmCall, } diff --git a/packages/lib-ts/tests/intents/EvmDynamicCall.spec.ts b/packages/lib-ts/tests/intents/EvmDynamicCall.spec.ts new file mode 100644 index 00000000..f35c8788 --- /dev/null +++ b/packages/lib-ts/tests/intents/EvmDynamicCall.spec.ts @@ -0,0 +1,164 @@ +import { JSON } from 'json-as' + +import { + EvmDynamicArg, + EvmDynamicArgKind, + EvmDynamicCall, + EvmDynamicCallBuilder, + EvmDynamicCallData, + EvmDynamicStaticCallArg, + OperationEvent, + OperationType, +} from '../../src/intents' +import { Address, BigInt, Bytes, EvmEncodeParam } from '../../src/types' +import { randomBytes, randomEvmAddress, randomSettler, setContext, setEvmEncode } from '../helpers' + +describe('EvmDynamicCall', () => { + it('creates a simple operation with default values and stringifies it', () => { + const chainId = 1 + const user = randomEvmAddress() + const target = randomEvmAddress() + const selector = Bytes.fromHexString('0x12345678') + const argument = new EvmDynamicArg(EvmDynamicArgKind.Literal, randomBytes(64)) + const settler = randomSettler(chainId) + + setContext(1, 1, user.toString(), [settler], 'trigger-123') + + const call = new EvmDynamicCall(chainId, [new EvmDynamicCallData(target, selector, [argument])]) + expect(call.opType).toBe(OperationType.EvmDynamicCall) + expect(call.user).toBe(user.toString()) + expect(call.chainId).toBe(chainId) + expect(call.events.length).toBe(0) + expect(call.calls.length).toBe(1) + expect(call.calls[0].target).toBe(target.toString()) + expect(call.calls[0].value).toBe('0') + expect(call.calls[0].selector).toBe(selector.toHexString()) + expect(call.calls[0].arguments.length).toBe(1) + expect(call.calls[0].arguments[0].kind).toBe(EvmDynamicArgKind.Literal) + expect(call.calls[0].arguments[0].data).toBe(argument.data) + + expect(JSON.stringify(call)).toBe( + `{"opType":4,"chainId":${chainId},"user":"${user}","events":[],"calls":[{"target":"${target}","value":"0","selector":"${selector.toHexString()}","arguments":[{"kind":0,"data":"${argument.data}"}]}]}` + ) + }) + + it('creates an operation with explicit user and events', () => { + const chainId = 1 + const user = randomEvmAddress() + const settler = randomSettler(chainId) + const target = randomEvmAddress() + const selector = Bytes.fromHexString('0x90abcdef') + const argument = new EvmDynamicArg(EvmDynamicArgKind.Variable, randomBytes(64)) + const value = BigInt.fromI32(10) + + setContext(1, 1, user.toString(), [settler], 'trigger-123') + + const call = new EvmDynamicCall(chainId, [new EvmDynamicCallData(target, selector, [argument], value)], user, [ + new OperationEvent(Bytes.fromUTF8('topic'), Bytes.fromUTF8('data')), + ]) + + expect(call.opType).toBe(OperationType.EvmDynamicCall) + expect(call.user).toBe(user.toString()) + expect(call.chainId).toBe(chainId) + expect(call.calls[0].value).toBe(value.toString()) + expect(call.events.length).toBe(1) + expect(call.events[0].topic).toBe('0x746f706963') + expect(call.events[0].data).toBe('0x64617461') + expect(JSON.stringify(call)).toBe( + `{"opType":4,"chainId":${chainId},"user":"${user}","events":[{"topic":"0x746f706963","data":"0x64617461"}],"calls":[{"target":"${target}","value":"${value.toString()}","selector":"${selector.toHexString()}","arguments":[{"kind":1,"data":"${argument.data}"}]}]}` + ) + }) + + it('throws an error when there is no call data', () => { + expect(() => { + new EvmDynamicCall(1, []) + }).toThrow('Call list cannot be empty') + }) + + it('throws an error when the selector is not 4 bytes', () => { + expect(() => { + new EvmDynamicCallData(randomEvmAddress(), Bytes.fromHexString('0x1234')) + }).toThrow('Selector must be 4 bytes') + }) +}) + +describe('EvmDynamicArg', () => { + it('encodes literal arguments', () => { + const emptyString = Bytes.fromUTF8('').toHexString() + setEvmEncode('string', emptyString, '0x1234') + + const argument = EvmDynamicArg.literal([EvmEncodeParam.fromValue('uint256', BigInt.fromI32(1))]) + + expect(argument.kind).toBe(EvmDynamicArgKind.Literal) + expect(argument.data).toBe('0x1234') + }) + + it('encodes variable references', () => { + setEvmEncode('uint256', '1', '0x5678') + + const argument = EvmDynamicArg.variable(1, 0) + + expect(argument.kind).toBe(EvmDynamicArgKind.Variable) + expect(argument.data).toBe('0x5678') + }) + + it('encodes static-call arguments', () => { + const target = randomEvmAddress() + const selector = Bytes.fromHexString('0x12345678') + const nested = new EvmDynamicArg(EvmDynamicArgKind.Literal, randomBytes(64)) + const staticCallArg = new EvmDynamicStaticCallArg(target, selector, [nested]) + + setEvmEncode('()', staticCallArg.toEvmEncodeParam().value, '0x9abc') + + const argument = EvmDynamicArg.staticCall(target, selector, [nested]) + + expect(argument.kind).toBe(EvmDynamicArgKind.StaticCall) + expect(argument.data).toBe('0x9abc') + }) +}) + +describe('EvmDynamicCallBuilder', () => { + const chainId = 1 + const target1Str = '0x0000000000000000000000000000000000000001' + const target2Str = '0x0000000000000000000000000000000000000002' + + it('adds multiple calls and builds an operation', () => { + const target1 = Address.fromString(target1Str) + const target2 = Address.fromString(target2Str) + const selector1 = Bytes.fromHexString('0x12345678') + const selector2 = Bytes.fromHexString('0x90abcdef') + + const builder = EvmDynamicCallBuilder.forChain(chainId) + builder.addCall( + target1, + selector1, + [new EvmDynamicArg(EvmDynamicArgKind.Literal, randomBytes(64))], + BigInt.fromString('1') + ) + builder.addCall( + target2, + selector2, + [new EvmDynamicArg(EvmDynamicArgKind.Variable, randomBytes(64))], + BigInt.fromString('2') + ) + + const call = builder.build() + expect(call.calls.length).toBe(2) + expect(call.calls[0].target).toBe(target1Str) + expect(call.calls[0].selector).toBe(selector1.toHexString()) + expect(call.calls[1].target).toBe(target2Str) + expect(call.calls[1].selector).toBe(selector2.toHexString()) + }) + + it('adds call with default arguments and value', () => { + const target = Address.fromString(target1Str) + const selector = Bytes.fromHexString('0x12345678') + + const builder = EvmDynamicCallBuilder.forChain(chainId) + builder.addCall(target, selector) + + const call = builder.build() + expect(call.calls[0].arguments.length).toBe(0) + expect(call.calls[0].value).toBe('0') + }) +}) diff --git a/packages/lib-ts/tests/intents/Intent.spec.ts b/packages/lib-ts/tests/intents/Intent.spec.ts index 710f574b..6dd3c44f 100644 --- a/packages/lib-ts/tests/intents/Intent.spec.ts +++ b/packages/lib-ts/tests/intents/Intent.spec.ts @@ -2,7 +2,14 @@ import { JSON } from 'json-as' import { SerializableSettler } from '../../src/context' import { NULL_ADDRESS } from '../../src/helpers' -import { EvmCallBuilder, IntentBuilder, SwapBuilder } from '../../src/intents' +import { + EvmCallBuilder, + EvmDynamicArg, + EvmDynamicArgKind, + EvmDynamicCall, + IntentBuilder, + SwapBuilder, +} from '../../src/intents' import { TokenAmount } from '../../src/tokens' import { Address, BigInt, Bytes } from '../../src/types' import { randomERC20Token, randomSettler, setContext } from '../helpers' @@ -82,6 +89,29 @@ describe('IntentBuilder', () => { `{"settler":"${settler}","feePayer":"${feePayer}","deadline":"123456789","nonce":"0xabcdef123456","maxFees":[],"operations":[{"opType":2,"chainId":${chainId},"user":"0x0000000000000000000000000000000000000004","events":[],"calls":[{"target":"${target}","data":"0x1234","value":"0"}]}]}` ) }) + + it('adds a dynamic call operation from raw parameters', () => { + const target = Address.fromString(targetAddressStr) + const settler = randomSettler(chainId) + + setContext(0, 1, userAddressStr, [settler], 'trigger-dynamic-call') + + const intent = new IntentBuilder() + .addEvmDynamicCallOperation( + chainId, + target, + Bytes.fromHexString('0x12345678'), + [new EvmDynamicArg(EvmDynamicArgKind.Literal, Bytes.fromHexString('0x' + '00'.repeat(64)))], + BigInt.fromI32(7) + ) + .build() + + expect(intent.operations.length).toBe(1) + expect(intent.operations[0].opType).toBe(4) + const operation = changetype(intent.operations[0]) + expect(operation.calls[0].selector).toBe('0x12345678') + expect(operation.calls[0].value).toBe('7') + }) }) describe('when the settler is zero', () => { diff --git a/packages/lib-ts/tests/intents/SvmCall.spec.ts b/packages/lib-ts/tests/intents/SvmCall.spec.ts index d54bb33e..b91ae505 100644 --- a/packages/lib-ts/tests/intents/SvmCall.spec.ts +++ b/packages/lib-ts/tests/intents/SvmCall.spec.ts @@ -36,7 +36,7 @@ describe('SvmCall', () => { expect(svmCall.events.length).toBe(0) expect(JSON.stringify(svmCall)).toBe( - `{"opType":4,"chainId":${ChainId.SOLANA_MAINNET},"user":"${user}","events":[],"instructions":[{"programId":"${programId.toBase58String()}","accountsMeta":[{"pubkey":"${accountsMeta[0].pubkey}","isWritable":${accountsMeta[0].isWritable},"isSigner":${accountsMeta[0].isSigner}}],"data":"${data.toHexString()}"}]}` + `{"opType":5,"chainId":${ChainId.SOLANA_MAINNET},"user":"${user}","events":[],"instructions":[{"programId":"${programId.toBase58String()}","accountsMeta":[{"pubkey":"${accountsMeta[0].pubkey}","isWritable":${accountsMeta[0].isWritable},"isSigner":${accountsMeta[0].isSigner}}],"data":"${data.toHexString()}"}]}` ) }) @@ -69,7 +69,7 @@ describe('SvmCall', () => { expect(svmCall.events[0].data).toBe('0x64617461') expect(JSON.stringify(svmCall)).toBe( - `{"opType":4,"chainId":${ChainId.SOLANA_MAINNET},"user":"${user}","events":[{"topic":"0x746f706963","data":"0x64617461"}],"instructions":[{"programId":"${programId.toBase58String()}","accountsMeta":[{"pubkey":"${accountsMeta[0].pubkey}","isWritable":${accountsMeta[0].isWritable},"isSigner":${accountsMeta[0].isSigner}}],"data":"${data.toHexString()}"}]}` + `{"opType":5,"chainId":${ChainId.SOLANA_MAINNET},"user":"${user}","events":[{"topic":"0x746f706963","data":"0x64617461"}],"instructions":[{"programId":"${programId.toBase58String()}","accountsMeta":[{"pubkey":"${accountsMeta[0].pubkey}","isWritable":${accountsMeta[0].isWritable},"isSigner":${accountsMeta[0].isSigner}}],"data":"${data.toHexString()}"}]}` ) }) }) @@ -107,7 +107,7 @@ describe('SvmCall', () => { expect(svmCall.events.length).toBe(0) expect(JSON.stringify(svmCall)).toBe( - `{"opType":4,"chainId":${ChainId.SOLANA_MAINNET},"user":"${user}","events":[],"instructions":[{"programId":"${programId1.toBase58String()}","accountsMeta":[{"pubkey":"${accountsMeta1[0].pubkey}","isWritable":${accountsMeta1[0].isWritable},"isSigner":${accountsMeta1[0].isSigner}}],"data":"${data1.toHexString()}"},{"programId":"${programId2.toBase58String()}","accountsMeta":[{"pubkey":"${accountsMeta2[0].pubkey}","isWritable":${accountsMeta2[0].isWritable},"isSigner":${accountsMeta2[0].isSigner}}],"data":"${data2.toHexString()}"}]}` + `{"opType":5,"chainId":${ChainId.SOLANA_MAINNET},"user":"${user}","events":[],"instructions":[{"programId":"${programId1.toBase58String()}","accountsMeta":[{"pubkey":"${accountsMeta1[0].pubkey}","isWritable":${accountsMeta1[0].isWritable},"isSigner":${accountsMeta1[0].isSigner}}],"data":"${data1.toHexString()}"},{"programId":"${programId2.toBase58String()}","accountsMeta":[{"pubkey":"${accountsMeta2[0].pubkey}","isWritable":${accountsMeta2[0].isWritable},"isSigner":${accountsMeta2[0].isSigner}}],"data":"${data2.toHexString()}"}]}` ) }) }) diff --git a/packages/test-ts/src/types.ts b/packages/test-ts/src/types.ts index 29d8ef5c..03666118 100644 --- a/packages/test-ts/src/types.ts +++ b/packages/test-ts/src/types.ts @@ -120,6 +120,10 @@ export type CallOperation = OperationBase & { calls: { target: string; data: string; value: string }[] } +export type DynamicCallOperation = OperationBase & { + calls: { target: string; value: string; selector: string; arguments: { kind: number; data: string }[] }[] +} + export type SvmCallOperation = OperationBase & { instructions: { programId: string @@ -128,7 +132,7 @@ export type SvmCallOperation = OperationBase & { }[] } -export type Operation = TransferOperation | SwapOperation | CallOperation | SvmCallOperation +export type Operation = TransferOperation | SwapOperation | CallOperation | DynamicCallOperation | SvmCallOperation export type Intent = { settler: string diff --git a/yarn.lock b/yarn.lock index 75a2ae9c..bf9479a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -337,6 +337,38 @@ resolved "https://registry.yarnpkg.com/@chevrotain/utils/-/utils-10.5.0.tgz#0ee36f65b49b447fbac71b9e5af5c5c6c98ac057" integrity sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ== +"@coral-xyz/anchor-errors@^0.31.1": + version "0.31.1" + resolved "https://registry.yarnpkg.com/@coral-xyz/anchor-errors/-/anchor-errors-0.31.1.tgz#d635cbac2533973ae6bfb5d3ba1de89ce5aece2d" + integrity sha512-NhNEku4F3zzUSBtrYz84FzYWm48+9OvmT1Hhnwr6GnPQry2dsEqH/ti/7ASjjpoFTWRnPXrjAIT1qM6Isop+LQ== + +"@coral-xyz/anchor@0.32.1": + version "0.32.1" + resolved "https://registry.yarnpkg.com/@coral-xyz/anchor/-/anchor-0.32.1.tgz#a07440d9d267840f4f99f1493bd8ce7d7f128e57" + integrity sha512-zAyxFtfeje2FbMA1wzgcdVs7Hng/MijPKpRijoySPCicnvcTQs/+dnPZ/cR+LcXM9v9UYSyW81uRNYZtN5G4yg== + dependencies: + "@coral-xyz/anchor-errors" "^0.31.1" + "@coral-xyz/borsh" "^0.31.1" + "@noble/hashes" "^1.3.1" + "@solana/web3.js" "^1.69.0" + bn.js "^5.1.2" + bs58 "^4.0.1" + buffer-layout "^1.2.2" + camelcase "^6.3.0" + cross-fetch "^3.1.5" + eventemitter3 "^4.0.7" + pako "^2.0.3" + superstruct "^0.15.4" + toml "^3.0.0" + +"@coral-xyz/borsh@^0.31.1": + version "0.31.1" + resolved "https://registry.yarnpkg.com/@coral-xyz/borsh/-/borsh-0.31.1.tgz#5328e1e0921b75d7f4a62dd3f61885a938bc7241" + integrity sha512-9N8AU9F0ubriKfNE3g1WF0/4dtlGXoBN/hd1PvbNBamBNwRgHxH4P+o3Zt7rSEloW1HUs6LfZEchlx9fW7POYw== + dependencies: + bn.js "^5.1.2" + buffer-layout "^1.2.0" + "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -693,11 +725,10 @@ "@mimicprotocol/runner-node-win32-arm64-msvc" "0.0.1-rc.14" "@mimicprotocol/runner-node-win32-x64-msvc" "0.0.1-rc.14" -"@mimicprotocol/sdk@^0.0.1-rc.33": - version "0.0.1-rc.33" - resolved "https://registry.yarnpkg.com/@mimicprotocol/sdk/-/sdk-0.0.1-rc.33.tgz#ef48a118b9a5c052c05f34147c5d00ad29d80529" - integrity sha512-CgVTt8nwWvNI1G0MmsTwdCYwm82QYnqOHWTmOldyCf29gZWvfFQ8GXuXXm5vmQV8EYOj72bVGNPNJreT8e5M/g== +"@mimicprotocol/sdk@../backend/packages/sdk": + version "0.0.1-rc.41" dependencies: + "@coral-xyz/anchor" "0.32.1" "@solana/web3.js" "^1.98.4" borsh "^2.0.0" cron-parser "^5.3.1" @@ -723,7 +754,7 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== -"@noble/hashes@1.8.0", "@noble/hashes@^1.4.0": +"@noble/hashes@1.8.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.4.0": version "1.8.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a" integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== @@ -866,7 +897,7 @@ chalk "^5.4.1" commander "^14.0.0" -"@solana/web3.js@^1.98.4": +"@solana/web3.js@^1.69.0", "@solana/web3.js@^1.98.4": version "1.98.4" resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.98.4.tgz#df51d78be9d865181ec5138b4e699d48e6895bbe" integrity sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw== @@ -1694,6 +1725,11 @@ bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5, bluebird@^3.7.2: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +bn.js@^5.1.2: + version "5.2.3" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.3.tgz#16a9e409616b23fef3ccbedb8d42f13bff80295e" + integrity sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w== + bn.js@^5.2.0, bn.js@^5.2.1: version "5.2.2" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.2.tgz#82c09f9ebbb17107cd72cb7fd39bd1f9d0aaa566" @@ -1765,6 +1801,11 @@ buffer-from@^1.0.0, buffer-from@^1.1.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer-layout@^1.2.0, buffer-layout@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/buffer-layout/-/buffer-layout-1.2.2.tgz#b9814e7c7235783085f9ca4966a0cfff112259d5" + integrity sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA== + buffer@6.0.3, buffer@^6.0.3, buffer@~6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" @@ -1862,7 +1903,7 @@ camelcase@^5.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.0.0: +camelcase@^6.0.0, camelcase@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== @@ -2222,6 +2263,13 @@ cron-parser@^5.3.1: dependencies: luxon "^3.7.1" +cross-fetch@^3.1.5: + version "3.2.0" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.2.0.tgz#34e9192f53bc757d6614304d9e5e6fb4edb782e3" + integrity sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q== + dependencies: + node-fetch "^2.7.0" + cross-spawn@^5.0.1: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" @@ -2915,6 +2963,11 @@ ethers@^6.15.0: tslib "2.7.0" ws "8.17.1" +eventemitter3@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + eventemitter3@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" @@ -5311,6 +5364,11 @@ pacote@^9.1.0, pacote@^9.5.12, pacote@^9.5.3: unique-filename "^1.1.1" which "^1.3.1" +pako@^2.0.3: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" + integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== + parallel-transform@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc" @@ -6416,6 +6474,11 @@ strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== +superstruct@^0.15.4: + version "0.15.5" + resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.15.5.tgz#0f0a8d3ce31313f0d84c6096cd4fa1bfdedc9dab" + integrity sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ== + superstruct@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-2.0.2.tgz#3f6d32fbdc11c357deff127d591a39b996300c54" @@ -6550,6 +6613,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +toml@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/toml/-/toml-3.0.0.tgz#342160f1af1904ec9d204d03a5d61222d762c5ee" + integrity sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w== + touch@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.1.tgz#097a23d7b161476435e5c1344a95c0f75b4a5694" From 87ba9b0bebe39358d66a9ab874833faa4846bcb4 Mon Sep 17 00:00:00 2001 From: lgalende Date: Fri, 17 Apr 2026 16:53:14 -0300 Subject: [PATCH 2/7] chore: undo yarn.lock changes --- yarn.lock | 82 +++++-------------------------------------------------- 1 file changed, 7 insertions(+), 75 deletions(-) diff --git a/yarn.lock b/yarn.lock index bf9479a9..75a2ae9c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -337,38 +337,6 @@ resolved "https://registry.yarnpkg.com/@chevrotain/utils/-/utils-10.5.0.tgz#0ee36f65b49b447fbac71b9e5af5c5c6c98ac057" integrity sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ== -"@coral-xyz/anchor-errors@^0.31.1": - version "0.31.1" - resolved "https://registry.yarnpkg.com/@coral-xyz/anchor-errors/-/anchor-errors-0.31.1.tgz#d635cbac2533973ae6bfb5d3ba1de89ce5aece2d" - integrity sha512-NhNEku4F3zzUSBtrYz84FzYWm48+9OvmT1Hhnwr6GnPQry2dsEqH/ti/7ASjjpoFTWRnPXrjAIT1qM6Isop+LQ== - -"@coral-xyz/anchor@0.32.1": - version "0.32.1" - resolved "https://registry.yarnpkg.com/@coral-xyz/anchor/-/anchor-0.32.1.tgz#a07440d9d267840f4f99f1493bd8ce7d7f128e57" - integrity sha512-zAyxFtfeje2FbMA1wzgcdVs7Hng/MijPKpRijoySPCicnvcTQs/+dnPZ/cR+LcXM9v9UYSyW81uRNYZtN5G4yg== - dependencies: - "@coral-xyz/anchor-errors" "^0.31.1" - "@coral-xyz/borsh" "^0.31.1" - "@noble/hashes" "^1.3.1" - "@solana/web3.js" "^1.69.0" - bn.js "^5.1.2" - bs58 "^4.0.1" - buffer-layout "^1.2.2" - camelcase "^6.3.0" - cross-fetch "^3.1.5" - eventemitter3 "^4.0.7" - pako "^2.0.3" - superstruct "^0.15.4" - toml "^3.0.0" - -"@coral-xyz/borsh@^0.31.1": - version "0.31.1" - resolved "https://registry.yarnpkg.com/@coral-xyz/borsh/-/borsh-0.31.1.tgz#5328e1e0921b75d7f4a62dd3f61885a938bc7241" - integrity sha512-9N8AU9F0ubriKfNE3g1WF0/4dtlGXoBN/hd1PvbNBamBNwRgHxH4P+o3Zt7rSEloW1HUs6LfZEchlx9fW7POYw== - dependencies: - bn.js "^5.1.2" - buffer-layout "^1.2.0" - "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -725,10 +693,11 @@ "@mimicprotocol/runner-node-win32-arm64-msvc" "0.0.1-rc.14" "@mimicprotocol/runner-node-win32-x64-msvc" "0.0.1-rc.14" -"@mimicprotocol/sdk@../backend/packages/sdk": - version "0.0.1-rc.41" +"@mimicprotocol/sdk@^0.0.1-rc.33": + version "0.0.1-rc.33" + resolved "https://registry.yarnpkg.com/@mimicprotocol/sdk/-/sdk-0.0.1-rc.33.tgz#ef48a118b9a5c052c05f34147c5d00ad29d80529" + integrity sha512-CgVTt8nwWvNI1G0MmsTwdCYwm82QYnqOHWTmOldyCf29gZWvfFQ8GXuXXm5vmQV8EYOj72bVGNPNJreT8e5M/g== dependencies: - "@coral-xyz/anchor" "0.32.1" "@solana/web3.js" "^1.98.4" borsh "^2.0.0" cron-parser "^5.3.1" @@ -754,7 +723,7 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== -"@noble/hashes@1.8.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.4.0": +"@noble/hashes@1.8.0", "@noble/hashes@^1.4.0": version "1.8.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a" integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== @@ -897,7 +866,7 @@ chalk "^5.4.1" commander "^14.0.0" -"@solana/web3.js@^1.69.0", "@solana/web3.js@^1.98.4": +"@solana/web3.js@^1.98.4": version "1.98.4" resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.98.4.tgz#df51d78be9d865181ec5138b4e699d48e6895bbe" integrity sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw== @@ -1725,11 +1694,6 @@ bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5, bluebird@^3.7.2: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -bn.js@^5.1.2: - version "5.2.3" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.3.tgz#16a9e409616b23fef3ccbedb8d42f13bff80295e" - integrity sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w== - bn.js@^5.2.0, bn.js@^5.2.1: version "5.2.2" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.2.tgz#82c09f9ebbb17107cd72cb7fd39bd1f9d0aaa566" @@ -1801,11 +1765,6 @@ buffer-from@^1.0.0, buffer-from@^1.1.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -buffer-layout@^1.2.0, buffer-layout@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/buffer-layout/-/buffer-layout-1.2.2.tgz#b9814e7c7235783085f9ca4966a0cfff112259d5" - integrity sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA== - buffer@6.0.3, buffer@^6.0.3, buffer@~6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" @@ -1903,7 +1862,7 @@ camelcase@^5.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.0.0, camelcase@^6.3.0: +camelcase@^6.0.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== @@ -2263,13 +2222,6 @@ cron-parser@^5.3.1: dependencies: luxon "^3.7.1" -cross-fetch@^3.1.5: - version "3.2.0" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.2.0.tgz#34e9192f53bc757d6614304d9e5e6fb4edb782e3" - integrity sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q== - dependencies: - node-fetch "^2.7.0" - cross-spawn@^5.0.1: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" @@ -2963,11 +2915,6 @@ ethers@^6.15.0: tslib "2.7.0" ws "8.17.1" -eventemitter3@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - eventemitter3@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" @@ -5364,11 +5311,6 @@ pacote@^9.1.0, pacote@^9.5.12, pacote@^9.5.3: unique-filename "^1.1.1" which "^1.3.1" -pako@^2.0.3: - version "2.1.0" - resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" - integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== - parallel-transform@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc" @@ -6474,11 +6416,6 @@ strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== -superstruct@^0.15.4: - version "0.15.5" - resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.15.5.tgz#0f0a8d3ce31313f0d84c6096cd4fa1bfdedc9dab" - integrity sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ== - superstruct@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-2.0.2.tgz#3f6d32fbdc11c357deff127d591a39b996300c54" @@ -6613,11 +6550,6 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -toml@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/toml/-/toml-3.0.0.tgz#342160f1af1904ec9d204d03a5d61222d762c5ee" - integrity sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w== - touch@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.1.tgz#097a23d7b161476435e5c1344a95c0f75b4a5694" From 22bb52f5169a928522976477768d9db2e2965f63 Mon Sep 17 00:00:00 2001 From: lgalende Date: Fri, 17 Apr 2026 16:53:39 -0300 Subject: [PATCH 3/7] lib: remove staticcall dynamic arg kind --- .../014-swap-and-dynamic-call/expected.log | 2 +- .../014-swap-and-dynamic-call/src/function.ts | 1 - .../lib-ts/src/intents/Call/EvmDynamicCall.ts | 54 ------------------- .../tests/intents/EvmDynamicCall.spec.ts | 15 ------ 4 files changed, 1 insertion(+), 71 deletions(-) diff --git a/packages/integration/tests/014-swap-and-dynamic-call/expected.log b/packages/integration/tests/014-swap-and-dynamic-call/expected.log index 06cfc875..dedf9859 100644 --- a/packages/integration/tests/014-swap-and-dynamic-call/expected.log +++ b/packages/integration/tests/014-swap-and-dynamic-call/expected.log @@ -1 +1 @@ -_sendIntent: {"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0xa000000000000000000000000000000000000002","amount":"10"}],"operations":[{"opType":0,"chainId":1,"user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","events":[],"sourceChain":1,"tokensIn":[{"token":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","amount":"100000000"}],"tokensOut":[{"token":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","minAmount":"95000000","recipient":"0xa000000000000000000000000000000000000001"}],"destinationChain":1},{"opType":4,"chainId":1,"user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","events":[],"calls":[{"target":"0xa000000000000000000000000000000000000001","value":"0","selector":"0x12345678","arguments":[{"kind":0,"data":"0x"},{"kind":1,"data":"0x"},{"kind":2,"data":"0x"}]}]}]} +_sendIntent: {"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0xa000000000000000000000000000000000000002","amount":"10"}],"operations":[{"opType":0,"chainId":1,"user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","events":[],"sourceChain":1,"tokensIn":[{"token":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","amount":"100000000"}],"tokensOut":[{"token":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","minAmount":"95000000","recipient":"0xa000000000000000000000000000000000000001"}],"destinationChain":1},{"opType":4,"chainId":1,"user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","events":[],"calls":[{"target":"0xa000000000000000000000000000000000000001","value":"0","selector":"0x12345678","arguments":[{"kind":0,"data":"0x"},{"kind":1,"data":"0x"}]}]}]} diff --git a/packages/integration/tests/014-swap-and-dynamic-call/src/function.ts b/packages/integration/tests/014-swap-and-dynamic-call/src/function.ts index c8145d9a..e4da9636 100644 --- a/packages/integration/tests/014-swap-and-dynamic-call/src/function.ts +++ b/packages/integration/tests/014-swap-and-dynamic-call/src/function.ts @@ -23,7 +23,6 @@ export default function main(): void { const call = EvmDynamicCallBuilder.forChain(chainId).addCall(inputs.target, inputs.selector, [ EvmDynamicArg.literal([EvmEncodeParam.fromValue('uint256', BigInt.fromI32(123))]), EvmDynamicArg.variable(0, 0), - EvmDynamicArg.staticCall(inputs.target, inputs.selector), ]) new IntentBuilder().addMaxFee(maxFee).addOperationsBuilders([swap, call]).send() diff --git a/packages/lib-ts/src/intents/Call/EvmDynamicCall.ts b/packages/lib-ts/src/intents/Call/EvmDynamicCall.ts index 40c2ed8d..8f8b3064 100644 --- a/packages/lib-ts/src/intents/Call/EvmDynamicCall.ts +++ b/packages/lib-ts/src/intents/Call/EvmDynamicCall.ts @@ -8,7 +8,6 @@ import { Operation, OperationBuilder, OperationEvent, OperationType } from '../O export enum EvmDynamicArgKind { Literal = 0, Variable = 1, - StaticCall = 2, } function validateSelector(selector: Bytes): void { @@ -166,44 +165,6 @@ export class EvmDynamicCallBuilder extends OperationBuilder { } } -/** - * Represents a static call argument specification for a dynamic argument. - */ -@json -export class EvmDynamicStaticCallArg { - public target: string - public selector: string - public arguments: EvmDynamicArg[] - - /** - * Creates a new EvmDynamicStaticCallArg instance. - * @param target - The contract address to call - * @param selector - The function selector to call - * @param arguments_ - The dynamic arguments to pass to the static call - */ - constructor(target: Address, selector: Bytes, arguments_: EvmDynamicArg[] = []) { - validateSelector(selector) - this.target = target.toString() - this.selector = selector.toHexString() - this.arguments = cloneArguments(arguments_) - } - - /** - * Converts this static call specification into an ABI tuple parameter. - * @returns The ABI tuple parameter representation - */ - toEvmEncodeParam(): EvmEncodeParam { - return EvmEncodeParam.fromValues('()', [ - EvmEncodeParam.fromValue('address', Address.fromString(this.target)), - EvmEncodeParam.fromValue('bytes4', Bytes.fromHexString(this.selector)), - EvmEncodeParam.fromValues( - '()[]', - this.arguments.map((argument: EvmDynamicArg) => argument.toEvmEncodeParam()) - ), - ]) - } -} - /** * Represents a single dynamic argument in a dynamic call. */ @@ -242,21 +203,6 @@ export class EvmDynamicArg { ) } - /** - * Creates a static-call dynamic argument. - * @param target - The contract address to call - * @param selector - The function selector to call - * @param arguments_ - The dynamic arguments to pass to the static call - * @returns A new static-call dynamic argument - */ - static staticCall(target: Address, selector: Bytes, arguments_: EvmDynamicArg[] = []): EvmDynamicArg { - const staticCallArg = new EvmDynamicStaticCallArg(target, selector, arguments_) - return new EvmDynamicArg( - EvmDynamicArgKind.StaticCall, - Bytes.fromHexString(evm.encode([staticCallArg.toEvmEncodeParam()])) - ) - } - /** * Creates a new EvmDynamicArg instance. * @param kind - The argument resolution strategy diff --git a/packages/lib-ts/tests/intents/EvmDynamicCall.spec.ts b/packages/lib-ts/tests/intents/EvmDynamicCall.spec.ts index f35c8788..1f0298fa 100644 --- a/packages/lib-ts/tests/intents/EvmDynamicCall.spec.ts +++ b/packages/lib-ts/tests/intents/EvmDynamicCall.spec.ts @@ -6,7 +6,6 @@ import { EvmDynamicCall, EvmDynamicCallBuilder, EvmDynamicCallData, - EvmDynamicStaticCallArg, OperationEvent, OperationType, } from '../../src/intents' @@ -101,20 +100,6 @@ describe('EvmDynamicArg', () => { expect(argument.kind).toBe(EvmDynamicArgKind.Variable) expect(argument.data).toBe('0x5678') }) - - it('encodes static-call arguments', () => { - const target = randomEvmAddress() - const selector = Bytes.fromHexString('0x12345678') - const nested = new EvmDynamicArg(EvmDynamicArgKind.Literal, randomBytes(64)) - const staticCallArg = new EvmDynamicStaticCallArg(target, selector, [nested]) - - setEvmEncode('()', staticCallArg.toEvmEncodeParam().value, '0x9abc') - - const argument = EvmDynamicArg.staticCall(target, selector, [nested]) - - expect(argument.kind).toBe(EvmDynamicArgKind.StaticCall) - expect(argument.data).toBe('0x9abc') - }) }) describe('EvmDynamicCallBuilder', () => { From 73fa43ba1ade2c856d55a6d1b74f0688f1f3481c Mon Sep 17 00:00:00 2001 From: lgalende Date: Fri, 17 Apr 2026 16:56:36 -0300 Subject: [PATCH 4/7] chore: improve changeset description --- .changeset/spicy-areas-switch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/spicy-areas-switch.md b/.changeset/spicy-areas-switch.md index a2743298..00caf654 100644 --- a/.changeset/spicy-areas-switch.md +++ b/.changeset/spicy-areas-switch.md @@ -4,4 +4,4 @@ "@mimicprotocol/cli": patch --- -Add dynamic call operation +Add dynamic call operation and bump to runner version v0.0.3 From 3fafde3a20515b47d54048c8fd0f569b2a3f1fb5 Mon Sep 17 00:00:00 2001 From: lgalende Date: Fri, 17 Apr 2026 18:39:53 -0300 Subject: [PATCH 5/7] lib: add minor improvements --- .../lib-ts/src/intents/Call/EvmDynamicCall.ts | 34 +++--------- .../tests/intents/EvmDynamicCall.spec.ts | 42 +++++++++++++++ packages/lib-ts/tests/intents/Intent.spec.ts | 52 +++++++++++-------- 3 files changed, 78 insertions(+), 50 deletions(-) diff --git a/packages/lib-ts/src/intents/Call/EvmDynamicCall.ts b/packages/lib-ts/src/intents/Call/EvmDynamicCall.ts index 8f8b3064..d6e57adb 100644 --- a/packages/lib-ts/src/intents/Call/EvmDynamicCall.ts +++ b/packages/lib-ts/src/intents/Call/EvmDynamicCall.ts @@ -10,19 +10,6 @@ export enum EvmDynamicArgKind { Variable = 1, } -function validateSelector(selector: Bytes): void { - if (selector.length != 4) throw new Error('Selector must be 4 bytes') -} - -function cloneArguments(arguments_: EvmDynamicArg[]): EvmDynamicArg[] { - const cloned = new Array(arguments_.length) - for (let i = 0; i < arguments_.length; i++) { - const argument = arguments_[i] - cloned[i] = new EvmDynamicArg(argument.kind, Bytes.fromHexString(argument.data)) - } - return cloned -} - /** * Builder for creating EVM dynamic call operations. */ @@ -76,7 +63,7 @@ export class EvmDynamicCallBuilder extends OperationBuilder { this.addCall( Address.fromString(calls[i].target), Bytes.fromHexString(calls[i].selector), - cloneArguments(calls[i].arguments), + calls[i].arguments, BigInt.fromString(calls[i].value) ) } @@ -212,17 +199,6 @@ export class EvmDynamicArg { this.kind = kind this.data = data.toHexString() } - - /** - * Converts this dynamic argument into an ABI tuple parameter. - * @returns The ABI tuple parameter representation - */ - toEvmEncodeParam(): EvmEncodeParam { - return EvmEncodeParam.fromValues('()', [ - EvmEncodeParam.fromValue('uint8', BigInt.fromI32(this.kind as i32)), - EvmEncodeParam.fromValue('bytes', Bytes.fromHexString(this.data)), - ]) - } } /** @@ -243,11 +219,15 @@ export class EvmDynamicCallData { * @param value - The native token value to send */ constructor(target: Address, selector: Bytes, arguments_: EvmDynamicArg[] = [], value: BigInt = BigInt.zero()) { - validateSelector(selector) + if (selector.length !== 4) throw new Error('Selector must be 4 bytes') this.target = target.toString() this.value = value.toString() this.selector = selector.toHexString() - this.arguments = cloneArguments(arguments_) + this.arguments = new Array(arguments_.length) + for (let i = 0; i < arguments_.length; i++) { + const argument = arguments_[i] + this.arguments[i] = new EvmDynamicArg(argument.kind, Bytes.fromHexString(argument.data)) + } } } diff --git a/packages/lib-ts/tests/intents/EvmDynamicCall.spec.ts b/packages/lib-ts/tests/intents/EvmDynamicCall.spec.ts index 1f0298fa..18ea4f40 100644 --- a/packages/lib-ts/tests/intents/EvmDynamicCall.spec.ts +++ b/packages/lib-ts/tests/intents/EvmDynamicCall.spec.ts @@ -68,6 +68,48 @@ describe('EvmDynamicCall', () => { ) }) + it('creates a complex operation with multiple calls', () => { + const chainId = 1 + const user = randomEvmAddress() + const settler = randomSettler(chainId) + const target1 = randomEvmAddress() + const target2 = randomEvmAddress() + const selector1 = Bytes.fromHexString('0x12345678') + const selector2 = Bytes.fromHexString('0x90abcdef') + const argument1 = new EvmDynamicArg(EvmDynamicArgKind.Literal, randomBytes(64)) + const argument2 = new EvmDynamicArg(EvmDynamicArgKind.Variable, randomBytes(64)) + + setContext(1, 1, user.toString(), [settler], 'trigger-123') + + const call = new EvmDynamicCall( + chainId, + [ + new EvmDynamicCallData(target1, selector1, [argument1], BigInt.fromI32(1)), + new EvmDynamicCallData(target2, selector2, [argument2], BigInt.fromI32(2)), + ], + user + ) + + expect(call.calls.length).toBe(2) + expect(call.calls[0].target).toBe(target1.toString()) + expect(call.calls[0].selector).toBe(selector1.toHexString()) + expect(call.calls[0].arguments.length).toBe(1) + expect(call.calls[0].arguments[0].kind).toBe(EvmDynamicArgKind.Literal) + expect(call.calls[0].arguments[0].data).toBe(argument1.data) + expect(call.calls[0].value).toBe('1') + + expect(call.calls[1].target).toBe(target2.toString()) + expect(call.calls[1].selector).toBe(selector2.toHexString()) + expect(call.calls[1].arguments.length).toBe(1) + expect(call.calls[1].arguments[0].kind).toBe(EvmDynamicArgKind.Variable) + expect(call.calls[1].arguments[0].data).toBe(argument2.data) + expect(call.calls[1].value).toBe('2') + + expect(JSON.stringify(call)).toBe( + `{"opType":4,"chainId":${chainId},"user":"${user}","events":[],"calls":[{"target":"${target1}","value":"1","selector":"${selector1.toHexString()}","arguments":[{"kind":0,"data":"${argument1.data}"}]},{"target":"${target2}","value":"2","selector":"${selector2.toHexString()}","arguments":[{"kind":1,"data":"${argument2.data}"}]}]}` + ) + }) + it('throws an error when there is no call data', () => { expect(() => { new EvmDynamicCall(1, []) diff --git a/packages/lib-ts/tests/intents/Intent.spec.ts b/packages/lib-ts/tests/intents/Intent.spec.ts index 6dd3c44f..103c7610 100644 --- a/packages/lib-ts/tests/intents/Intent.spec.ts +++ b/packages/lib-ts/tests/intents/Intent.spec.ts @@ -14,6 +14,8 @@ import { TokenAmount } from '../../src/tokens' import { Address, BigInt, Bytes } from '../../src/types' import { randomERC20Token, randomSettler, setContext } from '../helpers' +/* eslint-disable no-secrets/no-secrets */ + describe('IntentBuilder', () => { const chainId = 1 const targetAddressStr = '0x0000000000000000000000000000000000000001' @@ -89,29 +91,6 @@ describe('IntentBuilder', () => { `{"settler":"${settler}","feePayer":"${feePayer}","deadline":"123456789","nonce":"0xabcdef123456","maxFees":[],"operations":[{"opType":2,"chainId":${chainId},"user":"0x0000000000000000000000000000000000000004","events":[],"calls":[{"target":"${target}","data":"0x1234","value":"0"}]}]}` ) }) - - it('adds a dynamic call operation from raw parameters', () => { - const target = Address.fromString(targetAddressStr) - const settler = randomSettler(chainId) - - setContext(0, 1, userAddressStr, [settler], 'trigger-dynamic-call') - - const intent = new IntentBuilder() - .addEvmDynamicCallOperation( - chainId, - target, - Bytes.fromHexString('0x12345678'), - [new EvmDynamicArg(EvmDynamicArgKind.Literal, Bytes.fromHexString('0x' + '00'.repeat(64)))], - BigInt.fromI32(7) - ) - .build() - - expect(intent.operations.length).toBe(1) - expect(intent.operations[0].opType).toBe(4) - const operation = changetype(intent.operations[0]) - expect(operation.calls[0].selector).toBe('0x12345678') - expect(operation.calls[0].value).toBe('7') - }) }) describe('when the settler is zero', () => { @@ -187,4 +166,31 @@ describe('IntentBuilder', () => { }).toThrow('Cross-chain swap must be the only operation in an intent') }) }) + + describe('addEvmDynamicCallOperation', () => { + it('adds a dynamic call operation from raw parameters', () => { + const target = Address.fromString(targetAddressStr) + const settler = randomSettler(chainId) + const userAddressStr = '0x0000000000000000000000000000000000000002' + + setContext(0, 1, userAddressStr, [settler], 'trigger-dynamic-call') + + const intent = new IntentBuilder() + .addEvmDynamicCallOperation( + chainId, + target, + Bytes.fromHexString('0x12345678'), + [new EvmDynamicArg(EvmDynamicArgKind.Literal, Bytes.fromHexString('0x1234'))], + BigInt.fromI32(7) + ) + .build() + + expect(intent.operations.length).toBe(1) + expect(intent.operations[0].opType).toBe(4) + + const operation = changetype(intent.operations[0]) + expect(operation.calls[0].selector).toBe('0x12345678') + expect(operation.calls[0].value).toBe('7') + }) + }) }) From 1cfc84193b168baba488c876a28d7a31efeb9c34 Mon Sep 17 00:00:00 2001 From: lgalende Date: Mon, 20 Apr 2026 14:00:12 -0300 Subject: [PATCH 6/7] lib: rollback target version to 0.0.2 --- .changeset/spicy-areas-switch.md | 2 +- packages/lib-ts/constants.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/spicy-areas-switch.md b/.changeset/spicy-areas-switch.md index 00caf654..a2743298 100644 --- a/.changeset/spicy-areas-switch.md +++ b/.changeset/spicy-areas-switch.md @@ -4,4 +4,4 @@ "@mimicprotocol/cli": patch --- -Add dynamic call operation and bump to runner version v0.0.3 +Add dynamic call operation diff --git a/packages/lib-ts/constants.js b/packages/lib-ts/constants.js index 4da6230d..0500e6e1 100644 --- a/packages/lib-ts/constants.js +++ b/packages/lib-ts/constants.js @@ -1 +1 @@ -export const RUNNER_TARGET_VERSION = '0.0.3' +export const RUNNER_TARGET_VERSION = '0.0.2' From 7045a4610606c17f8c3909d2c4ac4b751059c8ff Mon Sep 17 00:00:00 2001 From: lgalende Date: Tue, 21 Apr 2026 11:01:51 -0300 Subject: [PATCH 7/7] chore: rename arguments_ as args --- .../lib-ts/src/intents/Call/EvmDynamicCall.ts | 16 ++++++++-------- packages/lib-ts/src/intents/Intent.ts | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/lib-ts/src/intents/Call/EvmDynamicCall.ts b/packages/lib-ts/src/intents/Call/EvmDynamicCall.ts index d6e57adb..5caa6f6a 100644 --- a/packages/lib-ts/src/intents/Call/EvmDynamicCall.ts +++ b/packages/lib-ts/src/intents/Call/EvmDynamicCall.ts @@ -39,17 +39,17 @@ export class EvmDynamicCallBuilder extends OperationBuilder { * Adds a dynamic contract call to the operation. * @param target - The contract address to call * @param selector - The function selector to call - * @param arguments_ - The dynamic call arguments + * @param args - The dynamic call arguments * @param value - The native token value to send * @returns This EvmDynamicCallBuilder instance for method chaining */ addCall( target: Address, selector: Bytes, - arguments_: EvmDynamicArg[] = [], + args: EvmDynamicArg[] = [], value: BigInt = BigInt.zero() ): EvmDynamicCallBuilder { - this.calls.push(new EvmDynamicCallData(target, selector, arguments_, value)) + this.calls.push(new EvmDynamicCallData(target, selector, args, value)) return this } @@ -215,17 +215,17 @@ export class EvmDynamicCallData { * Creates a new EvmDynamicCallData instance. * @param target - The contract address to call * @param selector - The function selector to call - * @param arguments_ - The dynamic arguments for the call + * @param args - The dynamic arguments for the call * @param value - The native token value to send */ - constructor(target: Address, selector: Bytes, arguments_: EvmDynamicArg[] = [], value: BigInt = BigInt.zero()) { + constructor(target: Address, selector: Bytes, args: EvmDynamicArg[] = [], value: BigInt = BigInt.zero()) { if (selector.length !== 4) throw new Error('Selector must be 4 bytes') this.target = target.toString() this.value = value.toString() this.selector = selector.toHexString() - this.arguments = new Array(arguments_.length) - for (let i = 0; i < arguments_.length; i++) { - const argument = arguments_[i] + this.arguments = new Array(args.length) + for (let i = 0; i < args.length; i++) { + const argument = args[i] this.arguments[i] = new EvmDynamicArg(argument.kind, Bytes.fromHexString(argument.data)) } } diff --git a/packages/lib-ts/src/intents/Intent.ts b/packages/lib-ts/src/intents/Intent.ts index 863f8bee..0c884ca3 100644 --- a/packages/lib-ts/src/intents/Intent.ts +++ b/packages/lib-ts/src/intents/Intent.ts @@ -90,7 +90,7 @@ export class IntentBuilder { * @param chainId - The blockchain network identifier * @param target - The contract address to call * @param selector - The function selector to call - * @param arguments_ - The dynamic arguments to resolve at execution time + * @param args - The dynamic arguments to resolve at execution time * @param value - The native token value to send * @param user - The user that should execute the operation * @param events - The operation events to emit @@ -100,13 +100,13 @@ export class IntentBuilder { chainId: ChainId, target: Address, selector: Bytes, - arguments_: EvmDynamicArg[] = [], + args: EvmDynamicArg[] = [], value: BigInt = BigInt.zero(), user: Address | null = null, events: OperationEvent[] | null = null ): IntentBuilder { return this.addOperation( - new EvmDynamicCall(chainId, [new EvmDynamicCallData(target, selector, arguments_, value)], user, events) + new EvmDynamicCall(chainId, [new EvmDynamicCallData(target, selector, args, value)], user, events) ) }