Build a Delta Order to Sign

Build a Delta Order to Sign

POST https://api.paraswap.io/delta/orders/build

This section explains how to construct a Delta Order, which is essential for executing trades using the Velora Delta API. The /orders/build endpoint retrieves a Delta price and allows fallback to market prices.

A successfully built order includes a structured object ready for signing.

Common errors include validation failures, unsupported assets, and signature mismatches.

Use this guide to generate a Delta Order and prepare it for signing before submission.

Request Body Parameters

Name
Type
Description

price*

DeltaPrice

chainId*

number

Chain ID. (Mainnet - 1, Optimism - 10, BSC - 56, Polygon - 137, Fantom - 250, zkEVM - 1101, Base - 8453, Arbitrum - 42161, Avalanche - 43114, Gnosis - 100).

owner*

string

Order owner address.

bridge

DeltaBridge

Required only for cross-chain orders. See below for details

beneficiary

string

Order beneficiary. Default: zero address, meaning owner address.

slippage

number

Slippage in base points (100 is 1%). Default: 100.

deadline

number

Order expiry time as UNIX timestamp. Default: 1 hour from order creation.

nonce

string

Arbitrary uint256 value to be used as order nonce. Default: value generated with internal algos. It is recommended to omit this param.

permit

string

Encoded permit to be used in order settlement. Supported permit types are described here. Default: 0x.

partiallyFillable

boolean

If true, order will be generated as partially fillable. Default: false.

partnerAddress

string

Address of the partner. Used to collect fees from the order. Default: zero address.

partnerFeeBps

number

Partner fee percent in base points (100 is 1%). Max value is 200. Default: 0.

partnerTakesSurplus

boolean

If true, partner will collect 50% of the order surplus instead of flat percent fee. Default: false.

DeltaBridge structure:

Name
Type
Description

maxRelayerFee

string

Maximum allowed fee by bridge relayer. The amount is speicifed in srcToken

destinationChainId

number

Destination chain ID. (Mainnet - 1, Optimism - 10, BSC - 56, Polygon - 137, Fantom - 250, zkEVM - 1101, Base - 8453, Arbitrum - 42161, Avalanche - 43114, Gnosis - 100).

outputToken

string

multiCallHandler

string

{
  "toSign": {
    "domain": {
      "name": "Portikus",
      "version": "2.0.0",
      "chainId": 8453,
      "verifyingContract": "0x1b6c933c4a855c9f4ad1afbd05eb3f51dbb83cf8"
    },
    "types": {
      "Order": [
        {
          "name": "owner",
          "type": "address"
        },
        {
          "name": "beneficiary",
          "type": "address"
        },
        {
          "name": "srcToken",
          "type": "address"
        },
        {
          "name": "destToken",
          "type": "address"
        },
        {
          "name": "srcAmount",
          "type": "uint256"
        },
        {
          "name": "destAmount",
          "type": "uint256"
        },
        {
          "name": "expectedDestAmount",
          "type": "uint256"
        },
        {
          "name": "deadline",
          "type": "uint256"
        },
        {
          "name": "nonce",
          "type": "uint256"
        },
        {
          "name": "partnerAndFee",
          "type": "uint256"
        },
        {
          "name": "permit",
          "type": "bytes"
        },
        {
          "name": "bridge",
          "type": "Bridge"
        }
      ],
      "Bridge": [
        {
          "name": "maxRelayerFee",
          "type": "uint256"
        },
        {
          "name": "destinationChainId",
          "type": "uint256"
        },
        {
          "name": "outputToken",
          "type": "address"
        },
        {
          "name": "multiCallHandler",
          "type": "address"
        }
      ]
    },
    "value": {
      "owner": "0x75c94990d2ad92d8da4e0a238d872d09ec16706e",
      "beneficiary": "0xcef715485e33d25efd027e1281e945bef292844b",
      "srcToken": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
      "destToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
      "srcAmount": "110000000",
      "destAmount": "60182718830547894",
      "expectedDestAmount": "60790625081361510",
      "deadline": 1744733790,
      "nonce": "1744730205674",
      "permit": "0x",
      "partnerAndFee": "0",
      "bridge": {
        "maxRelayerFee": "644565026335358",
        "destinationChainId": 1,
        "outputToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
        "multiCallHandler": "0x924a9f036260ddd5808007e1aa95f08ed08aa569"
      }
    }
  }
}

Most common error types

  • ValidationError - validation for params failed

  • UnsupportedChain - the chain ID is not supported by Delta.

  • UnsupportedToken - the token is not supported by Delta.

  • InvalidHmac - hmac check failed, meaning price object returned from /quote endpoint was mutated.

Supported Permits

  • Permit(ERC-2612) - expected to have 224 bytes length.

  • Permit2

    • TransferFrom format - expected length is 96 bytes (32 for permitNonce, 64 for compact signature), amount and deadline should be the same as in Order.

    • Allowance format - expected length is 192 bytes.

  • DAI Style Permit - expected length is 256 bytes.

  • 0x01 - special permit value that signifies existing Permit2 allowance.

Delta Contract should be specified as a spender is a permit.

Sign an Order

Once the Delta Order is built, it must be signed using the EIP-712 standard before submission. This section provides a working example using ethers.js and axios.

Example:

import { ethers, TypedDataEncoder } from "ethers"; // ethers V6
import axios from "axios";

const userWallet = new ethers.Wallet(ethers.id("alice"));
const userAddress = userWallet.address;
const chainId = 1;
// fetch price
const { data: quoteData } = await axios.get("https://api.paraswap.io/quote?mode=delta&...");
const price = quoteData.delta;
// prepare build order params
const buildOrderParams = {
    price,
    owner: userAddress,
    chainId,
    partnerAddress: "0x81037e7be71bce9591de0c54bb485ad3e048b8de",
    partnerFeeBps: 100, // 1%
    partnerTakesSurplus: false, // the default
};
// build the order to sign
const { data: buildOrderData } = await axios.post("https://api.paraswap.io/delta/orders/build", buildOrderParams);
const { domain, types, value: order } = buildOrderData.toSign;
// hash the order
const eip712Hash = TypedDataEncoder.hash(
  domain,
  types,
  order,
);
// sign and compact
const signature = userWallet.signingKey.sign(typedHash).compactSerialized; // ERC-2098 representation

Sign an order (cross-chain)

The following snippet provides an example implementation of constructing the DeltaBridge structure for the cross-chain swap

import { ethers, TypedDataEncoder } from "ethers"; // ethers V6
import axios from "axios";

const ETH_ADDRESS = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
const WETH_ADDRESS = "0x4200000000000000000000000000000000000006";

const userWallet = new ethers.Wallet(ethers.id("alice"));
const userAddress = userWallet.address;
const chainId = 1;
const destChainId = 10;

type BeneficiaryType = "EOA" | "SmartContract";

const getMultiCallHandler = async (destTokenDestChain: string, beneficiaryType: BeneficiaryType) => {
  const shouldReturnMulticall =
    (beneficiaryType === "EOA" && destTokenDestChain === WETH_ADDRESS) ||
    (beneficiaryType === "SmartContract" && destTokenDestChain === ETH_ADDRESS);
  
  if (!shouldReturnMulticall) {
    return ethers.ZeroAddress;
  }

  const { data: multiCallHandlers } = await axios.get('https://api.paraswap.io/delta/prices/multicall-handlers');

  return multiCallHandlers[destChainId];
}

const getDeltaBridge = async (destTokenDestChain: string, bridgeFee: string) => {
  const multiCallHandler = await getMulticallHandler(destTokenDestChain, 'EOA'); // a proper detection of the beneficiary type should be done here

  return {
    maxRelayerFee: bridgeFee,
    destinationChainId: destChainId,
    outputToken: destTokenDestChain,
    multiCallHandler: multiCallHandler,
  };
}
// fetch price
const { data: quoteData } = await axios.get(`https://api.paraswap.io/quote?mode=delta&chainId=${chainId}&destChainId=${destChainId}...`);
const price = quoteData.delta;
// construct delta bridge
const bridge = await getDeltaBridge(ETH_ADDRESS, price.bridgeFee);

// prepare build order params
const buildOrderParams = {
  price,
  owner: userAddress,
  chainId,
  partnerAddress: "0x81037e7be71bce9591de0c54bb485ad3e048b8de",
  partnerFeeBps: 100, // 1%
  partnerTakesSurplus: false, // the default,
  bridge,
};
// build the order to sign
const { data: buildOrderData } = await axios.post("https://api.paraswap.io/delta/orders/build", buildOrderParams);
const { domain, types, value: order } = buildOrderData.toSign;
// hash the order
const eip712Hash = TypedDataEncoder.hash(
  domain,
  types,
  order,
);
// sign and compact
const signature = userWallet.signingKey.sign(eip712Hash).compactSerialized; // ERC-2098 representation

Partner Fees

This is just technical explanation. If you are using our Order Building endpoint, this knowledge is not required.

The fees are enabled by encoding partnerAndFee param of the order, before its signed by a user.

The param includes three values:

  • partnerAddress - on-chain address which will be able to collect the fees.

  • partnerFeeBps - flat fee percent which will be taken from the order. The value is in base points (100is 1%), which the maximum allowed value of 200.

  • partnerTakeSurplus - a flag that, if set, allows the partner to collect 50% of the surplus as fees. It has no impact if passed together with partnerFeeBps, since partnerFeeBps takes precedence.

These are encoded into a single uint256 value, which then is used as partnerAndFee.

Last updated