Overview
When building multi-step transactions, especially across chains, you often need to use values that:
- Result from previous operations
- Change based on market conditions
- Depend on the exact amount received from bridges or swaps
Runtime injection solves this by creating placeholders that get filled with actual values during execution.
Support
You can use runtime value injection in most Instruction builders. If the parameter accepts the
RuntimeValue
type, you can use them.
Limitations
- Runtime values can only be used within parameters, not to determine which functions to call
- All constraints must be satisfied or the instruction will not execute
- Runtime values can't reference external APIs or off-chain data
Runtime parameter injection allows function call parameters to be determined at execution time rather than when the transaction is created. This is crucial for cross-chain operations where exact token amounts can't be known in advance.
Basic Usage
import { runtimeERC20BalanceOf, greaterThanOrEqualTo } from "@biconomy/abstractjs";
// Use the exact token balance at execution time
const swapInstruction = await oNexus.buildComposable({
type: "default",
data: {
// ...other parameters
args: [{
amountIn: runtimeERC20BalanceOf({
tokenAddress: "0xTokenAddress",
targetAddress: nexusAddress,
constraints: [greaterThanOrEqualTo(minAmount)]
}),
// Other arguments...
}]
}
});
Available Runtime Functions
Function | Purpose |
---|---|
runtimeERC20BalanceOf | Injects the balance of an ERC20 token at execution time |
runtimeEncodeAbiParameters | Builds a bytes object, encoding several runtime and/or static params together. |
runtimeERC20BalanceOf
This method allows injecting the ERC20 token balance of a given address as argument. This is useful to transfer the all the tokens of a given account w/o even knowing the amount of those tokens.
Example usage
const instructions: Instruction[] = await mcNexus.buildComposable({
type: "default",
data: {
to: testnetMcUSDC.addressOn(chain.id),
abi: erc20Abi,
functionName: "transfer",
args: [
b0b,
runtimeERC20BalanceOf({
targetAddress: a11ce,
tokenAddress: testnetMcUSDC.addressOn(chain.id)
})
],
chainId: chain.id
}
})
This example transfers the amount equal to b0b's USDC balance to a11ce.
runtimeEncodeAbiParameters
This function is useful, when some method requires an arg which is bytes
, that is built out of several elements abi.encoded together.
This function allows those elements to be runtime injected into the ABI encoding. It can be useful for methods similar to Uniswap Router's execute
method.
Example usage
const instructions: Instruction[] = await mcNexus.buildComposable({
type: "default",
data: {
to: fooContractAddress,
abi: FOO_CONTRACT_ABI as Abi,
functionName: "foo",
args: [
bar,
baz,
// qux
runtimeEncodeAbiParameters(
[
{ name: "x", type: "uint256" },
{ name: "y", type: "uint256" },
{ name: "z", type: "bool" }
],
[
420n,
runtimeERC20BalanceOf({
targetAddress: mcNexus.addressOn(chain.id, true),
tokenAddress: testnetMcUSDC.addressOn(chain.id),
constraints: []
}),
true
]
),
corge,
waldo
],
chainId: chain.id
}
})
In this example qux
argument is expected to be abi.encode(x,y,z)
and we inject the runtime value as the y
argument,
Constraints
Constraints define conditions that must be met for execution to proceed. They serve several critical purposes in cross-chain orchestration:
Handling Slippage
When bridging or swapping tokens, you never receive exactly the amount you expect due to fees, price movements, and slippage. Constraints ensure operations only proceed when acceptable amounts are received:
// Only proceed if at least 90% of expected amount received (10% max slippage)
const constraints = [
greaterThanOrEqualTo((expectedAmount * 90n) / 100n)
];
Enforcing Transaction Sequencing
Constraints create implicit dependencies between instructions. For example, a swap on the destination chain won't execute until sufficient tokens have arrived from the bridge, creating a natural sequence:
// This instruction will wait until the bridged amount has arrived
// and only execute if the minimum amount constraint is satisfied
const swapAfterBridge = await oNexus.buildComposable({
// ...
args: [{
amountIn: runtimeERC20BalanceOf({
tokenAddress: usdcOnOptimism,
targetAddress: nexusAddress,
constraints: [greaterThanOrEqualTo(minAmount)]
})
}]
});
Protecting User Assets
Constraints act as safety mechanisms, preventing transactions from executing under unfavorable conditions. If bridge slippage is too high or an exchange rate becomes unfavorable, the constraint will prevent execution rather than proceeding with suboptimal parameters.
Cross-Chain Example
This example bridges tokens from Base to Optimism, then swaps the exact received amount:
// Bridge USDC from Base to Optimism
const bridgeInstruction = await oNexus.buildComposable({
// Bridge configuration...
});
// Swap on Optimism using whatever amount was received
const swapInstruction = await oNexus.buildComposable({
type: "default",
data: {
chainId: optimism.id,
// ...swap configuration
args: [{
amountIn: runtimeERC20BalanceOf({
tokenAddress: usdcOnOptimism,
targetAddress: nexusOnOptimism,
constraints: [greaterThanOrEqualTo(minAmountToAccept)]
}),
// Other swap parameters...
}]
}
});
Best Practices
- Add Constraints: Always use constraints to handle minimum acceptable values
- Use for Uncertain Amounts: Apply runtime injection when amounts depend on external factors
- Combine with Transfer: Use with the transfer instruction to return all tokens to the user
- Error Handling: Plan for constraint failures by providing alternative paths
How Runtime Injection Works
Runtime parameter injection is built on Biconomy's Composability Stack, which uses smart account fallback handlers to dynamically modify transactions during execution.
Architecture Overview
When you use runtimeERC20BalanceOf
in your code:
- The function call is encoded as a special "composable execution" call
- The smart account's fallback handler intercepts this transaction
- At execution time, the handler:
- Reads the current token balance from the blockchain
- Injects this value into the transaction parameters
- Forwards the modified transaction to the target contract
This architecture eliminates the need to write custom smart contracts for basic composability patterns like using token balances or handling cross-chain values.
Under The Hood
The fallback handler calls the executeComposable
function with parameters that determine:
- Which values to read from the blockchain
- Where to inject these values in the function call
- How to handle return values
This all happens transparently when you use the AbstractJS SDK, letting you focus on building your application logic rather than transaction composition mechanics.