Closing a Quote (Instant Close)

Instant closes let you immediately close a position by calling the /instant_close endpoint. You must first obtain a SIWE access token via a login flow if you don't have one already, then use that token to call the instant close function.

circle-info

Instant Trading is only for MARKET orders


Instant Close Flow

Instant close allows you to quickly exit an open position. The process requires:

  1. SIWE Login (if you don't have an access token already) Fetch a nonce from the solver’s endpoint, build and sign a SIWE (Sign-In with Ethereum) message, and then log in to obtain an access token.

  2. Call the /instant_close Endpoint: With the access token, send a POST request that includes:

    • quote_id: The unique number identifying the quote/position.

    • quantity_to_close: The amount (as a string) you wish to close.

    • close_price: The price (as a string) at which you want to close the position.

circle-info

For market orders, you will need to adjust the close price (i.e. apply slippage) similar to the opening process. For CLOSE LONG: Decrease the price by your slippage factor.

For CLOSE SHORT: Increase the price by your slippage factor.

The following script shows a full example in Node.js using Axios and ethers.js, closing a LONG XRP position.


Instant Close Script Example

require("dotenv").config();
const { ethers } = require("ethers");
const axios = require("axios");

// --------------------------------------------------------------------
// SIWE / Login Configuration
// --------------------------------------------------------------------
const PRIVATE_KEY = process.env.PRIVATE_KEY;
const activeAccount = process.env.activeAccount; // Your sub-account address
const wallet = new ethers.Wallet(PRIVATE_KEY);

const SOLVER_BASE_URL = "https://www.perps-streaming.com/v1/42161a/0x141269E29a770644C34e05B127AB621511f20109";
const DOMAIN = "localhost";
const ORIGIN = "http://localhost:3000";
const CHAIN_ID = 42161;
const LOGIN_URI = `${SOLVER_BASE_URL}/login`;
const ISSUED_AT = new Date().toISOString();
// Set expiration to 24 hours from now for login
const EXPIRATION_DATE = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();

// --------------------------------------------------------------------
// Helper Functions
// --------------------------------------------------------------------

// SIWE message builder (EIP-4361 format)
function buildSiweMessage({ domain, address, statement, uri, version, chainId, nonce, issuedAt, expirationTime }) {
  return `${domain} wants you to sign in with your Ethereum account:
${address}

${statement}

URI: ${uri}
Version: ${version}
Chain ID: ${chainId}
Nonce: ${nonce}
Issued At: ${issuedAt}
Expiration Time: ${expirationTime}`;
}

// Fetch nonce for SIWE login
async function getNonce(address) {
  const url = `${SOLVER_BASE_URL}/nonce/${address}`;
  const { data } = await axios.get(url);
  return data.nonce;
}

// Fetch the asset price from Muon, convert the returned wei value to a standard decimal string.
async function fetchMuonPriceConverted() {
  try {
    const MUON_URL = ""; //This is the uPnL_A_WithSymbolPrice Muon API request.
    const response = await axios.get(MUON_URL);
    const fetchedPriceWei = response.data.result.data.result.price;
    if (!fetchedPriceWei) {
      throw new Error("Muon price not found in response.");
    }
    // Convert the wei value to a human-readable decimal (assumes 18 decimals)
    const priceDecimal = ethers.formatUnits(fetchedPriceWei, 18);
    return priceDecimal;
  } catch (error) {
    console.error("Error fetching Muon price:", error.response?.data || error.message);
    throw error;
  }
}

// Call the /instant_close endpoint to close a position.
// The endpoint expects an object with:
//   {
//     "quote_id": number,
//     "quantity_to_close": "string",
//     "close_price": "string"
//   }
async function closeInstantPosition(token, quote_id, quantity_to_close, close_price) {
  const payload = {
    quote_id,            // Example: 23688
    quantity_to_close,   // Example: "6.1"
    close_price,         // Example: "2.3"
  };

  const headers = {
    "Content-Type": "application/json",
    Authorization: `Bearer ${token}`,
  };

  const response = await axios.post(`${SOLVER_BASE_URL}/instant_close`, payload, { headers });
  return response.data;
}

// --------------------------------------------------------------------
// Main Flow: SIWE Login then Instant Close Trade
// --------------------------------------------------------------------
(async function main() {
  try {
    console.log(`\n[1/4] Wallet Address: ${wallet.address}`);
    const nonce = await getNonce(activeAccount);
    console.log(`[2/4] Got nonce: ${nonce}`);

    // Build the SIWE message.
    const siweMessage = buildSiweMessage({
      domain: DOMAIN,
      address: wallet.address,
      statement: `msg: ${activeAccount}`,
      uri: LOGIN_URI,
      version: "1",
      chainId: CHAIN_ID,
      nonce,
      issuedAt: ISSUED_AT,
      expirationTime: EXPIRATION_DATE,
    });
    console.log("\n[3/4] SIWE message to sign:\n", siweMessage);

    // Sign the SIWE message.
    const signature = await wallet.signMessage(siweMessage);
    console.log("\nSignature:", signature);

    // Build the login request body.
    const loginBody = {
      account_address: activeAccount,
      expiration_time: EXPIRATION_DATE,
      issued_at: ISSUED_AT,
      signature,
      nonce,
    };

    const loginHeaders = {
      "Content-Type": "application/json",
      Origin: ORIGIN,
      Referer: ORIGIN,
    };

    console.log("\n[4/4] Sending login request...");
    const loginResponse = await axios.post(`${SOLVER_BASE_URL}/login`, loginBody, { headers: loginHeaders });
    console.log("Login response:", loginResponse.data);

    // Extract the access token.
    const token = loginResponse.data.access_token;
    if (!token) {
      throw new Error("No access token received from login.");
    }

    // Fetch and log the Muon price in standard decimal format.
    const muonPrice = await fetchMuonPriceConverted();
    console.log("Fetched Muon Price (converted):", muonPrice);

    // ----------------------------------------------------------------
    // Instant Close Parameters – Replace these with your actual values:
    const quote_id = 24718;         // The quote ID (number) for the position to close.
    const quantity_to_close = "6.1"; // The quantity to close as a string.
    const close_price = (muonPrice * 0.95).toString(); //Position was LONG, so we're going to decrease a little to account for hedger spread.
    console.log("closePrice: ", close_price);

    console.log("\nSending instant close request...");
    const closeResponse = await closeInstantPosition(token, quote_id, quantity_to_close, close_price);
    console.log("Instant close response:", closeResponse);
  } catch (err) {
    console.error("Error in SIWE login flow or closing position:", err.response?.data || err.message);
  }
})();

Explanation

  1. SIWE Login Flow:

    • Nonce Retrieval: The script calls getNonce(activeAccount) to get a nonce from the solver.

    • SIWE Message: A SIWE message is built using the provided configuration (domain, chain ID, etc.) and then signed with your wallet.

    • Login Request: The signed SIWE message is sent to the /login endpoint. On success, an access token is returned.

  2. Instant Close Request:

    • Parameters:

      • quote_id: The identifier of the quote (or open position) you wish to close.

      • quantity_to_close: The amount to close (as a string).

      • close_price: The price at which you wish to close the position. Note: For market orders, remember to adjust this price for slippage if required.

    • API Call: The access token is included in the authorization header when making a POST request to the /instant_close endpoint with the above parameters.

Last updated