Search…
⌃K

Integrating with Clipper

Guidance for how developers can build Clipper transactions into their own DeFi application.
Clipper is designed for real humans, not for bots. Any automated trading with Clipper will be detected and blocked, and any user accounts linked to that automated trading will be banned from the Clipper community. If you are an application developer writing an application for real human users, please reach out to us on Discord for further integration details.

FMM Architecture Description

Clipper's Formula Market Maker (FMM) is implemented similar to a typical Request For Quotes (RFQ) system. In an RFQ architecture, you first ask our offchain server to quote a transaction price, specifying the buy and sell tokens and either a target input or output amount. You then have a short amount of time to accept that quote, and receive back a signed certificate from our offchain server. Then, you must pass that transaction and signed certificate to our onchain smart contracts to execute the swap.
Clipper supports a "send, then swap" modality and is designed to be as simple as possible to chain within a larger set of transactions, or to automate trading with bots.

Getting the Exchange Address and Asset Information

GET to https://api.clipper.exchange/rfq/pool?chain_id=.... with the chain_id set to the appropriate (integer) chain ID. The JSON response will include the pool's address and list of contract addresses on that chain. Example response:
{
"pool": {
"chain_id": 137,
"address": "0x6Bfce69d1Df30FD2B2C8e478EDEC9dAa643Ae3B8",
"num_assets": 6,
"pool_tokens": 1744171922596734892507136,
"value_in_usd": 1955850.4306469245
},
"assets": [{
"name": "USDC",
"address": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
"balance": "287945821795",
"price_in_usd": 0.9997835373,
"value_in_usd": 287883.49226496054,
"target_value_in_usd": 288882.55208657164
}, {
"name": "ETH",
"address": "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619",
"balance": "91082274931865214483",
"price_in_usd": 4559.0177414797,
"value_in_usd": 415245.7073487052,
"target_value_in_usd": 411368.75417127804
}, {
"name": "USDT",
"address": "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
"balance": "204708040486",
"price_in_usd": 1.0008107141,
"value_in_usd": 204874.0001808054,
"target_value_in_usd": 205684.37708563902
}, {
"name": "MATIC",
"address": "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270",
"balance": "255804849489186177066089",
"price_in_usd": 2.0681423682,
"value_in_usd": 529040.8472196101,
"target_value_in_usd": 514210.9427140975
}, {
"name": "DAI",
"address": "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
"balance": "202296673572783491062883",
"price_in_usd": 1.0024245729,
"value_in_usd": 202787.15660528824,
"target_value_in_usd": 205684.37708563902
}, {
"name": "WBTC",
"address": "0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6",
"balance": "450316030",
"price_in_usd": 62901.8231205992,
"value_in_usd": 283256.9926743044,
"target_value_in_usd": 288882.55208657164
}]
}
  • num_assets will be an integer value with the number of assets in the pool.
  • The assets key at the top level is a list of length num_assets describing the pool assets. The name and address of each asset are the most important fields.

Requesting a Quote

POST to the https://api.clipper.exchange/rfq/quote endpoint a JSON body with the following keys.
{
"output_asset_symbol": "ETH",
"input_asset_symbol": "USDT",
"chain_id": 137,
"output_amount": "1000000000000000000", // XOR: "input_amount": "1000000000000000000",
"time_in_seconds": 30
}
  • chain_id should be set to the appropriate (integer) chain ID. Polygon is 137.
  • Exactly one of input_amount or output_amount should be specified, and that value should be a string.
  • time_in_seconds refers to the amount of time between accepting a quote and receiving the quote on the blockchain. A good suggestion for Polygon is 30 seconds. As this value increases, the quote will get worse for the user.
  • input_asset_symbol and output_asset_symbol should correspond to the asset name returned from the /rfq/pool call above. Observe that "ETH" and "MATIC" are both assets, but that the Clipper exchange uses the wrapped version of these assets, a distinction made clear and unambiguous by the inclusion of the contract addresses in the response.
The response is a JSON object:
{
"id": "60aa826e-b4ee-4660-8b28-4ffb77e936f5",
"must_accept_by": "2021-10-25 22:39:45.408794+00:00",
"good_until": 1635201615,
"chain_id": 137,
"input_asset_address": "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619",
"input_amount": "3442034079",
"input_value_in_usd": 1.01,
"output_asset_address": "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
"output_amount": "1000000000000000000",
"output_value_in_usd": 1.00
}
must_accept_by is a human-readable UTC timestamp by which you must accept the quote and should be expected to be a short duration. If this time has passed already, request a new quote - the server will not sign a quote after must_accept_by has passed.

Accepting a Quote

Only members of the Clipper Community and DEX aggregator partners can get their quotes signed. If you're building a DEX aggregator, please reach out to the team on Discord for access (MNDA required).
POST to https://api.clipper.exchange/rfq/sign endpoint with the following JSON body:
{
"quote_id": "60aa826e-b4ee-4660-8b28-4ffb77e936f5",
"destination_address": "0x5901920A7b8cb1Bba39220FAC138Ffb3800dD212",
"sender_address": "0x5901920A7b8cb1Bba39220FAC138Ffb3800dD212"
}
  • quote_id should be set to the ID field of the quote requested above
  • destination_address will receive the output token.
  • sender_address is for DEX aggregator partners that are using their own smart contract to mediate the interaction between users and Clipper. Aggregators should use the address of the account that they will pull tokens from (i.e., the EOA user address) as opposed to the address of their deployed contract. This is an optional value on signing requests - by default, destination_address will be used.
The response is a JSON object that adds signature information to the quote:
{
"chain_id": 137,
"input_asset_address": "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619",
"output_asset_address": "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
"input_amount": "3442034079",
"output_amount": "1000000000000000000",
"good_until": "1635201615",
"destination_address": "0x5901920A7b8cb1Bba39220FAC138Ffb3800dD212",
"signature": {
"v": 28,
"r": "0x19e2400ad86ee2f353f7f7d7a859339e3ba9d056691872385ada9ddc1d104ea6",
"s": "0x5d8de6fe4e25b9c26817b0bc3c33a78ce1e24799aae3b89886f64ac6a98a3c37"
},
"clipper_exchange_address": "0x6Bfce69d1Df30FD2B2C8e478EDEC9dAa643Ae3B8"
}

Interacting with the Clipper Exchange

There are several different external functions for interacting with the Clipper exchange onchain, depending on your use case. In all of these functions, Signature is a struct defined by:
struct Signature {
uint8 v;
bytes32 r;
bytes32 s;
}

Avoiding Transaction Errors with the Clipper API

Clipper is designed to be used sequentially, so that it trades only one active and outstanding signed quote at a time. Once you execute a swap with Clipper all of your existing quotes from before that swap are "dirty" and may no longer be honored onchain.
If you try to sign multiple quotes, or use the same signed quote multiple times, the transactions are highly likely to revert on the blockchain, costing you gas. To avoid this, always call for fresh quotes after you transact with Clipper.

Swapping Tokens for Tokens

function swap(address inputToken, address outputToken, uint256 inputAmount, uint256 outputAmount, uint256 goodUntil, address destinationAddress, Signature calldata theSignature, bytes calldata auxiliaryData) external;
function transmitAndSwap(address inputToken, address outputToken, uint256 inputAmount, uint256 outputAmount, uint256 goodUntil, address destinationAddress, Signature calldata theSignature, bytes calldata auxiliaryData) external;
Observe that the signed quote has all of these values to use, except for auxiliaryData which can be set to any string and is used for identification purposes in the event logs.
The difference between swap and transmitAndSwap is that transmitAndSwap starts by transferring inputAmount of the inputToken from msg.sender to the exchange, while swap assumes that the appropriate amount of the input token has already been transmitted. Which function you should use depends on if and how you are connecting the Clipper swap to other operations.

Handling Native Currency

On each chain a native currency has special privileges and abilities and is often not ERC20 compatible. For instance, MATIC is the native currency on Polygon. Unlike the Clipper implementation on Ethereum mainnet, Clipper's RFQ architecture only uses ERC20 tokens, meaning that these native currencies must be wrapped before exchange with Clipper, and that Clipper can only return the wrapped version of these assets from the exchange.
However, for convenience, we provide two sets of functions for handling native currency in a direct fashion. For these swaps, you must get a signature for a transaction to or from the wrapped version of the native currency, as this is what will actually be exchanged.
For swaps of tokens to native currency (example: DAI to MATIC on Polygon), use:
function sellTokenForEth(address inputToken, uint256 inputAmount, uint256 outputAmount, uint256 goodUntil, address destinationAddress, Signature calldata theSignature, bytes calldata auxiliaryData) external;
function transmitAndSellTokenForEth(address inputToken, uint256 inputAmount, uint256 outputAmount, uint256 goodUntil, address destinationAddress, Signature calldata theSignature, bytes calldata auxiliaryData) external;
As with token-to-token swaps, the difference between these functions is that the first assumes that at least inputAmount of inputToken has already been sent, while the second pulls that input token directly from msg.sender. Observe further that:
  • The outputToken argument is not present (since it will be native currency)
  • These functions refer to the native currency as Eth regardless of chain
For swaps of native currency to tokens (example: MATIC to USDC on Polygon), use:
function sellEthForToken(address outputToken, uint256 inputAmount, uint256 outputAmount, uint256 goodUntil, address destinationAddress, Signature calldata theSignature, bytes calldata auxiliaryData) external payable;
This function can operate either by attaching the native currency to the call as msg.value or by transferring that native currency to the exchange contract prior to the function call.
On Moonbeam, Clipper uses the GLMR ERC20 pre-compile. As a result, GLMR can be used directly in the Clipper pool without wrapping by referring to it as a token with the contract address 0x0000000000000000000000000000000000000802.

Reduced calldata API

On L2s like Optimism, Clipper's exchange contracts include two special functions that operate with reduced calldata in order to lower transaction costs. These functions are:
function packedTransmitAndSwap(uint256 packedInput, uint256 packedOutput, uint256 packedGoodUntil, bytes32 auxData, bytes32 r, bytes32 vs) external payable;
function packedSwap(uint256 packedInput, uint256 packedOutput, uint256 packedGoodUntil, bytes32 auxData, bytes32 r, bytes32 vs) external payable;
Similar to the other Clipper functions, use packedTransmitAndSwap to transmit the token from msg.sender and use packedSwap if tokens have already been transmitted.

Handling Native Currency in the L2 API

Indicate native currency (e.g., ETH on Optimism) should be the input or output by setting the contract address to address(0).

Packed Format Values

The packed format is a way of compressing an integer amount and a contract address into a single uint256 value. The packed format puts the address as the (first, lowest order) 160 bits (= 40 hex values = 20 bytes) and the amount as the (second, highest order) 96 bits (= 24 hex values = 12 bytes). To created a packed value, leftshift the integer amount by 160 bits and add it to the binary representation of the contract address. The four packed arguments into this function, and the values they encode, are:
  • packedInput: input amount and contract address
  • packedOutput: output amount and contract address
  • packedGoodUntil: Simply use the good_until value received from the server - it is already a packed representation of state.
  • auxData - this is a bytes32 value that is a packed representation of an identifying string, and the destination_address (lowest order bytes). The identifying string is emitted as part of Swapped event and allows us to attribute trade sourced correctly.

Short Signatures

These functions use the EIP 2098 "short signature" representation for the signature returned from the Clipper Exchange server. Source code to convert from the v, r, s signature value to the r, vs short signature is available on the EIP.

Clipper Coves

Clipper Coves are a fully decentralized extension to Clipper that allows for the swap of any two crypto assets. On each network, Clipper Coves are a single contract that has a collection of coves. Each cove consists of an equal-weighted CPMM pair that trades any ERC20 token against ClipperLP token (the pool token that gives its holder a direct claim to a pro rata share of the assets in the Clipper pool).
Coves support "N to N" swaps in a fully decentralized way. To get a quote, call the getSellQuote function on the Cove:
function getSellQuote(address sellToken, uint256 sellAmount, address buyToken) public view returns (uint256)
Observe that, unlike transactions with the Clipper Pool, quotes with Clipper Coves are not "firm". Actual output values in a swap may change based on state changes.
To perform a transaction with a Clipper Cove, call SellTokenForToken or transmitAndSellTokenForToken:
function sellTokenForToken(address sellToken, address buyToken, uint256 minBuyAmount, address destinationAddress, bytes32 auxData) external returns (uint256 buyAmount);
function transmitAndSellTokenForToken(address sellToken, uint256 sellAmount, address buyToken, uint256 minBuyAmount, address destinationAddress, bytes32 auxData) external returns (uint256 buyAmount) ;
The two functions are the same except transmitAndSellTokenForToken pulls the specified sellAmount from msg.sender, while sellTokenForToken assumes that the token being sold has already been sent previously in the transaction.