From ec398a5d35d2b41525edbeb220c5039eace13982 Mon Sep 17 00:00:00 2001 From: PedroAraoz Date: Thu, 23 Apr 2026 14:19:50 -0300 Subject: [PATCH 1/4] chore: add operations --- developers/library.md | 82 +++++++++++++++--- developers/sdk.md | 58 ++++++++----- developers/tests.md | 104 +++++++++++++---------- examples/build-a-simple-function.md | 8 +- examples/events-and-function-chaining.md | 22 ++++- resources/glossary.md | 4 +- use-cases/automated-refunds.md | 2 +- use-cases/bridge-and-invest-in-aave.md | 6 +- 8 files changed, 199 insertions(+), 87 deletions(-) diff --git a/developers/library.md b/developers/library.md index 1b890df..3ccfa25 100644 --- a/developers/library.md +++ b/developers/library.md @@ -19,16 +19,18 @@ We're planning to expand language support beyond AssemblyScript to include other ### 1. Core Concepts -#### 1.1. Intents +#### 1.1. Intents and Operations Intents are declarations of what you want to happen. Instead of executing transactions directly, you create intents that describe your desired outcome. The protocol then finds the best way to fulfill them. -Three types of intents are available: +An intent is composed of one or more **operations** — the atomic actions to be executed. Three operation types are available: * **Call**: Execute smart contract functions * **Swap**: Exchange tokens across DEXs * **Transfer**: Move tokens between addresses +Grouping multiple operations into a single intent lets the protocol execute them together (all operations in an intent must share the same source chain). A cross-chain swap must always be the sole operation in its intent. + #### 1.2. Environment The Environment provides access to external data and execution capabilities: @@ -258,7 +260,9 @@ const timestampMs = ctx.timestamp Note: `getContext()` does not return a `Result` type as it cannot fail. -#### 3.3. Intent builders +#### 3.3. Operation builders + +Each operation type has its own builder. Builders produce operations, which are then wrapped into an intent when sent. Every builder exposes a `.send()` shortcut that creates a single-operation intent automatically. **3.3.1. Transfer** @@ -269,14 +273,14 @@ import { Address, ChainId, Ethereum, TokenAmount, TransferBuilder } from '@mimic const fee = TokenAmount.fromStringDecimal(Ethereum.USDC, '1') -const transferIntent = TransferBuilder.forChain(ChainId.ETHEREUM) +const transfer = TransferBuilder.forChain(ChainId.ETHEREUM) .addTransferFromStringDecimal(Ethereum.USDC, '1000', Address.fromString('0xrecipientAddress')) .addTransferFromStringDecimal(Ethereum.WBTC, '0.5', Address.fromString('0xrecipientAddress')) .addTransferFromStringDecimal(Ethereum.USDT, '500', Address.fromString('0xotherRecipientAddress')) .addMaxFee(fee) .build() -transferIntent.send() +transfer.send() ``` **3.3.2. Swap** @@ -284,17 +288,25 @@ transferIntent.send() Exchange tokens across DEXs: ```tsx -import { Address, ChainId, Ethereum, SwapBuilder, TokenAmount } from '@mimicprotocol/lib-ts' +import { Address, ChainId, Ethereum, Optimism, SwapBuilder } from '@mimicprotocol/lib-ts' const fee = TokenAmount.fromStringDecimal(Ethereum.USDC, '1') -const swapIntent = SwapBuilder.forChains(ChainId.ETHEREUM, ChainId.ETHEREUM) // same chain swap +const swap = SwapBuilder.forChains(ChainId.ETHEREUM, ChainId.ETHEREUM) // same chain swap .addTokenInFromStringDecimal(Ethereum.USDC, '1') .addTokenOutFromStringDecimal(Ethereum.USDT, '0.99', Address.fromString('0xrecipientAddress')) // 1% slippage .addMaxFee(fee) .build() -swapIntent.send() +swap.send() + +const cross = SwapBuilder.forChains(ChainId.ETHEREUM, ChainId.OPTIMISM) // cross chain swap + .addTokenInFromStringDecimal(Ethereum.USDC, '1000') + .addTokenOutFromStringDecimal(Optimism.USDC, '990', Address.fromString('0xrecipientAddress')) + .addMaxFee(fee) + .build() + +cross.send() ``` **3.3.3. Call** @@ -319,15 +331,63 @@ const encodedData = MyContractUtils.encodeMyFunction(Address.zero(), BigInt.zero const fee = TokenAmount.fromStringDecimal(Ethereum.USDC, '1') -const callIntent = EvmCallBuilder.forChain(ChainId.ETHEREUM) +const call = EvmCallBuilder.forChain(ChainId.ETHEREUM) .addCall(Address.fromString('0xcontractAddress'), encodedData) .addMaxFee(fee) .build() +call.send() +``` + +#### 3.4. IntentBuilder — composing multiple operations + +`IntentBuilder` lets you group multiple operations into a single intent. All operations must share the same source chain. Intent-level settings like `feePayer`, `settler`, `deadline`, and `nonce` are configured here. + +```tsx +import { Address, ChainId, Ethereum, EvmCallBuilder, IntentBuilder, SwapBuilder, TokenAmount, TransferBuilder } from '@mimicprotocol/lib-ts' + +const fee = TokenAmount.fromStringDecimal(Ethereum.USDC, '1') +const recipient = Address.fromString('0xrecipientAddress') + +const evmCall = EvmCallBuilder.forChain(ChainId.ETHEREUM) + .addCall(Address.fromString('0xcontractAddress'), encodedData) + +const swap = SwapBuilder.forChains(ChainId.ETHEREUM, ChainId.ETHEREUM) + .addTokenInFromStringDecimal(Ethereum.USDC, '500') + .addTokenOutFromStringDecimal(Ethereum.USDT, '495', recipient) + +const transfer = TransferBuilder.forChain(ChainId.ETHEREUM) + .addTransferFromStringDecimal(Ethereum.USDC, '100', recipient) + +// All three operations execute within a single intent +new IntentBuilder() + .addOperationsBuilders([evmCall, swap, transfer]) + .addMaxFee(fee) + .send() +``` + +You can also set a custom fee payer (defaults to the context user), or override the settler and deadline: -callIntent.send() +```tsx +new IntentBuilder() + .addOperationBuilder(evmCall) + .addMaxFee(fee) + .addFeePayerAsString('0xfeePayerAddress') + .addSettlerAsString('0xsettlerAddress') + .addDeadline(BigInt.fromString('1800000000')) + .send() +``` + +The `IntentBuilder` also provides convenience methods if you prefer to avoid instantiating builders directly: + +```tsx +new IntentBuilder() + .addEvmCallOperation(ChainId.ETHEREUM, Address.fromString('0xcontract'), encodedData) + .addTransferOperation(Ethereum.USDC, BigInt.fromString('1000000'), recipient) + .addMaxFee(fee) + .send() ``` -#### 3.4. EVM Utilities +#### 3.5. EVM Utilities ```tsx import { Address, BigInt, evm, EvmDecodeParam, EvmEncodeParam } from '@mimicprotocol/lib-ts' diff --git a/developers/sdk.md b/developers/sdk.md index 9c58bb2..b76fee3 100644 --- a/developers/sdk.md +++ b/developers/sdk.md @@ -273,10 +273,17 @@ const execution: Execution = await client.executions.getByHash('0x...') ### **Intents** -Query, encode and decode intents and proposals associated to executions. +Query, encode, and decode intents and their operations. An intent contains one or more operations (`intent.operations`); each operation has an `opType` that identifies what it does. ```typescript -import type { EvmCallIntent, Intent, SwapIntent, SwapProposal, TransferIntent } from '@mimicprotocol/sdk' +import type { + EvmCallOperation, + Intent, + Operation, + SwapOperation, + SwapProposal, + TransferOperation, +} from '@mimicprotocol/sdk' // List intents (filters are optional) const intents: Intent[] = await client.intents.get({ @@ -292,29 +299,31 @@ const intents: Intent[] = await client.intents.get({ const intent: Intent = await client.intents.getByHash('0x...') // Type guards and decoders -if (client.intents.isSwap(intent)) { - const swap: SwapIntent = client.intents.decodeSwapIntent(intent) - console.log(swap.sourceChain, swap.targetChain) -} else if (client.intents.isTransfer(intent)) { - const transfer: TransferIntent = client.intents.decodeTransferIntent(intent) - console.log(transfer.chainId, transfer.token) -} else if (client.intents.isEvmCall(intent)) { - const call: EvmCallIntent = client.intents.decodeEvmCallIntent(intent) - console.log(call.chainId, call.target) +for (const op of intent.operations) { + if (client.intents.isSwap(op)) { + const swap: SwapOperation = client.intents.decodeSwapOperation(op) + console.log(swap.sourceChain, swap.destinationChain, swap.tokensIn, swap.tokensOut) + } else if (client.intents.isTransfer(op)) { + const transfer: TransferOperation = client.intents.decodeTransferOperation(op) + console.log(transfer.chainId, transfer.transfers) + } else if (client.intents.isEvmCall(op)) { + const call: EvmCallOperation = client.intents.decodeEvmCallOperation(op) + console.log(call.chainId, call.calls) + } } -// ChainId helper -const chainId: number = client.intents.getChainId(intent) +// ChainId helper — reads chainId from a single operation +const chainId: number = client.intents.getChainId(intent.operations[0]) -// Proposal decoder (supports swap proposals) -const decoded: SwapProposal = client.intents.decodeProposal(intent.proposals[0]) +// Proposal decoder — pass the proposal and the index of the operation it belongs to +const decoded: SwapProposal = client.intents.decodeProposal(intent.proposals[0], 0) // Encoders const encodedIntent = client.intents.encodeIntent({ /* Intent fields */ }) const encodedProposal = client.intents.encodeProposal({ /* Proposal fields */ }, intent) -const swapDataHex = client.intents.encodeSwapIntentData({ /* SwapIntentData */ }) -const transferDataHex = client.intents.encodeTransferIntentData({ /* TransferIntentData */ }) -const callDataHex = client.intents.encodeEvmCallIntentData({ /* EvmCallIntentData */ }) +const swapDataHex = client.intents.encodeSwapOperationData({ /* SwapOperationData */ }) +const transferDataHex = client.intents.encodeTransferOperationData({ /* TransferOperationData */ }) +const callDataHex = client.intents.encodeEvmCallOperationData({ /* EvmCallOperationData */ }) ``` ### Error Handling @@ -347,21 +356,28 @@ import type { // Core types Client, InitOptions, - + // Domain types Trigger, Function, Execution, Intent, + Operation, Balance, - + + // Operation subtypes + SwapOperation, + TransferOperation, + EvmCallOperation, + SwapProposal, + // Authentication types Signer, EthersSigner, WindowEthereumSigner, ApiKeyAuth, BearerAuth, - + // Utility types Address, Signature, diff --git a/developers/tests.md b/developers/tests.md index 7587823..f7b9b8b 100644 --- a/developers/tests.md +++ b/developers/tests.md @@ -293,7 +293,7 @@ The `runFunction` function returns an object containing the following fields: **2.2.1. Intents** -The fields depend on the type of intents produced by the execution. +Each intent in `result.intents` contains intent-level metadata (`settler`, `feePayer`, `maxFees`) and an `operations` array. The shape of each operation depends on its type. **Transfer** @@ -303,34 +303,40 @@ If the function creates a transfer intent: const USDC = ERC20Token.fromString('0xUSDC', ChainId.OPTIMISM) const recipient = Address.fromString('0xRecipient') const amount = BigInt.fromStringDecimal('1', USDC.decimals) -const maxFee = BigInt.fromStringDecimal('0.1', USDC.decimals) +const maxFee = TokenAmount.fromStringDecimal(USDC, '0.1') -Transfer.create(USDC, amount, recipient, maxFee).send() +TransferBuilder.forChain(ChainId.OPTIMISM) + .addTransferFromTokenAmount(TokenAmount.fromBigInt(USDC, amount), recipient) + .addMaxFee(maxFee) + .build() + .send() ``` Then, the test should be: ```typescript import { Chains, OpType } from '@mimicprotocol/sdk' -import { runFunction, Transfer } from '@mimicprotocol/test-ts' +import { runFunction, TransferOperation } from '@mimicprotocol/test-ts' it('produces the expected intent', async () => { const result = await runFunction(/* parameters */) expect(result.success).to.be.true - + expect(result.intents).to.have.lengthOf(1) - const intent = result.intents[0] as Transfer - - expect(intent.op).to.be.equal(OpType.Transfer) + const intent = result.intents[0] + const op = intent.operations[0] as TransferOperation + + expect(op.opType).to.be.equal(OpType.Transfer) expect(intent.settler).to.be.equal(context.settlers[0].address) - expect(intent.user).to.be.equal(context.user) - expect(intent.chainId).to.be.equal(Chains.Optimism) + expect(op.user).to.be.equal(context.user) + expect(op.chainId).to.be.equal(Chains.Optimism) - expect(intent.transfers).to.have.lengthOf(1) - expect(intent.transfers[0].token).to.be.equal('0xUSDC') - expect(intent.transfers[0].amount).to.be.equal('1000000') // 1 USDC - expect(intent.transfers[0].recipient).to.be.equal('0xRecipient') + expect(op.transfers).to.have.lengthOf(1) + expect(op.transfers[0].token).to.be.equal('0xUSDC') + expect(op.transfers[0].amount).to.be.equal('1000000') // 1 USDC + expect(op.transfers[0].recipient).to.be.equal('0xRecipient') + expect(intent.feePayer).to.be.equal(context.user) expect(intent.maxFees).to.have.lengthOf(1) expect(intent.maxFees[0].token).to.be.equal('0xUSDC') expect(intent.maxFees[0].amount).to.be.equal('100000') // 0.1 USDC @@ -348,37 +354,45 @@ const amountIn = BigInt.fromStringDecimal('1', USDC.decimals) const WETH = ERC20Token.fromString('0xWETH', ChainId.OPTIMISM) const minAmountOut = BigInt.fromStringDecimal('0.001', WETH.decimals) -Swap.create(ChainId.OPTIMISM, USDC, amountIn, WETH, minAmountOut).send() +const recipient = environment.getContext().user + +SwapBuilder.forChains(ChainId.OPTIMISM, ChainId.OPTIMISM) + .addTokenInFromTokenAmount(TokenAmount.fromBigInt(USDC, amountIn)) + .addTokenOutFromTokenAmount(TokenAmount.fromBigInt(WETH, minAmountOut), recipient) + .build() + .send() ``` Then, the test should be: ```typescript import { Chains, OpType } from '@mimicprotocol/sdk' -import { runFunction, Swap } from '@mimicprotocol/test-ts' +import { runFunction, SwapOperation } from '@mimicprotocol/test-ts' it('produces the expected intent', async () => { const result = await runFunction(/* parameters */) expect(result.success).to.be.true - + expect(result.intents).to.have.lengthOf(1) - const intent = result.intents[0] as Swap - - expect(intent.op).to.be.equal(OpType.Swap) + const intent = result.intents[0] + const op = intent.operations[0] as SwapOperation + + expect(op.opType).to.be.equal(OpType.Swap) expect(intent.settler).to.be.equal(context.settlers[0].address) - expect(intent.user).to.be.equal(context.user) - expect(intent.sourceChain).to.be.equal(Chains.Optimism) - expect(intent.destinationChain).to.be.equal(Chains.Optimism) - - expect(intent.tokensIn).to.have.lengthOf(1) - expect(intent.tokensIn[0].token).to.be.equal('0xUSDC') - expect(intent.tokensIn[0].amount).to.be.equal('1000000') // 1 USDC - - expect(intent.tokensOut).to.have.lengthOf(1) - expect(intent.tokensOut[0].token).to.be.equal('0xWETH') - expect(intent.tokensOut[0].minAmount).to.be.equal('1' + '0'.repeat(15)) // 0.001 WETH - expect(intent.tokensOut[0].recipient).to.be.equal(context.user) - + expect(op.user).to.be.equal(context.user) + expect(op.sourceChain).to.be.equal(Chains.Optimism) + expect(op.destinationChain).to.be.equal(Chains.Optimism) + + expect(op.tokensIn).to.have.lengthOf(1) + expect(op.tokensIn[0].token).to.be.equal('0xUSDC') + expect(op.tokensIn[0].amount).to.be.equal('1000000') // 1 USDC + + expect(op.tokensOut).to.have.lengthOf(1) + expect(op.tokensOut[0].token).to.be.equal('0xWETH') + expect(op.tokensOut[0].minAmount).to.be.equal('1' + '0'.repeat(15)) // 0.001 WETH + expect(op.tokensOut[0].recipient).to.be.equal(context.user) + + expect(intent.feePayer).to.be.equal(context.user) expect(intent.maxFees).to.have.lengthOf(0) }) ``` @@ -405,7 +419,7 @@ Then, the test should be: ```typescript import { Chains, OpType } from '@mimicprotocol/sdk' -import { EvmCall, runFunction } from '@mimicprotocol/test-ts' +import { CallOperation, runFunction } from '@mimicprotocol/test-ts' import { Interface } from 'ethers' import ERC20Abi from '../abis/ERC20.json' @@ -415,21 +429,23 @@ const ERC20Interface = new Interface(ERC20Abi) it('produces the expected intent', async () => { const result = await runFunction(/* parameters */) expect(result.success).to.be.true - + expect(result.intents).to.have.lengthOf(1) - const intent = result.intents[0] as EvmCall + const intent = result.intents[0] + const op = intent.operations[0] as CallOperation - expect(intent.op).to.be.equal(OpType.EvmCall) + expect(op.opType).to.be.equal(OpType.EvmCall) expect(intent.settler).to.be.equal(context.settlers[0].address) - expect(intent.user).to.be.equal('0xSmartAccount') - expect(intent.chainId).to.be.equal(Chains.Optimism) + expect(op.user).to.be.equal('0xSmartAccount') + expect(op.chainId).to.be.equal(Chains.Optimism) - expect(intent.calls).to.have.lengthOf(1) - expect(intent.calls[0].target).to.be.equal(USDC) - expect(intent.calls[0].value).to.be.equal('0') + expect(op.calls).to.have.lengthOf(1) + expect(op.calls[0].target).to.be.equal(USDC) + expect(op.calls[0].value).to.be.equal('0') const data = ERC20Interface.encodeFunctionData('approve', ['0xSpender', '1000000']) - expect(intent.calls[0].data).to.be.equal(data) - + expect(op.calls[0].data).to.be.equal(data) + + expect(intent.feePayer).to.be.equal(context.user) expect(intent.maxFees).to.have.lengthOf(1) expect(intent.maxFees[0].token).to.be.equal('0xUSDC') expect(intent.maxFees[0].amount).to.be.equal('100000') // 0.1 USDC diff --git a/examples/build-a-simple-function.md b/examples/build-a-simple-function.md index afe169b..2298672 100644 --- a/examples/build-a-simple-function.md +++ b/examples/build-a-simple-function.md @@ -97,11 +97,11 @@ Create a file `./src/function.ts` and implement the logic: As you can see, the generated contract artifacts can be accessed from your function code. -For now Mimic functions allows creating three types of intents: +Mimic functions express actions as **operations** wrapped inside an intent. Three operation types are available: -* Transfers -* Generic calls -* Cross-chain swaps +* **Transfer** — move tokens between addresses +* **Generic Call** — execute smart contract functions +* **Swap** — exchange tokens on a DEX (same-chain or cross-chain) *** diff --git a/examples/events-and-function-chaining.md b/examples/events-and-function-chaining.md index 04d5f2f..299f420 100644 --- a/examples/events-and-function-chaining.md +++ b/examples/events-and-function-chaining.md @@ -12,21 +12,35 @@ This page shows how to link your Mimic functions by **emitting and listening to ### Emit a custom event -**Goal:** Add an event with arbitrary information to an intent so it's emitted on-chain. +**Goal:** Add an event with arbitrary information to an operation so it's emitted on-chain when the intent is executed. + +Events are attached to **operations** (not to the intent itself), so you call `.addEvent()` on any operation builder — `EvmCallBuilder`, `SwapBuilder`, or `TransferBuilder` — before calling `.send()`. ```typescript -import { Bytes, evm, EvmEncodeParam } from '@mimicprotocol/lib-ts' +import { Address, Bytes, ChainId, Ethereum, EvmCallBuilder, evm, EvmEncodeParam, TokenAmount } from '@mimicprotocol/lib-ts' export default function main(): void { const topic = evm.keccak('First function') const data = evm.encode([EvmEncodeParam.fromValue('address', '0xSomeAddress')]) + const fee = TokenAmount.fromStringDecimal(Ethereum.USDC, '1') - builder + EvmCallBuilder.forChain(ChainId.ETHEREUM) + .addCall(Address.fromString('0xcontractAddress'), callData) .addEvent(Bytes.fromHexString(topic), Bytes.fromHexString(data)) - .addUser('0xUserAddress') + .send(fee) } ``` +The same `.addEvent()` method is available on `SwapBuilder` and `TransferBuilder`: + +```typescript +SwapBuilder.forChains(ChainId.ETHEREUM, ChainId.OPTIMISM) + .addTokenInFromStringDecimal(Ethereum.USDC, '1000') + .addTokenOutFromStringDecimal(Optimism.USDC, '990', recipient) + .addEvent(Bytes.fromHexString(topic), Bytes.fromHexString(data)) + .send() +``` + *** ### Trigger a function from a custom event diff --git a/resources/glossary.md b/resources/glossary.md index 4e145f2..6edc677 100644 --- a/resources/glossary.md +++ b/resources/glossary.md @@ -20,7 +20,9 @@ The Registry is the on-chain component of Mimic Protocol responsible for storing ## **Intents** -An intent is a structured request that specifies an action to be executed, subject to predefined conditions and constraints cryptographically signed. Intents serve as the core units of work within the protocol, enabling decentralized systems to process, validate, and execute user-defined operations. +An intent is a structured request that specifies one or more **operations** to be executed, subject to predefined conditions and constraints cryptographically signed. Intents serve as the core units of work within the protocol, enabling decentralized systems to process, validate, and execute user-defined operations. + +An intent carries intent-level metadata (settler address, fee payer, max fees, deadline, nonce) that applies to all its operations. Each operation within the intent specifies what action to perform (swap, transfer, or call) and who the user is for that action. Grouping multiple operations into one intent lets them be settled atomically in a single on-chain transaction. ## Relayers diff --git a/use-cases/automated-refunds.md b/use-cases/automated-refunds.md index 0b8a148..50d1783 100644 --- a/use-cases/automated-refunds.md +++ b/use-cases/automated-refunds.md @@ -18,4 +18,4 @@ The goal here is to integrate a backend to send refunds through Mimic Protocol. {% @github-files/github-code-block url="https://github.com/mimic-protocol/examples/blob/main/examples/11-automated-refunds/src/create-trigger.ts" visible="false" %} -[Github link](https://github.com/mimic-protocol/examples/blob/main/examples/11-automated-refunds/manifest.yaml) +[Github link](https://github.com/mimic-protocol/examples/blob/main/examples/11-automated-refunds/src/create-trigger.ts) diff --git a/use-cases/bridge-and-invest-in-aave.md b/use-cases/bridge-and-invest-in-aave.md index 78884cc..23ef283 100644 --- a/use-cases/bridge-and-invest-in-aave.md +++ b/use-cases/bridge-and-invest-in-aave.md @@ -7,7 +7,11 @@ To achieve this, the bridge must complete before the investment begins. Mimic en The first function should include something as follows: ```tsx -SwapBuilder.addEvent('0xCustomId', '0xDataForTheOtherFunction') +SwapBuilder.forChains(ChainId.SOURCE, ChainId.DESTINATION) + .addTokenInFromStringDecimal(tokenIn, '1000') + .addTokenOutFromStringDecimal(tokenOut, '990', recipient) + .addEvent(Bytes.fromHexString('0xCustomId'), Bytes.fromHexString('0xDataForTheOtherFunction')) + .send() ``` And the trigger for the second function should look like: From 376ceda2c926b4630d4d6836217a62ee945fb436 Mon Sep 17 00:00:00 2001 From: PedroAraoz Date: Thu, 23 Apr 2026 17:07:06 -0300 Subject: [PATCH 2/4] fixes --- developers/fees.md | 4 +-- developers/library.md | 17 +++++------ developers/tests.md | 6 ++-- skills/skill-function.md | 62 ++++++++++++++++------------------------ 4 files changed, 35 insertions(+), 54 deletions(-) diff --git a/developers/fees.md b/developers/fees.md index e98750e..cbca0e4 100644 --- a/developers/fees.md +++ b/developers/fees.md @@ -28,10 +28,10 @@ export default function main(): void { // Set the solver's maximum fee, paid in Mimic Credits (denominated in USD) // Pay up to 0.5 USD worth of credits + const fee = TokenAmount.fromStringDecimal(DenominationToken.USD(), '0.5') builder - .addMaxFee(TokenAmount.fromStringDecimal(DenominationToken.USD(), '0.5')) .build() - .send() + .send(fee) } ``` diff --git a/developers/library.md b/developers/library.md index 3ccfa25..a0a396e 100644 --- a/developers/library.md +++ b/developers/library.md @@ -277,10 +277,9 @@ const transfer = TransferBuilder.forChain(ChainId.ETHEREUM) .addTransferFromStringDecimal(Ethereum.USDC, '1000', Address.fromString('0xrecipientAddress')) .addTransferFromStringDecimal(Ethereum.WBTC, '0.5', Address.fromString('0xrecipientAddress')) .addTransferFromStringDecimal(Ethereum.USDT, '500', Address.fromString('0xotherRecipientAddress')) - .addMaxFee(fee) .build() -transfer.send() +transfer.send(fee) ``` **3.3.2. Swap** @@ -295,18 +294,16 @@ const fee = TokenAmount.fromStringDecimal(Ethereum.USDC, '1') const swap = SwapBuilder.forChains(ChainId.ETHEREUM, ChainId.ETHEREUM) // same chain swap .addTokenInFromStringDecimal(Ethereum.USDC, '1') .addTokenOutFromStringDecimal(Ethereum.USDT, '0.99', Address.fromString('0xrecipientAddress')) // 1% slippage - .addMaxFee(fee) .build() -swap.send() +swap.send(fee) const cross = SwapBuilder.forChains(ChainId.ETHEREUM, ChainId.OPTIMISM) // cross chain swap .addTokenInFromStringDecimal(Ethereum.USDC, '1000') .addTokenOutFromStringDecimal(Optimism.USDC, '990', Address.fromString('0xrecipientAddress')) - .addMaxFee(fee) .build() -cross.send() +cross.send(fee) ``` **3.3.3. Call** @@ -333,9 +330,9 @@ const fee = TokenAmount.fromStringDecimal(Ethereum.USDC, '1') const call = EvmCallBuilder.forChain(ChainId.ETHEREUM) .addCall(Address.fromString('0xcontractAddress'), encodedData) - .addMaxFee(fee) .build() -call.send() + +call.send(fee) ``` #### 3.4. IntentBuilder — composing multiple operations @@ -512,7 +509,7 @@ Write methods return an `EvmCallBuilder` so you can compose multiple calls and s ```ts // ABI: function approve(address spender, uint256 amount) nonpayable const approve = erc20.approve(spender, amount) // EvmCallBuilder -// approve.addMaxFee(...).build().send() // typical flow +// approve.build().send(maxFee) // typical flow ``` Encoding converts primitives as needed: @@ -605,7 +602,7 @@ const bal = balResult.unwrap() // Write method returns EvmCallBuilder const call = token.transfer(Address.fromString('0xrecipient'), BigInt.fromString('1000000000000000000')) // EvmCallBuilder -// call.addMaxFee(...).build().send() +// call.build().send(maxFee) // Event decoding const decoded = TransferEvent.decode(topics, data) diff --git a/developers/tests.md b/developers/tests.md index f7b9b8b..ceb9657 100644 --- a/developers/tests.md +++ b/developers/tests.md @@ -307,9 +307,8 @@ const maxFee = TokenAmount.fromStringDecimal(USDC, '0.1') TransferBuilder.forChain(ChainId.OPTIMISM) .addTransferFromTokenAmount(TokenAmount.fromBigInt(USDC, amount), recipient) - .addMaxFee(maxFee) .build() - .send() + .send(maxFee) ``` Then, the test should be: @@ -410,9 +409,8 @@ const data = ERC20Utils.encodeApprove(spender, amount) EvmCallBuilder.forChain(ChainId.OPTIMISM) .addCall(USDC, data) .addUser(smartAccount) - .addMaxFee(maxFee) .build() - .send() + .send(maxFee) ``` Then, the test should be: diff --git a/skills/skill-function.md b/skills/skill-function.md index f807667..4292f77 100644 --- a/skills/skill-function.md +++ b/skills/skill-function.md @@ -178,15 +178,16 @@ const myTokenOpt = ERC20Token.fromString('0x...', inputs.chainId, 18, 'MYT') // #### DenominationToken -`DenominationToken.USD()` is the standard way to express max fees. It is **not** a real on-chain token — it deducts from the user's Mimic credits. It can **only** be used with `.addMaxFee()`. You cannot transfer or swap it. +`DenominationToken.USD()` is the standard way to express max fees. It is **not** a real on-chain token — it deducts from the user's Mimic credits. It can **only** be used as the fee argument to `.send()` or `IntentBuilder.addMaxFee()`. You cannot transfer or swap it. ```typescript import { DenominationToken, TokenAmount } from '@mimicprotocol/lib-ts' const fee = TokenAmount.fromStringDecimal(DenominationToken.USD(), inputs.feeAmount) -// Only valid use — passing to .addMaxFee() -builder.addMaxFee(fee) +// Only valid use — passing to .send() or IntentBuilder.addMaxFee() +intentBuilder.addMaxFee(fee) +builder.build().send(fee) ``` #### TokenAmount @@ -354,7 +355,7 @@ const balance = balanceResult.unwrap() // Write methods — return an EvmCallBuilder, do not call the chain directly const approveBuilder = token.approve(spenderAddress, amount) // EvmCallBuilder -approveBuilder.addMaxFee(fee).build().send() +approveBuilder.build().send(fee) // Historical queries — pass a timestamp as third constructor arg const historicalToken = new ERC20(tokenAddress, ChainId.ETHEREUM, new Date(1640995200000)) @@ -425,33 +426,28 @@ const fee = TokenAmount.fromStringDecimal(Ethereum.USDC, '1') TransferBuilder.forChain(ChainId.ETHEREUM) .addTransferFromStringDecimal(Ethereum.USDC, '500', recipientAddress) .addTransferFromStringDecimal(Ethereum.WBTC, '0.01', recipientAddress) - .addMaxFee(fee) .build() - .send() + .send(fee) ``` #### Swap — exchange tokens ```typescript -import { ChainId, Ethereum, SwapBuilder, TokenAmount } from '@mimicprotocol/lib-ts' - -const fee = TokenAmount.fromStringDecimal(Ethereum.USDC, '1') +import { ChainId, Ethereum, SwapBuilder } from '@mimicprotocol/lib-ts' // Same-chain swap SwapBuilder.forChain(ChainId.ETHEREUM) .addTokenInFromStringDecimal(Ethereum.USDC, '1000') .addTokenOutFromStringDecimal(Ethereum.USDT, '990', recipientAddress) // min amount out - .addMaxFee(fee) .build() - .send() + .send(fee) // Cross-chain swap SwapBuilder.forChains(ChainId.ETHEREUM, ChainId.ARBITRUM) .addTokenInFromStringDecimal(Ethereum.USDC, '1000') .addTokenOutFromStringDecimal(Arbitrum.USDC, '990', recipientAddress) - .addMaxFee(fee) .build() - .send() + .send(fee) ``` The `tokenOut` amount is a **minimum** — relayers may deliver more. @@ -467,35 +463,31 @@ const fee = TokenAmount.fromStringDecimal(Ethereum.USDC, '1') // Single call from a generated wrapper contract.myFunction(arg1, arg2) // returns EvmCallBuilder - .addMaxFee(fee) .build() - .send() + .send(fee) // Multiple calls in one intent — use addCallsFromBuilder to merge generated wrappers. // This is preferred over extracting and re-encoding data manually with addCall. EvmCallBuilder.forChain(ChainId.ETHEREUM) .addCallsFromBuilder(contract.functionA(arg1)) .addCallsFromBuilder(contract.functionB(arg2)) - .addMaxFee(fee) .addUser(inputs.smartAccount) .build() - .send() + .send(fee) // Or pass all builders at once EvmCallBuilder.forChain(ChainId.ETHEREUM) .addCallsFromBuilders([contract.functionA(arg1), contract.functionB(arg2)]) - .addMaxFee(fee) .addUser(inputs.smartAccount) .build() - .send() + .send(fee) // Use addCall directly only when you have raw encoded data (e.g. from a multicall response) EvmCallBuilder.forChain(ChainId.ETHEREUM) .addCall(contractAddress, encodedData) // value defaults to 0 .addCall(contractAddress, encodedData2, BigInt.fromI32(1000)) // optional ETH value for payable calls - .addMaxFee(fee) .build() - .send() + .send(fee) ``` #### Common builder options @@ -505,9 +497,7 @@ All intent builders support these optional methods before `.build()`: | Method | Purpose | | ------ | ------- | | `.addUser(address)` | Override the user (defaults to `ctx.user`) | -| `.addDeadline(timestamp)` | Override the deadline (defaults to 5 minutes from now) | -| `.addNonce(string)` | Override the nonce (defaults to auto-generated) | -| `.addEvent(topic, data)` | Attach an on-chain event to the intent | +| `.addEvent(topic, data)` | Attach an on-chain event to the operation | | `.addEvents(events[])` | Attach multiple events | **When to use `.addUser()`:** @@ -519,17 +509,15 @@ All intent builders support these optional methods before `.build()`: // Transfer — ctx.user is the default, no .addUser() needed TransferBuilder.forChain(inputs.chainId) .addTransferFromTokenAmount(tokenAmount, recipientAddress) - .addMaxFee(fee) .build() - .send() + .send(fee) // EVM Call — smart account required EvmCallBuilder.forChain(inputs.chainId) .addCall(contractAddress, callData) - .addMaxFee(fee) .addUser(inputs.smartAccount) // required for generic calls .build() - .send() + .send(fee) ``` **Guarding against empty intents:** @@ -538,23 +526,22 @@ Never send an intent with no transfers, calls, or swaps. If you are conditionall ```typescript // Guard needed — items are added conditionally inside a loop -const builder = TransferBuilder.forChain(inputs.chainId).addMaxFee(fee) +const builder = TransferBuilder.forChain(inputs.chainId) for (let i = 0; i < tokenBalances.length; i++) { if (tokenBalances[i].amount.gt(BigInt.zero())) { builder.addTransferFromTokenAmount(tokenBalances[i], inputs.recipient) } } if (builder.transfers.length > 0) { // only send if something was added - builder.build().send() + builder.build().send(fee) } // No guard needed — the call is always added EvmCallBuilder.forChain(inputs.chainId) .addCall(contractAddress, callData) - .addMaxFee(fee) .addUser(inputs.smartAccount) .build() - .send() + .send(fee) ``` **Batching large numbers of items:** @@ -565,12 +552,12 @@ A single intent may not fit in one transaction if it contains too many calls, tr const BATCH_SIZE = 20 for (let i = 0; i < calls.length; i += BATCH_SIZE) { - const builder = EvmCallBuilder.forChain(inputs.chainId).addMaxFee(fee) + const builder = EvmCallBuilder.forChain(inputs.chainId) const end = i + BATCH_SIZE < calls.length ? i + BATCH_SIZE : calls.length for (let j = i; j < end; j++) { builder.addCall(calls[j].address, calls[j].data) } - builder.addUser(inputs.smartAccount).build().send() + builder.addUser(inputs.smartAccount).build().send(fee) } ``` @@ -651,8 +638,8 @@ const hash = evm.keccak('some data') ```typescript import { - Address, ChainId, DenominationToken, Ethereum, environment, log, - SwapBuilder, TokenAmount, + Address, ChainId, Ethereum, environment, log, + SwapBuilder, } from '@mimicprotocol/lib-ts' import { inputs } from './types' import { ERC20 } from './types/ERC20' @@ -685,10 +672,9 @@ export default function main(): void { SwapBuilder.forChain(ChainId.ETHEREUM) .addTokenInFromStringDecimal(Ethereum.USDC, balance.toString()) .addTokenOutFromTokenAmount(minOut, account) - .addMaxFee(fee) .addUser(inputs.smartAccount) .build() - .send() + .send(fee) log.info('Swap intent emitted') } From ddc3a30a6c9d6195da9ae4075f2acef6c590ff00 Mon Sep 17 00:00:00 2001 From: PedroAraoz Date: Mon, 27 Apr 2026 15:05:29 -0300 Subject: [PATCH 3/4] review changes --- developers/library.md | 12 ++++++------ examples/events-and-function-chaining.md | 3 ++- skills/skill-function.md | 18 ++++++++++++++---- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/developers/library.md b/developers/library.md index a0a396e..bd77c7a 100644 --- a/developers/library.md +++ b/developers/library.md @@ -221,7 +221,7 @@ Contract calls allow you to read contract information from the chain: import { environment, Address, ChainId } from '@mimicprotocol/lib-ts' const response = environment.evmCallQuery( - Address.fromString('0xcontractAddress'), + Address.fromString('0xContractAddress'), ChainId.ETHEREUM, '0x70a08231', // encoded function call data null // optional timestamp @@ -287,7 +287,7 @@ transfer.send(fee) Exchange tokens across DEXs: ```tsx -import { Address, ChainId, Ethereum, Optimism, SwapBuilder } from '@mimicprotocol/lib-ts' +import { Address, ChainId, Ethereum, Optimism, SwapBuilder, TokenAmount } from '@mimicprotocol/lib-ts' const fee = TokenAmount.fromStringDecimal(Ethereum.USDC, '1') @@ -296,13 +296,13 @@ const swap = SwapBuilder.forChains(ChainId.ETHEREUM, ChainId.ETHEREUM) // same c .addTokenOutFromStringDecimal(Ethereum.USDT, '0.99', Address.fromString('0xrecipientAddress')) // 1% slippage .build() -swap.send(fee) - const cross = SwapBuilder.forChains(ChainId.ETHEREUM, ChainId.OPTIMISM) // cross chain swap .addTokenInFromStringDecimal(Ethereum.USDC, '1000') .addTokenOutFromStringDecimal(Optimism.USDC, '990', Address.fromString('0xrecipientAddress')) .build() +// Two separate intents are created +swap.send(fee) cross.send(fee) ``` @@ -329,7 +329,7 @@ const encodedData = MyContractUtils.encodeMyFunction(Address.zero(), BigInt.zero const fee = TokenAmount.fromStringDecimal(Ethereum.USDC, '1') const call = EvmCallBuilder.forChain(ChainId.ETHEREUM) - .addCall(Address.fromString('0xcontractAddress'), encodedData) + .addCall(Address.fromString('0xContractAddress'), encodedData) .build() call.send(fee) @@ -346,7 +346,7 @@ const fee = TokenAmount.fromStringDecimal(Ethereum.USDC, '1') const recipient = Address.fromString('0xrecipientAddress') const evmCall = EvmCallBuilder.forChain(ChainId.ETHEREUM) - .addCall(Address.fromString('0xcontractAddress'), encodedData) + .addCall(Address.fromString('0xContractAddress'), encodedData) const swap = SwapBuilder.forChains(ChainId.ETHEREUM, ChainId.ETHEREUM) .addTokenInFromStringDecimal(Ethereum.USDC, '500') diff --git a/examples/events-and-function-chaining.md b/examples/events-and-function-chaining.md index 299f420..8858535 100644 --- a/examples/events-and-function-chaining.md +++ b/examples/events-and-function-chaining.md @@ -25,8 +25,9 @@ export default function main(): void { const fee = TokenAmount.fromStringDecimal(Ethereum.USDC, '1') EvmCallBuilder.forChain(ChainId.ETHEREUM) - .addCall(Address.fromString('0xcontractAddress'), callData) + .addCall(Address.fromString('0xContractAddress'), callData) .addEvent(Bytes.fromHexString(topic), Bytes.fromHexString(data)) + .addUser('0xSmartAccountAddress') .send(fee) } ``` diff --git a/skills/skill-function.md b/skills/skill-function.md index 4292f77..6aa0643 100644 --- a/skills/skill-function.md +++ b/skills/skill-function.md @@ -433,7 +433,9 @@ TransferBuilder.forChain(ChainId.ETHEREUM) #### Swap — exchange tokens ```typescript -import { ChainId, Ethereum, SwapBuilder } from '@mimicprotocol/lib-ts' +import { Arbitrum, ChainId, Ethereum, SwapBuilder } from '@mimicprotocol/lib-ts' + +const fee = TokenAmount.fromStringDecimal(Ethereum.USDC, '1') // Same-chain swap SwapBuilder.forChain(ChainId.ETHEREUM) @@ -492,7 +494,7 @@ EvmCallBuilder.forChain(ChainId.ETHEREUM) #### Common builder options -All intent builders support these optional methods before `.build()`: +All builders support these optional methods before `.build()`: | Method | Purpose | | ------ | ------- | @@ -500,6 +502,14 @@ All intent builders support these optional methods before `.build()`: | `.addEvent(topic, data)` | Attach an on-chain event to the operation | | `.addEvents(events[])` | Attach multiple events | +The Intent builder supports these optional methods before `.build()`: +| Method | Purpose | +| ------ | ------- | +| `.addFeePayer(address)` | Override the fee payer (defaults to `ctx.user`) | +| `.addDeadline(timestamp)` | Override the deadline (defaults to 5 minutes from now) | +| `.addNonce(string)` | Override the nonce (defaults to auto-generated) | +| `.addMaxFee(TokenAmount)` | Sets max fee that can be paied for the intent | + **When to use `.addUser()`:** - **Transfer / Swap intents** — `ctx.user` is the default and is correct for most cases. `ctx.user` is the EOA that signed and triggered the function, so you normally do not need to call `.addUser()`. @@ -638,8 +648,8 @@ const hash = evm.keccak('some data') ```typescript import { - Address, ChainId, Ethereum, environment, log, - SwapBuilder, + Address, ChainId, DenominationToken, Ethereum, environment, log, + SwapBuilder, TokenAmount, } from '@mimicprotocol/lib-ts' import { inputs } from './types' import { ERC20 } from './types/ERC20' From 8db9ebffe01d8bc391510bb46a8ed65cffc958fe Mon Sep 17 00:00:00 2001 From: Agustincito Date: Thu, 30 Apr 2026 14:05:34 -0300 Subject: [PATCH 4/4] Fix renaming --- developers/tests.md | 450 +++++++++++++++++++++++--------------------- 1 file changed, 233 insertions(+), 217 deletions(-) diff --git a/developers/tests.md b/developers/tests.md index ceb9657..cc9ad57 100644 --- a/developers/tests.md +++ b/developers/tests.md @@ -2,7 +2,7 @@ The Mimic Protocol Test Library (`@mimicprotocol/test-ts`) provides tools for simulating function execution to validate expected function behavior under different scenarios. -*** +--- ### 1. Getting Started @@ -11,23 +11,29 @@ The Mimic Protocol Test Library (`@mimicprotocol/test-ts`) provides tools for si Every test follows this structure: ```tsx -import { runFunction, /* types */ } from '@mimicprotocol/test-ts' +import { runFunction /* types */ } from "@mimicprotocol/test-ts"; -describe('Function', () => { +describe("Function", () => { // 1. Define context, inputs, mocks - const functionDir = './build' - const context = { /* required fields */ } - const inputs = { /* manifest inputs */ } - - it('produces the expected intents', async () => { + const functionDir = "./build"; + const context = { + /* required fields */ + }; + const inputs = { + /* manifest inputs */ + }; + + it("produces the expected intents", async () => { // 2. Execute the function - const result = await runFunction(functionDir, context, { inputs, /* needed mocks */ }) - + const result = await runFunction(functionDir, context, { + inputs /* needed mocks */, + }); + // 3. Check the function outputs - expect(result.success).to.be.true - expect(result.intents).to.have.lengthOf(N) - }) -}) + expect(result.success).to.be.true; + expect(result.intents).to.have.lengthOf(N); + }); +}); ``` #### 1.2. Project setup @@ -42,7 +48,7 @@ npx @mimicprotocol/cli init my-automation-function && cd my-automation-function yarn mimic test ``` -*** +--- ### 2. Function Runner Reference @@ -55,7 +61,7 @@ This section describes the parameters and outputs of the `runFunction` function. The directory where the compiled function `.wasm` is located. ```tsx -const functionDir = './build' +const functionDir = "./build"; ``` **2.1.2. Context** @@ -63,13 +69,13 @@ const functionDir = './build' The context includes the fields needed during function execution. ```tsx -import { Context } from '@mimicprotocol/test-ts' +import { Context } from "@mimicprotocol/test-ts"; const context: Context = { - user: '0xAddress', - settlers: [{ address: '0xAddress', chainId: 10 }], // One per chain used in the function + user: "0xAddress", + settlers: [{ address: "0xAddress", chainId: 10 }], // One per chain used in the function timestamp: Date.now(), // number (in milliseconds) -} +}; ``` **2.1.3. Inputs** @@ -89,10 +95,10 @@ Then, the inputs may be: ```tsx const inputs = { chainId: 10, - token: '0xAddress', - amount: '1.5', // 1.5 tokens - feeAmount: '100000', // 0.1 tokens (6 decimals) -} + token: "0xAddress", + amount: "1.5", // 1.5 tokens + feeAmount: "100000", // 0.1 tokens (6 decimals) +}; ``` **2.1.4. Prices mock** @@ -102,37 +108,37 @@ The responses for the price queries made in the function. For example, if the function does: ```tsx -import { environment } from '@mimicprotocol/lib-ts' +import { environment } from "@mimicprotocol/lib-ts"; -const price = environment.tokenPriceQuery(dai).unwrap() +const price = environment.tokenPriceQuery(dai).unwrap(); // or -const amountInUsd = amountInDai.toUsd().unwrap() +const amountInUsd = amountInDai.toUsd().unwrap(); // or -const wethAmount = usdcAmount.toTokenAmount(weth).unwrap() +const wethAmount = usdcAmount.toTokenAmount(weth).unwrap(); ``` Then, the prices mock may be: ```tsx -import { TokenPriceQueryMock } from '@mimicprotocol/test-ts' -import { fp } from '@mimicprotocol/sdk' +import { TokenPriceQueryMock } from "@mimicprotocol/test-ts"; +import { fp } from "@mimicprotocol/sdk"; const prices: TokenPriceQueryMock[] = [ // Mock for `tokenPriceQuery` and `toUsd` { - request: { token: '0xDAI', chainId: 10 }, + request: { token: "0xDAI", chainId: 10 }, response: [fp(1).toString()], // 1 DAI = 1 USD }, // Mocks for `toTokenAmount` { - request: { token: '0xUSDC', chainId: 10 }, + request: { token: "0xUSDC", chainId: 10 }, response: [fp(0.99).toString()], // 1 USDC = 0.99 USD }, { - request: { token: '0xWETH', chainId: 10 }, + request: { token: "0xWETH", chainId: 10 }, response: [fp(4200).toString()], // 1 WETH = 4200 USD }, -] +]; ``` **2.1.5. Relevant tokens mock** @@ -142,45 +148,47 @@ The responses for the relevant tokens queries made in the function. For example, if the function does: ```tsx -import { ChainId, environment } from '@mimicprotocol/lib-ts' - -const userTokens = environment.relevantTokensQuery( - context.user, - [ChainId.OPTIMISM], - USD.zero(), - [USDC, USDT], - ListType.AllowList -).unwrap() +import { ChainId, environment } from "@mimicprotocol/lib-ts"; + +const userTokens = environment + .relevantTokensQuery( + context.user, + [ChainId.OPTIMISM], + USD.zero(), + [USDC, USDT], + ListType.AllowList, + ) + .unwrap(); ``` Then, the relevant tokens mock may be: ```tsx -import { RelevantTokensQueryMock } from '@mimicprotocol/test-ts' +import { RelevantTokensQueryMock } from "@mimicprotocol/test-ts"; const relevantTokens: RelevantTokensQueryMock[] = [ { request: { - owner: '0xAddress', + owner: "0xAddress", chainIds: [10], - usdMinAmount: '0', + usdMinAmount: "0", tokenFilter: 0, // AllowList = 0, DenyList = 1 tokens: [ - { address: '0xUSDC', chainId: 10 }, - { address: '0xUSDT', chainId: 10 }, + { address: "0xUSDC", chainId: 10 }, + { address: "0xUSDT", chainId: 10 }, ], }, response: [ { timestamp: context.timestamp, balances: [ - { token: { address: '0xUSDC', chainId: 10 }, balance: '10000000' }, // 10 USDC - { token: { address: '0xUSDT', chainId: 10 }, balance: '10000' }, // 0.01 USDT + { token: { address: "0xUSDC", chainId: 10 }, balance: "10000000" }, // 10 USDC + { token: { address: "0xUSDT", chainId: 10 }, balance: "10000" }, // 0.01 USDT ], }, ], }, -] +]; ``` **2.1.6. Contract calls mock** @@ -192,56 +200,56 @@ For example, if the function does: ```tsx // `TokenAmount#fromStringDecimal` calls `decimals` -const tokenAmount = TokenAmount.fromStringDecimal(USDC, '10.5') +const tokenAmount = TokenAmount.fromStringDecimal(USDC, "10.5"); // `TokenAmount#toString` calls `symbol` -log.info(`Transfer amount: ${tokenAmount}`) +log.info(`Transfer amount: ${tokenAmount}`); // `balanceOf` needs a mock -const tokenContract = new ERC20(USDC, ChainId.OPTIMISM) -const balance = tokenContract.balanceOf(recipient).unwrap() +const tokenContract = new ERC20(USDC, ChainId.OPTIMISM); +const balance = tokenContract.balanceOf(recipient).unwrap(); // `mint` does not need a mock -tokenContract.mint(recipient, amount).build().send() +tokenContract.mint(recipient, amount).build().send(); ``` Then, the calls mock may be: ```tsx -import { EvmCallQueryMock } from '@mimicprotocol/test-ts' -import { Interface } from 'ethers' +import { EvmCallQueryMock } from "@mimicprotocol/test-ts"; +import { Interface } from "ethers"; -import ERC20Abi from '../abis/ERC20.json' +import ERC20Abi from "../abis/ERC20.json"; -const ERC20Interface = new Interface(ERC20Abi) +const ERC20Interface = new Interface(ERC20Abi); const calls: EvmCallQueryMock[] = [ { - request: { + request: { chainId: 10, - to: '0xUSDC', - fnSelector: ERC20Interface.getFunction('decimals').selector, + to: "0xUSDC", + fnSelector: ERC20Interface.getFunction("decimals").selector, }, - response: { value: '6', abiType: 'uint8' }, + response: { value: "6", abiType: "uint8" }, }, { - request: { + request: { chainId: 10, - to: '0xUSDC', - fnSelector: ERC20Interface.getFunction('symbol').selector, + to: "0xUSDC", + fnSelector: ERC20Interface.getFunction("symbol").selector, }, - response: { value: 'USDC', abiType: 'string' }, + response: { value: "USDC", abiType: "string" }, }, { request: { chainId: 10, - to: '0xUSDC', - fnSelector: ERC20Interface.getFunction('balanceOf').selector, - params: [{ value: '0xAddress', abiType: 'address' }], + to: "0xUSDC", + fnSelector: ERC20Interface.getFunction("balanceOf").selector, + params: [{ value: "0xAddress", abiType: "address" }], }, - response: { value: '10000000', abiType: 'uint256' }, // 10 USDC + response: { value: "10000000", abiType: "uint256" }, // 10 USDC }, -] +]; ``` **2.1.7. Subgraph queries mock** @@ -251,45 +259,47 @@ The responses for the subgraph queries made in the function. For example, if the function does: ```tsx -import { ChainId, environment } from '@mimicprotocol/lib-ts' - -const response = environment.subgraphQuery( - ChainId.OPTIMISM, - 'QmSubgraphId', - '{ tokens { symbol holders } }' -).unwrap() +import { ChainId, environment } from "@mimicprotocol/lib-ts"; + +const response = environment + .subgraphQuery( + ChainId.OPTIMISM, + "QmSubgraphId", + "{ tokens { symbol holders } }", + ) + .unwrap(); ``` Then, the subgraph queries mock may be: ```tsx -import { SubgraphQueryMock } from '@mimicprotocol/test-ts' +import { SubgraphQueryMock } from "@mimicprotocol/test-ts"; const subgraphQueries: SubgraphQueryMock[] = [ { request: { timestamp: context.timestamp, chainId: 10, - subgraphId: 'QmSubgraphId', - query: '{ tokens { id symbol } }', + subgraphId: "QmSubgraphId", + query: "{ tokens { id symbol } }", }, response: { blockNumber: 1, data: '{ "tokens": [{ "symbol": "WETH", "holders": "1857" }] }', }, }, -] +]; ``` #### 2.2. Output The `runFunction` function returns an object containing the following fields: -* `success` - Boolean. True if the execution ended properly, or false if it had an error. -* `timestamp` - Number. Execution timestamp in milliseconds. -* `fuelUsed` - Number. Amount of fuel used during the execution. -* `intents` - Array of intents produced by the execution. -* `logs` - Array of logs produced by the execution. It may include error logs. +- `success` - Boolean. True if the execution ended properly, or false if it had an error. +- `timestamp` - Number. Execution timestamp in milliseconds. +- `fuelUsed` - Number. Amount of fuel used during the execution. +- `intents` - Array of intents produced by the execution. +- `logs` - Array of logs produced by the execution. It may include error logs. **2.2.1. Intents** @@ -300,46 +310,46 @@ Each intent in `result.intents` contains intent-level metadata (`settler`, `feeP If the function creates a transfer intent: ```typescript -const USDC = ERC20Token.fromString('0xUSDC', ChainId.OPTIMISM) -const recipient = Address.fromString('0xRecipient') -const amount = BigInt.fromStringDecimal('1', USDC.decimals) -const maxFee = TokenAmount.fromStringDecimal(USDC, '0.1') +const USDC = ERC20Token.fromString("0xUSDC", ChainId.OPTIMISM); +const recipient = Address.fromString("0xRecipient"); +const amount = BigInt.fromStringDecimal("1", USDC.decimals); +const maxFee = TokenAmount.fromStringDecimal(USDC, "0.1"); TransferBuilder.forChain(ChainId.OPTIMISM) .addTransferFromTokenAmount(TokenAmount.fromBigInt(USDC, amount), recipient) .build() - .send(maxFee) + .send(maxFee); ``` Then, the test should be: ```typescript -import { Chains, OpType } from '@mimicprotocol/sdk' -import { runFunction, TransferOperation } from '@mimicprotocol/test-ts' - -it('produces the expected intent', async () => { - const result = await runFunction(/* parameters */) - expect(result.success).to.be.true - - expect(result.intents).to.have.lengthOf(1) - const intent = result.intents[0] - const op = intent.operations[0] as TransferOperation - - expect(op.opType).to.be.equal(OpType.Transfer) - expect(intent.settler).to.be.equal(context.settlers[0].address) - expect(op.user).to.be.equal(context.user) - expect(op.chainId).to.be.equal(Chains.Optimism) - - expect(op.transfers).to.have.lengthOf(1) - expect(op.transfers[0].token).to.be.equal('0xUSDC') - expect(op.transfers[0].amount).to.be.equal('1000000') // 1 USDC - expect(op.transfers[0].recipient).to.be.equal('0xRecipient') - - expect(intent.feePayer).to.be.equal(context.user) - expect(intent.maxFees).to.have.lengthOf(1) - expect(intent.maxFees[0].token).to.be.equal('0xUSDC') - expect(intent.maxFees[0].amount).to.be.equal('100000') // 0.1 USDC -}) +import { Chains, OpType } from "@mimicprotocol/sdk"; +import { runFunction, TransferOperation } from "@mimicprotocol/test-ts"; + +it("produces the expected intent", async () => { + const result = await runFunction(/* parameters */); + expect(result.success).to.be.true; + + expect(result.intents).to.have.lengthOf(1); + const intent = result.intents[0]; + const op = intent.operations[0] as TransferOperation; + + expect(op.opType).to.be.equal(OpType.Transfer); + expect(intent.settler).to.be.equal(context.settlers[0].address); + expect(op.user).to.be.equal(context.user); + expect(op.chainId).to.be.equal(Chains.Optimism); + + expect(op.transfers).to.have.lengthOf(1); + expect(op.transfers[0].token).to.be.equal("0xUSDC"); + expect(op.transfers[0].amount).to.be.equal("1000000"); // 1 USDC + expect(op.transfers[0].recipient).to.be.equal("0xRecipient"); + + expect(intent.feePayer).to.be.equal(context.user); + expect(intent.maxFees).to.have.lengthOf(1); + expect(intent.maxFees[0].token).to.be.equal("0xUSDC"); + expect(intent.maxFees[0].amount).to.be.equal("100000"); // 0.1 USDC +}); ``` **Swap** @@ -347,53 +357,56 @@ it('produces the expected intent', async () => { If the function creates a swap intent: ```typescript -const USDC = ERC20Token.fromString('0xUSDC', ChainId.OPTIMISM) -const amountIn = BigInt.fromStringDecimal('1', USDC.decimals) +const USDC = ERC20Token.fromString("0xUSDC", ChainId.OPTIMISM); +const amountIn = BigInt.fromStringDecimal("1", USDC.decimals); -const WETH = ERC20Token.fromString('0xWETH', ChainId.OPTIMISM) -const minAmountOut = BigInt.fromStringDecimal('0.001', WETH.decimals) +const WETH = ERC20Token.fromString("0xWETH", ChainId.OPTIMISM); +const minAmountOut = BigInt.fromStringDecimal("0.001", WETH.decimals); -const recipient = environment.getContext().user +const recipient = environment.getContext().user; SwapBuilder.forChains(ChainId.OPTIMISM, ChainId.OPTIMISM) .addTokenInFromTokenAmount(TokenAmount.fromBigInt(USDC, amountIn)) - .addTokenOutFromTokenAmount(TokenAmount.fromBigInt(WETH, minAmountOut), recipient) + .addTokenOutFromTokenAmount( + TokenAmount.fromBigInt(WETH, minAmountOut), + recipient, + ) .build() - .send() + .send(); ``` Then, the test should be: ```typescript -import { Chains, OpType } from '@mimicprotocol/sdk' -import { runFunction, SwapOperation } from '@mimicprotocol/test-ts' - -it('produces the expected intent', async () => { - const result = await runFunction(/* parameters */) - expect(result.success).to.be.true - - expect(result.intents).to.have.lengthOf(1) - const intent = result.intents[0] - const op = intent.operations[0] as SwapOperation - - expect(op.opType).to.be.equal(OpType.Swap) - expect(intent.settler).to.be.equal(context.settlers[0].address) - expect(op.user).to.be.equal(context.user) - expect(op.sourceChain).to.be.equal(Chains.Optimism) - expect(op.destinationChain).to.be.equal(Chains.Optimism) - - expect(op.tokensIn).to.have.lengthOf(1) - expect(op.tokensIn[0].token).to.be.equal('0xUSDC') - expect(op.tokensIn[0].amount).to.be.equal('1000000') // 1 USDC - - expect(op.tokensOut).to.have.lengthOf(1) - expect(op.tokensOut[0].token).to.be.equal('0xWETH') - expect(op.tokensOut[0].minAmount).to.be.equal('1' + '0'.repeat(15)) // 0.001 WETH - expect(op.tokensOut[0].recipient).to.be.equal(context.user) - - expect(intent.feePayer).to.be.equal(context.user) - expect(intent.maxFees).to.have.lengthOf(0) -}) +import { Chains, OpType } from "@mimicprotocol/sdk"; +import { runFunction, SwapOperation } from "@mimicprotocol/test-ts"; + +it("produces the expected intent", async () => { + const result = await runFunction(/* parameters */); + expect(result.success).to.be.true; + + expect(result.intents).to.have.lengthOf(1); + const intent = result.intents[0]; + const op = intent.operations[0] as SwapOperation; + + expect(op.opType).to.be.equal(OpType.Swap); + expect(intent.settler).to.be.equal(context.settlers[0].address); + expect(op.user).to.be.equal(context.user); + expect(op.sourceChain).to.be.equal(Chains.Optimism); + expect(op.destinationChain).to.be.equal(Chains.Optimism); + + expect(op.tokensIn).to.have.lengthOf(1); + expect(op.tokensIn[0].token).to.be.equal("0xUSDC"); + expect(op.tokensIn[0].amount).to.be.equal("1000000"); // 1 USDC + + expect(op.tokensOut).to.have.lengthOf(1); + expect(op.tokensOut[0].token).to.be.equal("0xWETH"); + expect(op.tokensOut[0].minAmount).to.be.equal("1" + "0".repeat(15)); // 0.001 WETH + expect(op.tokensOut[0].recipient).to.be.equal(context.user); + + expect(intent.feePayer).to.be.equal(context.user); + expect(intent.maxFees).to.have.lengthOf(0); +}); ``` **Call** @@ -401,53 +414,56 @@ it('produces the expected intent', async () => { If the function creates a call intent: ```typescript -const USDC = ERC20Token.fromString('0xUSDC', ChainId.OPTIMISM) -const amount = BigInt.fromStringDecimal('1', USDC.decimals) -const maxFee = TokenAmount.fromStringDecimal(USDC, '0.1') -const data = ERC20Utils.encodeApprove(spender, amount) +const USDC = ERC20Token.fromString("0xUSDC", ChainId.OPTIMISM); +const amount = BigInt.fromStringDecimal("1", USDC.decimals); +const maxFee = TokenAmount.fromStringDecimal(USDC, "0.1"); +const data = ERC20Utils.encodeApprove(spender, amount); EvmCallBuilder.forChain(ChainId.OPTIMISM) .addCall(USDC, data) .addUser(smartAccount) .build() - .send(maxFee) + .send(maxFee); ``` Then, the test should be: ```typescript -import { Chains, OpType } from '@mimicprotocol/sdk' -import { CallOperation, runFunction } from '@mimicprotocol/test-ts' -import { Interface } from 'ethers' - -import ERC20Abi from '../abis/ERC20.json' - -const ERC20Interface = new Interface(ERC20Abi) - -it('produces the expected intent', async () => { - const result = await runFunction(/* parameters */) - expect(result.success).to.be.true - - expect(result.intents).to.have.lengthOf(1) - const intent = result.intents[0] - const op = intent.operations[0] as CallOperation - - expect(op.opType).to.be.equal(OpType.EvmCall) - expect(intent.settler).to.be.equal(context.settlers[0].address) - expect(op.user).to.be.equal('0xSmartAccount') - expect(op.chainId).to.be.equal(Chains.Optimism) - - expect(op.calls).to.have.lengthOf(1) - expect(op.calls[0].target).to.be.equal(USDC) - expect(op.calls[0].value).to.be.equal('0') - const data = ERC20Interface.encodeFunctionData('approve', ['0xSpender', '1000000']) - expect(op.calls[0].data).to.be.equal(data) - - expect(intent.feePayer).to.be.equal(context.user) - expect(intent.maxFees).to.have.lengthOf(1) - expect(intent.maxFees[0].token).to.be.equal('0xUSDC') - expect(intent.maxFees[0].amount).to.be.equal('100000') // 0.1 USDC -}) +import { Chains, OpType } from "@mimicprotocol/sdk"; +import { EvmCallOperation, runFunction } from "@mimicprotocol/test-ts"; +import { Interface } from "ethers"; + +import ERC20Abi from "../abis/ERC20.json"; + +const ERC20Interface = new Interface(ERC20Abi); + +it("produces the expected intent", async () => { + const result = await runFunction(/* parameters */); + expect(result.success).to.be.true; + + expect(result.intents).to.have.lengthOf(1); + const intent = result.intents[0]; + const op = intent.operations[0] as EvmCallOperation; + + expect(op.opType).to.be.equal(OpType.EvmCall); + expect(intent.settler).to.be.equal(context.settlers[0].address); + expect(op.user).to.be.equal("0xSmartAccount"); + expect(op.chainId).to.be.equal(Chains.Optimism); + + expect(op.calls).to.have.lengthOf(1); + expect(op.calls[0].target).to.be.equal(USDC); + expect(op.calls[0].value).to.be.equal("0"); + const data = ERC20Interface.encodeFunctionData("approve", [ + "0xSpender", + "1000000", + ]); + expect(op.calls[0].data).to.be.equal(data); + + expect(intent.feePayer).to.be.equal(context.user); + expect(intent.maxFees).to.have.lengthOf(1); + expect(intent.maxFees[0].token).to.be.equal("0xUSDC"); + expect(intent.maxFees[0].amount).to.be.equal("100000"); // 0.1 USDC +}); ``` **2.2.2. Logs** @@ -455,34 +471,34 @@ it('produces the expected intent', async () => { For example, if the function does: ```typescript -import { log } from '@mimicprotocol/lib-ts' +import { log } from "@mimicprotocol/lib-ts"; -if (inputs.token == USDC) log.info('Function started') -else throw new Error('Token not supported') +if (inputs.token == USDC) log.info("Function started"); +else throw new Error("Token not supported"); ``` Then, the test should be: ```typescript -import { runFunction } from '@mimicprotocol/test-ts' - -describe('when the token is USDC', () => { - it('executes properly', async () => { - const result = await runFunction(/* 0xUSDC */) - expect(result.success).to.be.true - - expect(result.logs).to.have.lengthOf(1) - expect(result.logs[0]).to.include('Function started') - }) -}) - -describe('when the token is not USDC', () => { - it('throws an error', async () => { - const result = await runFunction(/* 0xWETH */) - expect(result.success).to.be.false - - expect(result.logs).to.have.lengthOf(1) - expect(result.logs[0]).to.include('Token not supported') - }) -}) +import { runFunction } from "@mimicprotocol/test-ts"; + +describe("when the token is USDC", () => { + it("executes properly", async () => { + const result = await runFunction(/* 0xUSDC */); + expect(result.success).to.be.true; + + expect(result.logs).to.have.lengthOf(1); + expect(result.logs[0]).to.include("Function started"); + }); +}); + +describe("when the token is not USDC", () => { + it("throws an error", async () => { + const result = await runFunction(/* 0xWETH */); + expect(result.success).to.be.false; + + expect(result.logs).to.have.lengthOf(1); + expect(result.logs[0]).to.include("Token not supported"); + }); +}); ```