Intent API

The Intent API lets you trade gasless, MEV-protected and at optimal rates.

Overview

This guide provides an overview of how to interact with the Intent API: Query for swap rates, construct a swap order, and submit an order for execution.

Prerequisites

Before you start, ensure you have:

  • Familiarity with HTTP requests and responses.

  • An Ethereum Mainnet wallet address with enough balance for the swap.

Steps to Submit an Intent Swap

1. Approve Permit2

Ensure that the tokens you intend to swap are approved for use by the Permit2 contract with at least the amount the user intends to swap.

Uniswap Labs developed the Permit2 contract and acts as an intermediary, allowing each swap operation to be approved via signatures, saving the user gas cost and increasing security. We recommend that the token is approved with the max uint256 amount, to avoid having to approve multiple times. -> Read more about what is Permit2.

Example
// Assuming you have a connected wallet and the token's contract instance
const tokenContract = new ethers.Contract(tokenAddress, tokenABI, wallet);

const amountToApprove = ethers.constants.MaxUint256; // Approve maximum for convenience
const spenderAddress = '0x000000000022d473030f116ddee9f6b43ac78ba3'; // Permit2 contract address

await tokenContract.approve(spenderAddress, amountToApprove);

2. Get a swap quote

To initiate a swap, you first need to query the current swap rate between two tokens. This involves sending a request to our solver API with the details of your intended swap, including the input token, output token, and the amount you wish to swap. Check the solver API reference guide for in-depth details.

Understanding Gas Fees in Transactions

When using intents, it's important to note that a transaction's executor (in this case, the filler) pays for the gas fees. As a result, the gas cost is deducted from the calculated output amount of your transaction. This ensures the network fees are covered, allowing your transaction to be processed smoothly on the blockchain.

To assist the user, we've included the `deduct_gas_cost` parameter. We will automatically convert and deduct the gas cost from the output quote amount when active.

Example

POST https://api.propellerheads.xyz/partner/v2/solver/quote?blockchain=ethereum&deduct_gas_cost=FirstOrder

Content-Type: application/json

// Body
{
  "orders": [
    {
      "origin_address": "0x0000000000000000000000000000000000000000",
      "sell_token": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
      "buy_token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      "sell_amount": "50000000000000000",
      "buy_amount": "0"
    }
  ],
  "amms": [
    "+all"
  ]
}

3. Fetch the Nonce

To ensure the uniqueness and sequence of your transactions, you must first fetch a nonce for your swap order. The nonce is a number that represents the current transaction count for your wallet address, ensuring that each transaction is processed exactly once. The nonce is saved in the execution contract, so it's different than the wallet's nonce.

GET https://api.propellerheads.xyz/partner/v2/intent/nonce

The parameters needed to call the nonce endpoint are the following:

ParameterDescription

address

(string) Your wallet address

blockchain

(string) The blockchain you are interacting with, e.g. 'ethereum'.

Example

curl -X 'GET' \
  'https://dev-api.propellerheads.xyz/partner/v2/intent/nonce?address=YOUR_WALLET_ADDRESS&blockchain=ethereum' \
  -H 'accept: application/json' \
  -H 'x-api-key: <api-key>'

4. Construct the Order

After obtaining the nonce and the price, construct your swap order. This payload outlines the specifics of the swap, including the tokens involved, the amounts, and the timing parameters.

To maintain consistency with other Intent APIs, the order follows the same interface as Uniswap X's Signed Order. To build the order, you can either use UniswapX's SDK - if you're using JavaScript / Typescript or encode the ABI in any other way you'd like.

We are developing our own SDK to allow for quick order creation in several languages.

Example 1: Using UniswapX SDK

The UniswapX SDK is available here.

import { DutchOrder, NonceManager } from '@uniswap/uniswapx-sdk';
import { ethers } from 'ethers';

const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const account = await provider.getSigner().getAddress(); 
const nonceMgr = new NonceManager(provider, 1); 
const nonce = await nonceMgr.useNonce(account); 

const chainId = 1;
const builder = new DutchOrderBuilder(chainId);
const order = builder
  .deadline(deadline)
  .decayEndTime(deadline)
  .decayStartTime(deadline - 100)
  .nonce(nonce)
  .input({
    token: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
    amount: BigNumber.from('1000000'),
  })
  .output({
    token: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
    startAmount: BigNumber.from('1000000000000000000'),
    endAmount: BigNumber.from('900000000000000000'),
    recipient: '0x0000000000000000000000000000000000000000',
  })
  .build();
 
// Sign the built order 
const { domain, types, values } = order.permitData();
const signature = wallet._signTypedData(domain, types, values);

const serializedOrder = order.serialize();
Example 2: Raw code
// ABI
[
    "tuple(address,address,uint256,uint256,address,bytes)",
    "uint256",
    "uint256",
    "address",
    "uint256",
    "tuple(address,uint256,uint256)",
    "tuple(address,uint256,uint256,address)[]",
]

// Schema
{
  "reactor": "Address of the reactor",
  "swapper": "Your wallet address",
  "nonce": "Incremented nonce value",
  "deadline": "UNIX timestamp of the deadline",
  "additionalValidationContract": "Address for additional validation, if any",
  "additionalValidationData": "Data for additional validation, if any",
  "decayStartTime": "UNIX timestamp for decay start",
  "decayEndTime": "UNIX timestamp for decay end",
  "exclusiveFiller": "Address of the exclusive filler, if any",
  "exclusivityOverrideBps": "Basis points for exclusivity override",
  "input": {
    "token": "Contract address of the input token",
    "startAmount": "Initial amount of the input token",
    "endAmount": "Final amount of the input token"
  },
  "outputs": [
    {
      "token": "Contract address of the output token",
      "startAmount": "Initial amount of the output token",
      "endAmount": "Final amount of the output token",
      "recipient": "Recipient's address"
    }
  ]
}

// Example code:

const orderData = {
  // Fill in with the structured order data as per the JSON structure above
};

const serializedOrder = serialize(orderData);

function serialize(orderData) {
  const abiCoder = new ethers.utils.AbiCoder();
  return abiCoder.encode(
    [
      "tuple(address,address,uint256,uint256,address,bytes)",
      "uint256",
      "uint256",
      "address",
      "uint256",
      "tuple(address,uint256,uint256)",
      "tuple(address,uint256,uint256,address)[]"
    ],
    [
      [
        orderData.reactor,
        orderData.swapper,
        orderData.nonce,
        orderData.deadline,
        orderData.additionalValidationContract,
        orderData.additionalValidationData
      ],
      orderData.decayStartTime,
      orderData.decayEndTime,
      orderData.exclusiveFiller,
      orderData.exclusivityOverrideBps,
      [
        orderData.input.token,
        orderData.input.startAmount,
        orderData.input.endAmount
      ],
      orderData.outputs.map((output) => [
        output.token,
        output.startAmount,
        output.endAmount,
        output.recipient
      ])
    ]
  );
}

5. Sign the order

All orders must be signed with your private key.

To sign the provided data for the transaction, users must prepare the data according to the Ethereum EIP-712 standard, which enables typed structured data signing. This process involves specifying a domain, types for the data being signed, and the values of the transaction.

Example
// Domain
const domain = {
  name: 'Permit2',
  chainId: 1,
  verifyingContract: '0x000000000022d473030f116ddee9f6b43ac78ba3'
};

// Types
const types = {
  PermitWitnessTransferFrom: [
    { name: 'permitted', type: 'TokenPermissions' },
    { name: 'spender', type: 'address' },
    { name: 'nonce', type: 'uint256' },
    { name: 'deadline', type: 'uint256' },
    { name: 'witness', type: 'ExclusiveDutchOrder' }
  ],
  TokenPermissions: [
    { name: 'token', type: 'address' },
    { name: 'amount', type: 'uint256' }
  ],
  ExclusiveDutchOrder: [
    { name: 'info', type: 'OrderInfo' },
    { name: 'decayStartTime', type: 'uint256' },
    { name: 'decayEndTime', type: 'uint256' },
    { name: 'exclusiveFiller', type: 'address' },
    { name: 'exclusivityOverrideBps', type: 'uint256' },
    { name: 'inputToken', type: 'address' },
    { name: 'inputStartAmount', type: 'uint256' },
    { name: 'inputEndAmount', type: 'uint256' },
    { name: 'outputs', type: 'DutchOutput[]' }
  ],
  OrderInfo: [
    { name: 'reactor', type: 'address' },
    { name: 'swapper', type: 'address' },
    { name: 'nonce', type: 'uint256' },
    { name: 'deadline', type: 'uint256' },
    { name: 'additionalValidationContract', type: 'address' },
    { name: 'additionalValidationData', type: 'bytes' }
  ],
  DutchOutput: [
    { name: 'token', type: 'address' },
    { name: 'startAmount', type: 'uint256' },
    { name: 'endAmount', type: 'uint256' },
    { name: 'recipient', type: 'address' }
  ]
};

// Values
const values = {
  // Include values as structured above, converting BigNumbers to strings for signing
};

// Sign the data
import { ethers } from 'ethers';

const swapper_wallet = new ethers.Wallet('YOUR_PRIVATE_KEY', provider);
const signature = await swapper_wallet._signTypedData(domain, types, values);

6. Submit the Order

Once the order is signed, submit it to the Propellerswap Helix API for execution. This involves sending a POST request to our order submission endpoint with the order details and signature.

POST https://api.propellerheads.xyz/partner/v2/intent/order

The parameters needed to call the order endpoint are the following:

Body ParameterDescription

blockchain

(string) The blockchain you are interacting with, e.g. 'ethereum'.

order

(string) The serialised order data

signature

(string) The order signature

quoteId

(optional string) The uuid4 string received from the quote endpoint

Example

curl -X 'POST' \
  'https://api.propellerheads.xyz/partner/v2/intent/order' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -H 'x-api-key: <api-key>' \
  -d '{
  "blockchain": "ethereum",
  "order": "0xa9059cbb0000000000000000000000005d3a536e4d6dbd6114cc1ead35777bab948e3643000000000000000000000000000000000000000000000016345785d8a0000",
  "signature": "0x2b5634c4206f3192b8bb7c5a6534663e52a02f5063c2f11372400b80911af5eb3b287e301f0238558d9c641c89843a48f4e9ae65024fb16805568959872ef8821b",
  "quote_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}'

Afterwards, your intent will be processed by Helix solvers, and will be filled if a viable solution is found.

7. Track order status

You can use the v2/intent/orders endpoint to track your intent status. GET https://api.propellerheads.xyz/partner/v2/intent/orders

The parameters needed to call the orders endpoint are the following:

ParameterDescription

blockchain

(string) The blockchain you are interacting with, e.g. 'ethereum'.

order_status

(optional string) Available values: open, expired, error, cancelled, filled, insufficient-funds

order_hashes

(optional string) Comma-separated order hashes. e.g. hash1,hash2,hash3

filter_query

(optional string) Filter query. For example: gt(UNIX_TIMESTAMP), between(1675872827, 1675872930), or lt(1675872930)

swapper

(string) The swapper's address ie. your address

limit

(integer) The maximum number of orders expected in the response

cursor

(string) Cursor param to page through results. This will be returned in the previous query if the results have been paginated.

Example

curl -X 'GET' \
  'https://dev-api.propellerheads.xyz/partner/v2/intent/orders?blockchain=ethereum&limit=10' \
  -H 'accept: application/json' \
  -H 'x-api-key: <api-key>'

Last updated