Order Request

Full example script is available create_order.ts.

For an example using a separate approve transaction rather than permit, see create_order_approve.ts.

Direct Orders

This example submits a BUY order request to the OrderProcessor contract. This process involves two method calls bundled together into one transaction using the OrderProcessor's multicall utility.

❗️

Only DAY time-in-force is supported for LIMIT order type.

// ------------------ Configure Order ------------------

// buy order amount (100 USDC)
const orderAmount = BigInt(100_000_000);
// buy order (Change to true for Sell Order)
const sellOrder = false;
// market order
const orderType = Number(0);

// check the order precision doesn't exceed max decimals
// applicable to sell orders only
if (sellOrder) {
  const allowedDecimalReduction = await orderProcessor.orderDecimalReduction(assetTokenAddress);
  const allowablePrecisionReduction = 10 ** allowedDecimalReduction;
  if (Number(orderAmount) % allowablePrecisionReduction != 0) {
    const assetTokenDecimals = await assetToken.decimals();
    const maxDecimals = assetTokenDecimals - allowedDecimalReduction;
    throw new Error(`Order amount precision exceeds max decimals of ${maxDecimals}`);
  }
}

const orderParams = {
  requestTimestamp: Date.now(),
  recipient: signer.address,
  assetToken: assetTokenAddress,
  paymentToken: paymentTokenAddress,
  sell: sellOrder,
  orderType: orderType,
  assetTokenQuantity: 0, // Asset amount to sell. Ignored for buys. Fees will be taken from proceeds for sells.
  paymentTokenQuantity: Number(orderAmount), // Payment amount to spend. Ignored for sells. Fees will be added to this amount for buys.
  price: 0, // Unused limit price
  tif: 0, // DAY
};

The first step is to determine the fees to add to the desired order amount.

// get fees, fees will be added to buy order deposit or taken from sell order proceeds
// TODO: get fees quote for sell order
const feeQuoteData = {
  chain_id: chainId,
  contract_address: orderProcessorAddress,
  order_data: orderParams
};
const feeQuoteResponse = await dinariClient.post("/api/v1/web3/orders/fee", feeQuoteData);
const fees = BigInt(feeQuoteResponse.data.fee_quote.fee);
const totalSpendAmount = orderAmount + fees;
console.log(`fees: ${ethers.utils.formatUnits(fees, 6)}`);

The first method call - selfPermit - gives the OrderProcessor permission to spend the payment token by pulling the order amount + fees from the user's account.

// ------------------ Configure Permit ------------------

// permit nonce for user
const nonce = await paymentToken.nonces(signer.address);
// 5 minute deadline from current blocktime
const blockNumber = await provider.getBlockNumber();
const blockTime = (await provider.getBlock(blockNumber))?.timestamp;
if (!blockTime) throw new Error("no block time");
const deadline = blockTime + 60 * 5;

// unique signature domain for payment token
const permitDomain = {
  name: await paymentToken.name(),
  version: await getContractVersion(paymentToken),
  chainId: (await provider.getNetwork()).chainId,
  verifyingContract: paymentTokenAddress,
};

// permit message to sign
const permitMessage = {
  owner: signer.address,
  spender: orderProcessorAddress,
  value: totalSpendAmount,
  nonce: nonce,
  deadline: deadline
};

// sign permit to spend payment token
const permitSignatureBytes = await signer._signTypedData(permitDomain, permitTypes, permitMessage);
const permitSignature = ethers.utils.splitSignature(permitSignatureBytes);

// create selfPermit call data
const selfPermitData = orderProcessor.interface.encodeFunctionData("selfPermit", [
  paymentTokenAddress,
  permitMessage.owner,
  permitMessage.value,
  permitMessage.deadline,
  permitSignature.v,
  permitSignature.r,
  permitSignature.s
]);

The second method call - createOrder - submits the order request to be filled by the protocol. This is submitted with the spending permit.

Once the transaction is mined, the event logs from resulting transaction receipt can be unpacked to obtain the order recipient's order index. The recipient's order index can be used to query the OrderProcessor for the current state of the order or look up the event history for that order.

❗️

The Permit and FeeQuote data have a deadline. If createOrder is attempted after one of these deadlines, the transaction will fail. As of this writing, the FeeQuote duration is 300 seconds.

// ------------------ Submit Order ------------------

// createOrder call data
// see IOrderProcessor.Order struct for order parameters
const requestOrderData = orderProcessor.interface.encodeFunctionData("createOrder", [[
  orderParams.requestTimestamp,
  orderParams.recipient,
  orderParams.assetToken,
  orderParams.paymentToken,
  orderParams.sell,
  orderParams.orderType,
  orderParams.assetTokenQuantity,
  orderParams.paymentTokenQuantity,
  orderParams.price,
  orderParams.tif,
], [
  feeQuoteResponse.data.fee_quote.orderId,
  feeQuoteResponse.data.fee_quote.requester,
  feeQuoteResponse.data.fee_quote.fee,
  feeQuoteResponse.data.fee_quote.timestamp,
  feeQuoteResponse.data.fee_quote.deadline,
], feeQuoteResponse.data.fee_quote_signature]);

// submit permit + create order multicall transaction
const tx = await orderProcessor.multicall([
  selfPermitData,
  requestOrderData,
]);
const receipt = await tx.wait();
console.log(`tx hash: ${tx.hash}`);

// get order id from event
const orderEvent = receipt.logs.filter((log: any) => log.topics[0] === orderProcessor.interface.getEventTopic("OrderCreated")).map((log: any) => orderProcessor.interface.parseLog(log))[0];
if (!orderEvent) throw new Error("no order event");
const orderId = orderEvent.args[0];
const orderAccount = orderEvent.args[1];
console.log(`Order ID: ${orderId}`);
console.log(`Order Account: ${orderAccount}`);

// use order id to get order status (ACTIVE, FULFILLED, CANCELLED)
const orderStatus = await orderProcessor.getOrderStatus(orderId);
console.log(`Order Status: ${orderStatus}`);