Skip to main content

Initiate a Swap from a Smart Contract


This is a beta feature of Meson. If you wish to use it, please reach out to the Meson team for further assistance.

To execute a Meson cross-chain swap through a smart contract, adhere to the following instructions:

1. Encoding the Swap

First, use the encode swap API to generate an encodedSwap. Make sure to input your smart contract's address in the fromAddress field. You should expect a response like the one below:

"result": {
"encoded": "0x0100004c4b40d80000000000c1f8707e000000271000652104df03c601232909",
"fromContract": "0xc597f80150d371fbb39e7b8507456d9d99380255",
"recipient": "0x666d6b8a44d226150ca9058beebafe0e3ac065a2",
"fee": {
"serviceFee": "0",
"lpFee": "0.01",
"totalFee": "0.01"
"initiator": "0x9ac467c53aa8535950be25c39660e9e4c1048dcc"

The fromContract field echoes the fromAddress provided in your initial request. It signifies the involvement of a smart contract. Unlike swaps initiated from standard addresses (EOAs), those from a smart contract omit the user's signature. Instead, you'll get an initiator address for subsequent steps.


The encoded swap obtained from this API merely indicates a provisional intent to execute a cross-chain swap, and it does not initiate any on-chain transactions. The data remains valid for 15 minutes. If no subsequent action is taken to post this request on-chain within this timeframe, the data can be safely disregarded. If you need to start a new swap, simply generate a new encoded swap.

2. On-chain Transaction

To initiate the transaction, invoke the postSwapFromContract function from the Meson contract.

When your smart contract interacts with the Meson contract, a validation occurs. The Meson contract uses the isAuthorized function to ensure the appropriate permissions are set. Below is a sample implementation:

interface IMesonMinimal {
function tokenForIndex(uint8 tokenIndex) external returns (address token);
function postSwapFromContract(uint256 encodedSwap, uint200 postingValue, address fromContract)
payable external;

contract TransferToMesonContract {
IMesonMinimal meson;
address private currentAuthorizer = address(0);

constructor(address mesonAddress) {
meson = IMesonMinimal(mesonAddress);

// Meson smart contract verification to ensure proper authorization
function isAuthorized(address addr) external view returns (bool) {
return addr != address(0) && addr == currentAuthorizer;

function transferToMeson(uint256 encodedSwap, address initiator) payable external {
uint8 tokenIndex = _inTokenIndexFrom(encodedSwap);

currentAuthorizer = initiator;
uint200 postingValue = (uint200(uint160(initiator)) << 40) + 1;

if (tokenIndex == 255) {
// ETH
uint256 amount = _amountFrom(encodedSwap, 18);
require(amount == msg.value, "ETH value does not match the amount");
meson.postSwapFromContract{value: amount}(encodedSwap, postingValue, address(this));
} else {
// Stablecoins
IERC20 tokenContract = IERC20(meson.tokenForIndex(tokenIndex));

uint8 tokenDecimals = tokenContract.decimals();
uint256 amount = _amountFrom(encodedSwap, tokenDecimals);

// Option to transfer tokens directly from user's address
tokenContract.transferFrom(msg.sender, address(this), amount);
tokenContract.approve(address(meson), amount);

meson.postSwapFromContract(encodedSwap, postingValue, address(this));

currentAuthorizer = address(0);

function _amountFrom(uint256 encodedSwap, uint8 tokenDecimals) internal pure returns (uint256 amount) {
uint256 amountInMeson = (encodedSwap >> 208) & 0xFFFFFFFFFF;

if (tokenDecimals == 6) {
amount = amountInMeson;
} else if (tokenDecimals >= 6) {
// NOTE: Meson's smart contract always use decimal 6 to hanle swap amount.
// Some tokens like ETH, BTC or stablecoins on BNB Chain & Conflux
// have other decimals, so a conversion is required.
amount = amountInMeson * 10 ** (tokenDecimals - 6);
} else {
require(amountInMeson % (10 ** (6 - tokenDecimals)) == 0, "Decimals overflow");
amount = amountInMeson / 10 ** (6 - tokenDecimals);

function _inTokenIndexFrom(uint256 encodedSwap) internal pure returns (uint8) {
return uint8(encodedSwap);

The initiator parameter corresponds to the API response from the first step. The postingValue can also be constructed using SDK as follows

const { utils } = require('ethers')
const postingValue = utils.solidityPack(['address', 'uint40'], [initiator, 1])

The initiator is a critical address that will later authorize the release or withdrawal of funds. If you rely on the initiator provided by the API, Meson Relayer will handle these processes for you. However, if you opt to use another ETH address (perhaps the user's), ensure you can generate a signature. The signature are essential to finalize the cross-chain swap or fund withdrawal. Failing to do so might result in a permanent loss of the swapped funds.

Once the transaction is submitted to the initial blockchain, Meson will pick up the order and complete the cross-chain swap. This process typically takes 1-2 minutes. To check the status, visit the Meson explorer at{encoded}. Here, replace {encoded} with your specific encoded swap value to track the progress of your swap.