Syncswap Multicall for ordered router operations
Updated -
Syncswap Multicall combines calls to the same router into one transaction, keeping their execution order and shared success condition. The helper accepts encoded function calls, runs them sequentially, and returns each call's result in the corresponding position. If an inner call fails, the helper reverts the batch, so earlier state changes do not persist. In the core router that uses a Vault, this supports combinations of compatible permit, swap, and liquidity functions while preserving their individual requirements.
Jump to a section
Key takeaway: Each Multicall entry keeps its own encoded arguments, so earlier return values do not automatically become later inputs.
Permits and liquidity additions in one transaction
Token authorization and liquidity addition are related router operations that an ordered batch can combine when the token supports the selected permit interface. A permit authorizes token spending through a signature. The router's self-permit helpers identify the caller as token owner and the router as spender. A later liquidity call can then use that allowance to transfer the specified inputs. Authorization must precede the transfer whenever the existing allowance cannot cover it.
The combined
addLiquidityWithPermit
entrypoint already applies supplied permits before adding liquidity. The
selfPermitIfNecessary
helper skips a permit when the caller's existing allowance covers the supplied value. Multicall is useful when the intended operation requires a different collection of exposed router methods. Both approaches retain the token's signature rules and the pool's liquidity requirements. Signing produces a permit signature. A successful permit call sets the allowance; the liquidity function performs the transfer and requests newly minted liquidity. The authorization and asset movement remain distinct operations even when one transaction contains both.
Encoded calls remain local to the router
The helper is an execution wrapper inside the router, with each array entry containing a function selector and encoded arguments.
Its interface is
multicall(bytes[] data)
, returning a
bytes[]
result array. Each input entry identifies a function through the router's application binary interface, or ABI. That interface defines the function's parameter types and return format. Correct encoding therefore requires the ABI of the actual deployed router. A familiar method name alone cannot establish compatibility with another implementation.
For every entry, the helper invokes
address(this).delegatecall(data[i])
. The target stays at the router itself. Pool addresses or factory addresses inside a method's arguments serve that method's operation. They do not replace the helper's delegatecall target. This distinction separates local method batching from an interface that accepts arbitrary external call destinations.
Delegatecall preserves the caller that the router's methods see as
msg.sender
. The self-permit helpers can therefore authorize that caller's tokens, and transfer functions can reference the same account. The router's storage also remains the execution context. Batching does not create a new token owner or replace the spender specified by those helpers.
The selected methods still make their own external calls to tokens, pools, and the Vault under their normal transfer and authorization rules.
Execution order and independent return values
Array order determines when each method executes, so dependencies between authorization, token movement, and pool operations remain part of batch construction. Later calls can encounter state changes that earlier calls made during the same execution. That ordering matters whenever a method requires an allowance or another condition established by an earlier operation.
Each successful call contributes raw return bytes at its matching result position. The integration must decode those bytes using that method's return types. Multicall does not rewrite later calldata with an earlier return value. In particular, receiving an amount from one function does not automatically populate another function's amount parameter. A swap's own
SwapPath
and
SwapStep
structures handle routing through pools within the swap method. They solve a different task from grouping multiple router methods.
Batch failure and operation-specific limits
A batch has one shared success boundary across its included operations and the external calls that those operations make. The core Multicall helper reverts the batch if an inner delegatecall fails. That reversal includes token movements and authorization changes made earlier inside the same batch. The helper offers no option to keep successful entries while skipping a failed one.
Output protection remains inside the selected operation. A swap checks
amountOutMin
against the accumulated output that its paths return. Adding liquidity checks minted liquidity against
minLiquidity
. Withdrawal methods check their returned token amounts against
minAmounts
, or check a single output against
minAmount
. These parameters measure different outputs, so one threshold cannot replace all the others.
Wrapping the methods does not weaken those checks or supply their values automatically. Failure of a required output check therefore also fails the enclosing batch. The helper has no separate deadline parameter; methods that enforce deadlines retain their checks.
Native ETH and payable entrypoints
Native ETH funding uses the value sent with the router call, which each local delegatecall inherits as
msg.value
. The helper does not assign separate ETH payments to array entries. For native inputs, the router forwards the specified amount to the Vault, so available funding must cover the intended transfers. A payable Multicall entrypoint does not make every inner method payable. Nonpayable methods reject a nonzero inherited value, even when their own operation does not spend ETH. This limits which methods can coexist in an ETH-funded batch. The helper itself does not refund unused ETH.
Gas costs grow with the work inside the batch
Combining methods reduces the number of outer transactions, while execution still performs the token transfers, pool calculations, and storage changes that those methods require. The wrapper also adds work for delegatecalls and return-data handling. Total cost follows the chosen calls and the network's transaction pricing; batching does not establish a fixed saving. A failed execution can still consume gas despite reversing its state changes. Comparing complete costs requires the same operations and funding conditions, rather than counting transaction confirmations alone.
Pool creation and staking through the same interface
Pool creation is another exposed router operation:
createPool
forwards the chosen factory and deployment data to the factory's creation method. Its wrapper allows creation to participate in a batch. The factory still determines how creation works and what data it accepts. Receiving a new pool address in the return array does not automatically insert that address into later encoded calls.
The
stake
method transfers the caller's specified token amount into the router, then calls the selected staking pool. Its
onBehalf
parameter names the account for staking. The router checks its own allowance to that staking pool and grants the maximum allowance when the existing amount is insufficient. This allowance has a different owner and spender from the caller's authorization to the router. Multicall groups the exposed methods without changing those relationships.
Choosing a batch, a combined method, or separate transactions
A permit paired with an exchange operation presents a specific choice between a combined entrypoint and a batch of local methods. Multicall accommodates other compatible combinations when their dependencies and shared failure boundary fit the intended operation.
- The deployed router exposes every encoded function, with parameter types that match its ABI.
- Each permit uses an interface that the selected token supports and authorizes the appropriate caller and router.
- Authorization and other prerequisites occur before the methods that require them.
- Every included method accepts the batch's ETH context and retains the output limits appropriate to its operation.
- A failure should cancel all included operations, including successful earlier calls.
swapWithPermit
and
addLiquidityWithPermit
already combine permits with their respective exchange operations. A broader compatible collection can use Multicall. Separate transactions preserve confirmed earlier operations when a later transaction fails, allowing those tasks to commit independently.
Still wondering about Syncswap?
Can a contract use Syncswap Multicall to spend a wallet's tokens?
A calling contract becomes msg.sender for the router's local methods. Token transfers that use msg.sender therefore draw from that contract, and self-permit helpers name that contract as owner. Having a wallet initiate the outer transaction does not substitute the wallet as the router's immediate caller.
Which token interface does selfPermit2 use inside a multicall?
The selfPermit2 helper calls permit2 directly on the selected token through IERC20Permit2. It accepts a byte-encoded signature, while selfPermit uses separate v, r, and s parameters. The token must implement the interface that the chosen helper expects. The authorization still identifies the caller as owner and the router as spender.
Why can a failed multicall hide the router's custom error?
This core Multicall implementation discards failure data shorter than 68 bytes and reverts without a message. Argument-free custom errors, such as TooLittleReceived(), return only a four-byte selector. Longer data goes through string decoding, so the wrapper does not reliably preserve every custom-error format.
How can a router view query be included in Multicall?
A view query can appear as an encoded local call when the deployed router exposes it. The core router includes enteredPoolsLength, which reports an account's recorded pool count. This query is nonpayable, so the router call must carry zero ETH value. Its returned bytes occupy the corresponding result position. The query reads state when that position executes, so earlier state changes within the batch can affect its answer.
Is addLiquidity2 a switch to Syncswap V2?
The suffix in addLiquidity2 distinguishes a router entrypoint, not a protocol-version switch. In this core router, it follows the same transfer-and-mint path as addLiquidity, then records the caller's pool entry. Its name does not establish the pool model or protocol version that a particular deployment uses.
Will a failed batch undo an approval confirmed in an earlier transaction?
A batch failure restores the allowance state that existed before that batch. It does not undo a previously confirmed approval transaction. Any permit or allowance change inside the reverted batch disappears, while the earlier authorization remains subject to its own scope and any subsequent successful changes.