API Access and Usage

LiquidMesh API Access & Usage

This guide explains how to access and use LiquidMesh APIs securely and efficiently.


Public API Endpoints

You can call our public API endpoints with your API key.

FunctionEndpoint
Quotehttps://api.liquidmesh.io/v1/{networkId}/quote
Swaphttps://api.liquidmesh.io/v1/{networkId}/swap
Orderhttps://api.liquidmesh.io/v1/{networkId}/order
Send Transactionhttps://api.liquidmesh.io/v1/{networkId}/send-transaction

Please reference here to get the correct networkId.

API Key

How to apply

Request an API Key by contacting: [email protected]


IP Whitelisting

For security purposes, we recommend IP whitelisting.

  • Please provide your outbound IP addresses to [email protected].
  • Only whitelisted IPs will be able to make requests to restricted endpoints.


Using JWTs for LiquidMesh API Requests

All LiquidMesh Gateway integrations use Ed25519-signed JWTs going forward. Every call must include both the LM-API-KEY header and a short-lived Bearer token to prove request integrity.


Prerequisites

  1. Generate an Ed25519 key pair encoded with Base64.
    • Run the Node.js snippet below (Node 18+):
      node - <<'NODE'
      import { generateKeyPairSync } from 'crypto';
      
      const { publicKey, privateKey } = generateKeyPairSync('ed25519');
      
      const seed = privateKey.export({ type: 'pkcs8', format: 'der' }).subarray(16, 48);
      const pubKeyRaw = publicKey.export({ type: 'spki', format: 'der' }).subarray(-32);
      const fullPrivateKey = Buffer.concat([seed, pubKeyRaw]);
      
      console.log('PUBLIC_KEY_BASE64:', pubKeyRaw.toString('base64'));
      console.log('PRIVATE_KEY_BASE64 (seed+public):', fullPrivateKey.toString('base64'));
      console.log('PRIVATE_KEY_BASE64_SEED (Base64URL for JWT d):', seed.toString('base64url'));
      NODE
    • Persist PRIVATE_KEY_BASE64, PRIVATE_KEY_BASE64_SEED, and PUBLIC_KEY_BASE64 securely (never commit them).
  2. Send the public key to LiquidMesh at [email protected] so it can be registered with your account.
  3. Receive the API key bound to your public key along with your gateway base URL (e.g. https://api.liquidmesh.io). Store both secrets in a dedicated key vault or secrets manager and load them securely at runtime.

JWT structure

FieldLocationDescription
typHeaderAlways JWT.
algHeaderAlways EdDSA (Ed25519).
timPayloadMillisecond timestamp when the token is created. Used for replay protection.
messagePayloadHex-encoded SHA-256 hash of the preimage {timestamp}{METHOD}{PATH}{BODY}.
issPayloadYour API key (issuer).
iat / expPayloadIssued-at and expiration timestamps in seconds. Recommended TTL ≤ 2 seconds.

Authorization: Bearer <jwt> must accompany every request together with LM-API-KEY.


Signing workflow

  1. Capture the exact request components:
    • timestampMs = Date.now()
    • Upper-case HTTP method (e.g. POST)
    • Canonical request path (e.g. /v1/bsc/swap, no scheme or host)
    • Raw request body string (empty for GET calls)
  2. Concatenate them: preimage = `${timestampMs}${method}${path}${body}` .
  3. Compute message = sha256(preimage) and encode as lowercase hex.
  4. Build the payload { tim: timestampMs, message, iss: API_KEY } plus optional iat/exp.
  5. Sign with your Ed25519 private key using the EdDSA algorithm.

Examples (Node.js)

import { SignJWT, importJWK } from 'jose';
import { createHash } from 'crypto';

const apiKey = process.env.API_KEY;
const path = '/v1/bsc/quote';
const body = '';
const timestamp = Date.now();
const preimage = `${timestamp}GET${path}${body}`;
const message = createHash('sha256').update(preimage).digest('hex');

const privateKey = await importJWK({
  kty: 'OKP',
  crv: 'Ed25519',
  d: process.env.PRIVATE_KEY_BASE64_SEED,
  x: process.env.PUBLIC_KEY_BASE64
}, 'EdDSA');

const token = await new SignJWT({ tim: timestamp, message, iss: apiKey })
  .setProtectedHeader({ typ: 'JWT', alg: 'EdDSA' })
  .setIssuedAt()
  .setExpirationTime('2s')
  .sign(privateKey);

Quote request (GET)

const baseUrl = process.env.BASE_URL;
const query = new URLSearchParams({
  amount: '10000000000',
  chainId: '56',
  inputToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
  outputToken: '0x55d398326f99059fF775485246999027B3197955'
}).toString();

const quotePath = `/v1/bsc/quote?${query}`;
const quoteTimestamp = Date.now();
const quotePreimage = `${quoteTimestamp}GET${quotePath}`;
const quoteMessage = createHash('sha256').update(quotePreimage).digest('hex');

const quoteToken = await new SignJWT({ tim: quoteTimestamp, message: quoteMessage, iss: apiKey })
  .setProtectedHeader({ typ: 'JWT', alg: 'EdDSA' })
  .setIssuedAt()
  .setExpirationTime('2s')
  .sign(privateKey);

const quoteResponse = await fetch(`${baseUrl}${quotePath}`, {
  method: 'GET',
  headers: {
    'LM-API-KEY': apiKey,
    'Authorization': `Bearer ${quoteToken}`,
    'Content-Type': 'application/json'
  }
});

Swap request (POST)

const swapPath = '/v1/bsc/swap';
const swapBody = JSON.stringify({
  userAddress: '0x5EA0E65751c95bA3CEdaeC5BcD95606094160ce1',
  slippageBps: 10000,
  swapInfo: {
    chainId: '56',
    inputToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
    inputAmount: '10000000000',
    outputToken: '0x55d398326f99059fF775485246999027B3197955',
    slippageBps: 5000,
    outputAmount: '1',
    routePlans: [/* ... */]
  }
});

const swapTimestamp = Date.now();
const swapPreimage = `${swapTimestamp}POST${swapPath}${swapBody}`;
const swapMessage = createHash('sha256').update(swapPreimage).digest('hex');

const swapToken = await new SignJWT({ tim: swapTimestamp, message: swapMessage, iss: apiKey })
  .setProtectedHeader({ typ: 'JWT', alg: 'EdDSA' })
  .setIssuedAt()
  .setExpirationTime('2s')
  .sign(privateKey);

const swapResponse = await fetch(`${baseUrl}${swapPath}`, {
  method: 'POST',
  headers: {
    'LM-API-KEY': apiKey,
    'Authorization': `Bearer ${swapToken}`,
    'Content-Type': 'application/json'
  },
  body: swapBody
});

Best practices

  • Keep private keys in a dedicated secrets manager and never commit them to version control.

Need help? Contact [email protected].