@@ -51,6 +51,10 @@ import {
5151 createCondition ,
5252 getMEEVersion ,
5353 greaterThanOrEqualTo ,
54+ greaterThanOrEqualToSigned ,
55+ lessThanOrEqualTo ,
56+ lessThanOrEqualToSigned ,
57+ orConstraint ,
5458 runtimeERC20BalanceOf ,
5559 runtimeNativeBalanceOf
5660} from "../../../modules"
@@ -78,8 +82,10 @@ describe.runIf(runLifecycleTests)("mee.buildComposable", () => {
7882
7983 let mcNexus : MultichainSmartAccount
8084 let mcNexus_compos_v1_1_0 : MultichainSmartAccount
85+ let mcNexus_compos_v1_1_1 : MultichainSmartAccount
8186 let meeClient : MeeClient
8287 let meeClient_compos_v1_1_0 : MeeClient
88+ let meeClient_compos_v1_1_1 : MeeClient
8389 let accountConfigs : {
8490 name : string
8591 mcNexus : MultichainSmartAccount
@@ -126,6 +132,18 @@ describe.runIf(runLifecycleTests)("mee.buildComposable", () => {
126132 ]
127133 } )
128134
135+ mcNexus_compos_v1_1_1 = await toMultichainNexusAccount ( {
136+ signer : eoaAccount ,
137+ index : 1n ,
138+ chainConfigurations : [
139+ {
140+ chain : chain ,
141+ transport : http ( network . rpcUrl ) ,
142+ version : getMEEVersion ( MEEVersion . V2_2_2 )
143+ }
144+ ]
145+ } )
146+
129147 meeClient = await createMeeClient ( {
130148 account : mcNexus
131149 } )
@@ -134,6 +152,10 @@ describe.runIf(runLifecycleTests)("mee.buildComposable", () => {
134152 account : mcNexus_compos_v1_1_0
135153 } )
136154
155+ meeClient_compos_v1_1_1 = await createMeeClient ( {
156+ account : mcNexus_compos_v1_1_1
157+ } )
158+
137159 accountConfigs = [
138160 {
139161 name : "Composability v1.0.0" ,
@@ -146,6 +168,12 @@ describe.runIf(runLifecycleTests)("mee.buildComposable", () => {
146168 mcNexus : mcNexus_compos_v1_1_0 ,
147169 meeClient : meeClient_compos_v1_1_0 ,
148170 eoaAccount
171+ } ,
172+ {
173+ name : "Composability v1.1.1" ,
174+ mcNexus : mcNexus_compos_v1_1_1 ,
175+ meeClient : meeClient_compos_v1_1_1 ,
176+ eoaAccount
149177 }
150178 ]
151179
@@ -1808,6 +1836,295 @@ describe.runIf(runLifecycleTests)("mee.buildComposable", () => {
18081836 } )
18091837 } )
18101838
1839+ // ========== Composability v1.1.1 — OR and signed-integer constraint tests ==========
1840+
1841+ it ( "should reject OR constraint when composability version < 1.1.1" , async ( ) => {
1842+ // mcNexus is on v1.0.0 — OR constraints must be rejected at build time
1843+ await expect (
1844+ mcNexus . buildComposable ( {
1845+ type : "default" ,
1846+ data : {
1847+ to : tokenAddress ,
1848+ abi : erc20Abi ,
1849+ functionName : "transferFrom" ,
1850+ args : [
1851+ eoaAccount . address ,
1852+ mcNexus . addressOn ( chain . id , true ) ,
1853+ runtimeERC20BalanceOf ( {
1854+ targetAddress : eoaAccount . address ,
1855+ tokenAddress,
1856+ constraints : [
1857+ orConstraint ( [ greaterThanOrEqualTo ( parseUnits ( "0.01" , 6 ) ) ] )
1858+ ]
1859+ } )
1860+ ] ,
1861+ chainId : chain . id
1862+ }
1863+ } )
1864+ ) . rejects . toThrow ( "OR constraints require Composability v1.1.1" )
1865+ } )
1866+
1867+ it ( "should reject GTE_SIGNED constraint when composability version < 1.1.1" , async ( ) => {
1868+ // mcNexus_compos_v1_1_0 is on v1.1.0 — signed constraints must still be rejected
1869+ await expect (
1870+ mcNexus_compos_v1_1_0 . buildComposable ( {
1871+ type : "default" ,
1872+ data : {
1873+ to : tokenAddress ,
1874+ abi : erc20Abi ,
1875+ functionName : "transferFrom" ,
1876+ args : [
1877+ eoaAccount . address ,
1878+ mcNexus_compos_v1_1_0 . addressOn ( chain . id , true ) ,
1879+ runtimeERC20BalanceOf ( {
1880+ targetAddress : eoaAccount . address ,
1881+ tokenAddress,
1882+ constraints : [ greaterThanOrEqualToSigned ( 0n ) ]
1883+ } )
1884+ ] ,
1885+ chainId : chain . id
1886+ }
1887+ } )
1888+ ) . rejects . toThrow ( "signed integer" )
1889+ } )
1890+
1891+ it ( "should execute composable sweep with OR constraint on composability v1.1.1" , async ( ) => {
1892+ const amountToSupply = parseUnits ( "0.1" , 6 )
1893+ const amountToTransfer = parseUnits ( "0.05" , 6 )
1894+
1895+ // Static transfer: Nexus → runtimeTransferAddress (fixed amount, funded by trigger)
1896+ const staticTransferInstruction =
1897+ await mcNexus_compos_v1_1_1 . buildComposable ( {
1898+ type : "transfer" ,
1899+ data : {
1900+ recipient : runtimeTransferAddress as Address ,
1901+ tokenAddress,
1902+ amount : amountToTransfer ,
1903+ chainId : chain . id
1904+ }
1905+ } )
1906+
1907+ // Runtime sweep: runtimeTransferAddress → EOA, only if balance is within OR constraint range
1908+ const sweepInstruction = await mcNexus_compos_v1_1_1 . buildComposable ( {
1909+ type : "default" ,
1910+ data : {
1911+ to : runtimeTransferAddress ,
1912+ abi : COMPOSABILITY_RUNTIME_TRANSFER_ABI as Abi ,
1913+ functionName : "transferFunds" ,
1914+ args : [
1915+ tokenAddress ,
1916+ eoaAccount . address ,
1917+ runtimeERC20BalanceOf ( {
1918+ targetAddress : runtimeTransferAddress ,
1919+ tokenAddress,
1920+ constraints : [
1921+ orConstraint ( [
1922+ greaterThanOrEqualTo ( 1n ) ,
1923+ lessThanOrEqualTo ( parseUnits ( "100" , 6 ) )
1924+ ] )
1925+ ]
1926+ } )
1927+ ] ,
1928+ chainId : chain . id
1929+ }
1930+ } )
1931+
1932+ const { hash } = await meeClient_compos_v1_1_1 . executeFusionQuote ( {
1933+ fusionQuote : await meeClient_compos_v1_1_1 . getFusionQuote ( {
1934+ trigger : { chainId : chain . id , tokenAddress, amount : amountToSupply } ,
1935+ instructions : [ ...staticTransferInstruction , ...sweepInstruction ] ,
1936+ feeToken : { chainId : chain . id , address : tokenAddress }
1937+ } )
1938+ } )
1939+
1940+ const { transactionStatus, explorerLinks } =
1941+ await meeClient_compos_v1_1_1 . waitForSupertransactionReceipt ( {
1942+ hash,
1943+ confirmations : TEST_BLOCK_CONFIRMATIONS
1944+ } )
1945+ expect ( transactionStatus ) . to . be . eq ( "MINED_SUCCESS" )
1946+ console . log ( "[v1.1.1] OR constraint composable sweep test:" , {
1947+ explorerLinks,
1948+ hash
1949+ } )
1950+ } )
1951+
1952+ it ( "should execute composable sweep with GTE_SIGNED constraint on composability v1.1.1" , async ( ) => {
1953+ const amountToSupply = parseUnits ( "0.1" , 6 )
1954+ const amountToTransfer = parseUnits ( "0.05" , 6 )
1955+
1956+ // Static transfer: Nexus → runtimeTransferAddress (funded by trigger)
1957+ const staticTransferInstruction =
1958+ await mcNexus_compos_v1_1_1 . buildComposable ( {
1959+ type : "transfer" ,
1960+ data : {
1961+ recipient : runtimeTransferAddress as Address ,
1962+ tokenAddress,
1963+ amount : amountToTransfer ,
1964+ chainId : chain . id
1965+ }
1966+ } )
1967+
1968+ // Runtime sweep using GTE_SIGNED: runtime balance must be >= 1 (int256 comparison)
1969+ const sweepInstruction = await mcNexus_compos_v1_1_1 . buildComposable ( {
1970+ type : "default" ,
1971+ data : {
1972+ to : runtimeTransferAddress ,
1973+ abi : COMPOSABILITY_RUNTIME_TRANSFER_ABI as Abi ,
1974+ functionName : "transferFunds" ,
1975+ args : [
1976+ tokenAddress ,
1977+ eoaAccount . address ,
1978+ runtimeERC20BalanceOf ( {
1979+ targetAddress : runtimeTransferAddress ,
1980+ tokenAddress,
1981+ constraints : [ greaterThanOrEqualToSigned ( 1n ) ]
1982+ } )
1983+ ] ,
1984+ chainId : chain . id
1985+ }
1986+ } )
1987+
1988+ const { hash } = await meeClient_compos_v1_1_1 . executeFusionQuote ( {
1989+ fusionQuote : await meeClient_compos_v1_1_1 . getFusionQuote ( {
1990+ trigger : { chainId : chain . id , tokenAddress, amount : amountToSupply } ,
1991+ instructions : [ ...staticTransferInstruction , ...sweepInstruction ] ,
1992+ feeToken : { chainId : chain . id , address : tokenAddress }
1993+ } )
1994+ } )
1995+
1996+ const { transactionStatus, explorerLinks } =
1997+ await meeClient_compos_v1_1_1 . waitForSupertransactionReceipt ( {
1998+ hash,
1999+ confirmations : TEST_BLOCK_CONFIRMATIONS
2000+ } )
2001+ expect ( transactionStatus ) . to . be . eq ( "MINED_SUCCESS" )
2002+ console . log ( "[v1.1.1] GTE_SIGNED constraint composable sweep test:" , {
2003+ explorerLinks,
2004+ hash
2005+ } )
2006+ } )
2007+
2008+ it ( "should execute composable sweep with LTE_SIGNED constraint on composability v1.1.1" , async ( ) => {
2009+ const amountToSupply = parseUnits ( "0.1" , 6 )
2010+ const amountToTransfer = parseUnits ( "0.05" , 6 )
2011+ const amountCap = parseUnits ( "100" , 6 ) // cap at 100 USDC (signed)
2012+
2013+ // Static transfer: Nexus → runtimeTransferAddress (funded by trigger)
2014+ const staticTransferInstruction =
2015+ await mcNexus_compos_v1_1_1 . buildComposable ( {
2016+ type : "transfer" ,
2017+ data : {
2018+ recipient : runtimeTransferAddress as Address ,
2019+ tokenAddress,
2020+ amount : amountToTransfer ,
2021+ chainId : chain . id
2022+ }
2023+ } )
2024+
2025+ // Runtime sweep using LTE_SIGNED: runtime balance must be <= 100 USDC (int256 comparison)
2026+ const sweepInstruction = await mcNexus_compos_v1_1_1 . buildComposable ( {
2027+ type : "default" ,
2028+ data : {
2029+ to : runtimeTransferAddress ,
2030+ abi : COMPOSABILITY_RUNTIME_TRANSFER_ABI as Abi ,
2031+ functionName : "transferFunds" ,
2032+ args : [
2033+ tokenAddress ,
2034+ eoaAccount . address ,
2035+ runtimeERC20BalanceOf ( {
2036+ targetAddress : runtimeTransferAddress ,
2037+ tokenAddress,
2038+ constraints : [ lessThanOrEqualToSigned ( amountCap ) ]
2039+ } )
2040+ ] ,
2041+ chainId : chain . id
2042+ }
2043+ } )
2044+
2045+ const { hash } = await meeClient_compos_v1_1_1 . executeFusionQuote ( {
2046+ fusionQuote : await meeClient_compos_v1_1_1 . getFusionQuote ( {
2047+ trigger : { chainId : chain . id , tokenAddress, amount : amountToSupply } ,
2048+ instructions : [ ...staticTransferInstruction , ...sweepInstruction ] ,
2049+ feeToken : { chainId : chain . id , address : tokenAddress }
2050+ } )
2051+ } )
2052+
2053+ const { transactionStatus, explorerLinks } =
2054+ await meeClient_compos_v1_1_1 . waitForSupertransactionReceipt ( {
2055+ hash,
2056+ confirmations : TEST_BLOCK_CONFIRMATIONS
2057+ } )
2058+ expect ( transactionStatus ) . to . be . eq ( "MINED_SUCCESS" )
2059+ console . log ( "[v1.1.1] LTE_SIGNED constraint composable sweep test:" , {
2060+ explorerLinks,
2061+ hash
2062+ } )
2063+ } )
2064+
2065+ it ( "should execute composable sweep with OR constraint mixing signed and unsigned sub-constraints on composability v1.1.1" , async ( ) => {
2066+ const amountToSupply = parseUnits ( "0.1" , 6 )
2067+ const amountToTransfer = parseUnits ( "0.05" , 6 )
2068+ const amountCap = parseUnits ( "100" , 6 )
2069+
2070+ // Static transfer: Nexus → runtimeTransferAddress (funded by trigger)
2071+ const staticTransferInstruction =
2072+ await mcNexus_compos_v1_1_1 . buildComposable ( {
2073+ type : "transfer" ,
2074+ data : {
2075+ recipient : runtimeTransferAddress as Address ,
2076+ tokenAddress,
2077+ amount : amountToTransfer ,
2078+ chainId : chain . id
2079+ }
2080+ } )
2081+
2082+ // Runtime sweep: transfer only if balance satisfies OR(GTE_SIGNED(-1), LTE(100 USDC))
2083+ // This mixes a signed sub-constraint with an unsigned one inside a single OR.
2084+ const sweepInstruction = await mcNexus_compos_v1_1_1 . buildComposable ( {
2085+ type : "default" ,
2086+ data : {
2087+ to : runtimeTransferAddress ,
2088+ abi : COMPOSABILITY_RUNTIME_TRANSFER_ABI as Abi ,
2089+ functionName : "transferFunds" ,
2090+ args : [
2091+ tokenAddress ,
2092+ eoaAccount . address ,
2093+ runtimeERC20BalanceOf ( {
2094+ targetAddress : runtimeTransferAddress ,
2095+ tokenAddress,
2096+ constraints : [
2097+ orConstraint ( [
2098+ greaterThanOrEqualToSigned ( - 1n ) ,
2099+ lessThanOrEqualTo ( amountCap )
2100+ ] )
2101+ ]
2102+ } )
2103+ ] ,
2104+ chainId : chain . id
2105+ }
2106+ } )
2107+
2108+ const { hash } = await meeClient_compos_v1_1_1 . executeFusionQuote ( {
2109+ fusionQuote : await meeClient_compos_v1_1_1 . getFusionQuote ( {
2110+ trigger : { chainId : chain . id , tokenAddress, amount : amountToSupply } ,
2111+ instructions : [ ...staticTransferInstruction , ...sweepInstruction ] ,
2112+ feeToken : { chainId : chain . id , address : tokenAddress }
2113+ } )
2114+ } )
2115+
2116+ const { transactionStatus, explorerLinks } =
2117+ await meeClient_compos_v1_1_1 . waitForSupertransactionReceipt ( {
2118+ hash,
2119+ confirmations : TEST_BLOCK_CONFIRMATIONS
2120+ } )
2121+ expect ( transactionStatus ) . to . be . eq ( "MINED_SUCCESS" )
2122+ console . log (
2123+ "[v1.1.1] OR constraint (signed + unsigned mix) composable sweep test:" ,
2124+ { explorerLinks, hash }
2125+ )
2126+ } )
2127+
18112128 // test the new 'runtimeParamViaCustomStaticCall' helper function and the injectable target at the same time
18122129 it ( "should execute composable transaction using runtimeParamViaCustomStaticCall (for composability v1.1.0+ only)" , async ( ) => {
18132130 const amount = 101n // 101 wei
0 commit comments