Skip to main content
Version: 2.0

Gasless API - Technical Appendix

Learn how to sign the trade & approval objects, split signatures, submit, and compute the trade hash for a gasless trade

The following is the typical flow when using Gasless API. This guide covers how to complete steps 3 - 4.

  1. Get an indicative price using /gasless/price
  2. Get a firm quote using /gasless/quote
  3. Submit the transaction using /gasless/submit
    1. Sign the gasless approval object (if applicable)
    2. Sign the trade object
    3. Split the signatures
    4. Package split signed objects into a format that can be POST to /gasless/submit
    5. Compute trade hash
  4. Check the trade status using /gasless/status/{tradeHash}
tip

Checkout the Gasless API Runnable Headless Example to see these steps in action

Signing objects​

To take advantage of gaslesses approvals and gasless trades, user must sign the approval.eip712 and the trade.eip712 objects returned by /quote. These objects contain everything (domain, types, primaryType, message) needed when signing these objects .

There are different EIP-712 signing strategies if you are user facing wallet that shows the users the details of what they are signing. Some commonly used tools for this include:

Sign gasless approval object​

If a token supports gasless approvals, the /quote response will return an β€œapproval” object which contains the necessary information to process a gasless approval, see below:

 "approval":{
"type": "permit", // this is approval.type from the /quote endpoint
"hash": "0xf3849ebcd806e518f2d3457b76d31ccf41be07fe64f0a25bbe798f1b9edde872",
"eip712": {
"types": {/* this is approval.eip712.types from the /quote endpoint */},
"domain": {/* this is approval.eip712.domain from the /quote endpoint */},
"message": {/* this is approval.eip712.message from the /quote endpoint */},
"primaryType": "Permit",
}
}
}

Keep in mind that the domain field of this object must follow a strict ordering as specified in EIP-712. The approval.eip712 object will present the correct ordering, but make sure that this ordering is preserved when obtaining the signature:

  • name , version, chainId, verifyingContract, salt
    • Contracts may not utilize all of these fields (i.e. one or more may be missing), but if they are present, they must be in this order
  • Any other field must follow in alphabetical order

Here is an example to sign it using viem's signTypedData:

// Sign the data returned by approval.712 in the quote response
async function signApprovalObject(): Promise<any> {
// Logic to sign approval object
const approvalSignature = await client.signTypedData({
types: quote.approval.eip712.types,
domain: quote.approval.eip712.domain,
message: quote.approval.eip712.message,
primaryType: quote.approval.eip712.primaryType,
});
return approvalSignature;
}

Sign trade object​

Similar to gasless approval, to submit a trade, you must have your user sign trade.eip712 object returned at the time of the /quote. All the instructions & caveats are the same as previous section.

tip

See example code to sign trade object

Here is an example to sign it using viem's signTypedData:

// Sign the data returned by trade.712 in the quote response
async function signTradeObject(): Promise<any> {
// Logic to sign trade object
const tradeSignature = await client.signTypedData({
types: quote.trade.eip712.types,
domain: quote.trade.eip712.domain,
message: quote.trade.eip712.message,
primaryType: quote.trade.eip712.primaryType,
});
return tradeSignature;
}

Split signatures​

Once signed, the signature needs to be split to its individual components (v, r, s) and to be formatted in an object that can be POST to /submit (see object format here).

tip

Example code showing how to implement a split signature function. This is a variation of this splitSignature implementation.

Example code using split signature to split approval and trade signatures

POST the split signatures to /submit​

Example request​

Once the signatures have been split, in order to POST to /submit, approval and trade objects must be formatted to contain the following key/value pairs:

curl -X POST '<https://api.0x.org/gasless/submit>' --header '0x-api-key: <API_KEY>' --header '0x-version: v2' --data '{
"trade": {
"type": "settler_metatransaction", // this is trade.type from the /quote endpoint
"eip712": { /* this is trade.eip712 from the /quote endpoint */ },
"signature": {
"v": 27,
"r": "0xeaac9ddbbf9b251a384eb4a545a2800a6d7aca4ceb5e5a3154ddd0d2923533d2",
"s": "0x601deadfd33bd8b0ad35468613eabcac0a250a7a32035e261a13e2dcbc462e49",
"signatureType": 2
}
},
"approval":{
"type": "permit", // this is approval.type from the /quote endpoint
"eip712": {/* this is approval.eip712 from the /quote endpoint */},
"signature": {
"v": 28,
"r": "0x55c26901285d5cb4d9d1ebb2828122fd6c334b343901944d34a810b3d2d79773",
"s": "0x20854d829e3118c3f780228ca83fac6154060328a90d2a21267c9f7d1ae9e53b",
"signatureType": 2
}
}
}'

Here is an example code snippet of how to submit it using JavaScript:

 // Make a POST request to submit trade with split signed trade object (and approval object if available)
async function submitTrade(
tradeDataToSubmit: any,
approvalDataToSubmit: any
): Promise<void> {
try {
let successfulTradeHash;
const requestBody: any = {
trade: tradeDataToSubmit,
chainId: client.chain.id,
};
if (approvalDataToSubmit) {
requestBody.approval = approvalDataToSubmit;
}
const response = await fetch("https://api.0x.org/gasless/submit", {
method: "POST",
headers: {
"0x-api-key": process.env.ZERO_EX_API_KEY as string,
"0x-version": process.env.ZERO_EX_API_VERSION as string,
"Content-Type": "application/json",
},
body: JSON.stringify(requestBody),
});
const data = await response.json();
successfulTradeHash = data.tradeHash;
console.log("#️⃣ tradeHash: ", successfulTradeHash);
return successfulTradeHash;
} catch (error) {
console.error("Error submitting the gasless swap", error);
}
}

Example response​

If the submitted trade is successful, and object with type, tradeHash, and zid are returned.

{
"tradeHash": "0xcb3285b35c024fca76037bea9ea4cb68645fed3bdd84030956577de2f1592aa9",
"type": "settler_metatransaction",
"zid": "0x111111111111111111111111"
}

Status Code​

  • 201 if successful
  • 400:
    • If the trade is too close to expiration time.
    • If the signature in the payload is invalid.
    • If the balance / allowance of the taker is less than the trade amount.
    • (otc only) If the trade has been outstanding for too long.
    • (otc only) If the balance / allowance of the market maker selected to settle the trade is less than the trade amount (very unlikely).
    • If the query params are not able to pass validation.
  • 429 if there is already a trade associated with a taker address and a taker token that's not been settled by our relayers yet. For example, if address A already has a USDC -> WETH trade submitted and it has not settled yet, then a subsequent /submit call with address A and USDC -> * trade will fail with 429. The taker is, however, allowed to submit other trades with a different taker token.
  • 500 if there is an internal server error.

Note for go-ethereum​

  • If you're using go-ethereum, for domain, make sure you order the fields in the exact same order as specified in https://eips.ethereum.org/EIPS/eip-712 since go-ethereum does not enforce ordering. Also, make sure you skipped fields that are absent.
- string name: the user readable name of signing domain, i.e. the name of the DApp or the protocol.
- string version: the current major version of the signing domain. Signatures from different - versions are not compatible.
- uint256 chainId: the EIP-155 chain id. The user-agent should refuse signing if it does not match the currently active chain.
- address verifyingContract: the address of the contract that will verify the signature. The user-agent may do contract specific phishing prevention.
- bytes32 salt: an disambiguating salt for the protocol. This can be used as a domain separator of last resort.

The EIP712Domain fields should be the order as above, skipping any absent fields
  • If you're using ethers v6 (v5 and v4 are fine), there is an issue for signing EIP-712 object. Make sure you updated ethers version to >= 6.3.0.