Integrate the Universal API
Integrate just-in-time mint/redeems of 80+ uAssets directly in your app or protocol.
Last updated
Integrate just-in-time mint/redeems of 80+ uAssets directly in your app or protocol.
Last updated
The Universal API allows developers to seamlessly integrate uAssets into their applications. The API acts as an intermediary, connecting users with Merchants to facilitate buying, selling, and cross-chain transactions.
DeFi typically relies on liquidity pools (LPs) to provide assets for swaps. However, LPs are capital inefficient because liquidity sits unused until needed.
Universal solves this with JIT (just-in-time) Liquidity via the Universal API meaning:
Instead of pre-depositing liquidity, Merchants mint and fulfill orders dynamically when demand arises.
Liquidity is provided on an as-needed basis, optimizing capital efficiency.
To obtain API access with higher rate limits, please reach out to the Universal team for an API key.
The user will need to approve the Permit2 contract address 0x000000000022D473030F116dDEE9F6B43aC78BA3
to spend USDC or token address to be used for purchase, and the uAsset address for selling.
The compatible Typescript SDK is the simplest way to integrate with the Universal API.
For signing orders, you’ll need your private key. We strongly recommend storing private keys securely using environment variables. For example, create a .env file in your project root:
PRIVATE_KEY=your_private_key_here
Load the .env file in your code by installing the dotenv package:
npm install dotenv
Then, in your project:
import dotenv from "dotenv";
dotenv.config();
Below is a complete TypeScript example that shows you how to use the SDK to request a quote, generate EIP‑712 typed data, sign the data, and submit an order:
import {
UniversalRelayerSDK,
generateTypedData,
QuoteRequest,
} from "universal-sdk";
import { privateKeyToAccount } from "viem/accounts";
import { parseUnits } from "viem";
import dotenv from "dotenv";
dotenv.config();
async function executeOrder() {
// Initialize the SDK (pass your API key if required)
const universal = new UniversalRelayerSDK();
// Create an account from your private key
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
// Configure your quote parameters
const quoteRequest: QuoteRequest = {
type: "BUY",
token: "BTC",
pair_token: "USDC",
blockchain: "BASE",
slippage_bips: 20,
user_address: account.address,
pair_token_amount: parseUnits("5", 6).toString(), // 5 USDC, using 6 decimals
};
// Request a quote from the API
const quote = await universal.getQuote(quoteRequest);
console.log("Quote response:", quote);
// Generate typed data as per EIP‑712 for signing
const { typedData } = await generateTypedData(quote);
// Sign the typed data using your account
const signature = await account.signTypedData(typedData);
// Combine the quote with its signature to form an order request
const orderRequest = {
...quote,
signature,
};
// Submit the order
const orderResponse = await universal.submitOrder(orderRequest);
console.log("Order response:", orderResponse);
}
executeOrder()
.then(() => console.log("Order executed successfully"))
.catch((error) => console.error("Error executing order:", error));
This is the base URL for all API requests.
https://relayer.universal.xyz/api (with an API key)
https://www.universal.xyz/api/v1 (without an API key)
All API requests require authentication via a Bearer Token.
Authorization: Bearer <YOUR_API_KEY>
Ensure that your API key is included in every request to authenticate and authorize API usage.
TokenName
: BTC, SOL, XRP, DOGE, DOT, NEAR, LTC, ADA, BCH, ALGO, ...
PairTokenName
: USDC
BlockchainName
: BASE | ARBITRUM | POLYGON | WORLD
Quote
type: "BUY" | "SELL";
token: TokenName;
token_amount: string; // wei
pair_token: PairTokenName | string; // USDC or token address
slippage_bips: number; // 0-100
blockchain: BlockchainName;
deadline: string; // unix epoch
pair_token_amount: string; // wei
id: string;
user_address: string; // 0x..
merchant_address: string; // 0x..
gas_fee_nominal: string; // wei
gas_fee_dollars: number; // e.g. $1.23 = 1.23
relayer_nonce: number;
merchant_id: string;
mode: "DIRECT" | "BRIDGED"
Send a Quote Request
To retrieve a price quote for a uAsset transaction, make a POST
request to the following endpoint:
POST /quote
type
: Specifies if the user wants to BUY
or SELL
.
token
: The uAsset being bought or sold.
token_amount
: (Optional) The amount of the asset being transacted.
pair_token
: The stable asset used for the trade (e.g., USDC).
pair_token_amount
: (Optional) The amount of the paired asset.
slippage_bips
: Slippage tolerance in basis points (0-100).
blockchain
: The blockchain network where the transaction is executed.
user_address
: The wallet address initiating the trade.
{
type: "BUY" | "SELL";
token: TokenName;
token_amount?: string;
pair_token_amount?: string;
pair_token: PairTokenName;
slippage_bips: number;
blockchain: BlockchainName;
user_address: string;
}
Upon success, the API returns a quote containing:
Quoted amounts
Gas fees
Merchant details
A unique quote ID
for later use
{
...Quote
}
The user must sign the quote using EIP-712 signature standards before submitting the order.
import { DutchOrderBuilder } from "@uniswap/uniswapx-sdk";
import { BigNumber } from "ethers5"; // ethers v5
type TokenName = "BTC" | "SOL" | "XRP" | "DOGE" | "DOT" | "NEAR" | "LTC" | "ADA" | "BCH" | "ALGO";
type PairTokenName = "USDC";
type BlockchainName = "BASE";
interface Quote {
type: "BUY" | "SELL";
token: TokenName;
token_amount: bigint;
pair_token: PairTokenName;
slippage_bips: number;
blockchain: BlockchainName;
deadline: bigint;
pair_token_amount: bigint;
id: string;
user_address: string;
merchant_address: string;
gas_fee_nominal: bigint;
gas_fee_dollars: number;
relayer_nonce: number;
}
const CHAIN_ID: number = <CHAIN_ID>;
const REACTOR_ADDRESS: string = <REACTOR_ADDRESS>;
const PERMIT2_ADDRESS: string = <PERMIT2_ADDRESS>;
async function signTypedQuote(quote: Quote) {
const builder = new DutchOrderBuilder(
CHAIN_ID,
REACTOR_ADDRESS,
PERMIT2_ADDRESS
);
const TOKEN_ADDRESS = <TOKEN_ADDRESS>;
const PAIR_TOKEN_ADDRESS = <PAIR_TOKEN_ADDRESS>;
const order = builder
.deadline(Number(quote.deadline))
.decayEndTime(Number(quote.deadline))
.decayStartTime(Number(quote.deadline) - 100)
.nonce(BigNumber.from(quote.relayer_nonce))
.input({
token: quote.type == "BUY" ? PAIR_TOKEN_ADDRESS : TOKEN_ADDRESS,
startAmount:
quote.type == "BUY"
? BigNumber.from(quote.pair_token_amount.toString())
: BigNumber.from(quote.token_amount.toString()),
endAmount:
quote.type == "BUY"
? BigNumber.from(quote.pair_token_amount.toString())
: BigNumber.from(quote.token_amount.toString()),
})
.output({
token: quote.type == "BUY" ? TOKEN_ADDRESS : PAIR_TOKEN_ADDRESS,
startAmount:
quote.type == "BUY"
? BigNumber.from(quote.token_amount.toString())
: BigNumber.from(quote.pair_token_amount.toString()),
endAmount:
quote.type == "BUY"
? BigNumber.from(quote.token_amount.toString())
: BigNumber.from(quote.pair_token_amount.toString()),
recipient: quote.user_address,
})
.swapper(quote.user_address)
.exclusiveFiller(quote.merchant_address, BigNumber.from(0))
.build();
const { domain, types, values } = order.permitData();
const primaryType: "PermitWitnessTransferFrom" = "PermitWitnessTransferFrom";
const typedData = {
domain,
types,
primaryType,
message: values,
};
const signer = <SIGNER>;
const signature = await signer.signTypedData(typedData);
return signature;
}
Submit a Signed Order
Once the quote is signed, submit it using the /order
endpoint:
POST /order
This request includes the signed quote along with the necessary parameters from the previous steps.
{
...Quote,
"signature": string, // Quote signed by the user
}
Response (200 OK)
Upon success, the API will return the transaction hash confirming the execution of the trade.Response
{
"transaction_hash": string // Transaction hash of the fulfillment
(optional) "transaction_hash_2": string // Transaction hash of the bridged fulfillment
}