Skip to content

API Cheat Sheet

Quick reference for common STRATO operations using REST APIs.

Important: ethers.js Does NOT Work

You CANNOT use ethers.js or web3.js with STRATO.

Use STRATO REST APIs: /strato/v2.3, /cirrus/search, /bloc/v2.2

About Endpoints

All examples use localhost for local development.

For production, use public endpoints: - Mainnet: https://app.strato.nexus - Testnet: https://buildtest.mercata-testnet.blockapps.net

Optional: Local setup guide


Setup

import axios, { AxiosInstance } from 'axios';

// For local dev:
const NODE_URL = 'http://localhost:8080';

// For production (replace with):
// const NODE_URL = 'https://app.strato.nexus';  // mainnet
// const NODE_URL = 'https://buildtest.mercata-testnet.blockapps.net';  // testnet

function createApiClient(baseURL: string): AxiosInstance {
  return axios.create({
    baseURL,
    headers: { 'Content-Type': 'application/json' },
    timeout: 60_000,
  });
}

// API clients
export const strato = createApiClient(`${NODE_URL}/strato/v2.3`);
export const cirrus = createApiClient(`${NODE_URL}/cirrus/search`);
export const bloc = createApiClient(`${NODE_URL}/bloc/v2.2`);

Authentication

Get OAuth Token

import axios from 'axios';

const OAUTH_DISCOVERY_URL = process.env.OAUTH_DISCOVERY_URL!;
const CLIENT_ID = process.env.OAUTH_CLIENT_ID!;
const CLIENT_SECRET = process.env.OAUTH_CLIENT_SECRET!;

let tokenEndpoint: string;

export async function initAuth() {
  const { data } = await axios.get(
    `${OAUTH_DISCOVERY_URL}/.well-known/openid-configuration`
  );
  tokenEndpoint = data.token_endpoint;
}

export async function getAccessToken(): Promise<string> {
  const response = await axios.post(
    tokenEndpoint,
    new URLSearchParams({
      grant_type: 'client_credentials',
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET,
    }),
    {
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
    }
  );

  return response.data.access_token;
}

Transaction Builder

Build Function Transaction

interface FunctionInput {
  contractName: string;
  contractAddress: string;
  method: string;
  args: Record<string, any>;
}

function buildFunctionTx(inputs: FunctionInput | FunctionInput[]) {
  const inputArray = Array.isArray(inputs) ? inputs : [inputs];

  const txs = inputArray.map(input => ({
    type: 'FUNCTION',
    payload: {
      contractName: input.contractName,
      contractAddress: input.contractAddress,
      method: input.method,
      args: input.args,
    },
  }));

  return {
    txs,
    txParams: {
      gasLimit: 32_100_000_000,
      gasPrice: 1,
    },
  };
}

Submit Transaction

async function submitTransaction(accessToken: string, tx: any) {
  const response = await strato.post(
    '/transaction/parallel?resolve=true',
    tx,
    {
      headers: { Authorization: `Bearer ${accessToken}` }
    }
  );

  return response.data;
}

Token Operations

Transfer Tokens

const tx = buildFunctionTx({
  contractName: 'Token',
  contractAddress: '0x...',
  method: 'transfer',
  args: {
    to: '0x...',
    value: '1000000000000000000' // 1 token (18 decimals)
  }
});

const result = await submitTransaction(accessToken, tx);

Approve Tokens

const tx = buildFunctionTx({
  contractName: 'Token',
  contractAddress: TOKEN_ADDRESS,
  method: 'approve',
  args: {
    spender: SPENDER_ADDRESS,
    value: '1000000000000000000'
  }
});

await submitTransaction(accessToken, tx);

Query Token Balance

async function getTokenBalance(
  accessToken: string,
  tokenAddress: string,
  userAddress: string
) {
  const response = await cirrus.get('/Token-_balances', {
    headers: { Authorization: `Bearer ${accessToken}` },
    params: {
      address: `eq.${tokenAddress}`,
      key: `eq.${userAddress}`,
      select: 'value::text'
    }
  });

  return BigInt(response.data[0]?.value || '0');
}

Get All Tokens

async function getAllTokens(accessToken: string) {
  const response = await cirrus.get('/Token', {
    headers: { Authorization: `Bearer ${accessToken}` },
    params: {
      select: 'address,_name,_symbol,_totalSupply::text',
      limit: 100
    }
  });

  return response.data;
}

Lending Operations

Supply Collateral

// Multi-step: Approve + Supply
const tx = buildFunctionTx([
  {
    contractName: 'Token',
    contractAddress: ETHST_TOKEN,
    method: 'approve',
    args: {
      spender: LENDING_POOL,
      value: amount
    }
  },
  {
    contractName: 'LendingPool',
    contractAddress: LENDING_POOL,
    method: 'supplyCollateral',
    args: {
      asset: ETHST_TOKEN,
      amount: amount
    }
  }
]);

await submitTransaction(accessToken, tx);

Borrow

const tx = buildFunctionTx({
  contractName: 'LendingPool',
  contractAddress: LENDING_POOL,
  method: 'borrow',
  args: {
    asset: USDST_TOKEN,
    amount: '1000000000000000000000' // 1000 USDST
  }
});

await submitTransaction(accessToken, tx);

Repay

const tx = buildFunctionTx([
  {
    contractName: 'Token',
    contractAddress: USDST_TOKEN,
    method: 'approve',
    args: {
      spender: LENDING_POOL,
      value: amount
    }
  },
  {
    contractName: 'LendingPool',
    contractAddress: LENDING_POOL,
    method: 'repay',
    args: {
      asset: USDST_TOKEN,
      amount: amount
    }
  }
]);

await submitTransaction(accessToken, tx);

Query User Collateral

async function getUserCollateral(
  accessToken: string,
  collateralVaultAddress: string,
  userAddress: string
) {
  const response = await cirrus.get('/CollateralVault-userCollaterals', {
    headers: { Authorization: `Bearer ${accessToken}` },
    params: {
      address: `eq.${collateralVaultAddress}`,
      key: `eq.${userAddress}`,
      select: 'key2,value::text'
    }
  });

  return response.data;
}

CDP Operations

Deposit Collateral to CDP

const tx = buildFunctionTx([
  {
    contractName: 'Token',
    contractAddress: ETHST_TOKEN,
    method: 'approve',
    args: {
      spender: CDP_ENGINE,
      value: amount
    }
  },
  {
    contractName: 'CDPEngine',
    contractAddress: CDP_ENGINE,
    method: 'deposit',
    args: {
      collateralType: ETHST_TOKEN,
      amount: amount
    }
  }
]);

await submitTransaction(accessToken, tx);

Mint USDST

const tx = buildFunctionTx({
  contractName: 'CDPEngine',
  contractAddress: CDP_ENGINE,
  method: 'mint',
  args: {
    collateralType: ETHST_TOKEN,
    amount: '1000000000000000000000' // 1000 USDST
  }
});

await submitTransaction(accessToken, tx);

Repay CDP Debt

const tx = buildFunctionTx([
  {
    contractName: 'Token',
    contractAddress: USDST_TOKEN,
    method: 'approve',
    args: {
      spender: CDP_ENGINE,
      value: amount
    }
  },
  {
    contractName: 'CDPEngine',
    contractAddress: CDP_ENGINE,
    method: 'repay',
    args: {
      collateralType: ETHST_TOKEN,
      amount: amount
    }
  }
]);

await submitTransaction(accessToken, tx);

Swap Operations

Swap Tokens

const tx = buildFunctionTx([
  {
    contractName: 'Token',
    contractAddress: TOKEN_IN,
    method: 'approve',
    args: {
      spender: ROUTER,
      value: amountIn
    }
  },
  {
    contractName: 'Router',
    contractAddress: ROUTER,
    method: 'swapExactTokensForTokens',
    args: {
      amountIn: amountIn,
      amountOutMin: amountOutMin,
      path: [TOKEN_IN, TOKEN_OUT],
      to: userAddress,
      deadline: Math.floor(Date.now() / 1000) + 1200 // 20 minutes
    }
  }
]);

await submitTransaction(accessToken, tx);

Query Pool Reserves

async function getPoolReserves(
  accessToken: string,
  poolAddress: string
) {
  const response = await cirrus.get('/Pool', {
    headers: { Authorization: `Bearer ${accessToken}` },
    params: {
      address: `eq.${poolAddress}`,
      select: '_token0,_token1,_reserve0::text,_reserve1::text'
    }
  });

  return response.data[0];
}

Liquidity Operations

Add Liquidity

const tx = buildFunctionTx([
  {
    contractName: 'Token',
    contractAddress: TOKEN_A,
    method: 'approve',
    args: {
      spender: ROUTER,
      value: amountA
    }
  },
  {
    contractName: 'Token',
    contractAddress: TOKEN_B,
    method: 'approve',
    args: {
      spender: ROUTER,
      value: amountB
    }
  },
  {
    contractName: 'Router',
    contractAddress: ROUTER,
    method: 'addLiquidity',
    args: {
      tokenA: TOKEN_A,
      tokenB: TOKEN_B,
      amountADesired: amountA,
      amountBDesired: amountB,
      amountAMin: amountAMin,
      amountBMin: amountBMin,
      to: userAddress,
      deadline: Math.floor(Date.now() / 1000) + 1200
    }
  }
]);

await submitTransaction(accessToken, tx);

Remove Liquidity

const tx = buildFunctionTx([
  {
    contractName: 'Pool',
    contractAddress: POOL_ADDRESS,
    method: 'approve',
    args: {
      spender: ROUTER,
      value: lpTokenAmount
    }
  },
  {
    contractName: 'Router',
    contractAddress: ROUTER,
    method: 'removeLiquidity',
    args: {
      tokenA: TOKEN_A,
      tokenB: TOKEN_B,
      liquidity: lpTokenAmount,
      amountAMin: amountAMin,
      amountBMin: amountBMin,
      to: userAddress,
      deadline: Math.floor(Date.now() / 1000) + 1200
    }
  }
]);

await submitTransaction(accessToken, tx);

Bridge Operations

Request Withdrawal

const tx = buildFunctionTx([
  {
    contractName: 'Token',
    contractAddress: STRATO_TOKEN,
    method: 'approve',
    args: {
      spender: BRIDGE,
      value: amount
    }
  },
  {
    contractName: 'MercataBridge',
    contractAddress: BRIDGE,
    method: 'requestWithdrawal',
    args: {
      externalChainId: '1', // Ethereum mainnet
      externalRecipient: ethereumAddress,
      externalToken: externalTokenAddress,
      stratoTokenAmount: amount
    }
  }
]);

await submitTransaction(accessToken, tx);

Query Bridge Transactions

async function getBridgeTransactions(
  accessToken: string,
  userAddress: string
) {
  const response = await cirrus.get('/MercataBridge-Deposit', {
    headers: { Authorization: `Bearer ${accessToken}` },
    params: {
      recipient: `eq.${userAddress}`,
      select: '*',
      order: 'timestamp.desc',
      limit: 50
    }
  });

  return response.data;
}

Rewards Operations

Claim Rewards

const tx = buildFunctionTx({
  contractName: 'Rewards',
  contractAddress: REWARDS_ADDRESS,
  method: 'claimRewards',
  args: {
    activityIdsToSettle: [1, 2, 3] // Activity IDs
  }
});

await submitTransaction(accessToken, tx);

Query User Rewards

async function getUserRewards(
  accessToken: string,
  rewardsAddress: string,
  userAddress: string
) {
  const response = await cirrus.get('/Rewards-userInfo', {
    headers: { Authorization: `Bearer ${accessToken}` },
    params: {
      address: `eq.${rewardsAddress}`,
      key: `eq.${userAddress}`,
      select: '*'
    }
  });

  return response.data;
}

Utility Functions

Wait for Transaction

async function waitForTransaction(
  accessToken: string,
  txHash: string,
  timeout: number = 60000
): Promise<any> {
  const start = Date.now();

  while (true) {
    const response = await bloc.post(
      '/transactions/results',
      [txHash],
      {
        headers: { Authorization: `Bearer ${accessToken}` }
      }
    );

    const result = response.data[0];

    if (result.status === 'Failure') {
      throw new Error(`Transaction failed: ${result.message}`);
    }

    if (result.status !== 'Pending') {
      return result;
    }

    if (Date.now() - start >= timeout) {
      throw new Error('Transaction timeout');
    }

    await new Promise(resolve => setTimeout(resolve, 2000));
  }
}

Batch Queries

async function batchQuery(
  accessToken: string,
  queries: Array<{ table: string; params: any }>
) {
  const promises = queries.map(({ table, params }) =>
    cirrus.get(`/${table}`, {
      headers: { Authorization: `Bearer ${accessToken}` },
      params
    })
  );

  const results = await Promise.all(promises);
  return results.map(r => r.data);
}

// Usage
const [tokens, pools, balances] = await batchQuery(accessToken, [
  { table: 'Token', params: { limit: 10 } },
  { table: 'Pool', params: { limit: 10 } },
  { table: 'Token-_balances', params: { key: `eq.${userAddress}` } }
]);

Error Handling

Retry with Backoff

async function retryWithBackoff<T>(
  fn: () => Promise<T>,
  maxRetries: number = 3,
  baseDelay: number = 1000
): Promise<T> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error: any) {
      if (i === maxRetries - 1) throw error;

      const delay = baseDelay * Math.pow(2, i);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  throw new Error('Max retries exceeded');
}

// Usage
const result = await retryWithBackoff(() =>
  submitTransaction(accessToken, tx)
);

Handle Common Errors

try {
  const result = await submitTransaction(accessToken, tx);
} catch (error: any) {
  if (error.response?.status === 401) {
    // Token expired - refresh
    accessToken = await getAccessToken();
  } else if (error.response?.status === 400) {
    // Transaction failed
    console.error('Transaction error:', error.response.data);
  } else if (error.code === 'ECONNREFUSED') {
    // STRATO node not reachable
    console.error('Cannot connect to STRATO');
  }
}

Reference Implementation

The mercata backend provides complete examples:

  • Transaction Builder - mercata/backend/src/utils/txBuilder.ts
  • Transaction Helper - mercata/backend/src/utils/txHelper.ts
  • API Clients - mercata/backend/src/utils/mercataApiHelper.ts
  • Services - mercata/backend/src/api/services/

Next Steps