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.
- Get an indicative price using
/gasless/price
- Get a firm quote using
/gasless/quote
- Submit the transaction using
/gasless/submit
- Sign the gasless approval object (if applicable)
- Sign the trade object
- Split the signatures
- Package split signed objects into a format that can be POST to
/gasless/submit
- Compute trade hash
- Check the trade status using
/gasless/status/{tradeHash}
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:
- integrating with MetaMask (via
signTypedData_v4
) - viem's signTypedData
- wagmi's signTypedData
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.
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).
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 successful400
:- 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, ifaddress A
already has aUSDC -> WETH
trade submitted and it has not settled yet, then a subsequent/submit
call withaddress A
andUSDC -> *
trade will fail with429
. 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
, fordomain
, make sure you order the fields in the exact same order as specified in https://eips.ethereum.org/EIPS/eip-712 sincego-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
andv4
are fine), there is an issue for signing EIP-712 object. Make sure you updatedethers
version to>= 6.3.0
.