Skip to main content
Version: v0.18

PulseTrade

PulseTrade, also referred to as the order system, is a trading mechanism engineered to mitigate the price impact of high-volume trades. It achieves this by allowing users to submit long-term orders that execute incrementally, thereby reducing the trade's overall price impact. Furthermore, to enhance the user experience and facilitate flexible fund management, the system locks only the funds required for each step of the order. The remaining funds are locked on-demand as the order progresses.

A unique feature of the order system allows off-chain solvers to propose matches between orders. These matches can be executed instantaneously, providing orders with efficient execution without the need for long-term waiting. As compensation for their input, solvers receive a fee. Therefore, the order system serves as a potent tool for traders aiming to execute high-volume trades with minimal price impact and optimal efficiency.

Specifically, an order can be placed to sell a specified quantity of one token for another in incremental steps. This order executes against a particular pool or a path among several pools. The data structure of an order is as follows:

message Order {
uint64 id = 1;
string creator = 2;
uint64 pool_id = 3;
string token_in = 4;
string token_out = 5;
bool whitelisted_route = 6;
bool allow_matching = 7;
string amount_per_step = 8 [
(cosmos_proto.scalar) = "cosmos.Int",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false
];
string remaining_amount = 9 [
(cosmos_proto.scalar) = "cosmos.Int",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false
];
string deposited_amount = 10 [
(cosmos_proto.scalar) = "cosmos.Int",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false
];
int64 min_millis_interval = 11;
string max_step_spot_price = 12 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
string max_matching_spot_price = 13 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = true
];
}

The Order message is a protobuf message comprising the following fields:

  • id: auto-generated integer that serves as the order identifier.
  • creator: the account address of the order's creator.
  • pool_id: identifier of the pool to process the order.
  • token_in: type of token to sell.
  • token_out: type of token to buy.
  • whitelisted_route: a boolean value indicating whether the order should use a specific pool or a pre-approved route between the input and output tokens.
  • allow_matching: a boolean value indicating if solvers can match the order.
  • amount_per_step: amount of token to sell per step.
  • remaining_amount: the unprocessed tokens for sale, updated as the order is processed.
  • deposited_amount: amount of tokens deposited for processing in the next step.
  • min_millis_interval: integer representing the minimum time interval between steps in milliseconds.
  • max_step_spot_price: the maximum price for executing the order step-by-step. If the pool's computed price exceeds this limit, the order remains in the queue.
  • max_matching_spot_price: the highest price allowed for matching proposals.

Whitelisted Routes

The amm module supports both batch and single step swaps. Users may want to place multi-step orders. To ensure efficient execution, only a governance-approved set of multi-step orders is allowed. Governance can maintain a list of approved routes, each defining a multi-step route from one token to another. These routes are bidirectional. The structure of an approved route is as follows:

message WhitelistedRoute {
repeated RouteStep steps = 1 [(gogoproto.nullable) = false];
bool enabled = 2;
}

message RouteStep {
uint64 pool_id = 1;
string token_in = 2;
string token_out = 3;
}

Order Queues

The order system maintains two main queues to respect timing and price criteria:

  1. Execution Queue: These orders are ready for single-step execution. This implies:
    1. The next step's amountIn has been taken from the user and is in the amm vault.
    2. The order's min_millis_interval has elapsed since the last step execution.
    3. At each block's beginning, if the order's max_step_spot_price criteria is met, it can be executed for one step and moved to the scheduler queue.
    4. The key for this queue is composed of: whitelisted | poolId | tokenIn | tokenOut | maxPrice | orderId. Orders for the same pair are sorted by maxPrice.
  2. Schedule Queue: These orders have been executed for at least one step and will be added to the execution queue once the required time interval has passed since their last execution. These orders are stored with a key of form: time | orderId, meaning that orders are sorted by their next execution time, equal to o.MinMillisInterval+lastExecution. It is worth mentioning that the o.MinMillisInterval is moved to the nearest number in the range of minimum and maximum number allowed by order parameters.

When a user submits a new order, the first step of their order is immediately scheduled for execution. Therefore, we:

  1. Take the one-step amount from the user account and transfer it to our vault.
  2. Insert the step into the Execution Queue.

Execution at each block: To execute orders, we iterate through pairs in the system and first calculate the current spot price for each pair. We then query orders using a key equal to or larger than whitelisted | poolId | tokenIn | tokenOut | currentPrice. We improve performance and minimize price impact by initially offsetting orders in opposite directions, then executing the remaining orders against pools. After an order step is executed, it is added to the scheduler queue for its subsequent execution.

Block-by-Block Scheduling:

For each block, we first examine the scheduling queue to schedule trades. We select trades from the queue with a key equal to or larger than the current block time and add them to the execution queue. It's important to note that all orders in the scheduling queue have already been executed at least once; thus, their spot price is reasonable and closely aligned with the current spot price. This makes them likely candidates for execution.

Order Matching

To enhance user experience, we offer a zero price impact matching system that also eliminates the need for long-term execution. Users can enable order matching and set a price limit. It's important to distinguish that the step-by-step execution price limit is separate from the matching price limit. Users can set a higher max-price for matching since it provides instant execution without price impact.

A solver can examine orders enabled for matching and identify matching orders in the system. Since we don't lock the entire order amount upon submission, users may not have the necessary amount for a complete order execution. Therefore, solvers must ensure the proposed orders are executable. Keeping these considerations in mind, solvers can submit a list of match proposals by providing the following information for each pair:

message PairMatchProposal {
uint64 pool_id = 1;
bool whitelisted_route = 2;
string token_in = 3;
string token_out = 4;
repeated uint64 buy_orders = 5;
repeated uint64 sell_orders = 6;
}

Buy orders are for purchasing token_out, and sell orders are the reverse. When a list of matching orders is proposed, the AMM module attempts to offset these orders and fulfill as many as possible at the current pool-provided price. Note that not all orders will be completely executed after a match proposal; there may be some remaining for future execution through step execution or another match proposal.

Solvers receive a portion of the matched amount from both sides of the orders. This ratio is defined in the module parameters as MatchingSolverFeeRatio.