Nordstern

Quick Start

Get your first swap route in under a minute. This example swaps 1 ETH for USDC on Base.

This example swaps 1 ETH for USDC on Base. Replace the addresses and chain ID for any other token pair on any supported chain.

Step 1 · Request a route

# Swap 1 WETH → USDC on Base (chain 8453)
# To swap native ETH directly, use 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE as src
curl 'https://api.nordstern.finance/aggregator/8453?src=0x4200000000000000000000000000000000000006&dst=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913&amount=1000000000000000000&from=0xYourWalletAddress&slippage=0.5' \
  -H "Referer: https://yourdomain.com"
// Swap 1 WETH → USDC on Base (chain 8453)
// To swap native ETH directly, use 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE as src
const params = new URLSearchParams({
  src: "0x4200000000000000000000000000000000000006",  // WETH (or 0xEeee... for native ETH)
  dst: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",  // USDC
  amount: "1000000000000000000",                       // 1 ETH in wei
  from: "0xYourWalletAddress",
  slippage: "0.5",
});

const response = await fetch(
  `https://api.nordstern.finance/aggregator/8453?${params}`,
  { headers: { "Referer": "https://yourdomain.com" } }
);
const route = await response.json();
import requests

# Swap 1 WETH → USDC on Base (chain 8453)
# To swap native ETH directly, use 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE as src
url = "https://api.nordstern.finance/aggregator/8453"
params = {
    "src":      "0x4200000000000000000000000000000000000006",  # WETH (or 0xEeee... for native ETH)
    "dst":      "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",  # USDC
    "amount":   "1000000000000000000",                          # 1 ETH in wei
    "from":     "0xYourWalletAddress",
    "slippage":  0.5,
}
route = requests.get(url, params=params, headers={"Referer": "https://yourdomain.com"}).json()

Step 2 · Read the response

The response contains the optimal route and a fully encoded tx object ready to pass to your wallet or provider. Before proceeding, verify that tx is present and toAmount is non-zero. If either check fails, no valid route is available.

{
  "src": "0x4200000000000000000000000000000000000006",
  "dst": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  "fromAmount": "1000000000000000000",
  "toAmount": "2120362157",
  "gasEstimate": "1045694",
  "tx": {
    "from": "0xYourWalletAddress",
    "to":   "0xC87De04e2EC1F4282dFF2933A2D58199f688fC3d",
    "data": "0x3f0bde25...",
    "value": "0"
  }
}

Step 3 · Approve and execute

Before the first swap of an ERC-20 token, approve the Guard Contract (tx.to) to spend your input token. Once granted, you don't need to repeat it for the same token. Native token swaps (0xEeee...EEeE) skip this step — the value is passed via tx.value. Then submit tx directly.

import { createPublicClient, createWalletClient, http, parseAbi, maxUint256 } from "viem";
import { base } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";

const account   = privateKeyToAccount("0xYourPrivateKey");
const transport = http("https://your-rpc-url");
const publicClient = createPublicClient({ chain: base, transport });
const walletClient = createWalletClient({ account, chain: base, transport });

// Guard: no valid route
if (!route.tx || route.toAmount === "0") {
  throw new Error("No valid route available");
}

const NATIVE = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
const isNative = route.src.toLowerCase() === NATIVE.toLowerCase();

// Native input: value is set on tx, no ERC-20 approval needed
if (!isNative) {
  const ERC20_ABI = parseAbi([
    "function allowance(address,address) view returns (uint256)",
    "function approve(address,uint256) returns (bool)",
  ]);

  // 1. Check allowance on the input token
  const allowance = await publicClient.readContract({
    address: route.src,
    abi: ERC20_ABI,
    functionName: "allowance",
    args: [account.address, route.tx.to],
  });

  // 2. Approve the router if needed (max approval to avoid repeating)
  if (allowance < BigInt(route.fromAmount)) {
    await walletClient.writeContract({
      address: route.src,
      abi: ERC20_ABI,
      functionName: "approve",
      args: [route.tx.to, maxUint256],
    });
  }
}

// 3. Submit the swap
const hash = await walletClient.sendTransaction({
  to:    route.tx.to,
  data:  route.tx.data,
  value: BigInt(route.tx.value),
});
import { ethers } from "ethers";

const provider = new ethers.JsonRpcProvider("https://your-rpc-url");
const wallet   = new ethers.Wallet("0xYourPrivateKey", provider);

// Guard: no valid route
if (!route.tx || route.toAmount === "0") {
  throw new Error("No valid route available");
}

const NATIVE = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
const isNative = route.src.toLowerCase() === NATIVE.toLowerCase();

// Native input: value is set on tx, no ERC-20 approval needed
if (!isNative) {
  const ERC20_ABI = [
    "function allowance(address owner, address spender) view returns (uint256)",
    "function approve(address spender, uint256 amount) returns (bool)",
  ];

  const token = new ethers.Contract(route.src, ERC20_ABI, wallet);

  // 1. Check allowance on the input token
  const allowance = await token.allowance(wallet.address, route.tx.to);

  // 2. Approve the router if needed (max approval to avoid repeating)
  if (allowance < BigInt(route.fromAmount)) {
    await token.approve(route.tx.to, ethers.MaxUint256);
  }
}

// 3. Submit the swap
const tx = await wallet.sendTransaction({
  to:    route.tx.to,
  data:  route.tx.data,
  value: BigInt(route.tx.value),
});
from web3 import Web3

w3      = Web3(Web3.HTTPProvider("https://your-rpc-url"))
account = w3.eth.account.from_key("0xYourPrivateKey")

# Guard: no valid route
if "tx" not in route or route["toAmount"] == "0":
    raise ValueError("No valid route available")

NATIVE    = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"
is_native = route["src"].lower() == NATIVE.lower()

# Native input: value is set on tx, no ERC-20 approval needed
if not is_native:
    ERC20_ABI = [
        {"name": "allowance", "type": "function", "stateMutability": "view",
         "inputs":  [{"name": "owner",   "type": "address"},
                     {"name": "spender", "type": "address"}],
         "outputs": [{"name": "", "type": "uint256"}]},
        {"name": "approve", "type": "function", "stateMutability": "nonpayable",
         "inputs":  [{"name": "spender", "type": "address"},
                     {"name": "amount",  "type": "uint256"}],
         "outputs": [{"name": "", "type": "bool"}]},
    ]

    src_token      = w3.eth.contract(address=route["src"], abi=ERC20_ABI)
    router_address = route["tx"]["to"]
    from_amount    = int(route["fromAmount"])

    # 1. Check allowance on the input token
    allowance = src_token.functions.allowance(account.address, router_address).call()

    # 2. Approve the router if needed (max approval to avoid repeating)
    if allowance < from_amount:
        approve_tx = src_token.functions.approve(router_address, 2**256 - 1).build_transaction({
            "from":  account.address,
            "nonce": w3.eth.get_transaction_count(account.address),
        })
        signed = account.sign_transaction(approve_tx)
        w3.eth.send_raw_transaction(signed.raw_transaction)

# 3. Submit the swap
swap_tx = {
    "from":  account.address,
    "to":    route["tx"]["to"],
    "data":  route["tx"]["data"],
    "value": int(route["tx"]["value"]),
    "nonce": w3.eth.get_transaction_count(account.address),
    "gas":   int(int(route["gasEstimate"]) * 1.2),  # add 20% buffer
}
signed_swap = account.sign_transaction(swap_tx)
tx_hash = w3.eth.send_raw_transaction(signed_swap.raw_transaction)

What's next

On this page