> For the complete documentation index, see [llms.txt](https://docs.swapkit.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.swapkit.dev/smart-contract-limitations-and-edge-cases.md).

# Smart contract limitations & edge cases

Key corner cases to take into account when integrating with our cross-chain providers

When integrating with THORChain or Mayachain, certain limitations at the smart contract and transaction level must be taken into account. Failing to do so may result in failed swaps, rejected transactions, or permanent loss of funds.

### EVM Transaction Types

THORChain and Mayachain do not support EVM Type 4 transactions introduced in [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702), which involve account abstraction. This includes:

* All `type: 4` transactions
* Transactions from wallets based on abstracted accounts

Any transaction using this format will be rejected and **may lead to loss of funds** if funds are transferred under an unsupported transaction structure.

The following EVM transaction types are supported:

* `type: 0` (Legacy)
* `type: 2` (EIP-1559)

The `includeTx` optional argument when requesting a quote returns a legacy transaction object.

Chainflip, by contrast, does not impose restrictions on transaction types. All EVM transaction types, including Type 4, are supported when depositing on a Chainflip channel.

### Transaction Log Limit

For inbound transactions into THORChain and Mayachain, the maximum number of logs that a transaction can include is **50 logs**.

This limitation is particularly important for integrators who use smart contract wrappers to:

* Perform token swaps
* Route through aggregators
* Execute any custom logic **before** sending funds to THORChain or Mayachain

If the resulting transaction generates more than 50 logs (e.g., due to complex DeFi interactions), **THORChain will not process it**.

### THORChain, Mayachain & Smart Contract Limitations <a href="#transaction-log-limit" id="transaction-log-limit"></a>

Thorchain has strict gas limits when sending assets to smart contracts. Wallet-emitting events require gas and might break compatibility with Thorchain normal swap flow.\
By default, SwapKit API checks if the sender and recipient addresses are smart contract and will filter out `THORCHAIN`, `THORCHAIN_STREAMING`, `MAYACHAIN` and `MAYACHAIN_STREAMING` providers.\
You may use the `allowSmartContractReceiver` after confirming with SwapKit developers that your wallet respects Thorchain gas requirements.

### Solana multisig vaults & PDAs (off-curve addresses)

Ordinary Solana wallets are on-curve ed25519 public keys. Program Derived Addresses (PDAs) are off-curve by construction and have no private key, so they cannot sign transactions. This includes Squads multisig vaults, Associated token accounts (ATAs) and Program-owned state accounts.

These are valid, well-formed Solana addresses, so SwapKit treats them the same way it treats EVM smart-contract counterparties: valid, but only accepted when you explicitly opt in. SwapKit rejects them by default (`invalidDestinationAddress` / `invalidSourceAddress`), funds sent to one are unrecoverable. Never send to a token account (ATA); use the owner address.

**To swap into a vault you control:**

1. `/v3/quote` — `destinationAddress` is optional and *never validated* (used only for screening).
2. `/v3/swap` — pass `destinationAddress` + `allowSmartContractReceiver: true`.

`allowSmartContractSender` works the same way for off-curve *source* addresses, on `/v3/swap` only. (A PDA can't sign a plain transfer, so a multisig source needs your own signing flow regardless.)

**Current limitations:**

Chainflip → SOL token (via Jupiter) — requires on-curve owner

### Exchange deposit addresses as source or refund address <a href="#transaction-log-limit" id="transaction-log-limit"></a>

Refunds return to `sourceAddress` unless you pass a [`refundAddress`](/swapkit-api/v3-quote-request-a-swap-quote.md#refunding-to-a-different-address). Whichever applies is what SwapKit carries to the provider — Chainflip's `refundParameters.refundAddress`, or the refund address in the Maya memo for Zcash.

Do not use a centralised exchange deposit address for either. It passes validation and screening, so nothing in the API rejects it, but the exchange may not credit the refund — some only credit while actively expecting a deposit, and some flag a provider's outbound address as risky and stop crediting anything from it. The refund succeeds on-chain while the user still cannot reach the funds, and recovery is up to the exchange's support process. The same applies to `destinationAddress`.

Use a self-custody address the user holds keys for, and surface this if your interface lets them paste an arbitrary address.

### XRP through THORChain limitations <a href="#transaction-log-limit" id="transaction-log-limit"></a>

Integrators should be aware of if they are not using our `includeTx` optional argument to build transactions for XRP swaps through THORChain:

* **Transaction Type**: Only `Payment` transactions are supported for both inbound and outbound transfers.
* **Address Format**: Only **classic XRP addresses** (starting with `r...`) are supported. X-addresses (which encode both address and destination tag) are **not supported** and must be decoded before use.
* **Memo Field**:
  * THORChain supports only the first entry in the `Memos[]` array.
  * Additional memos (if present) will be ignored.
  * Maximum memo length: 250 characters, any excess will be ignored.

For reliable behavior, always use classic addresses and ensure your memo data fits within the 250-character limit.

### Litecoin MWEB addresses

No provider SwapKit routes Litecoin through can settle **MWEB** (Litecoin's Mimblewimble Extension Blocks). Two different cases follow from that, and only one of them is detectable.

**An MWEB address is rejected.** An `ltcmweb1…` address supplied as either `sourceAddress` or `destinationAddress` returns `400 litecoinMwebAddressUnsupported`. It has its own error code rather than the generic `invalidSourceAddress` / `invalidDestinationAddress` so it's distinguishable from a typo. Note it is **not** waived by `disableSecurityChecks`, unlike the other address checks — that flag relaxes our heuristics, but no provider can settle MWEB, so waiving it would only permit a swap that cannot complete.

**Funding from MWEB is not detectable, and can lose funds.** A user can hold LTC inside MWEB, give you an ordinary `ltc1…` source address, and then fund the deposit with a peg-out. That request is byte-for-byte identical to a normal Litecoin swap, so nothing on our side can spot it — but the peg-out's sender cannot be identified, so if the swap needs to refund, there is nowhere to send the funds.

Because it can't be detected, it can only be warned about: **every route selling LTC carries a `litecoinMwebNotSupported` entry in `warnings[]`**, on both `/v3/quote` and `/v3/swap`. The warning is unconditional on the sell side — its presence says nothing about whether this particular user is using MWEB. Surface it to anyone selling Litecoin, and tell them to fund from a regular Litecoin balance rather than an MWEB one.

Both `/v3/quote` and `/v3/swap` still return routes and a transaction; no route is filtered or reordered.

### USDT on Ethereum needs two approval transactions

Ethereum mainnet USDT (`0xdAC17F958D2ee523a2206206994597C13D831ec7`) predates ERC-20 and its `approve()` reverts when an allowance moves from one non-zero value to another. Raising an existing allowance therefore takes two transactions: reset to `0`, then approve the new amount. Ethereum mainnet USDT is the only token SwapKit treats this way — USDT on other chains is approved in one transaction like any other ERC-20.

**Today the API hands you only the first of the two.** When the sell asset is Ethereum USDT and the wallet already holds an allowance that is non-zero but smaller than the sell amount, the `approvalTx` returned by [`/v3/swap`](/swapkit-api/v3-swap-obtain-swap-transaction-details.md#the-approvaltx-object) and [`/v3/limit/build`](/swapkit-api/v3-limit-place-and-manage-limit-orders.md#the-approvaltx-object) encodes `approve(spender, 0)` — the reset — and nothing else in the response distinguishes it from a normal approval.

To complete the approval:

1. Broadcast the returned `approvalTx` and wait for it to confirm. The allowance is now `0`.
2. Call `/v3/swap` or `/v3/limit/build` again. With the allowance at `0`, the response now carries the real `approve(spender, sellAmount)`.
3. Broadcast that one, then proceed with the swap or order.

You can recognise a reset without making the second call: `approvalTx.data` is a standard `approve(address,uint256)` payload, so its final 32 bytes are the amount. All-zero means a reset rather than the approval itself.

{% hint style="warning" %}
**Do not stop after step 1.** Abandoning the flow there leaves the user worse off than before — the pre-existing partial allowance is gone, and the swap or order reverts if broadcast. Either carry it through both approvals, or do not start it.
{% endhint %}
