# Introduction
Mayan.finance(https://mayan.finance/) is a lightning-fast, intent-based cross-chain swap protocol that enables seamless, instant, and cost-effective transfers of any asset on any chain to any asset on any other chain — all through a single, easy-to-use interface and a set of developer tools.
As of March 6, 2026 with over $17.5 billion in total volume processed, 7.6 million swaps completed, and 2.2 million unique wallets served, Mayan supports transfers between the major blockchains like Solana, Ethereum, BSC, Arbitrum, Optimism and Sui.
Last year, we found a very dangerous bug in Mayan Swift, which can Permanent freezing of User's funds in bridge.
# Details
https://github.com/mayan-finance/swap-bridge/blob/main/src/MayanSwift.sol#L531-L572
In MayanSwift.sol, an canceler can call `cancelOrder` function when a user's order is timeout, and set the `canceler` value to a address to receive the cancelFee ( `[0]` ). Once the `cancelOrder` function is called, the user's order will be permanently marked as `canceled` and cannot be cancel again ( `[1]` ). Finally the refundMsg will be sent to srcChain by wormhole.
```solidity
function cancelOrder(
bytes32 tokenIn,
OrderParams memory params,
uint16 srcChainId,
uint8 protocolBps,
bytes32 canceler // [0]
) public nonReentrant payable returns (uint64 sequence) {
params.destChainId = wormhole.chainId();
Key memory key = buildKey(params, tokenIn, srcChainId, protocolBps);
bytes32 orderHash = keccak256(encodeKey(key));
Order memory order = orders[orderHash];
if (block.timestamp <= key.deadline) {
revert DeadlineViolation();
}
if (order.status != Status.CREATED) { // []
revert InvalidOrderStatus();
}
orders[orderHash].status = Status.CANCELED;
RefundMsg memory refundMsg = RefundMsg({
action: uint8(Action.REFUND),
orderHash: orderHash,
srcChainId: key.srcChainId,
tokenIn: key.tokenIn,
recipient: key.trader,
canceler: canceler,
cancelFee: key.cancelFee,
refundFee: key.refundFee
});
bytes memory encoded = encodeRefundMsg(refundMsg);
sequence = wormhole.publishMessage{
value : msg.value
}(0, encoded, consistencyLevel);
emit OrderCanceled(orderHash, sequence);
}
```
https://github.com/mayan-finance/swap-bridge/blob/main/src/MayanSwift.sol#L574-L628
And in `refundOrder` function, anyone can refund a order with a verifed wormhole message. If the tokenIn is `address(0)` which means to native token like ETH in ethereum, the function will call `payViaCall` to send ETH to the `canceler` by `call` low-level function. If the `canceler` is a contract address with unavailable `receive` function, this order will be permanently locked and cannot be refunded. **This means that the user's funds will be frozen indefinitely in the bridge.**
```solidity
function refundOrder(bytes memory encodedVm) nonReentrant() public {
(IWormhole.VM memory vm, bool valid, string memory reason) = wormhole.parseAndVerifyVM(encodedVm);
require(valid, reason);
RefundMsg memory refundMsg = parseRefundPayload(vm.payload);
Order memory order = orders[refundMsg.orderHash];
if (refundMsg.srcChainId != wormhole.chainId()) {
revert InvalidSrcChain();
}
if (order.destChainId == 0) {
revert OrderNotExists();
}
if (order.status != Status.CREATED) {
revert InvalidOrderStatus();
}
orders[refundMsg.orderHash].status = Status.REFUNDED;
if (vm.emitterChainId != order.destChainId) {
revert InvalidEmitterChain();
}
if (vm.emitterAddress != solanaEmitter && truncateAddress(vm.emitterAddress) != address(this)) {
revert InvalidEmitterAddress();
}
address recipient = truncateAddress(refundMsg.recipient);
// no error if canceler is invalid
address canceler = address(uint160(uint256(refundMsg.canceler)));
address tokenIn = truncateAddress(refundMsg.tokenIn);
uint8 decimals;
if (tokenIn == address(0)) {
decimals = NATIVE_DECIMALS;
} else {
decimals = decimalsOf(tokenIn);
}
uint256 cancelFee = deNormalizeAmount(refundMsg.cancelFee, decimals);
uint256 refundFee = deNormalizeAmount(refundMsg.refundFee, decimals);
uint256 amountIn = deNormalizeAmount(order.amountIn, decimals);
uint256 netAmount = amountIn - cancelFee - refundFee;
if (tokenIn == address(0)) {
payViaCall(canceler, cancelFee);
payViaCall(msg.sender, refundFee);
payViaCall(recipient, netAmount);
} else {
IERC20(tokenIn).safeTransfer(canceler, cancelFee);
IERC20(tokenIn).safeTransfer(msg.sender, refundFee);
IERC20(tokenIn).safeTransfer(recipient, netAmount);
}
emit OrderRefunded(refundMsg.orderHash, netAmount);
}
```
# Step To Attack
1. An attacker deploy a contract address(named it revertAddr) in srcChain, An example canceler contract like
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract RevertAddr {
constructor() {
}
receive() external payable {
revert();
}
fallback() external payable {
revert();
}
}
```
2. a victim create a bridge order in Mayan Swift.
3. When the order is timeout, an attacker call `cancelOrder` with revertAddr in destChain.
4. This order will be permanently locked and cannot be refunded.
As an attentive reader, you might have noticed that this Mayan vulnerability requires a specific prerequisite to be triggered: the victim's cross-chain transaction must fail. One might assume that because this condition seems difficult to achieve, the impact of the vulnerability is limited. However, this is far from the truth, as attackers have multiple methods to actively engineer a cross-chain failure. For example, an attacker could act as a cross-chain relayer on Mayan to 'snatch' specific orders, effectively sniping a targeted user. Alternatively, an attacker could construct a Uniswap pool with malicious hooks to lure users into a cross-chain swap and then intentionally cause the transaction to fail.
So this is an very dangerous vulnerability that can Freeze user's fund permanently.
# The fix
Swift v2 fixed this vulnerability and is online on Feb-24-2026 01:06:35 PM UTC: https://etherscan.io/address/0x5bd484dde8dB2f79782EdBc76F0C3c405D1Ff350
The mayan team documented that: "Sift v1 will not be supported in the long term. New integrations should use Swift v2, and existing integrations are strongly encouraged to migrate" (https://docs.mayan.finance/architecture/swift)
However, at the time of writing this article, there are some people still using Mayan Swift v1, which is very dangerous. We urge the community pivot to Mayan Swift v2.
# Timeline
2025.11.04 Report the bug to info@mayan.finance and try to reach the team via twitter
2025.11.26 No response for 3 weeks
2025.11.26 Reach the team via Discord and sent the bug report again
2025.11.30 Got the Mayan team's responses: "The Report have been passed along to the team for review"
2025.12.05 Got the Mayan team's conclusion: "thank you for your report. I can confirm that this issue exists, and we’ve been aware of it for some time. We’re planning to migrate to Swift v2 in the coming weeks, which will address this issue and introduce additional improvements to the protocol. Since the issue was identified prior to your report, it isn’t eligible for a bounty"
2026.02.24 Swift v2 was deployed
2026.03.06 This report was released. Although Swift v2 is online, but there are people still using the vulerable version
# Credit
Zhiniang Peng & kamakura