Intent API

Our Swap, Intent, and Price API will be discontinued permanently on 31.10.24

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. We will automatically convert and deduct the gas cost from the output quote amount when the venue is set to helix.

Example

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

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:

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
      ])
    ]
  );
}

4.1 Fee Taking (Optional)

Fees can be automatically deducted from the transaction proceeds, ensuring that the user experiences no additional upfront costs while the provider receives compensation for their service.

Currently, we only support taking fees in the same token as the output token (outToken).

To implement fee-taking, follow these steps:

  1. Calculate the fee amount you want to charge.

  2. Subtract the fee amount from the original output amount.

  3. Add a new output to the outputs array with the fee amount and the wallet address that should receive the fee.

Note: The sum of all output amounts (including the fee) should be less than or equal to the original expected output amount.

Here's how to modify the existing code to implement fee-taking:

Some code

Example 1: Using UniswapX SDK
import { DutchOrder, NonceManager } from '@uniswap/uniswapx-sdk';
import { ethers } from 'ethers';

// ... (previous code remains the same)

const feeAmount = BigNumber.from('1000000000000000'); // Example fee of 0.001  
const feeRecipient = '0x1234567890123456789012345678901234567890'; // Wallet to receive the fee

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('999000000000000000'), // Reduced by fee amount
    endAmount: BigNumber.from('899000000000000000'), // Reduced by fee amount
    recipient: '0x0000000000000000000000000000000000000000',
  })
  .output({
    token: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
    startAmount: feeAmount,
    endAmount:  ,
    recipient: feeRecipient,
  })
  .build();

// ... (rest of the code remains the same)
// Some code
Example 2: Raw Code
// ... (previous code remains the same)

const feeAmount = ethers.utils.parseEther('0.001'); // Example fee of 0.001 ETH
const feeRecipient = '0x1234567890123456789012345678901234567890'; // Wallet to receive the fee

const orderData = {
  // ... (other fields remain the same)
  outputs: [
    {
      token: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
      startAmount: ethers.utils.parseEther('0.999'), // Reduced by fee amount
      endAmount: ethers.utils.parseEther('0.899'), // Reduced by fee amount
      recipient: "0x0000000000000000000000000000000000000000"
    },
    {
      token: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
      startAmount: feeAmount,
      endAmount: feeAmount,
      recipient: feeRecipient
    }
  ]
};

const serializedOrder = serialize(orderData);

// ... (rest of the code remains the same)

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:

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:

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