Swap
Cross-chain swaps between any supported assets including Monero (XMR).
API key is only required if you want to collect integrator fees. Without an API key, swaps work normally but no fees are collected.
Quick Start
# 1. Get a quote
curl -X POST https://api.wagyu.xyz/v1/quote \
-H "Content-Type: application/json" \
-H "X-API-KEY: wg_your_api_key" \
-d '{
"fromChainId": 42161,
"toChainId": 0,
"fromToken": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"toToken": "XMR",
"fromAmount": "100000000"
}'
# 2. Create order
curl -X POST https://api.wagyu.xyz/v1/order \
-H "Content-Type: application/json" \
-H "X-API-KEY: wg_your_api_key" \
-d '{
"fromChainId": 42161,
"toChainId": 0,
"fromToken": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"toToken": "XMR",
"fromAmount": "100000000",
"toAddress": "4AdUndXHHZ..."
}'
# 3. User sends funds to depositAddress
# 4. Poll status until completed
curl https://api.wagyu.xyz/v1/order/ABC123 \
-H "X-API-KEY: wg_your_api_key"
Endpoints
Get Supported Chains
Returns all supported chains and their configurations.
GET /v1/chains
Response:
{
"chains": [
{
"chainId": 42161,
"name": "Arbitrum",
"nativeToken": "ETH",
"minSwapUsd": 20
},
{
"chainId": 1151111081099710,
"name": "Solana",
"nativeToken": "SOL",
"minSwapUsd": 20
},
{
"chainId": 0,
"name": "Monero",
"nativeToken": "XMR",
"minSwapUsd": 25
}
]
}
XML Rates Feed
XML feed generated from Wagyu backend quote simulation.
GET /rates.xml
Response Headers:
| Header | Value |
|---|---|
Content-Type | application/xml; charset=utf-8 |
Cache-Control | public, max-age=30 |
Current pair coverage:
USDTARBITRUM -> XMRUSDCARBITRUM -> XMRETHARBITRUM -> XMRWBTCARBITRUM -> XMRXMR -> USDTARBITRUMXMR -> USDCARBITRUMXMR -> ETHARBITRUM
Each <item> is one simulated quote sample and includes <from>, <to>, <in>, <out>, <amount>, <tofee>, <minamount>, <maxamount>, <exchange>, and optional <eta>.
Example Response:
<?xml version="1.0" encoding="UTF-8"?>
<rates>
<item>
<from>USDTARBITRUM</from>
<to>XMR</to>
<in>100</in>
<out>0.3054</out>
<amount>0.3054</amount>
<tofee>0 XMR</tofee>
<minamount>25 USDTARBITRUM</minamount>
<maxamount>100 USDTARBITRUM</maxamount>
<exchange>Wagyu</exchange>
<eta>6 min</eta>
</item>
</rates>
Get Quote
Get an estimated exchange rate and output amount.
POST /v1/quote
Headers:
| Header | Required | Description |
|---|---|---|
Content-Type | Yes | application/json |
X-API-KEY | No | Your API key (for fee collection) |
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
fromChainId | number | Yes | Source chain ID (0 = Monero) |
toChainId | number | Yes | Destination chain ID (0 = Monero) |
fromToken | string | Yes | Token address or "XMR" |
toToken | string | Yes | Token address or "XMR" |
fromAmount | string | Yes | Amount in smallest units |
Example Request:
curl -X POST https://api.wagyu.xyz/v1/quote \
-H "Content-Type: application/json" \
-d '{
"fromChainId": 42161,
"toChainId": 0,
"fromToken": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"toToken": "XMR",
"fromAmount": "100000000"
}'
Response:
{
"fromAmount": "100000000",
"fromAmountUsd": "100.00",
"fromSymbol": "USDC",
"toAmount": "156250000000",
"toAmountUsd": "98.50",
"toSymbol": "XMR",
"estimatedTime": 300,
"minReceived": "0.1523"
}
If you include an API key with a configured fee, the response will also include an integratorFee field showing the fee that will be collected.
Create Order
Create a new swap order and get a deposit address.
POST /v1/order
Headers:
| Header | Required | Description |
|---|---|---|
Content-Type | Yes | application/json |
X-API-KEY | No | Your API key (for fee collection) |
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
fromChainId | number | Yes | Source chain ID |
toChainId | number | Yes | Destination chain ID |
fromToken | string | Yes | Token address or "XMR" |
toToken | string | Yes | Token address or "XMR" |
fromAmount | string | Yes | Amount in smallest units |
toAddress | string | Yes | Destination address for output |
sessionId | string | No | UUID v4 session ID for order history (generated if not provided) |
twapConfig | object | No | Custom TWAP settings (XMR orders only) |
twapConfig Object (XMR orders only):
| Field | Type | Description |
|---|---|---|
disabled | boolean | Disable TWAP, use market orders only (default: false) |
chunkSizeUsd | number | Chunk size in USD (min 250, default: 5000) |
intervalMs | number | Interval between chunks in ms (1000-60000, default: 8000) |
Example Request:
curl -X POST https://api.wagyu.xyz/v1/order \
-H "Content-Type: application/json" \
-d '{
"fromChainId": 42161,
"toChainId": 0,
"fromToken": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"toToken": "XMR",
"fromAmount": "100000000",
"toAddress": "4AdUndXHHZ6ieMLmq4D2qSohBXCQSTGL5yzpPw5EfU8hTJEPHWPmZx7s6Hb2wTv3Se7S1VQqNsLstUHCMMGFKZKaCUBthxR"
}'
Example with custom TWAP (for XMR orders):
curl -X POST https://api.wagyu.xyz/v1/order \
-H "Content-Type: application/json" \
-d '{
"fromChainId": 42161,
"toChainId": 0,
"fromToken": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"toToken": "XMR",
"fromAmount": "50000000000",
"toAddress": "4AdUndXHHZ...",
"twapConfig": {
"chunkSizeUsd": 20000,
"intervalMs": 10000
}
}'
Example disabling TWAP (market orders only):
curl -X POST https://api.wagyu.xyz/v1/order \
-H "Content-Type: application/json" \
-d '{
"fromChainId": 42161,
"toChainId": 0,
"fromToken": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"toToken": "XMR",
"fromAmount": "50000000000",
"toAddress": "4AdUndXHHZ...",
"twapConfig": {
"disabled": true
}
}'
Response:
{
"orderId": "F9B6RT",
"depositAddress": "0xc2294EDad11418Fd1dEff00746F247112c12F5BB",
"depositChain": "Arbitrum",
"depositChainId": 42161,
"depositToken": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"depositTokenSymbol": "USDC",
"depositAmount": "100000000",
"toAddress": "4AdUndXHHZ...",
"expectedOutput": "156250000000",
"expiresAt": "2026-01-19T14:00:00.000Z",
"status": "awaiting_deposit"
}
Send exactly depositAmount of depositTokenSymbol to depositAddress on chain depositChainId.
Get Order Status
Check the status of an existing order.
GET /v1/order/:orderId
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
verify | string | No | Destination address for verification (see Security below) |
Example Request:
curl https://api.wagyu.xyz/v1/order/F9B6RT
Example with verification:
curl "https://api.wagyu.xyz/v1/order/F9B6RT?verify=4AdUndXHHZ..."
To prevent unauthorized access to order details, you can optionally provide the destination address as a verify query parameter. If the provided address doesn't match the order's destination address, the API returns a 403 error. This is useful when sharing order links - users must know the destination address to view the order.
Get Order History
Get all orders for a session.
GET /v1/orders?session=<SESSION_ID>
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
session | string | Yes | Session ID (UUID v4 returned when creating order) |
Example Request:
curl "https://api.wagyu.xyz/v1/orders?session=550e8400-e29b-41d4-a716-446655440000"
Response:
{
"orders": [
{
"id": "F9B6RT",
"from_chain_id": 42161,
"from_token_symbol": "USDC",
"from_amount": "100000000",
"to_chain_id": 0,
"to_token_symbol": "XMR",
"to_amount_expected": "156250000000",
"to_address": "4AdUndXHHZ...",
"status": "completed",
"created_at": "2026-01-19T12:00:00.000Z",
"completed_at": "2026-01-19T12:05:23.000Z"
}
]
}
The sessionId is returned when you create an order. Store it in localStorage to track all orders created from the same browser. If a user loses their session ID, they can still access individual orders using the verify parameter with their destination address.
Response (completed order):
{
"orderId": "F9B6RT",
"status": "completed",
"depositAddress": "0xc2294EDad11418...",
"depositChainId": 42161,
"depositTokenSymbol": "USDC",
"depositAmount": "100000000",
"depositTxHash": "0x4a8b2c...",
"toAddress": "4AdUndXHHZ...",
"expectedOutput": "156250000000",
"actualOutput": "155890000000",
"createdAt": "2026-01-19T12:00:00.000Z",
"completedAt": "2026-01-19T12:05:23.000Z"
}
Response (refunded order):
{
"orderId": "DX432K",
"status": "refunded",
"depositAddress": "bc1qy9epejgvcegjh6q2nsypzpeunnfe0vm0t9c07h",
"depositChainId": 20000000000001,
"depositTokenSymbol": "BTC",
"depositAmount": "24917839",
"depositTxHash": "cb16de60d94c6ecb0aa3...",
"errorMessage": "All bridge routes rejected your transaction. Please try again later or with a different amount.",
"refundTxHash": "7a3b8c9d2e1f...",
"createdAt": "2026-01-20T17:10:00.000Z"
}
Order Statuses:
| Status | Description |
|---|---|
awaiting_deposit | Waiting for user to send funds |
deposit_detected | Deposit seen, waiting for confirmations |
deposit_confirmed | Deposit confirmed, processing |
executing_swap | Swap in progress |
completed | Swap completed successfully |
refunding | Swap could not be completed, refund in progress |
refunded | Funds have been returned to the user |
failed | Swap failed (see errorMessage) |
expired | No deposit received within 2 hours |
When a swap cannot be completed (e.g., all bridge routes rejected the transaction), the order enters refunding status and funds are automatically returned to the original sender. The errorMessage field contains the reason for the refund. Once complete, status changes to refunded and refundTxHash contains the refund transaction hash.
Integrator Fees (Optional)
If you're building an integration and want to earn fees, you can create an API key with a fee percentage.
API keys are only required for fee collection. Without an API key, swaps work normally with no fees.
How It Works
- Create an API key at wagyu.xyz/api
- Configure your fee percentage (0-5%) and payout addresses
- Include your API key in requests via
X-API-KEYheader - Your fee is automatically deducted from deposits and sent to your address
Fee Configuration
| Setting | Type | Description |
|---|---|---|
fee_percent | number | Fee percentage (0-5%) |
fee_address | string | EVM address for fees (required) |
fee_address_sol | string | Solana address for fees (required for Solana swaps) |
fee_address_btc | string | Bitcoin address for fees (optional, for BTC swaps) |
Provide all three fee addresses to collect fees on every chain:
fee_address(EVM) — required for EVM and XMR swapsfee_address_sol(Solana) — required for Solana swapsfee_address_btc(Bitcoin, optional) — required for Bitcoin swaps
If a fee address is missing for a chain, fees on that chain will be skipped.
Fee Collection by Chain
| Source Chain | Fee Collected In | Sent To |
|---|---|---|
| EVM (Arbitrum, etc.) | Deposited token (USDC, ETH, etc.) | fee_address |
| Solana | Deposited token (SOL, SPL tokens) | fee_address_sol |
| Bitcoin | BTC (before swap) | fee_address_btc |
| Monero (XMR → Any) | USDC on Hyperliquid (after conversion) | fee_address |
Fee Thresholds
Fees are only collected if the fee amount exceeds the transaction cost. For very small swaps, the fee may be skipped if it's not economically viable to send (the gas cost would exceed the fee value).
Quote Response with Fee
When you include an API key with a configured fee, the /v1/quote response includes fee details:
{
"fromAmount": "100000000",
"toAmount": "156250000000",
"integratorFee": {
"percent": 1,
"amount": "1000000",
"amountUsd": "1.00"
}
}
Tracking Your Fees
You can view your collected fees and statistics in the API Dashboard after connecting your wallet.
TWAP Execution (XMR Orders)
For large XMR orders, TWAP (Time-Weighted Average Price) execution is used to minimize slippage by splitting orders into smaller chunks executed over time on Hyperliquid's XMR1/USDC orderbook.
Why TWAP?
Large market orders can cause significant slippage due to limited orderbook depth. TWAP splits your order into smaller chunks executed over time, achieving a better average price.
Example: A $50,000 XMR buy executed all at once might get 2-3% slippage. With TWAP (10 chunks of $5k each, 8 seconds apart), you typically see <0.5% slippage.
Automatic TWAP Thresholds
TWAP is automatically enabled when order size exceeds:
| Direction | Threshold | Default Chunk Size |
|---|---|---|
| XMR → Any (sell) | > $5,000 | 6 XMR (~$3,600 at $600/XMR) |
| Any → XMR (buy) | > $25,000 | $5,000 USD |
Orders below these thresholds use instant market orders.
Custom TWAP Configuration
You can customize TWAP behavior by including twapConfig in your order request:
{
"twapConfig": {
"disabled": false,
"chunkSizeUsd": 20000,
"intervalMs": 10000
}
}
| Setting | Range | Default | Description |
|---|---|---|---|
disabled | boolean | false | Skip TWAP entirely, use market orders regardless of size |
chunkSizeUsd | min 250 | 5000 | Size of each chunk in USD |
intervalMs | 1000-60000 | 8000 | Time between chunks in milliseconds |
Chunk Size Examples
| Chunk Size | Best For | Trade-off |
|---|---|---|
| $250 | Maximum price precision, thin liquidity | Longest execution time |
| $1,000 | High precision | Longer execution time |
| $5,000 | Balanced (default) | Good for most orders |
| $20,000 | Large orders, moderate liquidity | Faster, slightly more slippage |
| $50,000+ | Very large orders, good liquidity | Fast execution, more slippage |
If you set chunk size larger than your order (e.g., $100k chunk for $10k order), the entire order executes in one market order.
Disabling TWAP
If you want immediate execution regardless of order size (accepting potential slippage), set disabled: true:
{
"twapConfig": {
"disabled": true
}
}
This forces a single market order for the full amount.
TWAP Status in Order Response
When TWAP is active, the order status response includes detailed progress:
{
"orderId": "ABC123",
"status": "executing_swap",
"twap": {
"enabled": true,
"status": "active",
"chunksCompleted": 3,
"chunksTotal": 10,
"filledQty": 15000,
"totalQty": 50000,
"chunkSizeUsd": 5000,
"intervalMs": 8000,
"estimatedTimeRemainingMs": 56000
}
}
| Field | Description |
|---|---|
status | pending, active, completed, or failed |
chunksCompleted | Number of chunks executed |
chunksTotal | Total chunks planned |
filledQty | USD value already filled |
totalQty | Total USD value to fill |
estimatedTimeRemainingMs | Estimated time until completion |
View the Orderbook
You can view the live XMR1/USDC orderbook to understand current liquidity:
app.wagyu.xyz/trade?market=XMR/USDC
TWAP configuration only applies to orders involving XMR. For other swaps (e.g., ETH → USDC), this parameter is ignored.
Common Token Addresses
Arbitrum (42161)
| Token | Address |
|---|---|
| USDC | 0xaf88d065e77c8cC2239327C5EDb3A432268e5831 |
| USDT | 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9 |
| ETH | 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE |
Solana (1151111081099710)
| Token | Address |
|---|---|
| SOL | 11111111111111111111111111111111 |
| USDC | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v |
Monero (0)
| Token | Address |
|---|---|
| XMR | XMR |
Code Examples
JavaScript/Node.js
const API_KEY = 'wg_your_api_key';
const BASE_URL = 'https://api.wagyu.xyz';
async function createSwap(fromChainId, toChainId, fromToken, toToken, fromAmount, toAddress) {
const response = await fetch(`${BASE_URL}/v1/order`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-KEY': API_KEY,
},
body: JSON.stringify({
fromChainId,
toChainId,
fromToken,
toToken,
fromAmount,
toAddress,
}),
});
return response.json();
}
async function getOrderStatus(orderId) {
const response = await fetch(`${BASE_URL}/v1/order/${orderId}`, {
headers: { 'X-API-KEY': API_KEY },
});
return response.json();
}
// Example: Swap 100 USDC on Arbitrum to XMR
const order = await createSwap(
42161, // Arbitrum
0, // Monero
'0xaf88d065e77c8cC2239327C5EDb3A432268e5831', // USDC
'XMR',
'100000000', // 100 USDC (6 decimals)
'4AdUndXHHZ...' // XMR address
);
console.log('Deposit', order.depositAmount, order.depositTokenSymbol);
console.log('To:', order.depositAddress);
Python
import requests
API_KEY = 'wg_your_api_key'
BASE_URL = 'https://api.wagyu.xyz'
def create_swap(from_chain, to_chain, from_token, to_token, amount, to_address):
response = requests.post(
f'{BASE_URL}/v1/order',
headers={
'Content-Type': 'application/json',
'X-API-KEY': API_KEY,
},
json={
'fromChainId': from_chain,
'toChainId': to_chain,
'fromToken': from_token,
'toToken': to_token,
'fromAmount': amount,
'toAddress': to_address,
}
)
return response.json()
def get_order_status(order_id):
response = requests.get(
f'{BASE_URL}/v1/order/{order_id}',
headers={'X-API-KEY': API_KEY}
)
return response.json()
# Example: Swap 100 USDC on Arbitrum to XMR
order = create_swap(
42161, # Arbitrum
0, # Monero
'0xaf88d065e77c8cC2239327C5EDb3A432268e5831', # USDC
'XMR',
'100000000', # 100 USDC
'4AdUndXHHZ...' # XMR address
)
print(f"Send {order['depositAmount']} {order['depositTokenSymbol']} to {order['depositAddress']}")