For the complete documentation index, see llms.txt. This page is also available as Markdown.

๐ŸงญAggregators

Take advantage of liquidity in Ekubo pools to provide better pricing for users

The code samples on this page are written in Cairo for the Starknet deployment. The same lock/callback flow applies on EVM chains โ€” see Swapping and the EVM contracts repository for Solidity equivalents.

Summary

Ekubo is a singleton AMM that utilizes the "till" pattern. The till pattern was publicly introduced at EthCC[5] and is also described here.

Every interaction with Ekubo starts with ICore#lock. In order to interact with Ekubo, you must first implement the ILocker interface in your calling contract:

#[starknet::interface]
trait ICore<TStorage> {
    // ...

    // Main entrypoint for any actions, which must be called before any other pool functions can be called.
    // Other functions must be called within the callback to lock. The ILocker#locked function is called with the input data,
    // and the returned array is passed through to the caller.
    fn lock(ref self: TStorage, data: Array<felt252>) -> Array<felt252>;

    // Make a swap against a pool.
    // You must call this within a lock callback.
    fn swap(ref self: TStorage, pool_key: PoolKey, params: SwapParameters) -> Delta;

    // ...
}

// This interface must be implemented by any contract that intends to call ICore#lock
#[starknet::interface]
trait ILocker<TStorage> {
    // This function is called on the caller of lock, i.e. a callback
    // The input is the data passed to ICore#lock, the output is passed back through as the return value of #lock
    fn locked(ref self: TStorage, id: u32, data: Array<felt252>) -> Array<felt252>;
}

You must then perform your swaps within the callback. To know which swaps you need to do, encode your parameters, such as the pools against which you'd like to swap, into the data argument

An example locker might look like this:

Instead of withdrawing a delta, you can also save it for use later using #save or load it using #load.

Locker utility method

You may wish to use this shared code to call core with some calldata and automagically deserialize the result:

Note on extensions

Extensions are third-party code that can change the result of swapping against a pool, usually by updating liquidity positions before the swap โ€” though an extension may also front-run a swap with one of its own.

There are two ways to handle extensions:

  • read the code for the extension and support it by off-chain simulation

  • use the quoter to simulate swaps across pools, which always includes extension behavior

With the latter approach you remain exposed to per-block changes in an extension's behavior, but this is no different from exposure to any other front-runner. Always check the resulting output amount against an expected slippage tolerance, protecting users from a misbehaving extension exactly as you would from front-running.

Last updated

Was this helpful?