Receiving an RFQ

Taker make an order

When receiving a Request for Quote (RFQ) while interacting with both the Upshot Pool and the Upshot Vault, you need to sign the EIP712 data twice. This is because each protocol requires a separate signature. If you use the same wallet to create both the Upshot Pool and Upshot Vault, you can utilize that wallet to sign for both protocols. Example Message

{
  "messageType": "rfqT";
  "message": {
  "protocols": ["pool", "vault"],
    "data": {
      "rfqId": string;
      "source": string;
      "nonce": number;
      "baseToken": string;
      "quoteToken": string;
      "trader": string;
      "effectiveTrader": string;
      "baseTokenAmount"?: number;
      "quoteTokenAmount"?: number;
      "baseChain": {
        "chainType": string;
        "chainId": number;
      };
      "quoteChain": {
        "chainType": string;
        "chainId": number;
      };
     "feesBps": number;
     "pool": {}, // This data is for signing EIP712, but you can ignore it.
     "vault": {},
    }
  }
}

and the response message will be

{
  "messageType": "rfqTQuote",
  "message": {
    "protocols": ["pool", "vault"],
    "data": {
      "rfqId": string;
      "source": string;
      "nonce": number;
      "baseToken": string;
      "quoteToken": string
      "trader": string;
      "poolAddress": string;
      "quoteExpiry": number;
      "effectiveTrader": string;
      "baseTokenAmount": number;
      "quoteTokenAmount": number;
      "externalAccount": number;
      "baseChain": { 
        "chainType": string; // 'evm'
        "chainId": number; 
      };
      "quoteChain": { 
        "chainType": string;  // 'evm'
	"chainId": number; 
      };
      "feesBps": number;
      "signatures": [poolsignature, vaultsignature];
    },
  },
}

Below is the adjusted code to handle signing the RFQ data for both the Upshot Pool and Upshot Vault:

Example: Receiving the RFQ, Signing EIP712 Data, and Sending Back RFQQuote

The following code snippet demonstrates how to receive a Request for Quote (RFQ) via the socket, sign the EIP712 data, and then send back the signed RFQQuote. You will need to fill in the fulfilledAmount, poolAddress, and externalAccount based on your specific setup.

const { ethers } = require("ethers");
const WebSocket = require("ws");

const path = ''
const jwtToken = ''
const ws = new WebSocket(path, {
  headers: { 'websocket-authorization': jwtToken }
});

const PRIVATE_KEY = "YOUR_PRIVATE_KEY_HERE";
const wallet = new ethers.Wallet(PRIVATE_KEY);
const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";

function startSendingPriceLevels() {
  setInterval(() => {
    sendPriceLevels();
  }, 30000); // 30 seconds interval
}

function fillMissingFields(request, fulfilledAmount, poolAddress, externalAccount) {
  if (request.message.protocols.includes('vault')) {
    request.message.data.vault.eip712Data.message.amountIn = fulfilledAmount.toString();
  }
  if (request.message.protocols.includes('pool')) {
    request.message.data.pool.eip712Data.message.quoteTokenAmount = fulfilledAmount.toString();
    request.message.data.pool.eip712Data.message.externalAccount = externalAccount;
    request.message.data.pool.eip712Data.domain.verifyingContract = poolAddress;
  }
}

async function signEIP712Pool(protocol, request) {
  let domain = request.message.data[protocol].eip712Data.domain;
  const types = {
    "RFQTQuote": request.message.data[protocol].eip712Data.types["RFQTQuote"]
  }
  const message = request.message.data[protocol].eip712Data.message;

  const signature = await wallet._signTypedData(domain, types, message);
  return signature;
}

async function signEIP712Vault(protocol, request) {
  let domain = request.message.data[protocol].eip712Data.domain;
  const types = {
    "Order": request.message.data[protocol].eip712Data.types["Order"]
  }
  const message = request.message.data[protocol].eip712Data.message;

  const signature = await wallet._signTypedData(domain, types, message);
  return signature;
}

async function processSignatureRequest(request) {
  console.log("Request Signature");
  try {
    const fulfilledAmount = ''
    const poolAddress = ''
    let externalAccount = ''

    const signatures = [];
    if (request.message.protocols.includes('pool') && request.message.protocols.includes(vault')) {
      externalAccount = request.message.data.vault.eip712Data.domain.verifyingContract
      fillMissingFields(request, fulfilledAmount, poolAddress, externalAccount);

      const poolSignature = await signEIP712Pool('pool', request);
      signatures.push({ protocol: 'pool', signature: poolSignature });

      const vaultSignature = await signEIP712Vault('vault', request);
      signatures.push({ protocol: 'vault', signature: vaultSignature });
    }
    else if (request.message.protocols.includes('pool')) {
      externalAccount = (externalAccount === poolAddress) ? ZERO_ADDRESS : externalAccount;
      fillMissingFields(request, fulfilledAmount, poolAddress, externalAccount);
      const poolSignature = await signEIP712Pool('pool', request);
      signatures.push({ protocol: 'pool', signature: poolSignature });
    }
    else if (request.message.protocols.includes('vault')) {
      fillMissingFields(request, fulfilledAmount, poolAddress, externalAccount);
      const vaultSignature = await signEIP712Vault('vault', request);
      signatures.push({ protocol: 'vault', signature: vaultSignature });
    }

    const baseTokenAmount = request.message.data.baseTokenAmount || fulfilledAmount.toString();
    const quoteTokenAmount = request.message.data.quoteTokenAmount || fulfilledAmount.toString();

    return {
      messageType: "rfqTQuote",
      message: {
        protocols: request.message.protocols,
        data: {
          rfqId: request.message.data.rfqId,
          source: request.message.data.source,
          nonce: request.message.data.nonce,
          quoteExpiry: request.message.data.pool ? request.message.data.pool.eip712Data.message.quoteExpiry : request.message.data.vault.eip712Data.message.expiredAt,
          baseToken: request.message.data.baseToken,
          quoteToken: request.message.data.quoteToken,
          trader: request.message.data.trader,
          poolAddress: poolAddress,
          effectiveTrader: request.message.data.effectiveTrader,
          baseTokenAmount: baseTokenAmount,
          quoteTokenAmount: quoteTokenAmount,
          externalAccount: externalAccount,
          baseChain: request.message.data.baseChain,
          quoteChain: request.message.data.quoteChain,
          feesBps: request.message.data.feesBps,
          signatures: signatures.map(sig => sig.signature),
        },
      },
    };
  } catch (error) {
    return {
      success: false,
      message: error.message,
    };
  }
}

function sendPriceLevels() {
  const priceLevelsMessage = {
    messageType: "priceLevels",
    message: {
      protocols: ["pool", "vault"],
      data: {
        baseChain: {
          chainType: "evm",
          chainId: 97
        },
        quoteChain: {
          chainType: "evm",
          chainId: 97
        },
        baseToken: "0xBaseTokenAddress", 
        quoteToken: "0xQuoteTokenAddress", 
        buyLevels: [
          {
            quantity: "1.0",
            price: "4.5"
          },
          {
            quantity: "1.0",
            price: "4.0"
          },
          {
            quantity: "1.0",
            price: "3.5"
          },
        ],
        sellLevels: [
          {
            quantity: "1.0",
            price: "5.0"
          },
          {
            quantity: "1.0",
            price: "5.5"
          },
        ],
        vaultAddress: "0xVaultAddress",
      }
    }
  };

  ws.send(JSON.stringify(priceLevelsMessage));
  console.log("Sent priceLevels message to server");
}

ws.on('open', () => {
  startSendingPriceLevels();
});

ws.on("message", async (message) => {
  const request = JSON.parse(message);
  console.log(request)
  if (request.messageType === "request-signature") {
    const response = await processSignatureRequest(request);
    console.log("res ", JSON.stringify(response))
    ws.send(JSON.stringify(response));
  }
});

The fulfilledAmount: This is the amount that the Upshot Pool will fulfill in response to the RFQ. Make sure to set this value based on the specific asset being traded, represented in its smallest unit.

The poolAddress: Set this to the Upshot Pool address obtained during the pool creation step.

The externalAccount: Set this to the Upshot Vault address, linking the RFQ to the vault's assets for the transaction.

Last updated