From 2dbb2a79a3b316a27b651de4679a30a545f683a6 Mon Sep 17 00:00:00 2001 From: Agustincito Date: Fri, 20 Mar 2026 15:13:14 -0300 Subject: [PATCH 01/17] Chore: Refactor intents with operations --- .../cli/tests/fixtures/functions/function.ts | 17 +- .../fixtures/functions/invalid-function.ts | 7 +- .../tests/001-init-intent/expected.log | 2 +- .../tests/001-init-intent/src/function.ts | 4 +- .../tests/002-evm-call-intent/expected.log | 2 +- .../tests/002-evm-call-intent/src/function.ts | 4 +- .../expected.log | 6 +- .../src/function.ts | 6 +- .../tests/004-three-intents/expected.log | 6 +- .../tests/004-three-intents/src/function.ts | 9 +- .../expected.log | 4 +- .../src/function.ts | 11 +- .../tests/009-solana-intents/expected.log | 6 +- .../tests/009-solana-intents/src/function.ts | 9 +- .../010-intents-with-events/expected.log | 2 +- .../010-intents-with-events/src/function.ts | 4 +- .../tests/013-token-inputs/expected.log | 2 +- .../tests/013-token-inputs/src/function.ts | 4 +- packages/lib-ts/src/environment.ts | 47 +-- packages/lib-ts/src/intents/Call/EvmCall.ts | 166 +++------- packages/lib-ts/src/intents/Call/SvmCall.ts | 124 ++------ packages/lib-ts/src/intents/Intent.ts | 301 ++++++++++++------ packages/lib-ts/src/intents/Operation.ts | 112 +++++++ packages/lib-ts/src/intents/Swap.ts | 118 ++----- packages/lib-ts/src/intents/Transfer.ts | 144 ++------- packages/lib-ts/src/intents/index.ts | 1 + packages/lib-ts/src/storage/storage.ts | 8 +- packages/lib-ts/tests/intents/EvmCall.spec.ts | 113 ++----- packages/lib-ts/tests/intents/Intent.spec.ts | 100 ++++-- packages/lib-ts/tests/intents/SvmCall.spec.ts | 150 +++------ packages/lib-ts/tests/intents/Swap.spec.ts | 208 +++--------- .../lib-ts/tests/intents/Transfer.spec.ts | 169 ++-------- packages/test-ts/src/RunnerMock.ts | 5 +- packages/test-ts/src/types.ts | 36 ++- packages/test-ts/src/utils.ts | 32 +- 35 files changed, 731 insertions(+), 1208 deletions(-) create mode 100644 packages/lib-ts/src/intents/Operation.ts diff --git a/packages/cli/tests/fixtures/functions/function.ts b/packages/cli/tests/fixtures/functions/function.ts index 9095fd2a..44c9bfd4 100644 --- a/packages/cli/tests/fixtures/functions/function.ts +++ b/packages/cli/tests/fixtures/functions/function.ts @@ -1,4 +1,13 @@ -import { Address, BigInt, Bytes, ERC20Token, EvmCallBuilder, NULL_ADDRESS, TokenAmount } from '@mimicprotocol/lib-ts' +import { + Address, + BigInt, + Bytes, + ERC20Token, + EvmCallBuilder, + IntentBuilder, + NULL_ADDRESS, + TokenAmount, +} from '@mimicprotocol/lib-ts' /* eslint-disable @typescript-eslint/no-namespace */ declare namespace input { @@ -16,5 +25,9 @@ export default function main(): void { const maxFeeAmount = BigInt.fromI32(input.firstStaticNumber).times(BigInt.fromI32(input.secondStaticNumber)) const maxFee = TokenAmount.fromBigInt(maxFeeToken, maxFeeAmount) - EvmCallBuilder.forChain(chainId).addCall(target, data).addSettler(settler).addMaxFee(maxFee).build().send() + new IntentBuilder() + .addSettler(settler) + .addMaxFee(maxFee) + .addOperationBuilder(EvmCallBuilder.forChain(chainId).addCall(target, data)) + .send() } diff --git a/packages/cli/tests/fixtures/functions/invalid-function.ts b/packages/cli/tests/fixtures/functions/invalid-function.ts index cb75e020..fe05ac30 100644 --- a/packages/cli/tests/fixtures/functions/invalid-function.ts +++ b/packages/cli/tests/fixtures/functions/invalid-function.ts @@ -4,6 +4,7 @@ import { BlockchainToken, Bytes, EvmCallBuilder, + IntentBuilder, NULL_ADDRESS, TokenAmount, } from '@mimicprotocol/lib-ts' @@ -17,5 +18,9 @@ export default function main(): void { const maxFeeAmount = BigInt.zero().plus(BigInt.fromI32(undeclaredVariable)) const maxFee = TokenAmount.fromBigInt(maxFeeToken, maxFeeAmount) - EvmCallBuilder.forChain(chainId).addCall(target, data).addSettler(settler).addMaxFee(maxFee).build().send() + new IntentBuilder() + .addSettler(settler) + .addMaxFee(maxFee) + .addOperationBuilder(EvmCallBuilder.forChain(chainId).addCall(target, data)) + .send() } diff --git a/packages/integration/tests/001-init-intent/expected.log b/packages/integration/tests/001-init-intent/expected.log index 01f6f4a1..69e3663c 100644 --- a/packages/integration/tests/001-init-intent/expected.log +++ b/packages/integration/tests/001-init-intent/expected.log @@ -1 +1 @@ -_evmCall: {"op":2,"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0x0000000000000000000000000000000000000000","amount":"10"}],"events":[],"chainId":1,"calls":[{"target":"0x0000000000000000000000000000000000000000","data":"0x","value":"5"}]} \ No newline at end of file +_sendIntent: {"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0x0000000000000000000000000000000000000000","amount":"10"}],"operations":[{"opType":2,"chainId":1,"user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","events":[],"calls":[{"target":"0x0000000000000000000000000000000000000000","data":"0x","value":"5"}]}]} diff --git a/packages/integration/tests/001-init-intent/src/function.ts b/packages/integration/tests/001-init-intent/src/function.ts index 4cd3d870..148884ab 100644 --- a/packages/integration/tests/001-init-intent/src/function.ts +++ b/packages/integration/tests/001-init-intent/src/function.ts @@ -4,7 +4,7 @@ import { Bytes, ChainId, ERC20Token, - EvmCallBuilder, + IntentBuilder, NULL_ADDRESS, TokenAmount, } from '@mimicprotocol/lib-ts' @@ -16,5 +16,5 @@ export default function main(): void { const value = BigInt.fromI32(5) const fee = TokenAmount.fromI32(ERC20Token.fromString(NULL_ADDRESS, chainId), 10) - EvmCallBuilder.forChain(chainId).addCall(target, data, value).addMaxFee(fee).build().send() + new IntentBuilder().addMaxFee(fee).addEvmCallOperation(chainId, target, data, value).send() } diff --git a/packages/integration/tests/002-evm-call-intent/expected.log b/packages/integration/tests/002-evm-call-intent/expected.log index 1e04836a..5bd2c522 100644 --- a/packages/integration/tests/002-evm-call-intent/expected.log +++ b/packages/integration/tests/002-evm-call-intent/expected.log @@ -1 +1 @@ -_evmCall: {"op":2,"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","user":"0xa000000000000000000000000000000000000001","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0xa000000000000000000000000000000000000003","amount":"10"}],"events":[],"chainId":1,"calls":[{"target":"0xa000000000000000000000000000000000000002","data":"0xabcdef0123456789","value":"1000000000000000000"}]} \ No newline at end of file +_sendIntent: {"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0xa000000000000000000000000000000000000003","amount":"10"}],"operations":[{"opType":2,"chainId":1,"user":"0xa000000000000000000000000000000000000001","events":[],"calls":[{"target":"0xa000000000000000000000000000000000000002","data":"0xabcdef0123456789","value":"1000000000000000000"}]}]} diff --git a/packages/integration/tests/002-evm-call-intent/src/function.ts b/packages/integration/tests/002-evm-call-intent/src/function.ts index 4803367f..c42fde7f 100644 --- a/packages/integration/tests/002-evm-call-intent/src/function.ts +++ b/packages/integration/tests/002-evm-call-intent/src/function.ts @@ -9,7 +9,5 @@ export default function main(): void { EvmCallBuilder.forChain(inputs.chainId) .addCall(inputs.target, inputs.data, inputs.value) .addUser(inputs.user) - .addMaxFee(maxFee) - .build() - .send() + .send(maxFee) } diff --git a/packages/integration/tests/003-multiple-evm-call-intents/expected.log b/packages/integration/tests/003-multiple-evm-call-intents/expected.log index 0afb7dd2..8efb65c1 100644 --- a/packages/integration/tests/003-multiple-evm-call-intents/expected.log +++ b/packages/integration/tests/003-multiple-evm-call-intents/expected.log @@ -1,3 +1,3 @@ -_evmCall: {"op":2,"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0x0000000000000000000000000000000000000000","amount":"10"}],"events":[],"chainId":1,"calls":[{"target":"0x0000000000000000000000000000000000000000","data":"0x","value":"0"}]} -_evmCall: {"op":2,"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0x0000000000000000000000000000000000000000","amount":"9"}],"events":[],"chainId":1,"calls":[{"target":"0x0000000000000000000000000000000000000000","data":"0x","value":"0"}]} -_evmCall: {"op":2,"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0x0000000000000000000000000000000000000000","amount":"8"}],"events":[],"chainId":1,"calls":[{"target":"0x0000000000000000000000000000000000000000","data":"0x","value":"0"}]} \ No newline at end of file +_sendIntent: {"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0x0000000000000000000000000000000000000000","amount":"10"}],"operations":[{"opType":2,"chainId":1,"user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","events":[],"calls":[{"target":"0x0000000000000000000000000000000000000000","data":"0x","value":"0"}]}]} +_sendIntent: {"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0x0000000000000000000000000000000000000000","amount":"9"}],"operations":[{"opType":2,"chainId":1,"user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","events":[],"calls":[{"target":"0x0000000000000000000000000000000000000000","data":"0x","value":"0"}]}]} +_sendIntent: {"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0x0000000000000000000000000000000000000000","amount":"8"}],"operations":[{"opType":2,"chainId":1,"user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","events":[],"calls":[{"target":"0x0000000000000000000000000000000000000000","data":"0x","value":"0"}]}]} diff --git a/packages/integration/tests/003-multiple-evm-call-intents/src/function.ts b/packages/integration/tests/003-multiple-evm-call-intents/src/function.ts index a10e6cbc..09297974 100644 --- a/packages/integration/tests/003-multiple-evm-call-intents/src/function.ts +++ b/packages/integration/tests/003-multiple-evm-call-intents/src/function.ts @@ -8,11 +8,11 @@ export default function main(): void { const data = Bytes.empty() const fee1 = TokenAmount.fromI32(token, 10) - EvmCallBuilder.forChain(inputs.chainId).addCall(target, data).addMaxFee(fee1).build().send() + EvmCallBuilder.forChain(inputs.chainId).addCall(target, data).send(fee1) const fee2 = fee1.minus(TokenAmount.fromI32(token, 1)) - EvmCallBuilder.forChain(inputs.chainId).addCall(target, data).addMaxFee(fee2).build().send() + EvmCallBuilder.forChain(inputs.chainId).addCall(target, data).send(fee2) const fee3 = fee1.minus(TokenAmount.fromI32(token, 2)) - EvmCallBuilder.forChain(inputs.chainId).addCall(target, data).addMaxFee(fee3).build().send() + EvmCallBuilder.forChain(inputs.chainId).addCall(target, data).send(fee3) } diff --git a/packages/integration/tests/004-three-intents/expected.log b/packages/integration/tests/004-three-intents/expected.log index 0c78abf8..293967ca 100644 --- a/packages/integration/tests/004-three-intents/expected.log +++ b/packages/integration/tests/004-three-intents/expected.log @@ -1,3 +1,3 @@ -_evmCall: {"op":2,"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","amount":"10000000"}],"events":[],"chainId":1,"calls":[{"target":"0x0000000000000000000000000000000000000001","data":"0x","value":"0"},{"target":"0x0000000000000000000000000000000000000001","data":"0x7b000000","value":"0"}]} -_swap: {"op":0,"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[],"events":[],"sourceChain":1,"tokensIn":[{"token":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","amount":"100000000"}],"tokensOut":[{"token":"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599","minAmount":"9500000000","recipient":"0x0000000000000000000000000000000000000001"}],"destinationChain":1} -_transfer: {"op":1,"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","amount":"10000000"}],"events":[],"chainId":1,"transfers":[{"token":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","amount":"100000000","recipient":"0x0000000000000000000000000000000000000001"}]} \ No newline at end of file +_sendIntent: {"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","amount":"10000000"}],"operations":[{"opType":2,"chainId":1,"user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","events":[],"calls":[{"target":"0x0000000000000000000000000000000000000001","data":"0x","value":"0"},{"target":"0x0000000000000000000000000000000000000001","data":"0x7b000000","value":"0"}]}]} +_sendIntent: {"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[],"operations":[{"opType":0,"chainId":1,"user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","events":[],"sourceChain":1,"tokensIn":[{"token":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","amount":"100000000"}],"tokensOut":[{"token":"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599","minAmount":"9500000000","recipient":"0x0000000000000000000000000000000000000001"}],"destinationChain":1}]} +_sendIntent: {"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","amount":"10000000"}],"operations":[{"opType":1,"chainId":1,"user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","events":[],"transfers":[{"token":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","amount":"100000000","recipient":"0x0000000000000000000000000000000000000001"}]}]} diff --git a/packages/integration/tests/004-three-intents/src/function.ts b/packages/integration/tests/004-three-intents/src/function.ts index 6bd5205d..f875f92c 100644 --- a/packages/integration/tests/004-three-intents/src/function.ts +++ b/packages/integration/tests/004-three-intents/src/function.ts @@ -22,7 +22,7 @@ export default function main(): void { const bytes = Bytes.fromI32(123) const callFee = TokenAmount.fromI32(USDC, 10) - EvmCallBuilder.forChain(chainId).addCall(target).addCall(target, bytes).addMaxFee(callFee).build().send() + EvmCallBuilder.forChain(chainId).addCall(target).addCall(target, bytes).send(callFee) // Normal swap const minAmountOut = BigInt.fromI32(inputs.amount).times(BigInt.fromI32(inputs.slippage)).div(BigInt.fromI32(100)) @@ -32,16 +32,11 @@ export default function main(): void { SwapBuilder.forChains(chainId, chainId) .addTokenInFromTokenAmount(tokenIn) .addTokenOutFromTokenAmount(tokenOut, target) - .build() .send() // Normal Transfer const tokenAmount = TokenAmount.fromI32(USDC, inputs.amount) const transferFee = TokenAmount.fromI32(USDC, 10) - TransferBuilder.forChain(chainId) - .addTransferFromTokenAmount(tokenAmount, target) - .addMaxFee(transferFee) - .build() - .send() + TransferBuilder.forChain(chainId).addTransferFromTokenAmount(tokenAmount, target).send(transferFee) } diff --git a/packages/integration/tests/008-write-contract-calls-tests/expected.log b/packages/integration/tests/008-write-contract-calls-tests/expected.log index ba04b370..6ea4a15f 100644 --- a/packages/integration/tests/008-write-contract-calls-tests/expected.log +++ b/packages/integration/tests/008-write-contract-calls-tests/expected.log @@ -1,3 +1,3 @@ _encode: [{"abiType":"address","value":"0x7f5c764cbc14f9669b88837ca1490cca17c31607"},{"abiType":"uint256","value":"100000000"},{"abiType":"address","value":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0"}] -_evmCall: {"op":2,"settler":"0xdcf1d9d12a0488dfb70a8696f44d6d3bc303963d","user":"0x047be3bb46f9416732fe39a05134f20235c19334","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0x94b008aa00579c1307b0ef2c499ad98a8ce58e58","amount":"100000"}],"events":[],"chainId":10,"calls":[{"target":"0x794a61358d6845594f94dc1db02a252b5b4814ad","data":"0x69328dec00","value":"0"}]} -_evmCall: {"op":2,"settler":"0xdcf1d9d12a0488dfb70a8696f44d6d3bc303963d","user":"0x047be3bb46f9416732fe39a05134f20235c19334","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0x94b008aa00579c1307b0ef2c499ad98a8ce58e58","amount":"100000"}],"events":[],"chainId":10,"calls":[{"target":"0x4200000000000000000000000000000000000006","data":"0xd0e30db0","value":"10"}]} \ No newline at end of file +_sendIntent: {"settler":"0xdcf1d9d12a0488dfb70a8696f44d6d3bc303963d","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0x94b008aa00579c1307b0ef2c499ad98a8ce58e58","amount":"100000"}],"operations":[{"opType":2,"chainId":10,"user":"0x047be3bb46f9416732fe39a05134f20235c19334","events":[],"calls":[{"target":"0x794a61358d6845594f94dc1db02a252b5b4814ad","data":"0x69328dec00","value":"0"}]}]} +_sendIntent: {"settler":"0xdcf1d9d12a0488dfb70a8696f44d6d3bc303963d","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0x94b008aa00579c1307b0ef2c499ad98a8ce58e58","amount":"100000"}],"operations":[{"opType":2,"chainId":10,"user":"0x047be3bb46f9416732fe39a05134f20235c19334","events":[],"calls":[{"target":"0x4200000000000000000000000000000000000006","data":"0xd0e30db0","value":"10"}]}]} diff --git a/packages/integration/tests/008-write-contract-calls-tests/src/function.ts b/packages/integration/tests/008-write-contract-calls-tests/src/function.ts index 56b7bc6b..188a1797 100644 --- a/packages/integration/tests/008-write-contract-calls-tests/src/function.ts +++ b/packages/integration/tests/008-write-contract-calls-tests/src/function.ts @@ -22,12 +22,7 @@ export default function main(): void { const aaveContract = new AAVE(Address.fromString('0x794a61358d6845594f94dc1db02a252b5b4814ad'), chainId) - aaveContract - .withdraw(USDCe.address, userTokens[0].amount, context.user) - .addMaxFee(feeUsdt) - .addUser(inputs.smartAccount) - .build() - .send() - - weth.deposit(BigInt.fromI32(10)).addMaxFee(feeUsdt).addUser(inputs.smartAccount).build().send() + aaveContract.withdraw(USDCe.address, userTokens[0].amount, context.user).addUser(inputs.smartAccount).send(feeUsdt) + + weth.deposit(BigInt.fromI32(10)).addUser(inputs.smartAccount).send(feeUsdt) } diff --git a/packages/integration/tests/009-solana-intents/expected.log b/packages/integration/tests/009-solana-intents/expected.log index 28b4a9d3..e70ee4eb 100644 --- a/packages/integration/tests/009-solana-intents/expected.log +++ b/packages/integration/tests/009-solana-intents/expected.log @@ -1,3 +1,3 @@ -_transfer: {"op":1,"settler":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","user":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"So11111111111111111111111111111111111111112","amount":"1"}],"events":[],"chainId":507424,"transfers":[{"token":"So11111111111111111111111111111111111111112","amount":"4000000000000","recipient":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K"}]} -_swap: {"op":0,"settler":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","user":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K","deadline":"1438223473","nonce":"0xabcd","maxFees":[],"events":[],"sourceChain":507424,"tokensIn":[{"token":"So11111111111111111111111111111111111111112","amount":"1000000000000"}],"tokensOut":[{"token":"So11111111111111111111111111111111111111112","minAmount":"2000","recipient":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K"}],"destinationChain":507424} -_svmCall: {"op":3,"settler":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","user":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"So11111111111111111111111111111111111111112","amount":"1"}],"events":[],"chainId":507424,"instructions":[{"programId":"TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb","accountsMeta":[{"pubkey":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K","isWritable":false,"isSigner":true},{"pubkey":"So11111111111111111111111111111111111111112","isWritable":true,"isSigner":false}],"data":"0xabcd"}]} \ No newline at end of file +_sendIntent: {"settler":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"So11111111111111111111111111111111111111112","amount":"1"}],"operations":[{"opType":1,"chainId":507424,"user":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K","events":[],"transfers":[{"token":"So11111111111111111111111111111111111111112","amount":"4000000000000","recipient":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K"}]}]} +_sendIntent: {"settler":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[],"operations":[{"opType":0,"chainId":507424,"user":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K","events":[],"sourceChain":507424,"tokensIn":[{"token":"So11111111111111111111111111111111111111112","amount":"1000000000000"}],"tokensOut":[{"token":"So11111111111111111111111111111111111111112","minAmount":"2000","recipient":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K"}],"destinationChain":507424}]} +_sendIntent: {"settler":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"So11111111111111111111111111111111111111112","amount":"1"}],"operations":[{"opType":3,"chainId":507424,"user":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K","events":[],"instructions":[{"programId":"TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb","accountsMeta":[{"pubkey":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K","isWritable":false,"isSigner":true},{"pubkey":"So11111111111111111111111111111111111111112","isWritable":true,"isSigner":false}],"data":"0xabcd"}]}]} diff --git a/packages/integration/tests/009-solana-intents/src/function.ts b/packages/integration/tests/009-solana-intents/src/function.ts index 1232d491..7806db86 100644 --- a/packages/integration/tests/009-solana-intents/src/function.ts +++ b/packages/integration/tests/009-solana-intents/src/function.ts @@ -23,9 +23,7 @@ export default function main(): void { TransferBuilder.forChain(ChainId.SOLANA_MAINNET) .addTransferFromI32(splToken, 4000, solanaUser) .addUser(solanaUser) - .addMaxFee(new TokenAmount(splToken, BigInt.fromI32(1))) - .build() - .send() + .send(new TokenAmount(splToken, BigInt.fromI32(1))) // Swap @@ -33,7 +31,6 @@ export default function main(): void { .addTokenInFromStringDecimal(splToken, '1000') .addTokenOutFromTokenAmount(new TokenAmount(splToken, BigInt.fromI32(2000)), solanaUser) .addUser(solanaUser) - .build() .send() // Call @@ -50,7 +47,5 @@ export default function main(): void { SvmCallBuilder.forChain() .addInstruction(ix) .addUser(solanaUser) - .addMaxFee(new TokenAmount(splToken, BigInt.fromI32(1))) - .build() - .send() + .send(new TokenAmount(splToken, BigInt.fromI32(1))) } diff --git a/packages/integration/tests/010-intents-with-events/expected.log b/packages/integration/tests/010-intents-with-events/expected.log index f01411dd..842ffd59 100644 --- a/packages/integration/tests/010-intents-with-events/expected.log +++ b/packages/integration/tests/010-intents-with-events/expected.log @@ -1 +1 @@ -_evmCall: {"op":2,"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0x0000000000000000000000000000000000000348","amount":"1000000000000000000"}],"events":[{"topic":"0xabcd","data":"0x64617461"},{"topic":"0xabcd","data":"0x64617461"}],"chainId":1,"calls":[{"target":"0x0000000000000000000000000000000000000001","data":"0x","value":"0"}]} \ No newline at end of file +_sendIntent: {"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0x0000000000000000000000000000000000000348","amount":"1000000000000000000"}],"operations":[{"opType":2,"chainId":1,"user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","events":[{"topic":"0xabcd","data":"0x64617461"},{"topic":"0xabcd","data":"0x64617461"}],"calls":[{"target":"0x0000000000000000000000000000000000000001","data":"0x","value":"0"}]}]} diff --git a/packages/integration/tests/010-intents-with-events/src/function.ts b/packages/integration/tests/010-intents-with-events/src/function.ts index 4eca78c7..9bf43621 100644 --- a/packages/integration/tests/010-intents-with-events/src/function.ts +++ b/packages/integration/tests/010-intents-with-events/src/function.ts @@ -2,11 +2,9 @@ import { Address, Bytes, DenominationToken, environment, evm, EvmCallBuilder, To export default function main(): void { EvmCallBuilder.forChain(1) - .addMaxFee(TokenAmount.fromStringDecimal(DenominationToken.USD(), '1')) .addCall(Address.fromString('0x0000000000000000000000000000000000000001'), Bytes.empty()) .addUser(environment.getContext().user) .addEvent(Bytes.fromHexString(evm.keccak('event1')), Bytes.fromUTF8('data')) .addEvent(Bytes.fromHexString(evm.keccak('event2')), Bytes.fromUTF8('data')) - .build() - .send() + .send(TokenAmount.fromStringDecimal(DenominationToken.USD(), '1')) } diff --git a/packages/integration/tests/013-token-inputs/expected.log b/packages/integration/tests/013-token-inputs/expected.log index bffa69a4..546d9dd9 100644 --- a/packages/integration/tests/013-token-inputs/expected.log +++ b/packages/integration/tests/013-token-inputs/expected.log @@ -1 +1 @@ -_transfer: {"op":1,"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0x0b2c639c533813f4aa9d7837caf62653d097ff85","amount":"1000000"}],"events":[],"chainId":10,"transfers":[{"token":"0x0b2c639c533813f4aa9d7837caf62653d097ff85","amount":"100500000","recipient":"0xdd4c30aa2f3284e462df0b45c99a7e6e9ea9d186"}]} \ No newline at end of file +_sendIntent: {"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0x0b2c639c533813f4aa9d7837caf62653d097ff85","amount":"1000000"}],"operations":[{"opType":1,"chainId":10,"user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","events":[],"transfers":[{"token":"0x0b2c639c533813f4aa9d7837caf62653d097ff85","amount":"100500000","recipient":"0xdd4c30aa2f3284e462df0b45c99a7e6e9ea9d186"}]}]} diff --git a/packages/integration/tests/013-token-inputs/src/function.ts b/packages/integration/tests/013-token-inputs/src/function.ts index c9567bfa..fb933eea 100644 --- a/packages/integration/tests/013-token-inputs/src/function.ts +++ b/packages/integration/tests/013-token-inputs/src/function.ts @@ -9,7 +9,5 @@ export default function main(): void { TransferBuilder.forChain(Optimism.CHAIN_ID) .addTransferFromTokenAmount(amount, recipient) - .addMaxFee(TokenAmount.fromStringDecimal(Optimism.USDC, '1')) - .build() - .send() + .send(TokenAmount.fromStringDecimal(Optimism.USDC, '1')) } diff --git a/packages/lib-ts/src/environment.ts b/packages/lib-ts/src/environment.ts index d95a9240..12b93bd4 100644 --- a/packages/lib-ts/src/environment.ts +++ b/packages/lib-ts/src/environment.ts @@ -3,7 +3,7 @@ import { JSON } from 'json-as/assembly' import { Context, SerializableContext } from './context' import { evm } from './evm' import { Consensus, ListType, MIMIC_HELPER_ADDRESS } from './helpers' -import { EvmCall, SvmCall, Swap, Transfer } from './intents' +import { Intent } from './intents' import { EvmCallQuery, EvmCallQueryResponse, @@ -23,17 +23,8 @@ import { BlockchainToken, Token, TokenAmount, USD } from './tokens' import { Address, BigInt, Bytes, ChainId, EvmDecodeParam, EvmEncodeParam, Result } from './types' export namespace environment { - @external('environment', '_evmCall') - declare function _evmCall(params: string): void - - @external('environment', '_svmCall') - declare function _svmCall(params: string): void - - @external('environment', '_swap') - declare function _swap(params: string): void - - @external('environment', '_transfer') - declare function _transfer(params: string): void + @external('environment', '_sendIntent') + declare function _sendIntent(params: string): void @external('environment', '_tokenPriceQuery') declare function _tokenPriceQuery(params: string): string @@ -54,35 +45,11 @@ export namespace environment { declare function _getContext(): string /** - * Generates a EVM Call intent containing contract calls on the blockchain. - * @param call - The EvmCall intent to generate - */ - export function evmCall(call: EvmCall): void { - _evmCall(JSON.stringify(call)) - } - - /** - * Generates a SVM Call intent containing contract calls on the blockchain. - * @param call - The SvmCall intent to generate - */ - export function svmCall(call: SvmCall): void { - _svmCall(JSON.stringify(call)) - } - - /** - * Generates a Swap intent for token exchange operations. - * @param swap - The Swap intent to generate - */ - export function swap(swap: Swap): void { - _swap(JSON.stringify(swap)) - } - - /** - * Generates a Transfer intent for sending tokens to recipients. - * @param transfer - The Transfer intent to generate + * Sends an intent containing one or more operations to the execution environment. + * @param intent - The intent to send */ - export function transfer(transfer: Transfer): void { - _transfer(JSON.stringify(transfer)) + export function sendIntent(intent: Intent): void { + _sendIntent(JSON.stringify(intent)) } /** diff --git a/packages/lib-ts/src/intents/Call/EvmCall.ts b/packages/lib-ts/src/intents/Call/EvmCall.ts index 2e2260b7..2f156eef 100644 --- a/packages/lib-ts/src/intents/Call/EvmCall.ts +++ b/packages/lib-ts/src/intents/Call/EvmCall.ts @@ -1,18 +1,18 @@ import { environment } from '../../environment' import { TokenAmount } from '../../tokens' import { Address, BigInt, Bytes, ChainId } from '../../types' -import { Intent, IntentBuilder, IntentEvent, MaxFee, OperationType } from '../Intent' +import { IntentBuilder } from '../Intent' +import { Operation, OperationBuilder, OperationEvent, OperationType } from '../Operation' /** - * Builder for creating EVM Call intents with contract call operations. - * Allows chaining multiple contract calls and configuring fees and settlement parameters. + * Builder for creating EVM call operations. */ -export class EvmCallBuilder extends IntentBuilder { +export class EvmCallBuilder extends OperationBuilder { protected chainId: ChainId protected calls: EvmCallData[] = [] /** - * Creates a EvmCallBuilder for the specified EVM blockchain network. + * Creates an EvmCallBuilder for the specified EVM blockchain network. * @param chainId - The blockchain network identifier * @returns A new EvmCallBuilder instance */ @@ -30,10 +30,10 @@ export class EvmCallBuilder extends IntentBuilder { } /** - * Adds a contract call to the intent. + * Adds a contract call to the operation. * @param target - The contract address to call - * @param data - The call data (optional, defaults to empty bytes) - * @param value - The native token value to send (optional, defaults to zero) + * @param data - The call data + * @param value - The native token value to send * @returns This EvmCallBuilder instance for method chaining */ addCall(target: Address, data: Bytes = Bytes.empty(), value: BigInt = BigInt.zero()): EvmCallBuilder { @@ -42,7 +42,7 @@ export class EvmCallBuilder extends IntentBuilder { } /** - * Adds multiple contract calls to the intent. + * Adds multiple contract calls to the operation. * @param calls - The contract calls to add * @returns This EvmCallBuilder instance for method chaining */ @@ -84,34 +84,7 @@ export class EvmCallBuilder extends IntentBuilder { } /** - * Sets the settler address for this intent. - * @param settler - The settler address as an Address instance - * @returns This EvmCallBuilder instance for method chaining - */ - addSettler(settler: Address): EvmCallBuilder { - return changetype(super.addSettler(settler)) - } - - /** - * Sets the settler address from a string. - * @param settler - The settler address as a hex string - * @returns This EvmCallBuilder instance for method chaining - */ - addSettlerAsString(settler: string): EvmCallBuilder { - return changetype(super.addSettlerAsString(settler)) - } - - /** - * Sets the deadline for this intent. - * @param deadline - The deadline as a timestamp - * @returns This EvmCallBuilder instance for method chaining - */ - addDeadline(deadline: BigInt): EvmCallBuilder { - return changetype(super.addDeadline(deadline)) - } - - /** - * Sets the user address for this intent. + * Sets the user address for this operation. * @param user - The user address * @returns This EvmCallBuilder instance for method chaining */ @@ -129,27 +102,7 @@ export class EvmCallBuilder extends IntentBuilder { } /** - * Sets the nonce for this intent. - * @param nonce - A unique identifier to prevent replay attacks - * @returns This EvmCallBuilder instance for method chaining - */ - addNonce(nonce: string): EvmCallBuilder { - return changetype(super.addNonce(nonce)) - } - - /** - * Adds a max fee for this intent. - * @param fee - The max fee token amount (must be on same chain) - * @returns This EvmCallBuilder instance for method chaining - */ - addMaxFee(fee: TokenAmount): EvmCallBuilder { - if (!fee.token.hasChain(this.chainId)) throw new Error('Fee token must be on the same chain') - this.maxFees.push(fee) - return this - } - - /** - * Sets an event for the intent. + * Sets an event for the operation. * @param topic - The topic to be indexed in the event * @param data - The event data * @returns This EvmCallBuilder instance for method chaining @@ -159,34 +112,34 @@ export class EvmCallBuilder extends IntentBuilder { } /** - * Sets multiple events for the intent. + * Sets multiple events for the operation. * @param events - The list of events to be added * @returns This EvmCallBuilder instance for method chaining */ - addEvents(events: IntentEvent[]): EvmCallBuilder { + addEvents(events: OperationEvent[]): EvmCallBuilder { return changetype(super.addEvents(events)) } /** - * Builds and returns the final EvmCall intent. + * Builds and returns the final EvmCall operation. * @returns A new EvmCall instance with all configured parameters */ build(): EvmCall { - return new EvmCall( - this.chainId, - this.calls, - this.maxFees, - this.settler, - this.user, - this.deadline, - this.nonce, - this.events - ) + return new EvmCall(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 data for a single contract call within a Call intent. + * Represents data for a single contract call within an EVM call operation. * Contains the target address, call data, and value to send. */ @json @@ -198,8 +151,8 @@ export class EvmCallData { /** * Creates a new EvmCallData instance. * @param target - The contract address to call - * @param data - The call data (optional, defaults to empty bytes) - * @param value - The native token value to send (optional, defaults to zero) + * @param data - The call data + * @param value - The native token value to send */ constructor(target: Address, data: Bytes = Bytes.empty(), value: BigInt = BigInt.zero()) { this.target = target.toString() @@ -209,75 +162,38 @@ export class EvmCallData { } /** - * Represents a Call intent containing one or more contract calls to be executed. + * Represents an EVM call operation containing one or more contract calls to be executed. */ @json -export class EvmCall extends Intent { - public chainId: ChainId +export class EvmCall extends Operation { public calls: EvmCallData[] /** - * Creates a EvmCall intent with a single contract call. - * @param chainId - The blockchain network identifier - * @param target - The contract address to call - * @param data - The call data - * @param maxFee - The max fee to pay for the call intent - * @param value - The native token value to send (optional, defaults to zero) - * @param settler - The settler address (optional) - * @param user - The user address (optional) - * @param deadline - The deadline timestamp (optional) - * @param nonce - The nonce for replay protection (optional) - * @returns A new Call instance - */ - static create( - chainId: ChainId, - target: Address, - data: Bytes, - maxFee: TokenAmount, - value: BigInt = BigInt.zero(), - settler: Address | null = null, - user: Address | null = null, - deadline: BigInt | null = null, - nonce: string | null = null, - events: IntentEvent[] | null = null - ): EvmCall { - const callData = new EvmCallData(target, data, value) - return new EvmCall(chainId, [callData], [maxFee], settler, user, deadline, nonce, events) - } - - /** - * Creates a new EvmCall intent. + * Creates a new EvmCall operation. * @param chainId - The blockchain network identifier * @param calls - Array of contract calls to execute - * @param maxFees - The list of max fees to pay for the call intent - * @param settler - The settler address (optional) - * @param user - The user address (optional) - * @param deadline - The deadline timestamp (optional) - * @param nonce - The nonce for replay protection (optional) + * @param user - The user address + * @param events - The operation events to emit */ constructor( chainId: ChainId, calls: EvmCallData[], - maxFees: TokenAmount[], - settler: Address | null = null, user: Address | null = null, - deadline: BigInt | null = null, - nonce: string | null = null, - events: IntentEvent[] | null = null + events: OperationEvent[] | null = null ) { - const fees: MaxFee[] = maxFees.map((fee: TokenAmount) => MaxFee.fromTokenAmount(fee)) - super(OperationType.EvmCall, chainId, fees, settler, user, deadline, nonce, events) + super(OperationType.EvmCall, chainId, user, events) if (calls.length === 0) throw new Error('Call list cannot be empty') - if (maxFees.length == 0) throw new Error('At least a max fee must be specified') - this.calls = calls - this.chainId = chainId } /** - * Sends this EvmCall intent to the execution environment. + * Sends this EvmCall 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(): void { - environment.evmCall(this) + 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/SvmCall.ts b/packages/lib-ts/src/intents/Call/SvmCall.ts index e415200a..a1a70dfa 100644 --- a/packages/lib-ts/src/intents/Call/SvmCall.ts +++ b/packages/lib-ts/src/intents/Call/SvmCall.ts @@ -1,14 +1,15 @@ import { environment } from '../../environment' import { TokenAmount } from '../../tokens' -import { Address, BigInt, Bytes, ChainId } from '../../types' +import { Address, Bytes, ChainId } from '../../types' import { SvmAccountMeta } from '../../types/svm/SvmAccountMeta' -import { Intent, IntentBuilder, IntentEvent, MaxFee, OperationType } from '../Intent' +import { IntentBuilder } from '../Intent' +import { Operation, OperationBuilder, OperationEvent, OperationType } from '../Operation' /** * Builder for creating SVM Call intents with program call operations. * Allows chaining multiple calls and configuring fees and settlement parameters. */ -export class SvmCallBuilder extends IntentBuilder { +export class SvmCallBuilder extends OperationBuilder { protected chainId: ChainId protected instructions: SvmInstruction[] = [] @@ -75,33 +76,6 @@ export class SvmCallBuilder extends IntentBuilder { return this.instructions.slice(0) } - /** - * Sets the settler address for this intent. - * @param settler - The settler address as an Address instance - * @returns This SvmCallBuilder instance for method chaining - */ - addSettler(settler: Address): SvmCallBuilder { - return changetype(super.addSettler(settler)) - } - - /** - * Sets the settler address from a string. - * @param settler - The settler address as a hex string - * @returns This SvmCallBuilder instance for method chaining - */ - addSettlerAsString(settler: string): SvmCallBuilder { - return changetype(super.addSettlerAsString(settler)) - } - - /** - * Sets the deadline for this intent. - * @param deadline - The deadline as a timestamp - * @returns This SvmCallBuilder instance for method chaining - */ - addDeadline(deadline: BigInt): SvmCallBuilder { - return changetype(super.addDeadline(deadline)) - } - /** * Sets the user address for this intent. * @param user - The user address @@ -120,26 +94,6 @@ export class SvmCallBuilder extends IntentBuilder { return changetype(super.addUserAsString(user)) } - /** - * Sets the nonce for this intent. - * @param nonce - A unique identifier to prevent replay attacks - * @returns This SvmCallBuilder instance for method chaining - */ - addNonce(nonce: string): SvmCallBuilder { - return changetype(super.addNonce(nonce)) - } - - /** - * Adds a max fee for this intent. - * @param fee - The max fee token amount (must be on same chain) - * @returns This SvmCallBuilder instance for method chaining - */ - addMaxFee(fee: TokenAmount): SvmCallBuilder { - if (!fee.token.hasChain(this.chainId)) throw new Error('Fee token must be on the same chain') - this.maxFees.push(fee) - return this - } - /** * Sets an event for the intent. * @param topic - The topic to be indexed in the event @@ -155,7 +109,7 @@ export class SvmCallBuilder extends IntentBuilder { * @param events - The list of events to be added * @returns This SvmCallBuilder instance for method chaining */ - addEvents(events: IntentEvent[]): SvmCallBuilder { + addEvents(events: OperationEvent[]): SvmCallBuilder { return changetype(super.addEvents(events)) } @@ -164,7 +118,16 @@ export class SvmCallBuilder extends IntentBuilder { * @returns A new SvmCall instance with all configured parameters */ build(): SvmCall { - return new SvmCall(this.instructions, this.maxFees, this.settler, this.user, this.deadline, this.nonce, this.events) + return new SvmCall(this.instructions, 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) } } @@ -193,7 +156,7 @@ export class SvmInstructionBuilder { } instruction(): SvmInstruction { - return SvmInstruction.create(this.programId, this.accountsMeta, this.data) + return new SvmInstruction(this.programId.toBase58String(), this.accountsMeta, this.data.toHexString()) } } @@ -204,44 +167,15 @@ export class SvmInstruction { public accountsMeta: SvmAccountMeta[], public data: string ) {} - - static create(programId: Address, accountsMeta: SvmAccountMeta[], data: Bytes): SvmInstruction { - return new SvmInstruction(programId.toBase58String(), accountsMeta, data.toHexString()) - } } /** * Represents a SVM Call intent containing one or more program calls to be executed. */ @json -export class SvmCall extends Intent { - public chainId: ChainId +export class SvmCall extends Operation { public instructions: SvmInstruction[] - /** - * Creates a SvmCall intent with a single program call. - * @param maxFee - The max fee to pay for the call intent - * @param settler - The settler address (optional) - * @param user - The user address (optional) - * @param deadline - The deadline timestamp (optional) - * @param nonce - The nonce for replay protection (optional) - * @returns A new Call instance - */ - static create( - programId: Address, - accountsMeta: SvmAccountMeta[], - data: Bytes, - maxFee: TokenAmount, - settler: Address | null = null, - user: Address | null = null, - deadline: BigInt | null = null, - nonce: string | null = null, - events: IntentEvent[] | null = null - ): SvmCall { - const instruction = SvmInstruction.create(programId, accountsMeta, data) - return new SvmCall([instruction], [maxFee], settler, user, deadline, nonce, events) - } - /** * Creates a new SvmCall intent. * @param instructions - Array of instructions to execute @@ -251,28 +185,20 @@ export class SvmCall extends Intent { * @param deadline - The deadline timestamp (optional) * @param nonce - The nonce for replay protection (optional) */ - constructor( - instructions: SvmInstruction[], - maxFees: TokenAmount[], - settler: Address | null = null, - user: Address | null = null, - deadline: BigInt | null = null, - nonce: string | null = null, - events: IntentEvent[] | null = null - ) { - const fees: MaxFee[] = maxFees.map((fee: TokenAmount) => MaxFee.fromTokenAmount(fee)) - super(OperationType.SvmCall, ChainId.SOLANA_MAINNET, fees, settler, user, deadline, nonce, events) + constructor(instructions: SvmInstruction[], user: Address | null = null, events: OperationEvent[] | null = null) { + super(OperationType.SvmCall, ChainId.SOLANA_MAINNET, user, events) if (instructions.length === 0) throw new Error('Call list cannot be empty') - if (maxFees.length == 0) throw new Error('At least a max fee must be specified') - this.instructions = instructions - this.chainId = ChainId.SOLANA_MAINNET } /** * Sends this SvmCall intent to the execution environment. + * @param maxFee - The max fee to pay for the intent + * @param feePayer - The fee payer for the intent (optional) */ - public send(): void { - environment.svmCall(this) + 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/Intent.ts b/packages/lib-ts/src/intents/Intent.ts index 6433edff..1cea25fe 100644 --- a/packages/lib-ts/src/intents/Intent.ts +++ b/packages/lib-ts/src/intents/Intent.ts @@ -1,28 +1,159 @@ import { environment } from '../environment' import { evm } from '../evm' import { NULL_ADDRESS } from '../helpers' -import { Token, TokenAmount } from '../tokens' +import { BlockchainToken, Token, TokenAmount } from '../tokens' import { Address, BigInt, Bytes, ChainId } from '../types' +import { SvmAccountMeta } from '../types/svm/SvmAccountMeta' -export enum OperationType { - Swap, - Transfer, - EvmCall, - SvmCall, -} +import { EvmCall, EvmCallData } from './Call/EvmCall' +import { SvmCall, SvmInstruction } from './Call/SvmCall' +import { Operation, OperationBuilder, OperationEvent } from './Operation' +import { Swap, SwapTokenIn, SwapTokenOut } from './Swap' +import { Transfer, TransferData } from './Transfer' const DEFAULT_DEADLINE = 5 * 60 // 5 minutes in seconds /** - * Base builder for creating intents. + * Builder for creating intents with one or more operations. */ -export abstract class IntentBuilder { - protected user: Address | null = null +export class IntentBuilder { protected settler: Address | null = null + protected feePayer: Address | null = null protected deadline: BigInt | null = null protected nonce: string | null = null protected maxFees: TokenAmount[] = [] - protected events: IntentEvent[] = [] + protected operations: Operation[] = [] + + /** + * Adds an operation to this intent. + * @param operation - The operation to add + * @returns This IntentBuilder instance for method chaining + */ + addOperation(operation: Operation): IntentBuilder { + this.operations.push(operation) + return this + } + + /** + * Adds multiple operations to this intent. + * @param operations - The operations to add + * @returns This IntentBuilder instance for method chaining + */ + addOperations(operations: Operation[]): IntentBuilder { + for (let i = 0; i < operations.length; i++) this.addOperation(operations[i]) + return this + } + + /** + * Adds a built operation builder to this intent. + * @param operationBuilder - The operation builder to build and add + * @returns This IntentBuilder instance for method chaining + */ + addOperationBuilder(operationBuilder: OperationBuilder): IntentBuilder { + return this.addOperation(operationBuilder.build()) + } + + /** + * Adds multiple built operation builders to this intent. + * @param operationBuilders - The operation builders to build and add + * @returns This IntentBuilder instance for method chaining + */ + addOperationsBuilders(operationBuilders: OperationBuilder[]): IntentBuilder { + for (let i = 0; i < operationBuilders.length; i++) this.addOperationBuilder(operationBuilders[i]) + return this + } + + /** + * Adds a single EVM call operation to this intent from raw parameters. + * @param chainId - The blockchain network identifier + * @param target - The contract address to call + * @param data - The encoded call data + * @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 + */ + addEvmCallOperation( + chainId: ChainId, + target: Address, + data: Bytes = Bytes.empty(), + value: BigInt = BigInt.zero(), + user: Address | null = null, + events: OperationEvent[] | null = null + ): IntentBuilder { + return this.addOperation(new EvmCall(chainId, [new EvmCallData(target, data, value)], user, events)) + } + + /** + * Adds a single swap operation to this intent from raw parameters. + * @param sourceChain - The source blockchain network identifier + * @param tokenIn - The token to swap from + * @param amountIn - The amount to swap from + * @param tokenOut - The token to receive + * @param minAmountOut - The minimum amount to receive + * @param recipient - The recipient of the output token + * @param destinationChain - The destination blockchain network identifier + * @param user - The user that should execute the operation + * @param events - The operation events to emit + * @returns This IntentBuilder instance for method chaining + */ + addSwapOperation( + sourceChain: ChainId, + tokenIn: Token, + amountIn: BigInt, + tokenOut: Token, + minAmountOut: BigInt, + recipient: Address, + destinationChain: ChainId = sourceChain, + user: Address | null = null, + events: OperationEvent[] | null = null + ): IntentBuilder { + const swapIn = SwapTokenIn.fromBigInt(tokenIn, amountIn) + const swapOut = SwapTokenOut.fromBigInt(tokenOut, minAmountOut, recipient) + return this.addOperation(new Swap(sourceChain, [swapIn], [swapOut], destinationChain, user, events)) + } + + /** + * Adds a single transfer operation to this intent from raw parameters. + * @param token - The token to transfer + * @param amount - The amount to transfer + * @param recipient - The recipient of the transfer + * @param user - The user that should execute the operation + * @param events - The operation events to emit + * @returns This IntentBuilder instance for method chaining + */ + addTransferOperation( + token: Token, + amount: BigInt, + recipient: Address, + user: Address | null = null, + events: OperationEvent[] | null = null + ): IntentBuilder { + if (!(token instanceof BlockchainToken)) throw new Error('Transfer token must be a blockchain token') + const transferAmount = TokenAmount.fromBigInt(token, amount) + const transferData = TransferData.fromTokenAmount(transferAmount, recipient) + const chainId = changetype(token).chainId + return this.addOperation(new Transfer(chainId, [transferData], user, events)) + } + + /** + * Adds a single SVM call operation to this intent from raw parameters. + * @param programId - The program address to call + * @param accountsMeta - The accounts metadata for the instruction + * @param data - The encoded instruction data + * @param user - The user that should execute the operation + * @param events - The operation events to emit + * @returns This IntentBuilder instance for method chaining + */ + addSvmCallOperation( + programId: Address, + accountsMeta: SvmAccountMeta[], + data: Bytes, + user: Address | null = null, + events: OperationEvent[] | null = null + ): IntentBuilder { + return this.addOperation(new SvmCall([SvmInstruction.create(programId, accountsMeta, data)], user, events)) + } /** * Sets the settler address for this intent. @@ -44,32 +175,32 @@ export abstract class IntentBuilder { } /** - * Sets the deadline for this intent. - * @param deadline - The deadline as a timestamp + * Sets the fee payer address for this intent. + * @param feePayer - The fee payer address as an Address instance * @returns This IntentBuilder instance for method chaining */ - addDeadline(deadline: BigInt): IntentBuilder { - this.deadline = deadline + addFeePayer(feePayer: Address): IntentBuilder { + this.feePayer = feePayer return this } /** - * Sets the user address for this intent. - * @param user - The user address + * Sets the fee payer address from a string. + * @param feePayer - The fee payer address as a hex string * @returns This IntentBuilder instance for method chaining */ - addUser(user: Address): IntentBuilder { - this.user = user - return this + addFeePayerAsString(feePayer: string): IntentBuilder { + return this.addFeePayer(Address.fromString(feePayer)) } /** - * Sets the user address from a string. - * @param user - The user address as a hex string + * Sets the deadline for this intent. + * @param deadline - The deadline as a timestamp * @returns This IntentBuilder instance for method chaining */ - addUserAsString(user: string): IntentBuilder { - return this.addUser(Address.fromString(user)) + addDeadline(deadline: BigInt): IntentBuilder { + this.deadline = deadline + return this } /** @@ -83,41 +214,29 @@ export abstract class IntentBuilder { } /** - * Sets an event for the intent. - * @param topic - The topic to be indexed in the event - * @param data - The event data + * Adds a max fee for this intent. + * @param fee - The max fee token amount * @returns This IntentBuilder instance for method chaining */ - addEvent(topic: Bytes, data: Bytes): IntentBuilder { - const event = new IntentEvent(topic, data) - this.events.push(event) + addMaxFee(fee: TokenAmount): IntentBuilder { + this.maxFees.push(fee) return this } /** - * Sets multiple events for the intent. - * @param events - The list of events to be added - * @returns This IntentBuilder instance for method chaining + * Builds and returns the final intent. + * @returns A new intent */ - addEvents(events: IntentEvent[]): IntentBuilder { - for (let i = 0; i < events.length; i++) { - this.events.push(events[i]) - } - return this + build(): Intent { + return new Intent(this.maxFees, this.settler, this.feePayer, this.deadline, this.nonce, this.operations) } /** - * Adds a max fee for this intent. - * @param fee - The max fee token amount (must be on same chain) - * @returns This IntentBuilder instance for method chaining + * Builds and sends the final intent. */ - abstract addMaxFee(fee: TokenAmount): IntentBuilder - - /** - * Builds and returns the final intent. - * @returns A new intent - */ - abstract build(): Intent + send(): void { + this.build().send() + } } /** @@ -179,74 +298,58 @@ export class MaxFee { } } +let INTENT_INDEX: u32 = 0 + /** - * Represents an intent event. - * Specifies the topic and data for the event. The topic is an indexed parameter for the EVM events. + * Represents a sendable intent containing one or more operations plus intent-level metadata. */ @json -export class IntentEvent { - topic: string - data: string - - /** - * Creates a new Intent Event instance. - * @param topic - the topic that is going to be index in the event - * @param data - The event data - */ - constructor(topic: Bytes, data: Bytes) { - this.topic = topic.toHexString() - this.data = data.toHexString() - } -} - -let INTENT_INDEX: u32 = 0 -@json -export abstract class Intent { - public op: OperationType - public settler: string - public user: string - public deadline: string - public nonce: string - public maxFees: MaxFee[] - public events: IntentEvent[] +export class Intent { + public settler: string = '' + public feePayer: string = '' + public deadline: string = '' + public nonce: string = '' + public maxFees: MaxFee[] = [] + public operations: Operation[] /** * Creates a new intent. - * @param op - The type of intent to be created - * @param chainId - The chain ID for fetch the settler - * @param maxFees - The list of max fees to pay for the intent (optional) - * @param settler - The settler address (optional) - * @param user - The user address (optional) - * @param deadline - The deadline timestamp (optional) - * @param nonce - The nonce for replay protection (optional) - */ - protected constructor( - op: OperationType, - chainId: ChainId, - maxFees: MaxFee[] | null, - settler: Address | null, - user: Address | null, - deadline: BigInt | null, - nonce: string | null, - events: IntentEvent[] | null + * @param maxFees - The list of max fees to pay for the intent + * @param settler - The settler address + * @param feePayer - The fee payer address + * @param deadline - The deadline timestamp + * @param nonce - The nonce for replay protection + * @param operations - The operations to execute + */ + constructor( + maxFees: TokenAmount[] | null = null, + settler: Address | null = null, + feePayer: Address | null = null, + deadline: BigInt | null = null, + nonce: string | null = null, + operations: Operation[] | null = null ) { const context = environment.getContext() - this.op = op - this.maxFees = maxFees || [] - this.settler = settler ? settler.toString() : context.findSettler(chainId).toString() + this.operations = operations || [] + if (this.operations.length === 0) throw new Error('Operation list cannot be empty') + + this.maxFees = maxFees ? maxFees.map((fee: TokenAmount) => MaxFee.fromTokenAmount(fee)) : [] + const defaultChainId = this.operations[0].chainId + this.settler = settler ? settler.toString() : context.findSettler(defaultChainId).toString() + this.feePayer = feePayer ? feePayer.toString() : context.user.toString() this.deadline = deadline ? deadline.toString() : (context.timestamp / 1000 + DEFAULT_DEADLINE).toString() - this.user = user ? user.toString() : context.user.toString() - this.events = events || [] this.nonce = nonce ? nonce : evm.keccak(`${context.triggerSig}${context.timestamp}${context.triggerPayload.data}${++INTENT_INDEX}`) - if (!this.user || this.user == NULL_ADDRESS) throw new Error('A user must be specified') if (!this.settler || this.settler == NULL_ADDRESS) throw new Error('A settler contract must be specified') + if (!this.feePayer || this.feePayer == NULL_ADDRESS) throw new Error('A fee payer must be specified') } /** * Sends this intent to the execution environment. */ - abstract send(): void + send(): void { + environment.sendIntent(this) + } } diff --git a/packages/lib-ts/src/intents/Operation.ts b/packages/lib-ts/src/intents/Operation.ts new file mode 100644 index 00000000..d6baa9ec --- /dev/null +++ b/packages/lib-ts/src/intents/Operation.ts @@ -0,0 +1,112 @@ +import { environment } from '../environment' +import { NULL_ADDRESS } from '../helpers' +import { Address, Bytes, ChainId } from '../types' + +export enum OperationType { + Swap, + Transfer, + EvmCall, + SvmCall, +} + +/** + * Represents an operation event. + * Specifies the topic and data for the event. The topic is an indexed parameter for the EVM events. + */ +@json +export class OperationEvent { + topic: string + data: string + + /** + * Creates a new Operation Event instance. + * @param topic - the topic that is going to be index in the event + * @param data - The event data + */ + constructor(topic: Bytes, data: Bytes) { + this.topic = topic.toHexString() + this.data = data.toHexString() + } +} + +/** + * Base builder for creating operations. + */ +export abstract class OperationBuilder { + protected user: Address | null = null + protected events: OperationEvent[] = [] + + abstract build(): Operation + + /** + * Sets the user address for this intent. + * @param user - The user address + * @returns This OperationBuilder instance for method chaining + */ + addUser(user: Address): OperationBuilder { + this.user = user + return this + } + + /** + * Sets the user address from a string. + * @param user - The user address as a hex string + * @returns This OperationBuilder instance for method chaining + */ + addUserAsString(user: string): OperationBuilder { + return this.addUser(Address.fromString(user)) + } + + /** + * Sets an event for the intent. + * @param topic - The topic to be indexed in the event + * @param data - The event data + * @returns This OperationBuilder instance for method chaining + */ + addEvent(topic: Bytes, data: Bytes): OperationBuilder { + const event = new OperationEvent(topic, data) + this.events.push(event) + return this + } + + /** + * Sets multiple events for the intent. + * @param events - The list of events to be added + * @returns This OperationBuilder instance for method chaining + */ + addEvents(events: OperationEvent[]): OperationBuilder { + for (let i = 0; i < events.length; i++) { + this.events.push(events[i]) + } + return this + } +} + +@json +export abstract class Operation { + public opType: OperationType + public chainId: ChainId + public user: string + public events: OperationEvent[] + + /** + * Creates a new operation. + * @param opType - The type of operation to be created + * @param chainId - The source chain id for the operation + * @param user - The user address (optional) + * @param events - The list of events for the operation (optional) + */ + protected constructor( + opType: OperationType, + chainId: ChainId, + user: Address | null, + events: OperationEvent[] | null + ) { + const context = environment.getContext() + this.opType = opType + this.chainId = chainId + this.user = user ? user.toString() : context.user.toString() + this.events = events || [] + if (!this.user || this.user == NULL_ADDRESS) throw new Error('A user must be specified') + } +} diff --git a/packages/lib-ts/src/intents/Swap.ts b/packages/lib-ts/src/intents/Swap.ts index 3f01997f..9a43adc3 100644 --- a/packages/lib-ts/src/intents/Swap.ts +++ b/packages/lib-ts/src/intents/Swap.ts @@ -2,13 +2,14 @@ import { environment } from '../environment' import { Token, TokenAmount } from '../tokens' import { Address, BigInt, Bytes, ChainId } from '../types' -import { Intent, IntentBuilder, IntentEvent, MaxFee, OperationType } from './Intent' +import { IntentBuilder } from './Intent' +import { Operation, OperationBuilder, OperationEvent, OperationType } from './Operation' /** * Builder for creating Swap intents with token exchange operations. * Supports both single-chain and cross-chain swaps with multiple input and output tokens. */ -export class SwapBuilder extends IntentBuilder { +export class SwapBuilder extends OperationBuilder { protected sourceChain: ChainId protected destinationChain: ChainId protected tokensIn: SwapTokenIn[] = [] @@ -204,33 +205,6 @@ export class SwapBuilder extends IntentBuilder { return this.addTokenOut(SwapTokenOut.fromStringDecimal(token, amount, recipient)) } - /** - * Sets the settler address for this intent. - * @param settler - The settler address as an Address instance - * @returns This SwapBuilder instance for method chaining - */ - addSettler(settler: Address): SwapBuilder { - return changetype(super.addSettler(settler)) - } - - /** - * Sets the settler address from a string. - * @param settler - The settler address as a hex string - * @returns This SwapBuilder instance for method chaining - */ - addSettlerAsString(settler: string): SwapBuilder { - return changetype(super.addSettlerAsString(settler)) - } - - /** - * Sets the deadline for this intent. - * @param deadline - The deadline as a timestamp - * @returns This SwapBuilder instance for method chaining - */ - addDeadline(deadline: BigInt): SwapBuilder { - return changetype(super.addDeadline(deadline)) - } - /** * Sets the user address for this intent. * @param user - The user address @@ -249,26 +223,6 @@ export class SwapBuilder extends IntentBuilder { return changetype(super.addUserAsString(user)) } - /** - * Sets the nonce for this intent. - * @param nonce - A unique identifier to prevent replay attacks - * @returns This SwapBuilder instance for method chaining - */ - addNonce(nonce: string): SwapBuilder { - return changetype(super.addNonce(nonce)) - } - - /** - * Adds a max fee for this intent. - * @param fee - The max fee token amount (must be on same chain) - * @returns This SwapBuilder instance for method chaining - */ - addMaxFee(fee: TokenAmount): SwapBuilder { - if (!fee.token.hasChain(this.destinationChain)) throw new Error('Fee token must be on the destination chain') - this.maxFees.push(fee) - return this - } - /** * Sets an event for the intent. * @param topic - The topic to be indexed in the event @@ -284,7 +238,7 @@ export class SwapBuilder extends IntentBuilder { * @param events - The list of events to be added * @returns This SwapBuilder instance for method chaining */ - addEvents(events: IntentEvent[]): SwapBuilder { + addEvents(events: OperationEvent[]): SwapBuilder { return changetype(super.addEvents(events)) } @@ -295,18 +249,16 @@ export class SwapBuilder extends IntentBuilder { build(): Swap { if (this.tokensIn.length === 0 || this.tokensOut.length === 0) throw new Error('Tokens in and out are required') - return new Swap( - this.sourceChain, - this.tokensIn, - this.tokensOut, - this.destinationChain, - this.settler, - this.user, - this.deadline, - this.nonce, - this.maxFees, - this.events - ) + return new Swap(this.sourceChain, this.tokensIn, this.tokensOut, this.destinationChain, 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 (optional for swaps) + * @param feePayer - The fee payer for the intent (optional) + */ + send(maxFee: TokenAmount | null = null, feePayer: Address | null = null): void { + this.build().send(maxFee, feePayer) } } @@ -439,7 +391,7 @@ export class SwapTokenOut { * Represents a Swap intent for exchanging tokens between blockchain networks. */ @json -export class Swap extends Intent { +export class Swap extends Operation { /** * Creates a simple single-chain swap intent. * @param chainId - The blockchain network identifier @@ -448,29 +400,11 @@ export class Swap extends Intent { * @param tokenOut - The output token * @param minAmountOut - The minimum amount to receive * @param settler - The settler address (optional) - * @param user - The user address (optional) + * @param recipient - The recipient address (optional) * @param deadline - The deadline timestamp (optional) * @param nonce - The nonce for replay protection (optional) * @returns A new Swap instance */ - static create( - chainId: ChainId, - tokenIn: Token, - amountIn: BigInt, - tokenOut: Token, - minAmountOut: BigInt, - settler: Address | null = null, - user: Address | null = null, - deadline: BigInt | null = null, - nonce: string | null = null, - events: IntentEvent[] | null = null - ): Swap { - const context = environment.getContext() - const recipient = user || context.user - const swapIn = SwapTokenIn.fromBigInt(tokenIn, amountIn) - const swapOut = SwapTokenOut.fromBigInt(tokenOut, minAmountOut, recipient) - return new Swap(chainId, [swapIn], [swapOut], chainId, settler, user, deadline, nonce, [], events) - } /** * Creates a new Swap intent. @@ -489,23 +423,23 @@ export class Swap extends Intent { public tokensIn: SwapTokenIn[], public tokensOut: SwapTokenOut[], public destinationChain: ChainId, - settler: Address | null = null, user: Address | null = null, - deadline: BigInt | null = null, - nonce: string | null = null, - maxFees: TokenAmount[] | null = null, - events: IntentEvent[] | null = null + events: OperationEvent[] | null = null ) { - const fees: MaxFee[] = maxFees ? maxFees.map((fee: TokenAmount) => MaxFee.fromTokenAmount(fee)) : [] - super(OperationType.Swap, sourceChain, fees, settler, user, deadline, nonce, events) + super(OperationType.Swap, sourceChain, user, events) if (tokensIn.length === 0) throw new Error('TokenIn list cannot be empty') if (tokensOut.length === 0) throw new Error('TokenOut list cannot be empty') } /** * Sends this Swap intent to the execution environment. - */ - send(): void { - environment.swap(this) + * @param maxFee - The max fee to pay for the intent (optional for swaps) + * @param feePayer - The fee payer for the intent (optional) + */ + send(maxFee: TokenAmount | null = null, feePayer: Address | null = null): void { + const intentBuilder = new IntentBuilder().addOperation(this) + if (maxFee) intentBuilder.addMaxFee(maxFee) + if (feePayer) intentBuilder.addFeePayer(feePayer) + environment.sendIntent(intentBuilder.build()) } } diff --git a/packages/lib-ts/src/intents/Transfer.ts b/packages/lib-ts/src/intents/Transfer.ts index 22929758..caa5c9b3 100644 --- a/packages/lib-ts/src/intents/Transfer.ts +++ b/packages/lib-ts/src/intents/Transfer.ts @@ -1,14 +1,15 @@ import { environment } from '../environment' -import { ERC20Token, Token, TokenAmount } from '../tokens' +import { Token, TokenAmount } from '../tokens' import { Address, BigInt, Bytes, ChainId } from '../types' -import { Intent, IntentBuilder, IntentEvent, MaxFee, OperationType } from './Intent' +import { IntentBuilder } from './Intent' +import { Operation, OperationBuilder, OperationEvent, OperationType } from './Operation' /** * Builder for creating Transfer intents with token transfer operations. * Supports multiple transfers within a single transaction on the same chain. */ -export class TransferBuilder extends IntentBuilder { +export class TransferBuilder extends OperationBuilder { protected chainId: ChainId protected transfers: TransferData[] = [] @@ -135,33 +136,6 @@ export class TransferBuilder extends IntentBuilder { return this } - /** - * Sets the settler address for this intent. - * @param settler - The settler address as an Address instance - * @returns This TransferBuilder instance for method chaining - */ - addSettler(settler: Address): TransferBuilder { - return changetype(super.addSettler(settler)) - } - - /** - * Sets the settler address from a string. - * @param settler - The settler address as a hex or base58 string accordingly - * @returns This TransferBuilder instance for method chaining - */ - addSettlerAsString(settler: string): TransferBuilder { - return changetype(super.addSettlerAsString(settler)) - } - - /** - * Sets the deadline for this intent. - * @param deadline - The deadline as a timestamp - * @returns This TransferBuilder instance for method chaining - */ - addDeadline(deadline: BigInt): TransferBuilder { - return changetype(super.addDeadline(deadline)) - } - /** * Sets the user address for this intent. * @param user - The user address @@ -180,26 +154,6 @@ export class TransferBuilder extends IntentBuilder { return changetype(super.addUserAsString(user)) } - /** - * Sets the nonce for this intent. - * @param nonce - A unique identifier to prevent replay attacks - * @returns This TransferBuilder instance for method chaining - */ - addNonce(nonce: string): TransferBuilder { - return changetype(super.addNonce(nonce)) - } - - /** - * Adds a max fee for this intent. - * @param fee - The max fee token amount (must be on same chain) - * @returns This TransferBuilder instance for method chaining - */ - addMaxFee(fee: TokenAmount): TransferBuilder { - if (!fee.token.hasChain(this.chainId)) throw new Error('Fee token must be on the same chain') - this.maxFees.push(fee) - return this - } - /** * Sets an event for the intent. * @param topic - The topic to be indexed in the event @@ -215,7 +169,7 @@ export class TransferBuilder extends IntentBuilder { * @param events - The list of events to be added * @returns This TransferBuilder instance for method chaining */ - addEvents(events: IntentEvent[]): TransferBuilder { + addEvents(events: OperationEvent[]): TransferBuilder { return changetype(super.addEvents(events)) } @@ -224,16 +178,16 @@ export class TransferBuilder extends IntentBuilder { * @returns A new Transfer instance with all configured parameters */ build(): Transfer { - return new Transfer( - this.chainId, - this.transfers, - this.maxFees, - this.settler, - this.user, - this.deadline, - this.nonce, - this.events - ) + return new Transfer(this.chainId, this.transfers, 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) } } @@ -307,48 +261,9 @@ export class TransferData { * Represents a Transfer intent for sending tokens to recipients on a blockchain network. */ @json -export class Transfer extends Intent { - public chainId: ChainId +export class Transfer extends Operation { public transfers: TransferData[] - /** - * Creates a simple single-token transfer intent. - * @param token - The Token to transfer - * @param amount - The amount to transfer - * @param recipient - The address to receive the tokens - * @param maxFee - The max fee to pay for the transfer intent - * @param settler - The settler address (optional) - * @param user - The user address (optional) - * @param deadline - The deadline timestamp (optional) - * @param nonce - The nonce for replay protection (optional) - * @returns A new Transfer instance - */ - static create( - token: Token, - amount: BigInt, - recipient: Address, - maxFee: BigInt, - settler: Address | null = null, - user: Address | null = null, - deadline: BigInt | null = null, - nonce: string | null = null, - events: IntentEvent[] | null = null - ): Transfer { - const transferAmount = TokenAmount.fromBigInt(token, amount) - const transferData = TransferData.fromTokenAmount(transferAmount, recipient) - const maxFees = [TokenAmount.fromBigInt(token, maxFee)] - return new Transfer( - token instanceof ERC20Token ? (token as ERC20Token).chainId : ChainId.SOLANA_MAINNET, - [transferData], - maxFees, - settler, - user, - deadline, - nonce, - events - ) - } - /** * Creates a new Transfer intent. * @param chainId - The blockchain network identifier @@ -362,33 +277,22 @@ export class Transfer extends Intent { constructor( chainId: ChainId, transfers: TransferData[], - maxFees: TokenAmount[], - settler: Address | null = null, user: Address | null = null, - deadline: BigInt | null = null, - nonce: string | null = null, - events: IntentEvent[] | null = null + events: OperationEvent[] | null = null ) { - const fees: MaxFee[] = maxFees.map((fee: TokenAmount) => MaxFee.fromTokenAmount(fee)) - super(OperationType.Transfer, chainId, fees, settler, user, deadline, nonce, events) + super(OperationType.Transfer, chainId, user, events) if (transfers.length === 0) throw new Error('Transfer list cannot be empty') - if (maxFees.length == 0) throw new Error('At least a max fee must be specified') - this.transfers = transfers - this.chainId = chainId } /** * Sends this Transfer intent to the execution environment. + * @param maxFee - The max fee to pay for the intent + * @param feePayer - The fee payer for the intent (optional) */ - send(): void { - environment.transfer(this) - } - - /** - * Whether the chainId is Solana or not - */ - isSVM(): bool { - return this.chainId === ChainId.SOLANA_MAINNET + 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/index.ts b/packages/lib-ts/src/intents/index.ts index 1658efab..6921ba27 100644 --- a/packages/lib-ts/src/intents/index.ts +++ b/packages/lib-ts/src/intents/index.ts @@ -1,4 +1,5 @@ export * from './Call' export * from './Intent' +export * from './Operation' export * from './Swap' export * from './Transfer' diff --git a/packages/lib-ts/src/storage/storage.ts b/packages/lib-ts/src/storage/storage.ts index 4c6e82d5..8c5b9a60 100644 --- a/packages/lib-ts/src/storage/storage.ts +++ b/packages/lib-ts/src/storage/storage.ts @@ -2,7 +2,6 @@ import { environment } from '../environment' import { evm } from '../evm' import { MIMIC_HELPER_ADDRESS } from '../helpers' import { EvmCall, EvmCallBuilder } from '../intents' -import { TokenAmount } from '../tokens' import { Address, Bytes, ChainId, EvmDecodeParam, EvmEncodeParam, Result } from '../types' const ADDRESS = Address.fromHexString(MIMIC_HELPER_ADDRESS) @@ -11,7 +10,6 @@ const DEFAULT_CHAIN_ID = ChainId.OPTIMISM export namespace storage { export function createSetDataCall( smartAccount: Address, - maxFee: TokenAmount, key: string, data: Bytes, chainId: ChainId = DEFAULT_CHAIN_ID @@ -20,11 +18,7 @@ export namespace storage { '0x1c1bbd37' + evm.encode([EvmEncodeParam.fromValue('string', Bytes.fromUTF8(key)), EvmEncodeParam.fromValue('bytes', data)]) ) - return EvmCallBuilder.forChain(chainId) - .addUser(smartAccount) - .addMaxFee(maxFee) - .addCall(ADDRESS, encodedData) - .build() + return EvmCallBuilder.forChain(chainId).addUser(smartAccount).addCall(ADDRESS, encodedData).build() } export function getData( diff --git a/packages/lib-ts/tests/intents/EvmCall.spec.ts b/packages/lib-ts/tests/intents/EvmCall.spec.ts index 43f644f7..18d53916 100644 --- a/packages/lib-ts/tests/intents/EvmCall.spec.ts +++ b/packages/lib-ts/tests/intents/EvmCall.spec.ts @@ -1,144 +1,82 @@ import { JSON } from 'json-as' -import { EvmCall, EvmCallBuilder, EvmCallData, IntentEvent, OperationType } from '../../src/intents' -import { TokenAmount } from '../../src/tokens' +import { EvmCall, EvmCallBuilder, EvmCallData, OperationEvent, OperationType } from '../../src/intents' import { Address, BigInt, Bytes } from '../../src/types' -import { randomBytes, randomERC20Token, randomEvmAddress, randomSettler, setContext } from '../helpers' +import { randomBytes, randomEvmAddress, randomSettler, setContext } from '../helpers' -describe('Call', () => { - it('creates a simple Call with default values and stringifies it', () => { +describe('EvmCall', () => { + it('creates a simple operation with default values and stringifies it', () => { const chainId = 1 const user = randomEvmAddress() const target = randomEvmAddress() const calldata = randomBytes(32) - const fee = TokenAmount.fromI32(randomERC20Token(chainId), 100) const settler = randomSettler(chainId) setContext(1, 1, user.toString(), [settler], 'trigger-123') - const call = EvmCall.create(chainId, target, calldata, fee) - expect(call.op).toBe(OperationType.EvmCall) + const call = new EvmCall(chainId, [new EvmCallData(target, calldata)]) + expect(call.opType).toBe(OperationType.EvmCall) expect(call.user).toBe(user.toString()) - expect(call.settler).toBe(settler.address.toString()) expect(call.chainId).toBe(chainId) - expect(call.deadline).toBe('300') - expect(call.nonce).toBe('0x') - + expect(call.events.length).toBe(0) expect(call.calls.length).toBe(1) expect(call.calls[0].target).toBe(target.toString()) expect(call.calls[0].data).toBe(calldata.toHexString()) expect(call.calls[0].value).toBe('0') - expect(call.maxFees.length).toBe(1) - expect(call.maxFees[0].token).toBe(fee.token.address.toString()) - expect(call.maxFees[0].amount).toBe(fee.amount.toString()) - - expect(call.events.length).toBe(0) - expect(JSON.stringify(call)).toBe( - `{"op":2,"settler":"${settler.address}","user":"${user}","deadline":"300","nonce":"0x","maxFees":[{"token":"${fee.token.address.toString()}","amount":"${fee.amount.toString()}"}],"events":[],"chainId":${chainId},"calls":[{"target":"${target}","data":"${calldata.toHexString()}","value":"0"}]}` + `{"opType":2,"chainId":${chainId},"user":"${user}","events":[],"calls":[{"target":"${target}","data":"${calldata.toHexString()}","value":"0"}]}` ) }) - it('creates a simple Call with valid parameters and stringifies it', () => { + it('creates an operation with explicit user and events', () => { const chainId = 1 const user = randomEvmAddress() const settler = randomSettler(chainId) - const deadline = BigInt.fromI32(9999999) const target = randomEvmAddress() const calldata = randomBytes(32) const value = BigInt.fromI32(10) - const fee = TokenAmount.fromI32(randomERC20Token(chainId), 100) setContext(1, 1, user.toString(), [settler], 'trigger-123') - const call = EvmCall.create( - chainId, - target, - calldata, - fee, - value, - Address.fromString(settler.address), - user, - deadline, - null, - [new IntentEvent(Bytes.fromUTF8('topic'), Bytes.fromUTF8('data'))] - ) - expect(call.op).toBe(OperationType.EvmCall) + const call = new EvmCall(chainId, [new EvmCallData(target, calldata, value)], user, [ + new OperationEvent(Bytes.fromUTF8('topic'), Bytes.fromUTF8('data')), + ]) + expect(call.opType).toBe(OperationType.EvmCall) expect(call.user).toBe(user.toString()) - expect(call.settler).toBe(settler.address.toString()) expect(call.chainId).toBe(chainId) - expect(call.deadline).toBe(deadline.toString()) - expect(call.nonce).toBe('0x') - - expect(call.calls.length).toBe(1) - expect(call.calls[0].target).toBe(target.toString()) - expect(call.calls[0].data).toBe(calldata.toHexString()) expect(call.calls[0].value).toBe(value.toString()) - - expect(call.maxFees.length).toBe(1) - expect(call.maxFees[0].token).toBe(fee.token.address.toString()) - expect(call.maxFees[0].amount).toBe(fee.amount.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( - `{"op":2,"settler":"${settler.address}","user":"${user}","deadline":"${deadline}","nonce":"0x","maxFees":[{"token":"${fee.token.address.toString()}","amount":"${fee.amount.toString()}"}],"events":[{"topic":"0x746f706963","data":"0x64617461"}],"chainId":${chainId},"calls":[{"target":"${target}","data":"${calldata.toHexString()}","value":"${value}"}]}` + `{"opType":2,"chainId":${chainId},"user":"${user}","events":[{"topic":"0x746f706963","data":"0x64617461"}],"calls":[{"target":"${target}","data":"${calldata.toHexString()}","value":"${value.toString()}"}]}` ) }) - it('creates a complex Call with valid parameters and stringifies it', () => { + it('creates a complex operation with multiple calls', () => { const chainId = 1 const user = randomEvmAddress() const settler = randomSettler(chainId) - const deadline = BigInt.fromI32(9999999) - const fee = TokenAmount.fromI32(randomERC20Token(chainId), 100) const callData1 = new EvmCallData(randomEvmAddress(), randomBytes(32), BigInt.fromI32(1)) const callData2 = new EvmCallData(randomEvmAddress(), randomBytes(32), BigInt.fromI32(2)) - const callDatas = [callData1, callData2] - const call = new EvmCall(chainId, callDatas, [fee], Address.fromString(settler.address), user, deadline, '0x') - expect(call.op).toBe(OperationType.EvmCall) - expect(call.user).toBe(user.toString()) - expect(call.settler).toBe(settler.address.toString()) - expect(call.chainId).toBe(chainId) - expect(call.deadline).toBe(deadline.toString()) - expect(call.nonce).toBe('0x') + setContext(1, 1, user.toString(), [settler], 'trigger-123') + const call = new EvmCall(chainId, [callData1, callData2], user) expect(call.calls.length).toBe(2) expect(call.calls[0].target).toBe(callData1.target) - expect(call.calls[0].data).toBe(callData1.data) - expect(call.calls[0].value).toBe(callData1.value) - expect(call.calls[1].target).toBe(callData2.target) - expect(call.calls[1].data).toBe(callData2.data) - expect(call.calls[1].value).toBe(callData2.value) - - expect(call.maxFees.length).toBe(1) - expect(call.maxFees[0].token).toBe(fee.token.address.toString()) - expect(call.maxFees[0].amount).toBe(fee.amount.toString()) - - expect(call.events.length).toBe(0) - expect(JSON.stringify(call)).toBe( - `{"op":2,"settler":"${settler.address}","user":"${user}","deadline":"${deadline}","nonce":"0x","maxFees":[{"token":"${fee.token.address.toString()}","amount":"${fee.amount.toString()}"}],"events":[],"chainId":${chainId},"calls":[{"target":"${callData1.target}","data":"${callData1.data}","value":"${callData1.value}"},{"target":"${callData2.target}","data":"${callData2.data}","value":"${callData2.value}"}]}` + `{"opType":2,"chainId":${chainId},"user":"${user}","events":[],"calls":[{"target":"${callData1.target}","data":"${callData1.data}","value":"${callData1.value}"},{"target":"${callData2.target}","data":"${callData2.data}","value":"${callData2.value}"}]}` ) }) it('throws an error when there is not Call Data', () => { expect(() => { - new EvmCall(1, [], []) + new EvmCall(1, []) }).toThrow('Call list cannot be empty') }) - - it('throws an error when there is no max fee', () => { - expect(() => { - const callData = new EvmCallData(randomEvmAddress(), randomBytes(32), BigInt.fromI32(1)) - new EvmCall(1, [callData], []) - }).toThrow('At least a max fee must be specified') - }) }) describe('EvmCallBuilder', () => { @@ -146,14 +84,13 @@ describe('EvmCallBuilder', () => { const target1Str = '0x0000000000000000000000000000000000000001' const target2Str = '0x0000000000000000000000000000000000000002' - it('adds multiple calls and builds call', () => { + it('adds multiple calls and builds an operation', () => { const target1 = Address.fromString(target1Str) const target2 = Address.fromString(target2Str) const builder = EvmCallBuilder.forChain(chainId) builder.addCall(target1, randomBytes(2), BigInt.fromString('1')) builder.addCall(target2, randomBytes(2), BigInt.fromString('2')) - builder.addMaxFee(TokenAmount.fromI32(randomERC20Token(chainId), 100)) const call = builder.build() expect(call.calls.length).toBe(2) @@ -165,18 +102,10 @@ describe('EvmCallBuilder', () => { const target = Address.fromString(target1Str) const builder = EvmCallBuilder.forChain(chainId) - builder.addCall(target) // default Bytes.empty and BigInt.zero - builder.addMaxFee(TokenAmount.fromI32(randomERC20Token(chainId), 100)) + builder.addCall(target) const call = builder.build() expect(call.calls[0].data).toBe(Bytes.empty().toHexString()) expect(call.calls[0].value).toBe('0') }) - - it('throws if fee token chainId mismatches constructor chainId', () => { - expect(() => { - const fee = TokenAmount.fromI32(randomERC20Token(2), 9) - EvmCallBuilder.forChain(chainId).addMaxFee(fee) - }).toThrow('Fee token must be on the same chain as the one requested for the call') - }) }) diff --git a/packages/lib-ts/tests/intents/Intent.spec.ts b/packages/lib-ts/tests/intents/Intent.spec.ts index cc75084c..84c7905d 100644 --- a/packages/lib-ts/tests/intents/Intent.spec.ts +++ b/packages/lib-ts/tests/intents/Intent.spec.ts @@ -1,6 +1,8 @@ +import { JSON } from 'json-as' + import { SerializableSettler } from '../../src/context' import { NULL_ADDRESS } from '../../src/helpers' -import { EvmCallBuilder } from '../../src/intents' +import { EvmCallBuilder, IntentBuilder } from '../../src/intents' import { TokenAmount } from '../../src/tokens' import { Address, BigInt, Bytes } from '../../src/types' import { randomERC20Token, randomSettler, setContext } from '../helpers' @@ -9,7 +11,7 @@ describe('IntentBuilder', () => { const chainId = 1 const targetAddressStr = '0x0000000000000000000000000000000000000001' - describe('when the user is not zero', () => { + describe('when the fee payer is not zero', () => { const userAddressStr = '0x0000000000000000000000000000000000000002' describe('when the settler is not zero', () => { @@ -17,49 +19,68 @@ describe('IntentBuilder', () => { it('sets intent properties via builder methods', () => { const target = Address.fromString(targetAddressStr) - const user = Address.fromString(userAddressStr) const settler = Address.fromString(settlerAddressStr) + const feePayer = Address.fromString(userAddressStr) const fee = TokenAmount.fromI32(randomERC20Token(chainId), 9) const deadline = BigInt.fromString('123456789') const customNonce = '0xabcdef123456' - setContext(0, 1, '0x0000000000000000000000000000000000000000', [], '1') + setContext(0, 1, '0x0000000000000000000000000000000000000004', [], '1') - const builder = EvmCallBuilder.forChain(chainId) - .addCall(target, Bytes.fromHexString('0x1234'), BigInt.fromString('1')) - .addUser(user) + const intent = new IntentBuilder() + .addOperationBuilder(EvmCallBuilder.forChain(chainId).addCall(target, Bytes.fromHexString('0x1234'))) .addSettler(settler) + .addFeePayer(feePayer) .addDeadline(deadline) .addNonce(customNonce) .addMaxFee(fee) - - const call = builder.build() - expect(call.user).toBe(user.toString()) - expect(call.settler).toBe(settler.toString()) - expect(call.deadline).toBe('123456789') - expect(call.nonce).toBe(customNonce) - expect(call.maxFees.length).toBe(1) - expect(call.maxFees[0].token).toBe(fee.token.address.toString()) - expect(call.maxFees[0].amount).toBe(fee.amount.toString()) + .build() + + expect(intent.operations.length).toBe(1) + expect(intent.operations[0].user).toBe('0x0000000000000000000000000000000000000004') + expect(intent.settler).toBe(settler.toString()) + expect(intent.feePayer).toBe(feePayer.toString()) + expect(intent.deadline).toBe('123456789') + expect(intent.nonce).toBe(customNonce) + expect(intent.maxFees.length).toBe(1) + expect(intent.maxFees[0].token).toBe(fee.token.address.toString()) + expect(intent.maxFees[0].amount).toBe(fee.amount.toString()) }) - it('uses default user, deadline, nonce and settler if not explicitly set', () => { + it('uses default fee payer, deadline, nonce and settler if not explicitly set', () => { const target = Address.fromString(targetAddressStr) const settler = randomSettler(chainId) - const fee = TokenAmount.fromI32(randomERC20Token(chainId), 9) setContext(0, 1, userAddressStr, [settler], 'trigger-transfer') - const builder = EvmCallBuilder.forChain(chainId).addCall(target).addMaxFee(fee) + const intent = new IntentBuilder().addOperationBuilder(EvmCallBuilder.forChain(chainId).addCall(target)).build() - const call = builder.build() - expect(call.user).toBe(userAddressStr) - expect(call.settler).toBe(settler.address.toString()) - expect(call.deadline).toBe('300') - expect(call.nonce).toBe('0x') - expect(call.maxFees.length).toBe(1) - expect(call.maxFees[0].token).toBe(fee.token.address.toString()) - expect(call.maxFees[0].amount).toBe(fee.amount.toString()) + expect(intent.operations.length).toBe(1) + expect(intent.operations[0].user).toBe(userAddressStr) + expect(intent.settler).toBe(settler.address.toString()) + expect(intent.feePayer).toBe(userAddressStr) + expect(intent.deadline).toBe('300') + expect(intent.nonce).toBe('0x') + }) + + it('stringifies the built intent', () => { + const target = Address.fromString(targetAddressStr) + const settler = Address.fromString(settlerAddressStr) + const feePayer = Address.fromString(userAddressStr) + + setContext(0, 1, '0x0000000000000000000000000000000000000004', [], '1') + + const intent = new IntentBuilder() + .addOperationBuilder(EvmCallBuilder.forChain(chainId).addCall(target, Bytes.fromHexString('0x1234'))) + .addSettler(settler) + .addFeePayer(feePayer) + .addDeadline(BigInt.fromString('123456789')) + .addNonce('0xabcdef123456') + .build() + + expect(JSON.stringify(intent)).toBe( + `{"settler":"${settler}","feePayer":"${feePayer}","deadline":"123456789","nonce":"0xabcdef123456","maxFees":[],"operations":[{"opType":2,"chainId":${chainId},"user":"0x0000000000000000000000000000000000000004","events":[],"calls":[{"target":"${target}","data":"0x1234","value":"0"}]}]}` + ) }) }) @@ -69,14 +90,15 @@ describe('IntentBuilder', () => { setContext(0, 1, userAddressStr, [settler], 'trigger-call') expect(() => { - const builder = EvmCallBuilder.forChain(chainId) - builder.build() + new IntentBuilder() + .addOperationBuilder(EvmCallBuilder.forChain(chainId).addCall(Address.fromString(targetAddressStr))) + .build() }).toThrow('A settler contract must be specified') }) }) }) - describe('when the user is zero', () => { + describe('when the fee payer is zero', () => { const userAddressStr = NULL_ADDRESS it('throws an error', () => { @@ -84,9 +106,21 @@ describe('IntentBuilder', () => { setContext(0, 1, userAddressStr, [settler], 'trigger-call') expect(() => { - const builder = EvmCallBuilder.forChain(chainId) - builder.build() - }).toThrow('A user must be specified') + new IntentBuilder() + .addOperationBuilder(EvmCallBuilder.forChain(chainId).addCall(Address.fromString(targetAddressStr))) + .build() + }).toThrow('A fee payer must be specified') + }) + }) + + describe('when there are no operations', () => { + it('throws an error', () => { + const settler = randomSettler(chainId) + setContext(0, 1, '0x0000000000000000000000000000000000000002', [settler], 'trigger-call') + + expect(() => { + new IntentBuilder().build() + }).toThrow('Operation list cannot be empty') }) }) }) diff --git a/packages/lib-ts/tests/intents/SvmCall.spec.ts b/packages/lib-ts/tests/intents/SvmCall.spec.ts index 4376fae6..ac0b75a0 100644 --- a/packages/lib-ts/tests/intents/SvmCall.spec.ts +++ b/packages/lib-ts/tests/intents/SvmCall.spec.ts @@ -1,17 +1,16 @@ import { JSON } from 'json-as' import { - IntentEvent, + OperationEvent, OperationType, SvmCall, SvmCallBuilder, SvmInstruction, SvmInstructionBuilder, } from '../../src/intents' -import { TokenAmount } from '../../src/tokens' -import { Address, BigInt, Bytes, ChainId } from '../../src/types' +import { Address, Bytes, ChainId } from '../../src/types' import { SvmAccountMeta } from '../../src/types/svm/SvmAccountMeta' -import { randomBytes, randomSPLToken, randomSvmAddress, randomSvmSettler, setContext } from '../helpers' +import { randomBytes, randomSvmAddress, randomSvmSettler, setContext } from '../helpers' describe('SvmCall', () => { describe('create', () => { @@ -20,32 +19,24 @@ describe('SvmCall', () => { const programId = randomSvmAddress() const accountsMeta = [SvmAccountMeta.fromAddress(randomSvmAddress())] const data = randomBytes(32) - const fee = TokenAmount.fromI32(randomSPLToken(ChainId.SOLANA_MAINNET), 100) const settler = randomSvmSettler() setContext(1, 1, user.toString(), [settler], 'trigger-123') - const svmCall = SvmCall.create(programId, accountsMeta, data, fee) - expect(svmCall.op).toBe(OperationType.SvmCall) + const svmCall = new SvmCall([new SvmInstruction(programId.toBase58String(), accountsMeta, data.toHexString())]) + expect(svmCall.opType).toBe(OperationType.SvmCall) expect(svmCall.user).toBe(user.toString()) - expect(svmCall.settler).toBe(settler.address.toString()) expect(svmCall.chainId).toBe(ChainId.SOLANA_MAINNET) - expect(svmCall.deadline).toBe('300') - expect(svmCall.nonce).toBe('0x') expect(svmCall.instructions.length).toBe(1) expect(svmCall.instructions[0].programId).toBe(programId.toBase58String()) expect(svmCall.instructions[0].accountsMeta).toStrictEqual(accountsMeta) expect(svmCall.instructions[0].data).toBe(data.toHexString()) - expect(svmCall.maxFees.length).toBe(1) - expect(svmCall.maxFees[0].token).toBe(fee.token.address.toString()) - expect(svmCall.maxFees[0].amount).toBe(fee.amount.toString()) - expect(svmCall.events.length).toBe(0) expect(JSON.stringify(svmCall)).toBe( - `{"op":3,"settler":"${settler.address}","user":"${user}","deadline":"300","nonce":"0x","maxFees":[{"token":"${fee.token.address.toString()}","amount":"${fee.amount.toString()}"}],"events":[],"chainId":${ChainId.SOLANA_MAINNET},"instructions":[{"programId":"${programId.toBase58String()}","accountsMeta":[{"pubkey":"${accountsMeta[0].pubkey}","isWritable":${accountsMeta[0].isWritable},"isSigner":${accountsMeta[0].isSigner}}],"data":"${data.toHexString()}"}]}` + `{"opType":3,"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()}"}]}` ) }) @@ -54,46 +45,31 @@ describe('SvmCall', () => { const programId = randomSvmAddress() const accountsMeta = [SvmAccountMeta.fromAddress(randomSvmAddress())] const data = randomBytes(32) - const fee = TokenAmount.fromI32(randomSPLToken(ChainId.SOLANA_MAINNET), 100) const settler = randomSvmSettler() - const deadline = BigInt.fromI32(9999999) setContext(1, 1, user.toString(), [settler], 'trigger-123') - const svmCall = SvmCall.create( - programId, - accountsMeta, - data, - fee, - Address.fromString(settler.address), + const svmCall = new SvmCall( + [new SvmInstruction(programId.toBase58String(), accountsMeta, data.toHexString())], user, - deadline, - null, - [new IntentEvent(Bytes.fromUTF8('topic'), Bytes.fromUTF8('data'))] + [new OperationEvent(Bytes.fromUTF8('topic'), Bytes.fromUTF8('data'))] ) - expect(svmCall.op).toBe(OperationType.SvmCall) + expect(svmCall.opType).toBe(OperationType.SvmCall) expect(svmCall.user).toBe(user.toString()) - expect(svmCall.settler).toBe(settler.address.toString()) expect(svmCall.chainId).toBe(ChainId.SOLANA_MAINNET) - expect(svmCall.deadline).toBe(deadline.toString()) - expect(svmCall.nonce).toBe('0x') expect(svmCall.instructions.length).toBe(1) expect(svmCall.instructions[0].programId).toBe(programId.toBase58String()) expect(svmCall.instructions[0].accountsMeta).toStrictEqual(accountsMeta) expect(svmCall.instructions[0].data).toBe(data.toHexString()) - expect(svmCall.maxFees.length).toBe(1) - expect(svmCall.maxFees[0].token).toBe(fee.token.address.toString()) - expect(svmCall.maxFees[0].amount).toBe(fee.amount.toString()) - expect(svmCall.events.length).toBe(1) expect(svmCall.events[0].topic).toBe('0x746f706963') expect(svmCall.events[0].data).toBe('0x64617461') expect(JSON.stringify(svmCall)).toBe( - `{"op":3,"settler":"${settler.address}","user":"${user}","deadline":"${deadline}","nonce":"0x","maxFees":[{"token":"${fee.token.address.toString()}","amount":"${fee.amount.toString()}"}],"events":[{"topic":"0x746f706963","data":"0x64617461"}],"chainId":${ChainId.SOLANA_MAINNET},"instructions":[{"programId":"${programId.toBase58String()}","accountsMeta":[{"pubkey":"${accountsMeta[0].pubkey}","isWritable":${accountsMeta[0].isWritable},"isSigner":${accountsMeta[0].isSigner}}],"data":"${data.toHexString()}"}]}` + `{"opType":3,"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,29 +83,17 @@ describe('SvmCall', () => { const accountsMeta2 = [SvmAccountMeta.fromAddress(randomSvmAddress())] const data1 = randomBytes(32) const data2 = randomBytes(32) - const instruction1 = SvmInstruction.create(programId1, accountsMeta1, data1) - const instruction2 = SvmInstruction.create(programId2, accountsMeta2, data2) - const fee = TokenAmount.fromI32(randomSPLToken(ChainId.SOLANA_MAINNET), 100) + const instruction1 = new SvmInstruction(programId1.toBase58String(), accountsMeta1, data1.toHexString()) + const instruction2 = new SvmInstruction(programId2.toBase58String(), accountsMeta2, data2.toHexString()) const settler = randomSvmSettler() - const deadline = BigInt.fromI32(9999999) setContext(1, 1, user.toString(), [settler], 'trigger-123') - const svmCall = new SvmCall( - [instruction1, instruction2], - [fee], - Address.fromString(settler.address), - user, - deadline, - '0x' - ) + const svmCall = new SvmCall([instruction1, instruction2], user) - expect(svmCall.op).toBe(OperationType.SvmCall) + expect(svmCall.opType).toBe(OperationType.SvmCall) expect(svmCall.user).toBe(user.toString()) - expect(svmCall.settler).toBe(settler.address.toString()) expect(svmCall.chainId).toBe(ChainId.SOLANA_MAINNET) - expect(svmCall.deadline).toBe(deadline.toString()) - expect(svmCall.nonce).toBe('0x') expect(svmCall.instructions.length).toBe(2) expect(svmCall.instructions[0].programId).toBe(programId1.toBase58String()) @@ -140,14 +104,10 @@ describe('SvmCall', () => { expect(svmCall.instructions[1].accountsMeta).toStrictEqual(accountsMeta2) expect(svmCall.instructions[1].data).toBe(data2.toHexString()) - expect(svmCall.maxFees.length).toBe(1) - expect(svmCall.maxFees[0].token).toBe(fee.token.address.toString()) - expect(svmCall.maxFees[0].amount).toBe(fee.amount.toString()) - expect(svmCall.events.length).toBe(0) expect(JSON.stringify(svmCall)).toBe( - `{"op":3,"settler":"${settler.address}","user":"${user}","deadline":"${deadline}","nonce":"0x","maxFees":[{"token":"${fee.token.address.toString()}","amount":"${fee.amount.toString()}"}],"events":[],"chainId":${ChainId.SOLANA_MAINNET},"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":3,"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()}"}]}` ) }) }) @@ -155,20 +115,9 @@ describe('SvmCall', () => { describe('validations', () => { it('throws an error when instructions list is empty', () => { expect(() => { - const fee = TokenAmount.fromI32(randomSPLToken(ChainId.SOLANA_MAINNET), 100) - new SvmCall([], [fee]) + new SvmCall([]) }).toThrow('Call list cannot be empty') }) - - it('throws an error when there is no max fee', () => { - expect(() => { - const programId = randomSvmAddress() - const accountsMeta = [SvmAccountMeta.fromAddress(randomSvmAddress())] - const data = randomBytes(32) - const instruction = SvmInstruction.create(programId, accountsMeta, data) - new SvmCall([instruction], []) - }).toThrow('At least a max fee must be specified') - }) }) }) @@ -180,13 +129,12 @@ describe('SvmCallBuilder', () => { const accountsMeta2 = [SvmAccountMeta.fromAddress(randomSvmAddress())] const data1 = randomBytes(32) const data2 = randomBytes(32) - const instruction1 = SvmInstruction.create(programId1, accountsMeta1, data1) - const instruction2 = SvmInstruction.create(programId2, accountsMeta2, data2) + const instruction1 = new SvmInstruction(programId1.toBase58String(), accountsMeta1, data1.toHexString()) + const instruction2 = new SvmInstruction(programId2.toBase58String(), accountsMeta2, data2.toHexString()) const builder = SvmCallBuilder.forChain() builder.addInstruction(instruction1) builder.addInstruction(instruction2) - builder.addMaxFee(TokenAmount.fromI32(randomSPLToken(ChainId.SOLANA_MAINNET), 100)) const svmCall = builder.build() expect(svmCall.instructions.length).toBe(2) @@ -198,11 +146,10 @@ describe('SvmCallBuilder', () => { const programId = randomSvmAddress() const accountsMeta = [SvmAccountMeta.fromAddress(randomSvmAddress())] const data = randomBytes(32) - const instruction = SvmInstruction.create(programId, accountsMeta, data) + const instruction = new SvmInstruction(programId.toBase58String(), accountsMeta, data.toHexString()) const builder = SvmCallBuilder.forChain() builder.addInstruction(instruction) - builder.addMaxFee(TokenAmount.fromI32(randomSPLToken(ChainId.SOLANA_MAINNET), 100)) const svmCall = builder.build() expect(svmCall.instructions.length).toBe(1) @@ -210,41 +157,27 @@ describe('SvmCallBuilder', () => { expect(svmCall.instructions[0].data).toBe(data.toHexString()) }) - it('adds settler, user, deadline, nonce, and events', () => { + it('adds user and events', () => { const user = randomSvmAddress() - const settler = randomSvmAddress() - const deadline = BigInt.fromI32(9999999) - const nonce = '0x123456' + const settler = randomSvmSettler() const programId = randomSvmAddress() const accountsMeta = [SvmAccountMeta.fromAddress(randomSvmAddress())] const data = randomBytes(32) - const instruction = SvmInstruction.create(programId, accountsMeta, data) + const instruction = new SvmInstruction(programId.toBase58String(), accountsMeta, data.toHexString()) + + setContext(1, 1, randomSvmAddress().toString(), [settler], 'trigger-123') const builder = SvmCallBuilder.forChain() - builder.addSettler(settler) builder.addUser(user) - builder.addDeadline(deadline) - builder.addNonce(nonce) builder.addInstruction(instruction) - builder.addMaxFee(TokenAmount.fromI32(randomSPLToken(ChainId.SOLANA_MAINNET), 100)) builder.addEvent(Bytes.fromUTF8('topic'), Bytes.fromUTF8('data')) const svmCall = builder.build() expect(svmCall.user).toBe(user.toString()) - expect(svmCall.settler).toBe(settler.toString()) - expect(svmCall.deadline).toBe(deadline.toString()) - expect(svmCall.nonce).toBe(nonce) expect(svmCall.events.length).toBe(1) expect(svmCall.events[0].topic).toBe('0x746f706963') expect(svmCall.events[0].data).toBe('0x64617461') }) - - it('throws if fee token chainId mismatches Solana chainId', () => { - expect(() => { - const fee = TokenAmount.fromI32(randomSPLToken(ChainId.ETHEREUM), 9) - SvmCallBuilder.forChain().addMaxFee(fee) - }).toThrow('Fee token must be on the same chain') - }) }) describe('SvmInstructionBuilder', () => { @@ -270,25 +203,32 @@ describe('SvmInstructionBuilder', () => { it('builds instruction with hex data', () => { const programId = randomSvmAddress() const accountsMeta = [SvmAccountMeta.fromAddress(randomSvmAddress())] - const hexData = '0xdeadbeef' - const builder = new SvmInstructionBuilder() - builder.setProgram(programId) - builder.setAccounts(accountsMeta) - builder.setDataFromHex(hexData) + const instruction = new SvmInstructionBuilder() + .setProgram(programId) + .setAccounts(accountsMeta) + .setDataFromHex('0xabcd') + .instruction() - const instruction = builder.instruction() - expect(instruction.programId).toBe(programId.toBase58String()) - expect(instruction.accountsMeta).toStrictEqual(accountsMeta) - expect(instruction.data).toBe(hexData) + expect(instruction.data).toBe('0xabcd') }) - it('creates instruction with default values', () => { - const builder = new SvmInstructionBuilder() - const instruction = builder.instruction() + it('builds instruction with default empty data', () => { + const programId = randomSvmAddress() - expect(instruction.programId).toBe(Address.zero(32).toBase58String()) + const instruction = new SvmInstructionBuilder().setProgram(programId).instruction() + expect(instruction.programId).toBe(programId.toBase58String()) expect(instruction.accountsMeta.length).toBe(0) expect(instruction.data).toBe(Bytes.empty().toHexString()) }) + + it('uses provided address strings', () => { + const programId = Address.fromString('So11111111111111111111111111111111111111112') + const account = SvmAccountMeta.fromAddress(Address.fromString('So11111111111111111111111111111111111111113')) + + const instruction = new SvmInstructionBuilder().setProgram(programId).setAccounts([account]).instruction() + + expect(instruction.programId).toBe(programId.toBase58String()) + expect(instruction.accountsMeta[0].pubkey).toBe(account.pubkey) + }) }) diff --git a/packages/lib-ts/tests/intents/Swap.spec.ts b/packages/lib-ts/tests/intents/Swap.spec.ts index 6b72006c..92d819d9 100644 --- a/packages/lib-ts/tests/intents/Swap.spec.ts +++ b/packages/lib-ts/tests/intents/Swap.spec.ts @@ -2,7 +2,7 @@ import { JSON } from 'json-as' import { OperationType, Swap, SwapBuilder, SwapTokenIn, SwapTokenOut } from '../../src/intents' import { ERC20Token, SPLToken, TokenAmount } from '../../src/tokens' -import { Address, BigInt, ChainId } from '../../src/types' +import { Address, ChainId } from '../../src/types' import { randomERC20Token, randomEvmAddress, @@ -16,118 +16,51 @@ describe('Swap', () => { it('creates a simple Swap with default values', () => { const chainId = 1 const user = randomEvmAddress() - const tokenIn = ERC20Token.fromAddress(randomEvmAddress(), chainId) - const amountIn = BigInt.fromI32(10) - const tokenOut = ERC20Token.fromAddress(randomEvmAddress(), chainId) - const minAmountOut = BigInt.fromI32(100) + const tokenIn = SwapTokenIn.fromI32(randomERC20Token(chainId), 10) + const tokenOut = SwapTokenOut.fromI32(randomERC20Token(chainId), 100, randomEvmAddress()) const settler = randomSettler(chainId) setContext(1, 1, user.toString(), [settler], 'trigger-456') - const swap = Swap.create(chainId, tokenIn, amountIn, tokenOut, minAmountOut) - expect(swap.op).toBe(OperationType.Swap) + const swap = new Swap(chainId, [tokenIn], [tokenOut], chainId) + expect(swap.opType).toBe(OperationType.Swap) expect(swap.user).toBe(user.toString()) - expect(swap.settler).toBe(settler.address.toString()) - expect(swap.deadline).toBe('300') - expect(swap.nonce).toBe('0x') - expect(swap.maxFees.length).toBe(0) expect(swap.sourceChain).toBe(chainId) expect(swap.tokensIn.length).toBe(1) - expect(swap.tokensIn[0].token).toBe(tokenIn.address.toString()) - expect(swap.tokensIn[0].amount).toBe(amountIn.toString()) + expect(swap.tokensIn[0].token).toBe(tokenIn.token) + expect(swap.tokensIn[0].amount).toBe(tokenIn.amount) expect(swap.destinationChain).toBe(chainId) expect(swap.tokensOut.length).toBe(1) - expect(swap.tokensOut[0].token).toBe(tokenOut.address.toString()) - expect(swap.tokensOut[0].minAmount).toBe(minAmountOut.toString()) - expect(swap.tokensOut[0].recipient).toBe(user.toString()) + expect(swap.tokensOut[0].token).toBe(tokenOut.token) + expect(swap.tokensOut[0].minAmount).toBe(tokenOut.minAmount) + expect(swap.tokensOut[0].recipient).toBe(tokenOut.recipient) expect(swap.events.length).toBe(0) expect(JSON.stringify(swap)).toBe( - `{"op":0,"settler":"${settler.address}","user":"${user}","deadline":"300","nonce":"0x","maxFees":[],"events":[],"sourceChain":${chainId},"tokensIn":[{"token":"${tokenIn.address.toString()}","amount":"${amountIn}"}],"tokensOut":[{"token":"${tokenOut.address.toString()}","minAmount":"${minAmountOut}","recipient":"${user}"}],"destinationChain":${chainId}}` + `{"opType":0,"chainId":${chainId},"user":"${user}","events":[],"sourceChain":${chainId},"tokensIn":[{"token":"${tokenIn.token}","amount":"${tokenIn.amount}"}],"tokensOut":[{"token":"${tokenOut.token}","minAmount":"${tokenOut.minAmount}","recipient":"${tokenOut.recipient}"}],"destinationChain":${chainId}}` ) }) it('creates a simple Swap with valid parameters and stringifies it', () => { const chainId = 1 + const destinationChain = 10 + const randomUser = randomEvmAddress() const user = randomEvmAddress() const settler = randomSettler(chainId) - const deadline = BigInt.fromI32(999999) - const tokenIn = ERC20Token.fromAddress(randomEvmAddress(), chainId) - const amountIn = BigInt.fromI32(10) - const tokenOut = ERC20Token.fromAddress(randomEvmAddress(), chainId) - const minAmountOut = BigInt.fromI32(100) + const tokenIn = SwapTokenIn.fromI32(randomERC20Token(chainId), 10) + const tokenOut = SwapTokenOut.fromI32(randomERC20Token(chainId), 100, randomEvmAddress()) setContext(1, 1, user.toString(), [settler], 'trigger-456') - const swap = Swap.create( - chainId, - tokenIn, - amountIn, - tokenOut, - minAmountOut, - Address.fromString(settler.address), - user, - deadline, - null, - [] - ) - expect(swap.op).toBe(OperationType.Swap) - expect(swap.user).toBe(user.toString()) - expect(swap.settler).toBe(settler.address.toString()) - expect(swap.deadline).toBe(deadline.toString()) - expect(swap.nonce).toBe('0x') - expect(swap.maxFees.length).toBe(0) + const swap = new Swap(chainId, [tokenIn], [tokenOut], destinationChain, randomUser, []) + expect(swap.opType).toBe(OperationType.Swap) + expect(swap.user).toBe(randomUser.toString()) expect(swap.sourceChain).toBe(chainId) expect(swap.tokensIn.length).toBe(1) - expect(swap.tokensIn[0].token).toBe(tokenIn.address.toString()) - expect(swap.tokensIn[0].amount).toBe(amountIn.toString()) - - expect(swap.destinationChain).toBe(chainId) - expect(swap.tokensOut.length).toBe(1) - expect(swap.tokensOut[0].token).toBe(tokenOut.address.toString()) - expect(swap.tokensOut[0].minAmount).toBe(minAmountOut.toString()) - expect(swap.tokensOut[0].recipient).toBe(user.toString()) - - expect(swap.events.length).toBe(0) - - expect(JSON.stringify(swap)).toBe( - `{"op":0,"settler":"${settler.address}","user":"${user}","deadline":"${deadline}","nonce":"0x","maxFees":[],"events":[],"sourceChain":${chainId},"tokensIn":[{"token":"${tokenIn.address.toString()}","amount":"${amountIn}"}],"tokensOut":[{"token":"${tokenOut.address.toString()}","minAmount":"${minAmountOut}","recipient":"${user}"}],"destinationChain":${chainId}}` - ) - }) - - it('creates a complex Swap with valid parameters and stringifies it', () => { - const sourceChain = 1 - const destinationChain = 10 - const user = randomEvmAddress() - const settler = randomSettler(sourceChain) - const deadline = BigInt.fromI32(999999) - const tokenIn = SwapTokenIn.fromI32(randomERC20Token(sourceChain), 10) - const tokenOut = SwapTokenOut.fromI32(randomERC20Token(destinationChain), 100, randomEvmAddress()) - - setContext(1, 1, user.toString(), [settler], 'trigger-456') - - const swap = new Swap( - sourceChain, - [tokenIn], - [tokenOut], - destinationChain, - Address.fromString(settler.address), - user, - deadline - ) - expect(swap.op).toBe(OperationType.Swap) - expect(swap.user).toBe(user.toString()) - expect(swap.settler).toBe(settler.address.toString()) - expect(swap.deadline).toBe(deadline.toString()) - expect(swap.nonce).toBe('0x') - expect(swap.maxFees.length).toBe(0) - - expect(swap.sourceChain).toBe(sourceChain) - expect(swap.tokensIn.length).toBe(1) expect(swap.tokensIn[0].token).toBe(tokenIn.token) expect(swap.tokensIn[0].amount).toBe(tokenIn.amount) @@ -140,7 +73,7 @@ describe('Swap', () => { expect(swap.events.length).toBe(0) expect(JSON.stringify(swap)).toBe( - `{"op":0,"settler":"${settler.address}","user":"${user}","deadline":"${deadline}","nonce":"0x","maxFees":[],"events":[],"sourceChain":${sourceChain},"tokensIn":[{"token":"${tokenIn.token}","amount":"${tokenIn.amount}"}],"tokensOut":[{"token":"${tokenOut.token}","minAmount":"${tokenOut.minAmount}","recipient":"${tokenOut.recipient}"}],"destinationChain":${destinationChain}}` + `{"opType":0,"chainId":${chainId},"user":"${randomUser}","events":[],"sourceChain":${chainId},"tokensIn":[{"token":"${tokenIn.token}","amount":"${tokenIn.amount}"}],"tokensOut":[{"token":"${tokenOut.token}","minAmount":"${tokenOut.minAmount}","recipient":"${tokenOut.recipient}"}],"destinationChain":${destinationChain}}` ) }) @@ -182,7 +115,7 @@ describe('SwapBuilder', () => { builder.addTokenOutFromTokenAmount(tokenOutAmount, recipientAddress) const swap = builder.build() - expect(swap.op).toBe(OperationType.Swap) + expect(swap.opType).toBe(OperationType.Swap) expect(swap.sourceChain).toBe(sourceChain) expect(swap.destinationChain).toBe(destinationChain) expect(swap.tokensIn[0].token).toBe(tokenInAddress.toString()) @@ -275,37 +208,31 @@ describe('Swap - SVM', () => { it('creates a simple Swap with default values', () => { const chainId = ChainId.SOLANA_MAINNET const user = randomSvmAddress() - const tokenIn = SPLToken.fromAddress(randomSvmAddress(), chainId, 6, 'USDC') - const amountIn = BigInt.fromI32(10) - const tokenOut = SPLToken.fromAddress(randomSvmAddress(), chainId, 6, 'USDT') - const minAmountOut = BigInt.fromI32(100) + const tokenIn = SwapTokenIn.fromI32(randomSPLToken(chainId), 10) + const tokenOut = SwapTokenOut.fromI32(randomSPLToken(chainId), 100, randomSvmAddress()) const settler = randomSettler(chainId) setContext(1, 1, user.toString(), [settler], 'trigger-456') - const swap = Swap.create(chainId, tokenIn, amountIn, tokenOut, minAmountOut) - expect(swap.op).toBe(OperationType.Swap) + const swap = new Swap(chainId, [tokenIn], [tokenOut], chainId) + expect(swap.opType).toBe(OperationType.Swap) expect(swap.user).toBe(user.toString()) - expect(swap.settler).toBe(settler.address.toString()) - expect(swap.deadline).toBe('300') - expect(swap.nonce).toBe('0x') - expect(swap.maxFees.length).toBe(0) expect(swap.sourceChain).toBe(chainId) expect(swap.tokensIn.length).toBe(1) - expect(swap.tokensIn[0].token).toBe(tokenIn.address.toString()) - expect(swap.tokensIn[0].amount).toBe(amountIn.toString()) + expect(swap.tokensIn[0].token).toBe(tokenIn.token) + expect(swap.tokensIn[0].amount).toBe(tokenIn.amount) expect(swap.destinationChain).toBe(chainId) expect(swap.tokensOut.length).toBe(1) - expect(swap.tokensOut[0].token).toBe(tokenOut.address.toString()) - expect(swap.tokensOut[0].minAmount).toBe(minAmountOut.toString()) - expect(swap.tokensOut[0].recipient).toBe(user.toString()) + expect(swap.tokensOut[0].token).toBe(tokenOut.token) + expect(swap.tokensOut[0].minAmount).toBe(tokenOut.minAmount) + expect(swap.tokensOut[0].recipient).toBe(tokenOut.recipient) expect(swap.events.length).toBe(0) expect(JSON.stringify(swap)).toBe( - `{"op":0,"settler":"${settler.address}","user":"${user}","deadline":"300","nonce":"0x","maxFees":[],"events":[],"sourceChain":${chainId},"tokensIn":[{"token":"${tokenIn.address.toString()}","amount":"${amountIn}"}],"tokensOut":[{"token":"${tokenOut.address.toString()}","minAmount":"${minAmountOut}","recipient":"${user}"}],"destinationChain":${chainId}}` + `{"opType":0,"chainId":${chainId},"user":"${user}","events":[],"sourceChain":${chainId},"tokensIn":[{"token":"${tokenIn.token}","amount":"${tokenIn.amount}"}],"tokensOut":[{"token":"${tokenOut.token}","minAmount":"${tokenOut.minAmount}","recipient":"${tokenOut.recipient}"}],"destinationChain":${chainId}}` ) }) @@ -313,82 +240,21 @@ describe('Swap - SVM', () => { const chainId = ChainId.SOLANA_MAINNET const user = randomSvmAddress() const settler = randomSettler(chainId) - const deadline = BigInt.fromI32(999999) - const tokenIn = SPLToken.fromAddress(randomSvmAddress(), chainId, 6, 'USDC') - const amountIn = BigInt.fromI32(10) - const tokenOut = SPLToken.fromAddress(randomSvmAddress(), chainId, 6, 'USDT') - const minAmountOut = BigInt.fromI32(100) + const tokenIn = SwapTokenIn.fromI32(randomSPLToken(chainId), 10) + const tokenOut = SwapTokenOut.fromI32(randomSPLToken(chainId), 100, randomSvmAddress()) setContext(1, 1, user.toString(), [settler], 'trigger-456') - const swap = Swap.create( - chainId, - tokenIn, - amountIn, - tokenOut, - minAmountOut, - Address.fromString(settler.address), - user, - deadline - ) - expect(swap.op).toBe(OperationType.Swap) + const swap = new Swap(chainId, [tokenIn], [tokenOut], chainId, user, []) + expect(swap.opType).toBe(OperationType.Swap) expect(swap.user).toBe(user.toString()) - expect(swap.settler).toBe(settler.address.toString()) - expect(swap.deadline).toBe(deadline.toString()) - expect(swap.nonce).toBe('0x') - expect(swap.maxFees.length).toBe(0) expect(swap.sourceChain).toBe(chainId) expect(swap.tokensIn.length).toBe(1) - expect(swap.tokensIn[0].token).toBe(tokenIn.address.toString()) - expect(swap.tokensIn[0].amount).toBe(amountIn.toString()) - - expect(swap.destinationChain).toBe(chainId) - expect(swap.tokensOut.length).toBe(1) - expect(swap.tokensOut[0].token).toBe(tokenOut.address.toString()) - expect(swap.tokensOut[0].minAmount).toBe(minAmountOut.toString()) - expect(swap.tokensOut[0].recipient).toBe(user.toString()) - - expect(swap.events.length).toBe(0) - - expect(JSON.stringify(swap)).toBe( - `{"op":0,"settler":"${settler.address}","user":"${user}","deadline":"${deadline}","nonce":"0x","maxFees":[],"events":[],"sourceChain":${chainId},"tokensIn":[{"token":"${tokenIn.address.toString()}","amount":"${amountIn}"}],"tokensOut":[{"token":"${tokenOut.address.toString()}","minAmount":"${minAmountOut}","recipient":"${user}"}],"destinationChain":${chainId}}` - ) - }) - - it('creates a complex Swap with valid parameters and stringifies it', () => { - const sourceChain = ChainId.SOLANA_MAINNET - const destinationChain = ChainId.SOLANA_MAINNET - const user = randomSvmAddress() - const settler = randomSettler(sourceChain) - const deadline = BigInt.fromI32(999999) - const tokenIn = SwapTokenIn.fromI32(randomSPLToken(sourceChain), 10) - const tokenOut = SwapTokenOut.fromI32(randomSPLToken(destinationChain), 100, randomSvmAddress()) - - setContext(1, 1, user.toString(), [settler], 'trigger-456') - - const swap = new Swap( - sourceChain, - [tokenIn], - [tokenOut], - destinationChain, - Address.fromString(settler.address), - user, - deadline - ) - expect(swap.op).toBe(OperationType.Swap) - expect(swap.user).toBe(user.toString()) - expect(swap.settler).toBe(settler.address.toString()) - expect(swap.deadline).toBe(deadline.toString()) - expect(swap.nonce).toBe('0x') - expect(swap.maxFees.length).toBe(0) - - expect(swap.sourceChain).toBe(sourceChain) - expect(swap.tokensIn.length).toBe(1) expect(swap.tokensIn[0].token).toBe(tokenIn.token) expect(swap.tokensIn[0].amount).toBe(tokenIn.amount) - expect(swap.destinationChain).toBe(destinationChain) + expect(swap.destinationChain).toBe(chainId) expect(swap.tokensOut.length).toBe(1) expect(swap.tokensOut[0].token).toBe(tokenOut.token) expect(swap.tokensOut[0].minAmount).toBe(tokenOut.minAmount) @@ -397,7 +263,7 @@ describe('Swap - SVM', () => { expect(swap.events.length).toBe(0) expect(JSON.stringify(swap)).toBe( - `{"op":0,"settler":"${settler.address}","user":"${user}","deadline":"${deadline}","nonce":"0x","maxFees":[],"events":[],"sourceChain":${sourceChain},"tokensIn":[{"token":"${tokenIn.token}","amount":"${tokenIn.amount}"}],"tokensOut":[{"token":"${tokenOut.token}","minAmount":"${tokenOut.minAmount}","recipient":"${tokenOut.recipient}"}],"destinationChain":${destinationChain}}` + `{"opType":0,"chainId":${chainId},"user":"${user}","events":[],"sourceChain":${chainId},"tokensIn":[{"token":"${tokenIn.token}","amount":"${tokenIn.amount}"}],"tokensOut":[{"token":"${tokenOut.token}","minAmount":"${tokenOut.minAmount}","recipient":"${tokenOut.recipient}"}],"destinationChain":${chainId}}` ) }) @@ -439,7 +305,7 @@ describe('SwapBuilder - SVM', () => { builder.addTokenOutFromTokenAmount(tokenOutAmount, recipientAddress) const swap = builder.build() - expect(swap.op).toBe(OperationType.Swap) + expect(swap.opType).toBe(OperationType.Swap) expect(swap.sourceChain).toBe(sourceChain) expect(swap.destinationChain).toBe(destinationChain) expect(swap.tokensIn[0].token).toBe(tokenInAddress.toString()) diff --git a/packages/lib-ts/tests/intents/Transfer.spec.ts b/packages/lib-ts/tests/intents/Transfer.spec.ts index 72e08144..dd8f7eeb 100644 --- a/packages/lib-ts/tests/intents/Transfer.spec.ts +++ b/packages/lib-ts/tests/intents/Transfer.spec.ts @@ -1,6 +1,6 @@ import { JSON } from 'json-as' -import { IntentEvent, OperationType, Transfer, TransferBuilder, TransferData } from '../../src/intents' +import { OperationEvent, OperationType, Transfer, TransferBuilder, TransferData } from '../../src/intents' import { ERC20Token, SPLToken, TokenAmount } from '../../src/tokens' import { Address, BigInt, Bytes, ChainId } from '../../src/types' import { @@ -20,33 +20,24 @@ describe('Transfer', () => { const tokenAddress = randomEvmAddress() const token = new ERC20Token(tokenAddress, chainId) const amount = BigInt.fromI32(1000) - const fee = BigInt.fromI32(10) const recipient = randomEvmAddress() const settler = randomSettler(chainId) setContext(1, 1, user.toString(), [settler], 'trigger-transfer') - const transfer = Transfer.create(token, amount, recipient, fee) - expect(transfer.op).toBe(OperationType.Transfer) + const transfer = new Transfer(chainId, [TransferData.fromBigInt(token, amount, recipient)]) + expect(transfer.opType).toBe(OperationType.Transfer) expect(transfer.user).toBe(user.toString()) - expect(transfer.settler).toBe(settler.address.toString()) - expect(transfer.chainId).toBe(chainId) - expect(transfer.deadline).toBe('300') - expect(transfer.nonce).toBe('0x') expect(transfer.transfers.length).toBe(1) expect(transfer.transfers[0].token).toBe(tokenAddress.toString()) expect(transfer.transfers[0].recipient).toBe(recipient.toString()) expect(transfer.transfers[0].amount).toBe(amount.toString()) - expect(transfer.maxFees.length).toBe(1) - expect(transfer.maxFees[0].token).toBe(tokenAddress.toString()) - expect(transfer.maxFees[0].amount).toBe(fee.toString()) - expect(transfer.events.length).toBe(0) expect(JSON.stringify(transfer)).toBe( - `{"op":1,"settler":"${settler.address}","user":"${user}","deadline":"300","nonce":"0x","maxFees":[{"token":"${tokenAddress}","amount":"${fee.toString()}"}],"events":[],"chainId":${chainId},"transfers":[{"token":"${tokenAddress}","amount":"${amount}","recipient":"${recipient}"}]}` + `{"opType":1,"chainId":${chainId},"user":"${user}","events":[],"transfers":[{"token":"${transfer.transfers[0].token}","amount":"${transfer.transfers[0].amount}","recipient":"${transfer.transfers[0].recipient}"}]}` ) }) @@ -56,47 +47,30 @@ describe('Transfer', () => { const tokenAddress = randomEvmAddress() const token = new ERC20Token(tokenAddress, chainId) const amount = BigInt.fromI32(1000) - const fee = BigInt.fromI32(10) const recipient = randomEvmAddress() const settler = randomSettler(chainId) - const deadline = BigInt.fromI32(9999999) setContext(1, 1, user.toString(), [settler], 'trigger-transfer') - const transfer = Transfer.create( - token, - amount, - recipient, - fee, - Address.fromString(settler.address), - user, - deadline, - null, - [new IntentEvent(Bytes.fromUTF8('topic'), Bytes.fromUTF8('data'))] - ) + const transfer = new Transfer(chainId, [TransferData.fromBigInt(token, amount, recipient)], user, [ + new OperationEvent(Bytes.fromUTF8('topic'), Bytes.fromUTF8('data')), + ]) - expect(transfer.op).toBe(OperationType.Transfer) + expect(transfer.opType).toBe(OperationType.Transfer) expect(transfer.chainId).toBe(chainId) expect(transfer.user).toBe(user.toString()) - expect(transfer.settler).toBe(settler.address.toString()) - expect(transfer.deadline).toBe(deadline.toString()) - expect(transfer.nonce).toBe('0x') expect(transfer.transfers.length).toBe(1) expect(transfer.transfers[0].token).toBe(tokenAddress.toString()) expect(transfer.transfers[0].recipient).toBe(recipient.toString()) expect(transfer.transfers[0].amount).toBe(amount.toString()) - expect(transfer.maxFees.length).toBe(1) - expect(transfer.maxFees[0].token).toBe(tokenAddress.toString()) - expect(transfer.maxFees[0].amount).toBe(fee.toString()) - expect(transfer.events.length).toBe(1) expect(transfer.events[0].topic).toBe('0x746f706963') expect(transfer.events[0].data).toBe('0x64617461') expect(JSON.stringify(transfer)).toBe( - `{"op":1,"settler":"${settler.address}","user":"${user}","deadline":"${deadline}","nonce":"0x","maxFees":[{"token":"${tokenAddress}","amount":"${fee.toString()}"}],"events":[{"topic":"0x746f706963","data":"0x64617461"}],"chainId":${chainId},"transfers":[{"token":"${tokenAddress}","amount":"${amount}","recipient":"${recipient}"}]}` + `{"opType":1,"chainId":${chainId},"user":"${user}","events":[{"topic":"0x746f706963","data":"0x64617461"}],"transfers":[{"token":"${transfer.transfers[0].token}","amount":"${transfer.transfers[0].amount}","recipient":"${transfer.transfers[0].recipient}"}]}` ) }) }) @@ -106,60 +80,28 @@ describe('Transfer', () => { const chainId = 1 const user = randomEvmAddress() const transferData = TransferData.fromI32(randomERC20Token(chainId), 5000, randomEvmAddress()) - const fee = TokenAmount.fromI32(randomERC20Token(chainId), 10) const settler = randomSettler(chainId) - const deadline = BigInt.fromI32(9999999) setContext(1, 1, user.toString(), [settler], 'trigger-transfer') - const transfer = new Transfer( - chainId, - [transferData], - [fee], - Address.fromString(settler.address), - user, - deadline, - '0x' - ) + const transfer = new Transfer(chainId, [transferData], user, []) - expect(transfer.op).toBe(OperationType.Transfer) + expect(transfer.opType).toBe(OperationType.Transfer) expect(transfer.chainId).toBe(chainId) expect(transfer.user).toBe(user.toString()) - expect(transfer.settler).toBe(settler.address.toString()) - expect(transfer.deadline).toBe(deadline.toString()) - expect(transfer.nonce).toBe('0x') expect(transfer.transfers.length).toBe(1) expect(transfer.transfers[0].token).toBe(transferData.token) expect(transfer.transfers[0].recipient).toBe(transferData.recipient) expect(transfer.transfers[0].amount).toBe(transferData.amount) - expect(transfer.maxFees.length).toBe(1) - expect(transfer.maxFees[0].token).toBe(fee.token.address.toString()) - expect(transfer.maxFees[0].amount).toBe(fee.amount.toString()) - expect(transfer.events.length).toBe(0) expect(JSON.stringify(transfer)).toBe( - `{"op":1,"settler":"${settler.address}","user":"${user}","deadline":"${deadline}","nonce":"0x","maxFees":[{"token":"${fee.token.address.toString()}","amount":"${fee.amount.toString()}"}],"events":[],"chainId":${chainId},"transfers":[{"token":"${transferData.token}","amount":"${transferData.amount}","recipient":"${transferData.recipient}"}]}` + `{"opType":1,"chainId":${chainId},"user":"${user}","events":[],"transfers":[{"token":"${transferData.token}","amount":"${transferData.amount}","recipient":"${transferData.recipient}"}]}` ) }) }) - - describe('validations', () => { - it('throws an error when transfer list is empty', () => { - expect(() => { - new Transfer(1, [], []) - }).toThrow('Transfer list cannot be empty') - }) - - it('throws an error when there is no max fee', () => { - expect(() => { - const transferData = TransferData.fromI32(randomERC20Token(1), 5000, randomEvmAddress()) - new Transfer(1, [transferData], []) - }).toThrow('At least a max fee must be specified') - }) - }) }) describe('TransferBuilder', () => { @@ -176,10 +118,9 @@ describe('TransferBuilder', () => { const builder = TransferBuilder.forChain(chainId) builder.addTransferFromTokenAmount(tokenAmount, recipientAddress) - builder.addMaxFee(TokenAmount.fromI32(randomERC20Token(chainId), 9)) const transfer = builder.build() - expect(transfer.op).toBe(OperationType.Transfer) + expect(transfer.opType).toBe(OperationType.Transfer) expect(transfer.chainId).toBe(chainId) expect(transfer.transfers.length).toBe(1) expect(transfer.transfers[0].amount).toBe('5000') @@ -194,7 +135,6 @@ describe('TransferBuilder', () => { const builder = TransferBuilder.forChain(chainId) builder.addTransferFromStringDecimal(token, '3000', recipientAddress) - builder.addMaxFee(TokenAmount.fromI32(randomERC20Token(chainId), 9)) const transfer = builder.build() expect(transfer.transfers[0].amount).toBe('3000') @@ -213,7 +153,6 @@ describe('TransferBuilder', () => { const builder = TransferBuilder.forChain(chainId) builder.addTransfers([transfer1, transfer2]) - builder.addMaxFee(TokenAmount.fromI32(randomERC20Token(chainId), 9)) const transfer = builder.build() expect(transfer.transfers.length).toBe(2) @@ -230,7 +169,6 @@ describe('TransferBuilder', () => { const builder = TransferBuilder.forChain(chainId) builder.addTransfersFromTokenAmounts(tokenAmounts, recipientAddress) - builder.addMaxFee(TokenAmount.fromI32(randomERC20Token(chainId), 9)) const transfer = builder.build() expect(transfer.transfers.length).toBe(2) @@ -240,13 +178,6 @@ describe('TransferBuilder', () => { describe('validations', () => { describe('chainId', () => { - it('throws if fee token chainId mismatches the transfer chainId', () => { - expect(() => { - const fee = TokenAmount.fromI32(randomERC20Token(ChainId.GNOSIS), 2) - TransferBuilder.forChain(chainId).addMaxFee(fee) - }).toThrow('Fee token must be on the same chain as the one requested for the transfer') - }) - it('throws if addTransferFromStringDecimal has different chainId', () => { expect(() => { const tokenAddress = Address.fromString(tokenAddressStr) @@ -280,34 +211,24 @@ describe('Transfer - SVM support', () => { const tokenAddress = randomSvmAddress() const token = SPLToken.fromAddress(tokenAddress, chainId, 9, 'SOL') const amount = BigInt.fromI32(1000) - const fee = BigInt.fromI32(10) const recipient = randomSvmAddress() const settler = randomSvmSettler() setContext(1, 1, user.toString(), [settler], 'trigger-transfer') - const transfer = Transfer.create(token, amount, recipient, fee) - expect(transfer.op).toBe(OperationType.Transfer) + const transfer = new Transfer(chainId, [TransferData.fromBigInt(token, amount, recipient)]) + expect(transfer.opType).toBe(OperationType.Transfer) expect(transfer.user).toBe(user.toString()) - expect(transfer.settler).toBe(settler.address.toString()) - expect(transfer.chainId).toBe(chainId) - expect(transfer.deadline).toBe('300') - expect(transfer.nonce).toBe('0x') - expect(transfer.isSVM()).toBe(true) expect(transfer.transfers.length).toBe(1) expect(transfer.transfers[0].token).toBe(tokenAddress.toString()) expect(transfer.transfers[0].recipient).toBe(recipient.toString()) expect(transfer.transfers[0].amount).toBe(amount.toString()) - expect(transfer.maxFees.length).toBe(1) - expect(transfer.maxFees[0].token).toBe(tokenAddress.toString()) - expect(transfer.maxFees[0].amount).toBe(fee.toString()) - expect(transfer.events.length).toBe(0) expect(JSON.stringify(transfer)).toBe( - `{"op":1,"settler":"${settler.address}","user":"${user}","deadline":"300","nonce":"0x","maxFees":[{"token":"${tokenAddress}","amount":"${fee.toString()}"}],"events":[],"chainId":${chainId},"transfers":[{"token":"${tokenAddress}","amount":"${amount}","recipient":"${recipient}"}]}` + `{"opType":1,"chainId":${chainId},"user":"${user}","events":[],"transfers":[{"token":"${transfer.transfers[0].token}","amount":"${transfer.transfers[0].amount}","recipient":"${transfer.transfers[0].recipient}"}]}` ) }) @@ -315,15 +236,13 @@ describe('Transfer - SVM support', () => { const user = randomSvmAddress() const token = SPLToken.native() const amount = BigInt.fromI32(1000) - const fee = BigInt.fromI32(10) const recipient = randomSvmAddress() const settler = randomSvmSettler() setContext(1, 1, user.toString(), [settler], 'trigger-transfer') - const transfer = Transfer.create(token, amount, recipient, fee) + const transfer = new Transfer(ChainId.SOLANA_MAINNET, [TransferData.fromBigInt(token, amount, recipient)]) expect(transfer.chainId).toBe(ChainId.SOLANA_MAINNET) - expect(transfer.isSVM()).toBe(true) expect(transfer.transfers[0].token).toBe(token.address.toString()) expect(token.isNative()).toBe(true) }) @@ -335,29 +254,15 @@ describe('Transfer - SVM support', () => { const token2 = SPLToken.fromAddress(randomSvmAddress(), chainId, 6, 'USDC') const transferData1 = TransferData.fromI32(token1, 5000, randomSvmAddress()) const transferData2 = TransferData.fromI32(token2, 1000, randomSvmAddress()) - const fee = TokenAmount.fromI32(token1, 10) const settler = randomSvmSettler() - const deadline = BigInt.fromI32(9999999) setContext(1, 1, user.toString(), [settler], 'trigger-transfer') - const transfer = new Transfer( - chainId, - [transferData1, transferData2], - [fee], - Address.fromString(settler.address), - user, - deadline, - '0x' - ) + const transfer = new Transfer(chainId, [transferData1, transferData2], user, []) - expect(transfer.op).toBe(OperationType.Transfer) + expect(transfer.opType).toBe(OperationType.Transfer) expect(transfer.chainId).toBe(chainId) - expect(transfer.isSVM()).toBe(true) expect(transfer.user).toBe(user.toString()) - expect(transfer.settler).toBe(settler.address.toString()) - expect(transfer.deadline).toBe(deadline.toString()) - expect(transfer.nonce).toBe('0x') expect(transfer.transfers.length).toBe(2) expect(transfer.transfers[0].token).toBe(transferData1.token) @@ -366,10 +271,6 @@ describe('Transfer - SVM support', () => { expect(transfer.transfers[1].token).toBe(transferData2.token) expect(transfer.transfers[1].recipient).toBe(transferData2.recipient) expect(transfer.transfers[1].amount).toBe(transferData2.amount) - - expect(transfer.maxFees.length).toBe(1) - expect(transfer.maxFees[0].token).toBe(fee.token.address.toString()) - expect(transfer.maxFees[0].amount).toBe(fee.amount.toString()) }) }) }) @@ -386,12 +287,10 @@ describe('TransferBuilder - SVM support', () => { const builder = TransferBuilder.forChain(chainId) builder.addTransferFromTokenAmount(tokenAmount, recipientAddress) - builder.addMaxFee(TokenAmount.fromI32(token, 9)) const transfer = builder.build() - expect(transfer.op).toBe(OperationType.Transfer) + expect(transfer.opType).toBe(OperationType.Transfer) expect(transfer.chainId).toBe(chainId) - expect(transfer.isSVM()).toBe(true) expect(transfer.transfers.length).toBe(1) expect(transfer.transfers[0].amount).toBe('5000000000000') expect(transfer.transfers[0].recipient).toBe(recipientAddress.toString()) @@ -405,7 +304,6 @@ describe('TransferBuilder - SVM support', () => { const builder = TransferBuilder.forChain(chainId) builder.addTransferFromStringDecimal(token, '3000', recipientAddress) - builder.addMaxFee(TokenAmount.fromI32(token, 9)) const transfer = builder.build() expect(transfer.transfers[0].amount).toBe('3000000000000') @@ -423,7 +321,6 @@ describe('TransferBuilder - SVM support', () => { const builder = TransferBuilder.forChain(chainId) builder.addTransfers([transfer1, transfer2]) - builder.addMaxFee(TokenAmount.fromI32(token, 9)) const transfer = builder.build() expect(transfer.transfers.length).toBe(2) @@ -439,7 +336,6 @@ describe('TransferBuilder - SVM support', () => { const builder = TransferBuilder.forChain(chainId) builder.addTransfersFromTokenAmounts(tokenAmounts, recipientAddress) - builder.addMaxFee(TokenAmount.fromI32(token, 9)) const transfer = builder.build() expect(transfer.transfers.length).toBe(2) @@ -448,41 +344,24 @@ describe('TransferBuilder - SVM support', () => { it('creates a complete Solana Transfer with all builder methods', () => { const user = randomSvmAddress() - const settler = randomSvmAddress() const token = SPLToken.native() const recipient = randomSvmAddress() - const deadline = BigInt.fromI32(9999999) - const nonce = '0x123456' + const settler = randomSvmSettler() + + setContext(1, 1, user.toString(), [settler], 'trigger-transfer') const builder = TransferBuilder.forChain(chainId) - builder.addSettler(settler) - builder.addUser(user) - builder.addDeadline(deadline) - builder.addNonce(nonce) builder.addTransferFromI32(token, 1000, recipient) - builder.addMaxFee(TokenAmount.fromI32(token, 10)) const transfer = builder.build() - expect(transfer.op).toBe(OperationType.Transfer) + expect(transfer.opType).toBe(OperationType.Transfer) expect(transfer.chainId).toBe(chainId) - expect(transfer.isSVM()).toBe(true) expect(transfer.user).toBe(user.toString()) - expect(transfer.settler).toBe(settler.toString()) - expect(transfer.deadline).toBe(deadline.toString()) - expect(transfer.nonce).toBe(nonce) expect(transfer.transfers.length).toBe(1) - expect(transfer.maxFees.length).toBe(1) }) }) describe('chainId validations', () => { - it('throws if fee token chainId mismatches the Solana transfer chainId', () => { - expect(() => { - const fee = TokenAmount.fromI32(randomERC20Token(ChainId.ETHEREUM), 2) - TransferBuilder.forChain(chainId).addMaxFee(fee) - }).toThrow('Fee token must be on the same chain') - }) - it('throws if addTransferFromStringDecimal has different chainId for Solana', () => { expect(() => { const tokenAddress = randomSvmAddress() diff --git a/packages/test-ts/src/RunnerMock.ts b/packages/test-ts/src/RunnerMock.ts index c4e940ed..14860666 100644 --- a/packages/test-ts/src/RunnerMock.ts +++ b/packages/test-ts/src/RunnerMock.ts @@ -159,10 +159,7 @@ export default class RunnerMock { private getDefaultEnvImports(): WebAssembly.ModuleImports { const defaultResult = (data: unknown) => '{"success":"true","data":' + JSON.stringify(data) + ',"error":""}' return { - _evmCall: this.createLogFn('_evmCall'), - _svmCall: this.createLogFn('_svmCall'), - _swap: this.createLogFn('_swap'), - _transfer: this.createLogFn('_transfer'), + _sendIntent: this.createLogFn('_sendIntent'), _tokenPriceQuery: this.createMockFunction('_tokenPriceQuery', { default: defaultResult([]) }), _relevantTokensQuery: this.createMockFunction('_relevantTokensQuery', { default: defaultResult([]) }), _evmCallQuery: this.createMockFunction('_evmCallQuery', { default: defaultResult('0x') }), diff --git a/packages/test-ts/src/types.ts b/packages/test-ts/src/types.ts index a606db41..29d8ef5c 100644 --- a/packages/test-ts/src/types.ts +++ b/packages/test-ts/src/types.ts @@ -98,34 +98,46 @@ export type GenerateMockParams = { export type RunFunctionOptionalParams = Partial> -export type IntentBase = { - op: number - settler: string +export type OperationBase = { + opType: number user: string - deadline: string - nonce: string - maxFees: { token: string; amount: string }[] + chainId: number events: { topic: string; data: string }[] } -export type Transfer = IntentBase & { - chainId: number +export type TransferOperation = OperationBase & { transfers: { token: string; amount: string; recipient: string }[] } -export type Swap = IntentBase & { +export type SwapOperation = OperationBase & { sourceChain: number destinationChain: number tokensIn: { token: string; amount: string }[] tokensOut: { token: string; minAmount: string; recipient: string }[] } -export type Call = IntentBase & { - chainId: number +export type CallOperation = OperationBase & { calls: { target: string; data: string; value: string }[] } -export type Intent = Transfer | Swap | Call +export type SvmCallOperation = OperationBase & { + instructions: { + programId: string + accountsMeta: { pubkey: string; isSigner: boolean; isWritable: boolean }[] + data: string + }[] +} + +export type Operation = TransferOperation | SwapOperation | CallOperation | SvmCallOperation + +export type Intent = { + settler: string + feePayer: string + deadline: string + nonce: string + maxFees: { token: string; amount: string }[] + operations: Operation[] +} export type OracleResponse = AnyOracleResponse diff --git a/packages/test-ts/src/utils.ts b/packages/test-ts/src/utils.ts index 9950637e..87e542ae 100644 --- a/packages/test-ts/src/utils.ts +++ b/packages/test-ts/src/utils.ts @@ -10,13 +10,15 @@ import { import { Wallet } from 'ethers' import { - Call, + CallOperation, Intent, + Operation, OracleResponse, QueryMock, QueryProcessor, - Swap, - Transfer, + SvmCallOperation, + SwapOperation, + TransferOperation, ValidationErrorContext, } from './types' @@ -24,13 +26,23 @@ const SIGNER = new OracleSigner(EthersSigner.fromPrivateKey(Wallet.createRandom( export function toIntents(intentsJson: string) { const raw = JSON.parse(intentsJson) - return raw.map((intent: Partial) => { - if (intent.op == OpType.Swap) { - const { sourceChain, destinationChain } = intent as Swap - return { ...intent, sourceChain: Number(sourceChain), destinationChain: Number(destinationChain) } - } else { - const { chainId } = intent as Transfer | Call - return { ...intent, chainId: Number(chainId) } + return raw.map((intent: Intent) => { + return { + ...intent, + operations: intent.operations.map((operation: Operation) => { + if (operation.opType == OpType.Swap) { + const swap = operation as SwapOperation + return { + ...swap, + chainId: Number(swap.chainId), + sourceChain: Number(swap.sourceChain), + destinationChain: Number(swap.destinationChain), + } + } + + const nonSwap = operation as TransferOperation | CallOperation | SvmCallOperation + return { ...nonSwap, chainId: Number(nonSwap.chainId) } + }), } }) } From 5058681ad1d3a72a18288ce555a24a163c69028d Mon Sep 17 00:00:00 2001 From: Agustincito Date: Fri, 20 Mar 2026 15:14:36 -0300 Subject: [PATCH 02/17] Add changeset --- .changeset/late-cobras-turn.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/late-cobras-turn.md diff --git a/.changeset/late-cobras-turn.md b/.changeset/late-cobras-turn.md new file mode 100644 index 00000000..5ed0a1b3 --- /dev/null +++ b/.changeset/late-cobras-turn.md @@ -0,0 +1,7 @@ +--- +"@mimicprotocol/test-ts": patch +"@mimicprotocol/lib-ts": patch +"@mimicprotocol/cli": patch +--- + +Refactor intents to have operations From 5ff911c6847c68081144da1756081bbc595d5728 Mon Sep 17 00:00:00 2001 From: Agustincito Date: Tue, 31 Mar 2026 09:54:56 -0300 Subject: [PATCH 03/17] Add crosschain swap and checks --- packages/lib-ts/src/intents/Intent.ts | 14 ++++++-- packages/lib-ts/src/intents/Operation.ts | 1 + packages/lib-ts/src/intents/Swap.ts | 17 ++------- packages/lib-ts/tests/intents/Intent.spec.ts | 36 ++++++++++++++++++- packages/lib-ts/tests/intents/SvmCall.spec.ts | 6 ++-- packages/lib-ts/tests/intents/Swap.spec.ts | 6 ++-- packages/test-ts/src/utils.ts | 2 +- 7 files changed, 57 insertions(+), 25 deletions(-) diff --git a/packages/lib-ts/src/intents/Intent.ts b/packages/lib-ts/src/intents/Intent.ts index 1cea25fe..b6aff772 100644 --- a/packages/lib-ts/src/intents/Intent.ts +++ b/packages/lib-ts/src/intents/Intent.ts @@ -7,7 +7,7 @@ import { SvmAccountMeta } from '../types/svm/SvmAccountMeta' import { EvmCall, EvmCallData } from './Call/EvmCall' import { SvmCall, SvmInstruction } from './Call/SvmCall' -import { Operation, OperationBuilder, OperationEvent } from './Operation' +import { Operation, OperationBuilder, OperationEvent, OperationType } from './Operation' import { Swap, SwapTokenIn, SwapTokenOut } from './Swap' import { Transfer, TransferData } from './Transfer' @@ -333,8 +333,18 @@ export class Intent { this.operations = operations || [] if (this.operations.length === 0) throw new Error('Operation list cannot be empty') - this.maxFees = maxFees ? maxFees.map((fee: TokenAmount) => MaxFee.fromTokenAmount(fee)) : [] const defaultChainId = this.operations[0].chainId + for (let i = 0; i < this.operations.length; i++) { + const operation = this.operations[i] + + if (operation.chainId !== defaultChainId) throw new Error('All operations must have the same chainId') + + if (operation.opType === OperationType.CrossChainSwap) { + if (this.operations.length > 1) throw new Error('Cross-chain swap must be the only operation in an intent') + } + } + + this.maxFees = maxFees ? maxFees.map((fee: TokenAmount) => MaxFee.fromTokenAmount(fee)) : [] this.settler = settler ? settler.toString() : context.findSettler(defaultChainId).toString() this.feePayer = feePayer ? feePayer.toString() : context.user.toString() this.deadline = deadline ? deadline.toString() : (context.timestamp / 1000 + DEFAULT_DEADLINE).toString() diff --git a/packages/lib-ts/src/intents/Operation.ts b/packages/lib-ts/src/intents/Operation.ts index d6baa9ec..cf93f7cc 100644 --- a/packages/lib-ts/src/intents/Operation.ts +++ b/packages/lib-ts/src/intents/Operation.ts @@ -6,6 +6,7 @@ export enum OperationType { Swap, Transfer, EvmCall, + CrossChainSwap, SvmCall, } diff --git a/packages/lib-ts/src/intents/Swap.ts b/packages/lib-ts/src/intents/Swap.ts index 9a43adc3..0fa748e1 100644 --- a/packages/lib-ts/src/intents/Swap.ts +++ b/packages/lib-ts/src/intents/Swap.ts @@ -392,20 +392,6 @@ export class SwapTokenOut { */ @json export class Swap extends Operation { - /** - * Creates a simple single-chain swap intent. - * @param chainId - The blockchain network identifier - * @param tokenIn - The input token - * @param amountIn - The amount to swap from - * @param tokenOut - The output token - * @param minAmountOut - The minimum amount to receive - * @param settler - The settler address (optional) - * @param recipient - The recipient address (optional) - * @param deadline - The deadline timestamp (optional) - * @param nonce - The nonce for replay protection (optional) - * @returns A new Swap instance - */ - /** * Creates a new Swap intent. * @param sourceChain - The source blockchain network identifier @@ -426,7 +412,8 @@ export class Swap extends Operation { user: Address | null = null, events: OperationEvent[] | null = null ) { - super(OperationType.Swap, sourceChain, user, events) + const opType = sourceChain == destinationChain ? OperationType.Swap : OperationType.CrossChainSwap + super(opType, sourceChain, user, events) if (tokensIn.length === 0) throw new Error('TokenIn list cannot be empty') if (tokensOut.length === 0) throw new Error('TokenOut list cannot be empty') } diff --git a/packages/lib-ts/tests/intents/Intent.spec.ts b/packages/lib-ts/tests/intents/Intent.spec.ts index 84c7905d..710f574b 100644 --- a/packages/lib-ts/tests/intents/Intent.spec.ts +++ b/packages/lib-ts/tests/intents/Intent.spec.ts @@ -2,7 +2,7 @@ import { JSON } from 'json-as' import { SerializableSettler } from '../../src/context' import { NULL_ADDRESS } from '../../src/helpers' -import { EvmCallBuilder, IntentBuilder } from '../../src/intents' +import { EvmCallBuilder, IntentBuilder, SwapBuilder } from '../../src/intents' import { TokenAmount } from '../../src/tokens' import { Address, BigInt, Bytes } from '../../src/types' import { randomERC20Token, randomSettler, setContext } from '../helpers' @@ -123,4 +123,38 @@ describe('IntentBuilder', () => { }).toThrow('Operation list cannot be empty') }) }) + + describe('when operations have different chainIds', () => { + it('throws an error', () => { + const settler = randomSettler(chainId) + setContext(0, 1, '0x0000000000000000000000000000000000000002', [settler], 'trigger-call') + + expect(() => { + new IntentBuilder() + .addOperationBuilder(EvmCallBuilder.forChain(1).addCall(Address.fromString(targetAddressStr))) + .addOperationBuilder(EvmCallBuilder.forChain(10).addCall(Address.fromString(targetAddressStr))) + .build() + }).toThrow('All operations must have the same chainId') + }) + }) + + describe('when a cross-chain swap is combined with another operation', () => { + it('throws an error', () => { + const settler = randomSettler(chainId) + setContext(0, 1, '0x0000000000000000000000000000000000000002', [settler], 'trigger-call') + + expect(() => { + const tokenIn = randomERC20Token(1) + const tokenOut = randomERC20Token(10) + new IntentBuilder() + .addOperationBuilder( + SwapBuilder.forChains(1, 10) + .addTokenInFromStringDecimal(tokenIn, '1') + .addTokenOutFromStringDecimal(tokenOut, '1', Address.fromString(targetAddressStr)) + ) + .addOperationBuilder(EvmCallBuilder.forChain(1).addCall(Address.fromString(targetAddressStr))) + .build() + }).toThrow('Cross-chain swap must be the only operation in an intent') + }) + }) }) diff --git a/packages/lib-ts/tests/intents/SvmCall.spec.ts b/packages/lib-ts/tests/intents/SvmCall.spec.ts index ac0b75a0..d54bb33e 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":3,"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":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()}"}]}` ) }) @@ -69,7 +69,7 @@ describe('SvmCall', () => { expect(svmCall.events[0].data).toBe('0x64617461') expect(JSON.stringify(svmCall)).toBe( - `{"opType":3,"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":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()}"}]}` ) }) }) @@ -107,7 +107,7 @@ describe('SvmCall', () => { expect(svmCall.events.length).toBe(0) expect(JSON.stringify(svmCall)).toBe( - `{"opType":3,"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":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()}"}]}` ) }) }) diff --git a/packages/lib-ts/tests/intents/Swap.spec.ts b/packages/lib-ts/tests/intents/Swap.spec.ts index 92d819d9..192cae74 100644 --- a/packages/lib-ts/tests/intents/Swap.spec.ts +++ b/packages/lib-ts/tests/intents/Swap.spec.ts @@ -56,7 +56,7 @@ describe('Swap', () => { setContext(1, 1, user.toString(), [settler], 'trigger-456') const swap = new Swap(chainId, [tokenIn], [tokenOut], destinationChain, randomUser, []) - expect(swap.opType).toBe(OperationType.Swap) + expect(swap.opType).toBe(OperationType.CrossChainSwap) expect(swap.user).toBe(randomUser.toString()) expect(swap.sourceChain).toBe(chainId) @@ -73,7 +73,7 @@ describe('Swap', () => { expect(swap.events.length).toBe(0) expect(JSON.stringify(swap)).toBe( - `{"opType":0,"chainId":${chainId},"user":"${randomUser}","events":[],"sourceChain":${chainId},"tokensIn":[{"token":"${tokenIn.token}","amount":"${tokenIn.amount}"}],"tokensOut":[{"token":"${tokenOut.token}","minAmount":"${tokenOut.minAmount}","recipient":"${tokenOut.recipient}"}],"destinationChain":${destinationChain}}` + `{"opType":3,"chainId":${chainId},"user":"${randomUser}","events":[],"sourceChain":${chainId},"tokensIn":[{"token":"${tokenIn.token}","amount":"${tokenIn.amount}"}],"tokensOut":[{"token":"${tokenOut.token}","minAmount":"${tokenOut.minAmount}","recipient":"${tokenOut.recipient}"}],"destinationChain":${destinationChain}}` ) }) @@ -115,7 +115,7 @@ describe('SwapBuilder', () => { builder.addTokenOutFromTokenAmount(tokenOutAmount, recipientAddress) const swap = builder.build() - expect(swap.opType).toBe(OperationType.Swap) + expect(swap.opType).toBe(OperationType.CrossChainSwap) expect(swap.sourceChain).toBe(sourceChain) expect(swap.destinationChain).toBe(destinationChain) expect(swap.tokensIn[0].token).toBe(tokenInAddress.toString()) diff --git a/packages/test-ts/src/utils.ts b/packages/test-ts/src/utils.ts index 87e542ae..529eee6f 100644 --- a/packages/test-ts/src/utils.ts +++ b/packages/test-ts/src/utils.ts @@ -30,7 +30,7 @@ export function toIntents(intentsJson: string) { return { ...intent, operations: intent.operations.map((operation: Operation) => { - if (operation.opType == OpType.Swap) { + if (operation.opType == OpType.Swap || operation.opType == OpType.CrossChainSwap) { const swap = operation as SwapOperation return { ...swap, From d45680eab04ee654726a537b779e7c645e98e257 Mon Sep 17 00:00:00 2001 From: Agustincito Date: Tue, 7 Apr 2026 10:18:03 -0300 Subject: [PATCH 04/17] Bump runner version to v0.0.2 --- .changeset/late-cobras-turn.md | 2 +- packages/lib-ts/constants.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/late-cobras-turn.md b/.changeset/late-cobras-turn.md index 5ed0a1b3..eee70ad3 100644 --- a/.changeset/late-cobras-turn.md +++ b/.changeset/late-cobras-turn.md @@ -4,4 +4,4 @@ "@mimicprotocol/cli": patch --- -Refactor intents to have operations +Refactor intents to have operations and bump to runner version v0.0.2 diff --git a/packages/lib-ts/constants.js b/packages/lib-ts/constants.js index 8fbdd531..0500e6e1 100644 --- a/packages/lib-ts/constants.js +++ b/packages/lib-ts/constants.js @@ -1 +1 @@ -export const RUNNER_TARGET_VERSION = '0.0.1' +export const RUNNER_TARGET_VERSION = '0.0.2' From 7a32c4fd0180d2c6125e362d949263538ca0ba1c Mon Sep 17 00:00:00 2001 From: Agustincito Date: Mon, 13 Apr 2026 09:22:10 -0300 Subject: [PATCH 05/17] Add three intents into one operation --- .../tests/004-three-intents/expected.log | 1 + .../tests/004-three-intents/src/function.ts | 14 ++++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/integration/tests/004-three-intents/expected.log b/packages/integration/tests/004-three-intents/expected.log index 293967ca..2259357e 100644 --- a/packages/integration/tests/004-three-intents/expected.log +++ b/packages/integration/tests/004-three-intents/expected.log @@ -1,3 +1,4 @@ _sendIntent: {"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","amount":"10000000"}],"operations":[{"opType":2,"chainId":1,"user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","events":[],"calls":[{"target":"0x0000000000000000000000000000000000000001","data":"0x","value":"0"},{"target":"0x0000000000000000000000000000000000000001","data":"0x7b000000","value":"0"}]}]} _sendIntent: {"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[],"operations":[{"opType":0,"chainId":1,"user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","events":[],"sourceChain":1,"tokensIn":[{"token":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","amount":"100000000"}],"tokensOut":[{"token":"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599","minAmount":"9500000000","recipient":"0x0000000000000000000000000000000000000001"}],"destinationChain":1}]} _sendIntent: {"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","amount":"10000000"}],"operations":[{"opType":1,"chainId":1,"user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","events":[],"transfers":[{"token":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","amount":"100000000","recipient":"0x0000000000000000000000000000000000000001"}]}]} +_sendIntent: {"settler":"0x6b175474e89094c44da98b954eedeac495271d0f","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","amount":"10000000"}],"operations":[{"opType":2,"chainId":1,"user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","events":[],"calls":[{"target":"0x0000000000000000000000000000000000000001","data":"0x","value":"0"},{"target":"0x0000000000000000000000000000000000000001","data":"0x7b000000","value":"0"}]},{"opType":0,"chainId":1,"user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","events":[],"sourceChain":1,"tokensIn":[{"token":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","amount":"100000000"}],"tokensOut":[{"token":"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599","minAmount":"9500000000","recipient":"0x0000000000000000000000000000000000000001"}],"destinationChain":1},{"opType":1,"chainId":1,"user":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","events":[],"transfers":[{"token":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","amount":"100000000","recipient":"0x0000000000000000000000000000000000000001"}]}]} diff --git a/packages/integration/tests/004-three-intents/src/function.ts b/packages/integration/tests/004-three-intents/src/function.ts index f875f92c..f70429e2 100644 --- a/packages/integration/tests/004-three-intents/src/function.ts +++ b/packages/integration/tests/004-three-intents/src/function.ts @@ -4,6 +4,7 @@ import { Bytes, ERC20Token, EvmCallBuilder, + IntentBuilder, SwapBuilder, TokenAmount, TransferBuilder, @@ -22,21 +23,26 @@ export default function main(): void { const bytes = Bytes.fromI32(123) const callFee = TokenAmount.fromI32(USDC, 10) - EvmCallBuilder.forChain(chainId).addCall(target).addCall(target, bytes).send(callFee) + const evmcall = EvmCallBuilder.forChain(chainId).addCall(target).addCall(target, bytes) + evmcall.send(callFee) // Normal swap const minAmountOut = BigInt.fromI32(inputs.amount).times(BigInt.fromI32(inputs.slippage)).div(BigInt.fromI32(100)) const tokenIn = TokenAmount.fromI32(USDC, inputs.amount) const tokenOut = TokenAmount.fromStringDecimal(WBTC, minAmountOut.toString()) - SwapBuilder.forChains(chainId, chainId) + const swap = SwapBuilder.forChains(chainId, chainId) .addTokenInFromTokenAmount(tokenIn) .addTokenOutFromTokenAmount(tokenOut, target) - .send() + swap.send() // Normal Transfer const tokenAmount = TokenAmount.fromI32(USDC, inputs.amount) const transferFee = TokenAmount.fromI32(USDC, 10) - TransferBuilder.forChain(chainId).addTransferFromTokenAmount(tokenAmount, target).send(transferFee) + const transfer = TransferBuilder.forChain(chainId).addTransferFromTokenAmount(tokenAmount, target) + transfer.send(transferFee) + + // Same operations grouped in a single intent + new IntentBuilder().addMaxFee(callFee).addOperationsBuilders([evmcall, swap, transfer]).send() } From 6d546ed1a84a620c648fae977bf4525cd06502d0 Mon Sep 17 00:00:00 2001 From: Agustincito Date: Mon, 13 Apr 2026 09:22:30 -0300 Subject: [PATCH 06/17] Fix solana test --- packages/integration/tests/009-solana-intents/expected.log | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/integration/tests/009-solana-intents/expected.log b/packages/integration/tests/009-solana-intents/expected.log index e70ee4eb..024db06b 100644 --- a/packages/integration/tests/009-solana-intents/expected.log +++ b/packages/integration/tests/009-solana-intents/expected.log @@ -1,3 +1,3 @@ _sendIntent: {"settler":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"So11111111111111111111111111111111111111112","amount":"1"}],"operations":[{"opType":1,"chainId":507424,"user":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K","events":[],"transfers":[{"token":"So11111111111111111111111111111111111111112","amount":"4000000000000","recipient":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K"}]}]} _sendIntent: {"settler":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[],"operations":[{"opType":0,"chainId":507424,"user":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K","events":[],"sourceChain":507424,"tokensIn":[{"token":"So11111111111111111111111111111111111111112","amount":"1000000000000"}],"tokensOut":[{"token":"So11111111111111111111111111111111111111112","minAmount":"2000","recipient":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K"}],"destinationChain":507424}]} -_sendIntent: {"settler":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"So11111111111111111111111111111111111111112","amount":"1"}],"operations":[{"opType":3,"chainId":507424,"user":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K","events":[],"instructions":[{"programId":"TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb","accountsMeta":[{"pubkey":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K","isWritable":false,"isSigner":true},{"pubkey":"So11111111111111111111111111111111111111112","isWritable":true,"isSigner":false}],"data":"0xabcd"}]}]} +_sendIntent: {"settler":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"So11111111111111111111111111111111111111112","amount":"1"}],"operations":[{"opType":4,"chainId":507424,"user":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K","events":[],"instructions":[{"programId":"TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb","accountsMeta":[{"pubkey":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K","isWritable":false,"isSigner":true},{"pubkey":"So11111111111111111111111111111111111111112","isWritable":true,"isSigner":false}],"data":"0xabcd"}]}]} From a99a40689719a3b832edd73bf4a24889c9c3b81f Mon Sep 17 00:00:00 2001 From: lgalende <63872655+lgalende@users.noreply.github.com> Date: Wed, 29 Apr 2026 11:01:29 -0300 Subject: [PATCH 07/17] Lib: Add dynamic call operation (#225) --- .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 | 29 ++ .../lib-ts/src/intents/Call/EvmDynamicCall.ts | 272 ++++++++++++++++++ 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 | 216 ++++++++++++++ packages/lib-ts/tests/intents/Intent.spec.ts | 38 ++- packages/lib-ts/tests/intents/SvmCall.spec.ts | 6 +- packages/test-ts/src/types.ts | 6 +- 13 files changed, 619 insertions(+), 5 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..dedf9859 --- /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"}]}]}]} 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..e4da9636 --- /dev/null +++ b/packages/integration/tests/014-swap-and-dynamic-call/src/function.ts @@ -0,0 +1,29 @@ +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), + ]) + + 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 new file mode 100644 index 00000000..3d8d9fe5 --- /dev/null +++ b/packages/lib-ts/src/intents/Call/EvmDynamicCall.ts @@ -0,0 +1,272 @@ +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, +} + +/** + * 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 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, + args: EvmDynamicArg[] = [], + value: BigInt = BigInt.zero() + ): EvmDynamicCallBuilder { + this.calls.push(new EvmDynamicCallData(target, selector, args, 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), + 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 single dynamic argument in a dynamic call. + */ +@json +export class EvmDynamicArg { + public kind: EvmDynamicArgKind + public data: string + public isDynamic: bool + + /** + * Creates a literal dynamic argument from ABI-encoded parameters. + * @param parameters - The ABI parameters to encode as a literal argument + * @param isDynamic - Whether the resolved argument is ABI-dynamic + * @returns A new literal dynamic argument + */ + static literal(parameters: EvmEncodeParam[], isDynamic: bool): EvmDynamicArg { + return new EvmDynamicArg(EvmDynamicArgKind.Literal, Bytes.fromHexString(evm.encode(parameters)), isDynamic) + } + + /** + * Creates a variable reference dynamic argument. + * @param opIndex - The referenced operation index + * @param subIndex - The referenced output index within the operation + * @param isDynamic - Whether the resolved argument is ABI-dynamic + * @returns A new variable dynamic argument + */ + static variable(opIndex: u32, subIndex: u32, isDynamic: bool): EvmDynamicArg { + return new EvmDynamicArg( + EvmDynamicArgKind.Variable, + Bytes.fromHexString( + evm.encode([ + EvmEncodeParam.fromValue('uint256', BigInt.fromU32(opIndex)), + EvmEncodeParam.fromValue('uint256', BigInt.fromU32(subIndex)), + ]) + ), + isDynamic + ) + } + + /** + * Creates a new EvmDynamicArg instance. + * @param kind - The argument resolution strategy + * @param data - The ABI-encoded argument data + * @param isDynamic - Whether the resolved argument is ABI-dynamic + */ + constructor(kind: EvmDynamicArgKind, data: Bytes, isDynamic: bool) { + this.kind = kind + this.data = data.toHexString() + this.isDynamic = isDynamic + } +} + +/** + * 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 args - The dynamic arguments for the call + * @param value - The native token value to send + */ + 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(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), argument.isDynamic) + } + } +} + +/** + * 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..0c884ca3 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 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 + * @returns This IntentBuilder instance for method chaining + */ + addEvmDynamicCallOperation( + chainId: ChainId, + target: Address, + selector: Bytes, + 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, args, 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..164f5972 --- /dev/null +++ b/packages/lib-ts/tests/intents/EvmDynamicCall.spec.ts @@ -0,0 +1,216 @@ +import { JSON } from 'json-as' + +import { + EvmDynamicArg, + EvmDynamicArgKind, + EvmDynamicCall, + EvmDynamicCallBuilder, + EvmDynamicCallData, + 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), false) + 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(call.calls[0].arguments[0].isDynamic).toBe(false) + + 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}","isDynamic":false}]}]}` + ) + }) + + 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), true) + 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(call.calls[0].arguments[0].isDynamic).toBe(true) + 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}","isDynamic":true}]}]}` + ) + }) + + 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), false) + const argument2 = new EvmDynamicArg(EvmDynamicArgKind.Variable, randomBytes(64), false) + + 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].arguments[0].isDynamic).toBe(false) + 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].arguments[0].isDynamic).toBe(false) + 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}","isDynamic":false}]},{"target":"${target2}","value":"2","selector":"${selector2.toHexString()}","arguments":[{"kind":1,"data":"${argument2.data}","isDynamic":false}]}]}` + ) + }) + + 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', () => { + setEvmEncode('uint256', '1', '0x1234') + + const argument = EvmDynamicArg.literal([EvmEncodeParam.fromValue('uint256', BigInt.fromI32(1))], false) + + expect(argument.kind).toBe(EvmDynamicArgKind.Literal) + expect(argument.data).toBe('0x1234') + expect(argument.isDynamic).toBe(false) + }) + + it('encodes dynamic literal arguments', () => { + setEvmEncode('string', 'foo', '0x1234') + + const argument = EvmDynamicArg.literal([EvmEncodeParam.fromValue('string', 'foo')], true) + + expect(argument.kind).toBe(EvmDynamicArgKind.Literal) + expect(argument.data).toBe('0x1234') + expect(argument.isDynamic).toBe(true) + }) + + it('encodes variable references', () => { + setEvmEncode('uint256', '1', '0x5678') + + const argument = EvmDynamicArg.variable(1, 0, false) + + expect(argument.kind).toBe(EvmDynamicArgKind.Variable) + expect(argument.data).toBe('0x5678') + expect(argument.isDynamic).toBe(false) + }) + + it('encodes dynamic variable references', () => { + setEvmEncode('uint256', '1', '0x5678') + + const argument = EvmDynamicArg.variable(1, 0, true) + + expect(argument.kind).toBe(EvmDynamicArgKind.Variable) + expect(argument.data).toBe('0x5678') + expect(argument.isDynamic).toBe(true) + }) +}) + +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), false)], + BigInt.fromString('1') + ) + builder.addCall( + target2, + selector2, + [new EvmDynamicArg(EvmDynamicArgKind.Variable, randomBytes(64), false)], + 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..cf23d86b 100644 --- a/packages/lib-ts/tests/intents/Intent.spec.ts +++ b/packages/lib-ts/tests/intents/Intent.spec.ts @@ -2,11 +2,20 @@ 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' +/* eslint-disable no-secrets/no-secrets */ + describe('IntentBuilder', () => { const chainId = 1 const targetAddressStr = '0x0000000000000000000000000000000000000001' @@ -157,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'), false)], + 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') + }) + }) }) 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 From d145063edf692288f1e9f47ba10d2a4c2cc07847 Mon Sep 17 00:00:00 2001 From: lgalende Date: Wed, 29 Apr 2026 12:03:04 -0300 Subject: [PATCH 08/17] chore: upgrade sdk to v0.0.2-rc.1 and runner to v0.1.0-rc.1 --- packages/test-ts/package.json | 4 +- yarn.lock | 180 +++++++++++++++++++++++----------- 2 files changed, 127 insertions(+), 57 deletions(-) diff --git a/packages/test-ts/package.json b/packages/test-ts/package.json index 375351ca..1df0bb41 100644 --- a/packages/test-ts/package.json +++ b/packages/test-ts/package.json @@ -14,8 +14,8 @@ "dist" ], "dependencies": { - "@mimicprotocol/runner-node": "^0.0.1-rc.14", - "@mimicprotocol/sdk": "^0.0.1-rc.33", + "@mimicprotocol/runner-node": "^0.1.0-rc.1", + "@mimicprotocol/sdk": "^0.0.2-rc.1", "ethers": "^6.15.0", "zod": "^3.24.1" }, diff --git a/yarn.lock b/yarn.lock index 75a2ae9c..7cd23d8d 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" @@ -645,59 +677,60 @@ globby "^11.0.0" read-yaml-file "^1.1.0" -"@mimicprotocol/runner-node-darwin-arm64@0.0.1-rc.14": - version "0.0.1-rc.14" - resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-darwin-arm64/-/runner-node-darwin-arm64-0.0.1-rc.14.tgz#5eebff6872b02496bc88e9bdc4aff93d7304ac94" - integrity sha512-puFuzPwfMWNX6IHWU6O6oI240vJJBfhaQHv/A/TVg4sz/PSCKlGDw01CPJphnYg94EXBZbVmZvRFqRHpur3XhA== - -"@mimicprotocol/runner-node-darwin-x64@0.0.1-rc.14": - version "0.0.1-rc.14" - resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-darwin-x64/-/runner-node-darwin-x64-0.0.1-rc.14.tgz#532e1d3ac8cedc4a281c68facbc184f977a2347a" - integrity sha512-Lzt6ASRAUPXoaE7nBK4OlK3QO1l/2plwbPi8nVohuJaABXOiBwIfokntZR3qgHWTExTJ98TuxoGlpOCreSYkrw== - -"@mimicprotocol/runner-node-linux-arm64-gnu@0.0.1-rc.14": - version "0.0.1-rc.14" - resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-linux-arm64-gnu/-/runner-node-linux-arm64-gnu-0.0.1-rc.14.tgz#e7abc5e190bd3dbb0bc8c3851636229b11bd4cb8" - integrity sha512-Tg7uO6PW20rdPaCFDSL4WaCEQ+KIaw7uhma9bW+7UYfZPvTTl+nTuB6iI2phufjGCUMKVXVPBoCtSV8YHf/12w== - -"@mimicprotocol/runner-node-linux-x64-gnu@0.0.1-rc.14": - version "0.0.1-rc.14" - resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-linux-x64-gnu/-/runner-node-linux-x64-gnu-0.0.1-rc.14.tgz#76c352c59b5b6ca470bfdd10a0fff675492b2741" - integrity sha512-d/0yRvkp++ay6A78Rg8s+Cecnn16zbglJjNCzN05kVMhPFmjCv/teEAVI//FmnbpEhI5WppnZ+vxFTdY+bHCKA== - -"@mimicprotocol/runner-node-linux-x64-musl@0.0.1-rc.14": - version "0.0.1-rc.14" - resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-linux-x64-musl/-/runner-node-linux-x64-musl-0.0.1-rc.14.tgz#04cc31477d90842f3ca37a30f135822301160dbe" - integrity sha512-S+n+bwURJ9X253lxFduYZIsfZ6EWSTM2OSohqAPqh1OQPJ+Ei5r4AWyIwVBC30kFh80opm+GShWAZIeywUs9jw== - -"@mimicprotocol/runner-node-win32-arm64-msvc@0.0.1-rc.14": - version "0.0.1-rc.14" - resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-win32-arm64-msvc/-/runner-node-win32-arm64-msvc-0.0.1-rc.14.tgz#f7c3f9491cf08ec42085b1305ecb339e6a927156" - integrity sha512-hlmvF7y7yj7RliqTGYEyMfhzfZH2PCFeRe6fapd4WrcHwJaVlz9Lu/MwTMf4e5gIAw/7qXjD2TwmJN0p+qP0SQ== - -"@mimicprotocol/runner-node-win32-x64-msvc@0.0.1-rc.14": - version "0.0.1-rc.14" - resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-win32-x64-msvc/-/runner-node-win32-x64-msvc-0.0.1-rc.14.tgz#7ec62e93160dddf7a353a16abbc8c9ed8e41665a" - integrity sha512-7ZO6t8LuGW3swOXE5vNapIu5gZWIxTvu+Dpjz7Nuwp8Fhnihc8GH5wEFubVtrU+8B5qIHD297Ei6ISs5bc1UKg== - -"@mimicprotocol/runner-node@^0.0.1-rc.14": - version "0.0.1-rc.14" - resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node/-/runner-node-0.0.1-rc.14.tgz#4250e374db3717acd6578feb90716cf8301a2925" - integrity sha512-OyEayVUi26ZUj+9noLNG1nhrUb2ibr6JESzP/ZAVQIMwOorW+nQvK64CKFgCmtqB8xWYnqZPpcAYjn+HuL4r4Q== +"@mimicprotocol/runner-node-darwin-arm64@0.1.0-rc.1": + version "0.1.0-rc.1" + resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-darwin-arm64/-/runner-node-darwin-arm64-0.1.0-rc.1.tgz#c686a9d213dbf9c955d455636eec87479a6a7088" + integrity sha512-R9ZNvDj+snjAIKjB5u5wAdB9f8EBfqjvnpzNMHygOYboYuGEFwHIcj8LLYTNnIUX45CeBVwPhH1gckn1s62umA== + +"@mimicprotocol/runner-node-darwin-x64@0.1.0-rc.1": + version "0.1.0-rc.1" + resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-darwin-x64/-/runner-node-darwin-x64-0.1.0-rc.1.tgz#9bcfa6b2fb1859c52fbbf11cd939b3862a695ebc" + integrity sha512-P27DBztgn3afBRE9o7qPnM+aQfUiNkAA/OLumBUDxvJq2X0J9UnIgy3qKqRuMRX7IVoq0hGA0kVwLn+J1DctKg== + +"@mimicprotocol/runner-node-linux-arm64-gnu@0.1.0-rc.1": + version "0.1.0-rc.1" + resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-linux-arm64-gnu/-/runner-node-linux-arm64-gnu-0.1.0-rc.1.tgz#6db12a3dcfbc677aa160747c8de160ab242a8c70" + integrity sha512-MtMjSfS/L8/tQM4roA5FVa12ILMbdCWkXdwpowrTLcWg4Kc3vVOP4hT/rX8YHqcJEZK8kamWOZ85hLG8OHSwBQ== + +"@mimicprotocol/runner-node-linux-x64-gnu@0.1.0-rc.1": + version "0.1.0-rc.1" + resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-linux-x64-gnu/-/runner-node-linux-x64-gnu-0.1.0-rc.1.tgz#802910c8fb4adaba834c7636497ad5a150487f7e" + integrity sha512-4pUb3dpq9V4lZXcmVg2+RoNA7RaerV+j9pGhehoheXw5spLaLqyZttPm9sqQPfeEJx0D/xucpMy0FFRgUCRylA== + +"@mimicprotocol/runner-node-linux-x64-musl@0.1.0-rc.1": + version "0.1.0-rc.1" + resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-linux-x64-musl/-/runner-node-linux-x64-musl-0.1.0-rc.1.tgz#9fd2374d976c093e97dd5bee6078db124650fd56" + integrity sha512-80S5LmSbklgxAX3vHc60J5PHg9i7P90bAOkcuBZ5rPDwcWweHfAfKcIK9xZmG1rPL4xKgq4EFL8hm+mxajXUfQ== + +"@mimicprotocol/runner-node-win32-arm64-msvc@0.1.0-rc.1": + version "0.1.0-rc.1" + resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-win32-arm64-msvc/-/runner-node-win32-arm64-msvc-0.1.0-rc.1.tgz#b8a5968ca9032fe03619f0eba82d4b68b1c89cc8" + integrity sha512-B2fG9GEhWCzO+7GEuVFyVuFuyCo4KFzudrR1Hfxt6m03atTFnrLjmMLBFal+ZIdbD/iliAj7Q2h/efFDs/Rqkg== + +"@mimicprotocol/runner-node-win32-x64-msvc@0.1.0-rc.1": + version "0.1.0-rc.1" + resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-win32-x64-msvc/-/runner-node-win32-x64-msvc-0.1.0-rc.1.tgz#ff95dda3b4e97c6f36e759ac9d231f2803818eca" + integrity sha512-vi1oOB6dggs8ipsTBP4tP6bqhcKIKsCPT3s0uRKg1zV77pHCzXXwh8KhmQ+IQRqdbUeuDHd+g+eTM6mqQrsXxA== + +"@mimicprotocol/runner-node@^0.1.0-rc.1": + version "0.1.0-rc.1" + resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node/-/runner-node-0.1.0-rc.1.tgz#27d346172c5f99820c7b403f2a5cae2774cbcaec" + integrity sha512-oMVjEm9tnYNm2R+N9FcdT6wUEy4RBCswh1b0w6gwmDv5EysZ42uffn0wbEfEHn/XojBP7bKDGbPRw/FufGCmjA== optionalDependencies: - "@mimicprotocol/runner-node-darwin-arm64" "0.0.1-rc.14" - "@mimicprotocol/runner-node-darwin-x64" "0.0.1-rc.14" - "@mimicprotocol/runner-node-linux-arm64-gnu" "0.0.1-rc.14" - "@mimicprotocol/runner-node-linux-x64-gnu" "0.0.1-rc.14" - "@mimicprotocol/runner-node-linux-x64-musl" "0.0.1-rc.14" - "@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== - dependencies: + "@mimicprotocol/runner-node-darwin-arm64" "0.1.0-rc.1" + "@mimicprotocol/runner-node-darwin-x64" "0.1.0-rc.1" + "@mimicprotocol/runner-node-linux-arm64-gnu" "0.1.0-rc.1" + "@mimicprotocol/runner-node-linux-x64-gnu" "0.1.0-rc.1" + "@mimicprotocol/runner-node-linux-x64-musl" "0.1.0-rc.1" + "@mimicprotocol/runner-node-win32-arm64-msvc" "0.1.0-rc.1" + "@mimicprotocol/runner-node-win32-x64-msvc" "0.1.0-rc.1" + +"@mimicprotocol/sdk@^0.0.2-rc.1": + version "0.0.2-rc.1" + resolved "https://registry.yarnpkg.com/@mimicprotocol/sdk/-/sdk-0.0.2-rc.1.tgz#2d8f69a006709c15631ae60e9ec437be9ec178ab" + integrity sha512-mGuBKp4JCdJh0b/awozp1qUULYdbRmsXdmVuwbT1ZZfVnZmlSpANqkiA2PGPUB8VEdQ9NvhgWhZcFZJzHy2exQ== + dependencies: + "@coral-xyz/anchor" "0.32.1" "@solana/web3.js" "^1.98.4" borsh "^2.0.0" cron-parser "^5.3.1" @@ -723,7 +756,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 +899,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 +1727,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 +1803,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 +1905,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 +2265,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 +2965,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 +5366,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 +6476,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 +6615,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 73794fbca7ac785df9ee0041768edf2b74a5bf3d Mon Sep 17 00:00:00 2001 From: Tintoretto Date: Wed, 29 Apr 2026 12:04:31 -0300 Subject: [PATCH 09/17] Lib: Bump runner version (#228) --- .changeset/late-cobras-turn.md | 2 +- packages/lib-ts/constants.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/late-cobras-turn.md b/.changeset/late-cobras-turn.md index eee70ad3..1deeb030 100644 --- a/.changeset/late-cobras-turn.md +++ b/.changeset/late-cobras-turn.md @@ -4,4 +4,4 @@ "@mimicprotocol/cli": patch --- -Refactor intents to have operations and bump to runner version v0.0.2 +Refactor intents to have operations and bump to runner version v0.1.0 diff --git a/packages/lib-ts/constants.js b/packages/lib-ts/constants.js index 0500e6e1..c93f399c 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.1.0' From f9f7a117da797059ac0149b09205cbeb9dae2e46 Mon Sep 17 00:00:00 2001 From: lgalende Date: Wed, 29 Apr 2026 12:20:42 -0300 Subject: [PATCH 10/17] integration: fix tests --- packages/integration/tests/009-solana-intents/expected.log | 2 +- .../integration/tests/014-swap-and-dynamic-call/expected.log | 2 +- .../tests/014-swap-and-dynamic-call/src/function.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/integration/tests/009-solana-intents/expected.log b/packages/integration/tests/009-solana-intents/expected.log index 024db06b..6191a4b1 100644 --- a/packages/integration/tests/009-solana-intents/expected.log +++ b/packages/integration/tests/009-solana-intents/expected.log @@ -1,3 +1,3 @@ _sendIntent: {"settler":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"So11111111111111111111111111111111111111112","amount":"1"}],"operations":[{"opType":1,"chainId":507424,"user":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K","events":[],"transfers":[{"token":"So11111111111111111111111111111111111111112","amount":"4000000000000","recipient":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K"}]}]} _sendIntent: {"settler":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[],"operations":[{"opType":0,"chainId":507424,"user":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K","events":[],"sourceChain":507424,"tokensIn":[{"token":"So11111111111111111111111111111111111111112","amount":"1000000000000"}],"tokensOut":[{"token":"So11111111111111111111111111111111111111112","minAmount":"2000","recipient":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K"}],"destinationChain":507424}]} -_sendIntent: {"settler":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"So11111111111111111111111111111111111111112","amount":"1"}],"operations":[{"opType":4,"chainId":507424,"user":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K","events":[],"instructions":[{"programId":"TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb","accountsMeta":[{"pubkey":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K","isWritable":false,"isSigner":true},{"pubkey":"So11111111111111111111111111111111111111112","isWritable":true,"isSigner":false}],"data":"0xabcd"}]}]} +_sendIntent: {"settler":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","feePayer":"0x756f45e3fa69347a9a973a725e3c98bc4db0b5a0","deadline":"1438223473","nonce":"0xabcd","maxFees":[{"token":"So11111111111111111111111111111111111111112","amount":"1"}],"operations":[{"opType":5,"chainId":507424,"user":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K","events":[],"instructions":[{"programId":"TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb","accountsMeta":[{"pubkey":"HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K","isWritable":false,"isSigner":true},{"pubkey":"So11111111111111111111111111111111111111112","isWritable":true,"isSigner":false}],"data":"0xabcd"}]}]} 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 dedf9859..f9f10672 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"}]}]}]} +_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","isDynamic":false},{"kind":1,"data":"0x","isDynamic":false}]}]}]} 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 e4da9636..3ebcb019 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 @@ -21,8 +21,8 @@ export default function main(): void { .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.literal([EvmEncodeParam.fromValue('uint256', BigInt.fromI32(123))], false), + EvmDynamicArg.variable(0, 0, false), ]) new IntentBuilder().addMaxFee(maxFee).addOperationsBuilders([swap, call]).send() From 2df719f82342e6634f12cb96174e3fe064cc38b9 Mon Sep 17 00:00:00 2001 From: Agustincito Date: Wed, 29 Apr 2026 16:01:51 -0300 Subject: [PATCH 11/17] Fix comment --- packages/test-ts/src/types.ts | 11 ++++++++--- packages/test-ts/src/utils.ts | 4 ++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/test-ts/src/types.ts b/packages/test-ts/src/types.ts index 03666118..83833fe4 100644 --- a/packages/test-ts/src/types.ts +++ b/packages/test-ts/src/types.ts @@ -116,11 +116,11 @@ export type SwapOperation = OperationBase & { tokensOut: { token: string; minAmount: string; recipient: string }[] } -export type CallOperation = OperationBase & { +export type EvmCallOperation = OperationBase & { calls: { target: string; data: string; value: string }[] } -export type DynamicCallOperation = OperationBase & { +export type DynamicEvmCallOperation = OperationBase & { calls: { target: string; value: string; selector: string; arguments: { kind: number; data: string }[] }[] } @@ -132,7 +132,12 @@ export type SvmCallOperation = OperationBase & { }[] } -export type Operation = TransferOperation | SwapOperation | CallOperation | DynamicCallOperation | SvmCallOperation +export type Operation = + | TransferOperation + | SwapOperation + | EvmCallOperation + | DynamicEvmCallOperation + | SvmCallOperation export type Intent = { settler: string diff --git a/packages/test-ts/src/utils.ts b/packages/test-ts/src/utils.ts index 529eee6f..8827d4d5 100644 --- a/packages/test-ts/src/utils.ts +++ b/packages/test-ts/src/utils.ts @@ -10,7 +10,7 @@ import { import { Wallet } from 'ethers' import { - CallOperation, + EvmCallOperation, Intent, Operation, OracleResponse, @@ -40,7 +40,7 @@ export function toIntents(intentsJson: string) { } } - const nonSwap = operation as TransferOperation | CallOperation | SvmCallOperation + const nonSwap = operation as TransferOperation | EvmCallOperation | SvmCallOperation return { ...nonSwap, chainId: Number(nonSwap.chainId) } }), } From b4fba23308a77c522473be92961fb175a141e788 Mon Sep 17 00:00:00 2001 From: Agustincito Date: Thu, 30 Apr 2026 11:55:32 -0300 Subject: [PATCH 12/17] Bump versions --- packages/test-ts/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/test-ts/package.json b/packages/test-ts/package.json index cc48cfba..6b9d0dad 100644 --- a/packages/test-ts/package.json +++ b/packages/test-ts/package.json @@ -14,8 +14,8 @@ "dist" ], "dependencies": { - "@mimicprotocol/runner-node": "^0.1.0-rc.1", - "@mimicprotocol/sdk": "^0.0.2-rc.1", + "@mimicprotocol/runner-node": "~0.1.0", + "@mimicprotocol/sdk": "~0.1.0", "ethers": "^6.15.0", "zod": "^3.24.1" }, From a73ea2f42f1a6db74719311b57696db68881460c Mon Sep 17 00:00:00 2001 From: Tintoretto Date: Thu, 30 Apr 2026 11:57:17 -0300 Subject: [PATCH 13/17] Update packages/test-ts/src/types.ts Co-authored-by: lgalende <63872655+lgalende@users.noreply.github.com> --- packages/test-ts/src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/test-ts/src/types.ts b/packages/test-ts/src/types.ts index 83833fe4..5d4bf42c 100644 --- a/packages/test-ts/src/types.ts +++ b/packages/test-ts/src/types.ts @@ -120,7 +120,7 @@ export type EvmCallOperation = OperationBase & { calls: { target: string; data: string; value: string }[] } -export type DynamicEvmCallOperation = OperationBase & { +export type EvmDynamicCallOperation = OperationBase & { calls: { target: string; value: string; selector: string; arguments: { kind: number; data: string }[] }[] } From 20be6601003ee07df3355ccebbf8a148dbc22e6d Mon Sep 17 00:00:00 2001 From: Tintoretto Date: Thu, 30 Apr 2026 11:57:30 -0300 Subject: [PATCH 14/17] Update packages/test-ts/src/utils.ts Co-authored-by: lgalende <63872655+lgalende@users.noreply.github.com> --- packages/test-ts/src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/test-ts/src/utils.ts b/packages/test-ts/src/utils.ts index 8827d4d5..cb3a2557 100644 --- a/packages/test-ts/src/utils.ts +++ b/packages/test-ts/src/utils.ts @@ -40,7 +40,7 @@ export function toIntents(intentsJson: string) { } } - const nonSwap = operation as TransferOperation | EvmCallOperation | SvmCallOperation + const nonSwap = operation as TransferOperation | EvmCallOperation | EvmDynamicCallOperation | SvmCallOperation return { ...nonSwap, chainId: Number(nonSwap.chainId) } }), } From ee008c73af0b8b42513540ce8bfcbc079f8704c5 Mon Sep 17 00:00:00 2001 From: Tintoretto Date: Thu, 30 Apr 2026 11:57:50 -0300 Subject: [PATCH 15/17] Update packages/test-ts/src/types.ts Co-authored-by: lgalende <63872655+lgalende@users.noreply.github.com> --- packages/test-ts/src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/test-ts/src/types.ts b/packages/test-ts/src/types.ts index 5d4bf42c..2df2a0cd 100644 --- a/packages/test-ts/src/types.ts +++ b/packages/test-ts/src/types.ts @@ -136,7 +136,7 @@ export type Operation = | TransferOperation | SwapOperation | EvmCallOperation - | DynamicEvmCallOperation + | EvmDynamicCallOperation | SvmCallOperation export type Intent = { From a6fadbe738abb38f39c6ae4a28556fab5dead929 Mon Sep 17 00:00:00 2001 From: Agustincito Date: Thu, 30 Apr 2026 12:35:09 -0300 Subject: [PATCH 16/17] Fix tests --- packages/test-ts/src/utils.ts | 1 + yarn.lock | 102 +++++++++++++++++----------------- 2 files changed, 52 insertions(+), 51 deletions(-) diff --git a/packages/test-ts/src/utils.ts b/packages/test-ts/src/utils.ts index cb3a2557..a2a3f5b6 100644 --- a/packages/test-ts/src/utils.ts +++ b/packages/test-ts/src/utils.ts @@ -1,5 +1,6 @@ import { EthersSigner, + EvmDynamicCallOperation, OpType, OracleQueryName, OracleQueryParams, diff --git a/yarn.lock b/yarn.lock index 7cd23d8d..43f89355 100644 --- a/yarn.lock +++ b/yarn.lock @@ -677,58 +677,58 @@ globby "^11.0.0" read-yaml-file "^1.1.0" -"@mimicprotocol/runner-node-darwin-arm64@0.1.0-rc.1": - version "0.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-darwin-arm64/-/runner-node-darwin-arm64-0.1.0-rc.1.tgz#c686a9d213dbf9c955d455636eec87479a6a7088" - integrity sha512-R9ZNvDj+snjAIKjB5u5wAdB9f8EBfqjvnpzNMHygOYboYuGEFwHIcj8LLYTNnIUX45CeBVwPhH1gckn1s62umA== - -"@mimicprotocol/runner-node-darwin-x64@0.1.0-rc.1": - version "0.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-darwin-x64/-/runner-node-darwin-x64-0.1.0-rc.1.tgz#9bcfa6b2fb1859c52fbbf11cd939b3862a695ebc" - integrity sha512-P27DBztgn3afBRE9o7qPnM+aQfUiNkAA/OLumBUDxvJq2X0J9UnIgy3qKqRuMRX7IVoq0hGA0kVwLn+J1DctKg== - -"@mimicprotocol/runner-node-linux-arm64-gnu@0.1.0-rc.1": - version "0.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-linux-arm64-gnu/-/runner-node-linux-arm64-gnu-0.1.0-rc.1.tgz#6db12a3dcfbc677aa160747c8de160ab242a8c70" - integrity sha512-MtMjSfS/L8/tQM4roA5FVa12ILMbdCWkXdwpowrTLcWg4Kc3vVOP4hT/rX8YHqcJEZK8kamWOZ85hLG8OHSwBQ== - -"@mimicprotocol/runner-node-linux-x64-gnu@0.1.0-rc.1": - version "0.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-linux-x64-gnu/-/runner-node-linux-x64-gnu-0.1.0-rc.1.tgz#802910c8fb4adaba834c7636497ad5a150487f7e" - integrity sha512-4pUb3dpq9V4lZXcmVg2+RoNA7RaerV+j9pGhehoheXw5spLaLqyZttPm9sqQPfeEJx0D/xucpMy0FFRgUCRylA== - -"@mimicprotocol/runner-node-linux-x64-musl@0.1.0-rc.1": - version "0.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-linux-x64-musl/-/runner-node-linux-x64-musl-0.1.0-rc.1.tgz#9fd2374d976c093e97dd5bee6078db124650fd56" - integrity sha512-80S5LmSbklgxAX3vHc60J5PHg9i7P90bAOkcuBZ5rPDwcWweHfAfKcIK9xZmG1rPL4xKgq4EFL8hm+mxajXUfQ== - -"@mimicprotocol/runner-node-win32-arm64-msvc@0.1.0-rc.1": - version "0.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-win32-arm64-msvc/-/runner-node-win32-arm64-msvc-0.1.0-rc.1.tgz#b8a5968ca9032fe03619f0eba82d4b68b1c89cc8" - integrity sha512-B2fG9GEhWCzO+7GEuVFyVuFuyCo4KFzudrR1Hfxt6m03atTFnrLjmMLBFal+ZIdbD/iliAj7Q2h/efFDs/Rqkg== - -"@mimicprotocol/runner-node-win32-x64-msvc@0.1.0-rc.1": - version "0.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-win32-x64-msvc/-/runner-node-win32-x64-msvc-0.1.0-rc.1.tgz#ff95dda3b4e97c6f36e759ac9d231f2803818eca" - integrity sha512-vi1oOB6dggs8ipsTBP4tP6bqhcKIKsCPT3s0uRKg1zV77pHCzXXwh8KhmQ+IQRqdbUeuDHd+g+eTM6mqQrsXxA== - -"@mimicprotocol/runner-node@^0.1.0-rc.1": - version "0.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node/-/runner-node-0.1.0-rc.1.tgz#27d346172c5f99820c7b403f2a5cae2774cbcaec" - integrity sha512-oMVjEm9tnYNm2R+N9FcdT6wUEy4RBCswh1b0w6gwmDv5EysZ42uffn0wbEfEHn/XojBP7bKDGbPRw/FufGCmjA== +"@mimicprotocol/runner-node-darwin-arm64@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-darwin-arm64/-/runner-node-darwin-arm64-0.1.0.tgz#45548fa3fc51ec159734f2b420cfbdaf9d66a9a3" + integrity sha512-J3ykKFmW6wMI63h3M4Njrt+XjCvX7oXgrzoVHbDsj/Jx9Pjg5akZFL6fEZ9ljjY6jrmlvCE8DwM2b6eVyG3kEQ== + +"@mimicprotocol/runner-node-darwin-x64@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-darwin-x64/-/runner-node-darwin-x64-0.1.0.tgz#60721cc66ad78f643b03e245d0e7c0eb68c72eeb" + integrity sha512-07739siHMJndIsPBHY8nK0Uv00q62j1eybZ34QBEynoh8HniTCapEbTtU8H9ac9QBu2bdx2VfssLNQxE9mbCBQ== + +"@mimicprotocol/runner-node-linux-arm64-gnu@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-linux-arm64-gnu/-/runner-node-linux-arm64-gnu-0.1.0.tgz#7ae21a7b46009483608e6328fb5814980eaf1c60" + integrity sha512-RUtegSQLZqvYVUtvHcXgGcqitPJMIAEpvPlB41WV6+z7HAU04i6OciIGzOpct1DWvaEKj5lRAGD2M0af17kIVA== + +"@mimicprotocol/runner-node-linux-x64-gnu@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-linux-x64-gnu/-/runner-node-linux-x64-gnu-0.1.0.tgz#d0e0bd7b3d99071564f74f9a1cd9429fa7cb4c4e" + integrity sha512-HfuVdaWJW1t2GWNLYhAIGh+bBskUz21MivJlq4tmYyAHbic+4e0BxCrvpsuAQoks9aJR3y7Snftrc7eSjIompw== + +"@mimicprotocol/runner-node-linux-x64-musl@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-linux-x64-musl/-/runner-node-linux-x64-musl-0.1.0.tgz#d7de018fee7ac76094d6aee7ae6a28619eaf0dfe" + integrity sha512-QUOqXbtTaruJu+MffmtqwK4+pKhG11uAZQpoV3IoNWOSCugu2PL8glDgn24NN0mN4fzzFy66xB/5zJJlhMEWtQ== + +"@mimicprotocol/runner-node-win32-arm64-msvc@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-win32-arm64-msvc/-/runner-node-win32-arm64-msvc-0.1.0.tgz#991b4b930d857e7311c4b67d77210726ebde89f4" + integrity sha512-BgzVfTPhuzeNLD3sUt1c3VTcgmWP3JLOsv+n/Op6SgZIfGsa84uw3peIPh4OZSBmqOpWTjvXLHxjI9D2scxnhg== + +"@mimicprotocol/runner-node-win32-x64-msvc@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node-win32-x64-msvc/-/runner-node-win32-x64-msvc-0.1.0.tgz#640034312e7248ff178b33c01474ed0b44ed2585" + integrity sha512-kCefqdkMWj3s2gT3DROFhKPwO4viA/ilIL11baAlQiZaab1SnvKyE+sm7mf9knj5NKSSAuah7XyeRoI9tlgZpA== + +"@mimicprotocol/runner-node@~0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@mimicprotocol/runner-node/-/runner-node-0.1.0.tgz#33fa8e8088e80083ad4506bfbc5b86838e9e9082" + integrity sha512-3gy4ZQPIGaeZREwEO57GzQLcTSi8QdJ53AuFlHF5TDtbL3t5r9mksK3CjJ5dr5iPzy7IFgbb1pCP7QsRPlHAng== optionalDependencies: - "@mimicprotocol/runner-node-darwin-arm64" "0.1.0-rc.1" - "@mimicprotocol/runner-node-darwin-x64" "0.1.0-rc.1" - "@mimicprotocol/runner-node-linux-arm64-gnu" "0.1.0-rc.1" - "@mimicprotocol/runner-node-linux-x64-gnu" "0.1.0-rc.1" - "@mimicprotocol/runner-node-linux-x64-musl" "0.1.0-rc.1" - "@mimicprotocol/runner-node-win32-arm64-msvc" "0.1.0-rc.1" - "@mimicprotocol/runner-node-win32-x64-msvc" "0.1.0-rc.1" - -"@mimicprotocol/sdk@^0.0.2-rc.1": - version "0.0.2-rc.1" - resolved "https://registry.yarnpkg.com/@mimicprotocol/sdk/-/sdk-0.0.2-rc.1.tgz#2d8f69a006709c15631ae60e9ec437be9ec178ab" - integrity sha512-mGuBKp4JCdJh0b/awozp1qUULYdbRmsXdmVuwbT1ZZfVnZmlSpANqkiA2PGPUB8VEdQ9NvhgWhZcFZJzHy2exQ== + "@mimicprotocol/runner-node-darwin-arm64" "0.1.0" + "@mimicprotocol/runner-node-darwin-x64" "0.1.0" + "@mimicprotocol/runner-node-linux-arm64-gnu" "0.1.0" + "@mimicprotocol/runner-node-linux-x64-gnu" "0.1.0" + "@mimicprotocol/runner-node-linux-x64-musl" "0.1.0" + "@mimicprotocol/runner-node-win32-arm64-msvc" "0.1.0" + "@mimicprotocol/runner-node-win32-x64-msvc" "0.1.0" + +"@mimicprotocol/sdk@~0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@mimicprotocol/sdk/-/sdk-0.1.0.tgz#a0bb3661cd0129bac253cba6c79dea7b6026b44e" + integrity sha512-wJqjsZC9qQOKi5j7rOutCSHg9aV1/Sg8+nMaLt0IX/z5P1Dk2BBFb8OJc1q1MFW9PZOAJSaFhtZlRxKkzhoopA== dependencies: "@coral-xyz/anchor" "0.32.1" "@solana/web3.js" "^1.98.4" From 0ef8d212b2d61ccdf60acc00fa8b7e0d8f19e608 Mon Sep 17 00:00:00 2001 From: Agustincito Date: Thu, 30 Apr 2026 12:36:54 -0300 Subject: [PATCH 17/17] Fix changeset --- .changeset/spicy-areas-switch.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.changeset/spicy-areas-switch.md b/.changeset/spicy-areas-switch.md index a2743298..fced67ff 100644 --- a/.changeset/spicy-areas-switch.md +++ b/.changeset/spicy-areas-switch.md @@ -1,7 +1,7 @@ --- -"@mimicprotocol/test-ts": patch -"@mimicprotocol/lib-ts": patch -"@mimicprotocol/cli": patch +"@mimicprotocol/test-ts": minor +"@mimicprotocol/lib-ts": minor +"@mimicprotocol/cli": minor --- -Add dynamic call operation +Add operations and dynamic call operation