> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sodax.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> SODAX is mainnet-only (no testnet chains or RPC endpoints exist) with a hub-and-spoke architecture; Sonic is the hub. SODAX is non-custodial protocol/software: it routes and settles; independent solvers on the marketplace fill — never say 'our solver', 'the SODAX solver', that SODAX trades, takes custody, or fills orders. For frontend/React integrations, prefer @sodax/dapp-kit hooks over calling @sodax/sdk directly. SDK operations — the methods that build, submit or await a transaction, and the API/quote calls — return Result<T, E> ({ ok: true, value } or { ok: false, error }): check result.ok, never wrap them in try/catch or branch on error.message; discriminate on the narrow error.code union instead. Synchronous config getters (getPartnerFee, getSupportedSwapTokens, getVault, ...) return their value directly, not a Result.

# Swaps on Ethereum

> Intent-based cross-network swaps from an Ethereum perspective: one signed transaction, solver-filled, settled across networks.

SODAX swaps are intents, not bridges. Your user signs one transaction on Ethereum that locks the input and declares what they want on the destination network. Solvers fill it; the SODAX relayer carries proof between Ethereum, the hub (Sonic), and the destination. If nothing fills, the intent can be cancelled and funds recovered.

From your side it is one SDK call. From your user's side it is one wallet prompt, plus an approval the first time they swap an ERC-20.

## What this looks like on Ethereum

* **Source**: the intent transaction is an ordinary Ethereum transaction signed by the user's wallet. `ChainKeys.ETHEREUM_MAINNET` (`'ethereum'`) identifies the network everywhere in the SDK.
* **Reach**: any solver-compatible asset on any SODAX network is a valid destination, <span data-sodax-config="count" data-metric="swap-tokens">166</span> assets today. The [on-Ethereum asset list](/ethereum/networks-and-assets) only bounds what users hold on Ethereum itself, not what they can swap into.
* **Tokens**: ETH plus standard ERC-20s. Read addresses and decimals from the SDK config; never hard-code them.
* **Settlement**: output lands on the destination network at `dstAddress`. Swaps into Ethereum settle as ordinary ETH or ERC-20 balances at the recipient address.

<h2 id="approvals">
  Approvals
</h2>

`swap()` does not approve the input token for you. ETH is the native token and needs no approval. Every ERC-20 does: SODAX's asset manager pulls it through the ERC-20 interface, so check the allowance first and approve when it falls short.

```typescript theme={null}
const allowance = await sodax.swaps.isAllowanceValid({ params, walletProvider });
if (!allowance.ok) throw allowance.error;

if (!allowance.value) {
  const approval = await sodax.swaps.approve({ params, walletProvider });
  if (!approval.ok) throw approval.error;
  // wait for the approval to be mined before calling swap()
}
```

`params` is the same intent params object you pass to `swap()`.

<h3 id="usdt-approvals">
  USDT approvals
</h3>

Ethereum USDT comes from the 2017 TetherToken contract, which rejects an allowance change from one non-zero value to another. A wallet that already holds a USDT allowance for the asset manager, smaller than the new amount, cannot approve directly: the allowance has to go to zero first.

The SDK handles this for you. It simulates the approval before sending, and when the simulation shows a reset is needed, the signed `approve` call sends `approve(0)`, waits for it to be mined, then sends the real approval. The user signs twice, and `approve` still resolves to one hash, the last transaction's. If your UI shows an approving state, expect a second wallet prompt on USDT. Detection is by simulation, not a token list, so any other token with the same behavior is handled the same way.

If you build unsigned transactions instead (`raw: true`), `approve` returns only one transaction. Use `buildApproveTxs`, which returns the reset when one is needed:

```typescript theme={null}
const approvals = await sodax.swaps.buildApproveTxs({ params, raw: true });
if (!approvals.ok) throw approvals.error;

const { resetTx, approveTx } = approvals.value;
if (resetTx) {
  // approveTx is not valid until the reset has been mined
  await sendAndWait(resetTx);
}
await sendAndWait(approveTx);
```

`sendAndWait` stands for your own signer: broadcast the transaction and wait for its receipt. `resetTx` is absent for every other token and for a wallet with no allowance yet, so the common path is a single transaction.

## Quoting

The solver API quotes both directions:

```typescript theme={null}
const quoteResult = await sodax.swaps.getQuote({
  token_src: eth.address,             // token on Ethereum
  token_dst: usdc.address,            // token on the destination network
  token_src_blockchain_id: ChainKeys.ETHEREUM_MAINNET,
  token_dst_blockchain_id: ChainKeys.BASE_MAINNET,
  amount: 10n ** BigInt(eth.decimals) / 10n, // 0.1 ETH
  quote_type: 'exact_input',          // the only supported quote type
});
```

`getQuote` deducts any configured partner fee before quoting, so `quoted_amount` is the net output the user actually receives.

## Fees

Using SODAX is free to integrate. Two fees can apply to a trade:

* **Base fee**: a fixed 0.1% of the input, taken by the protocol. Not configurable; compute it ahead of time with `getSolverFee(inputAmount)`.
* **Your fee**: optional platform fee on top, set by you and paid to you. It is the only fee you control. Configure it at SDK setup and check it with `getPartnerFee(inputAmount)`. See [Monetize SDK](/developers/how-to/monetize_sdk).

The user also pays Ethereum's network fee for the intent transaction, in ETH, like any other Ethereum transaction. To show it before the user signs, build the transaction with `raw: true` and estimate it:

```typescript theme={null}
// The type arguments tell TypeScript this is the unsigned (raw) form
const intent = await sodax.swaps.createIntent<typeof ChainKeys.ETHEREUM_MAINNET, true>({ params, raw: true });
if (!intent.ok) throw intent.error;

const gas = await sodax.swaps.estimateGas({
  tx: intent.value.tx,
  chainKey: ChainKeys.ETHEREUM_MAINNET,
});
if (gas.ok) console.log('Gas units:', gas.value); // multiply by the current fee per gas
```

The same works for a raw approval. See [Estimate Gas](/developers/how-to/estimate_gas).

## Executing

Prefer `swap()`. It creates the intent on Ethereum, verifies it landed, submits it to the relay, waits for the packet on the hub, and notifies the solver. The [Quickstart](/ethereum/quickstart#4-quote-and-swap) has the complete call. Track progress with `getStatus(request)`, and cancel unfilled intents with `cancelIntent`. Limit orders (intents without deadlines) work from Ethereum too, via `createLimitOrder`.

Orchestrating manually with `createIntent` and `submitIntent` works the same as on any EVM network: Ethereum intents need no relay extra data.

## Error handling

Operations return `Result<T>` instead of throwing. The core methods (`swap`, `createIntent`, `postExecution`) return a typed `SodaxError` union you can `switch` on by `error.code`.

***

Full reference, including raw mode, limit orders, cancellation, and the complete error-code table: [Swaps](/developers/packages/foundation/sdk/functional-modules/swaps).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.