@orbs-network/twap
v2.2.9
Published
> Time-Weighted Average Price
Downloads
4,142
Maintainers
Keywords
Readme
TWAP
Time-Weighted Average Price
Security Audit Reports
Summary
This smart contract allows the incentivized execution of a TWAP order (either a Limit Order or a Market Order) on any DEX, with the possibility of partial fills. A TWAP order breaks a larger order down into smaller trades or "chunks", which are executed over a set period of time. This is a common strategy in traditional finance but it was not previously possible to execute such trades in a decentralized manner in DeFi systems.
In this smart contract, users (makers) create orders that wait in the contract to be filled. Once made, these orders enable an English Auction bidding war on each chunk at its time interval. Anyone willing to participate can serve as a “taker” by finding the best path to fill the order for the next chunk on any DEX, within the parameters set by the maker. Takers submit these paths as a bid to the contract, which selects the winner based on criteria described in detail below.
The winning taker receives a portion of the output tokens as a reward for their effort.
One honest taker (i.e., a taker who is willing to set the fee at the minimum amount needed to cover gas costs) is enough to ensure the entire system functions effectively at spot prices.
The contract is set to operate only up to the year 2106 (32bit timestamps), at which point it will no longer be usable.
The
TWAP
Smart Contract does not hold any funds, has no owners, administrators, or other roles and is entirely immutable once deployed on an EVM blockchain.
Use Cases
TWAP has several major benefits for users and trading platforms alike.
Price impact reduction
TWAP orders can limit the disproportionate price impact that often occurs when making large orders or long-tail low liquidity token pairs, especially on DEX/AMMs where liquidity is often fragmented among pools and prices are volatile.
Users should take into account the following considerations when using TWAP orders for this purpose:
- The gap between chunks should be sufficient to give arbitrageurs the time to close any price discrepancies that might occur in the affected pools after a trade and bring the reserves back to equilibrium (on par with spot price).
- Within this constraint, users should in most cases try to set the relevant time periods to be relatively short (i.e., a total duration of 5-30 minutes, with the interval between chunks of about 1-5 minutes). This allows the maker to enjoy maximum benefits, as keeping the order duration as short as possible minimizes the risk of market price volatility, while still allowing arbitrageurs sufficient time to close the gap, effectively increasing liquidity on the same pools.
- Additional consideration should be given to the amount of chunks, as executing trades for more chunks requires the user to expend more gas costs on the total order, which can add up to more than the benefit gained from spreading over time.
- Note that gas costs will eventually be rolled onto the
maker
, as takers will only be willing to bid if they can economically benefit from filling the transaction, which means that they will require a gas fee, at minimum. Users can therefore expect that, at minimum, a fee equal to the gas fee will be included by the taker in any bid.
However, the presence of one honest taker who is willing to take only reimbursement for their gas fees as payment is enough for the entire system to function as efficiently as possible. - A limit order (tight
dstMinAmount
) may also be only partially filled if price moves away while the order is in flight.
Long term DCA strategy
Dollar-cost averaging (DCA) is an investing strategy where the investor purchases an asset or set of assets having a certain dollar value on a specific schedule (i.e., on the first day of every month). TWAP trades can be used to generate an automated version of this strategy.
Users should take the following into account when using the TWAP contract for this purpose:
- By setting duration to be very long in the future (even years in advance), setting a high allowance and holding the entire amount of tokens, a maker can effectively implement an automated DCA bot that is resilient to price manipulations and requires no other action from the maker.
- Assuming there is one honest taker, setting a market order (near
0 dstMinAmount
) and a long fill delay will create a bidding war on the next chunk on a periodic basis (can be daily, weekly or monthly), while ensuring a price that is very close to spot market price. - The order will be visible on chain, and as bidding and execution can be predicted, some orders may be large enough to create an incentive for bidders to attempt to manipulate price across all markets just before it is executed. Due to this issue, utilizing TWAP for DCA is better reserved for large-cap "base" assets that are not easily susceptible to price manipulation of their entire market cap.
Architecture
Actors
maker
: User, the Order creator. In order to execute orders, is required to have an input token (srcToken
) balance and provide an approval to be swapped by theTWAP
contract on a specific (or any)exchange
.- Controls all Order restriction parameters such as limit price, expiration and the length of delay between chunks.
taker
: Incentivized independant participators that monitor Orders submitted by makers.- Takers try to find the best path for relevant chunks and submit bids for those chunks, including a
fee
for the taker. dstFee
: 0 or more of thedstToken
output (i.e., the token themaker
is purchasing) to be sent to thetaker
at the time the chunk is filled.- Spends the effort needed to find the best path, and risks being out-bid in the bidding war by another taker with a better path or lower fee.
- Takers try to find the best path for relevant chunks and submit bids for those chunks, including a
State diagram and execution flowchart
TWAP contract holds the order book, which is an array of all Orders
.
Before an Order can be filled, makers sign a transaction giving approval to the TWAP contract to remove the applicable input asset from their wallet.
Order is created by a maker
, who, after granting the approval, sends an ask
transaction to the TWAP contract containing certain requested parameters (specified below).
Takers monitor the Orders held by the contract, and can send bid transactions specifying the DEX on which it proposes to execute the next chunk, the output amount it can receive and the requested fee.
As a result of this process, an Order
is generated and held in the TWAP
contract, which contains the following parameters and constraints:
id
: the index in the order book, generatedstatus
: canceled, completed, or deadlinefilledTime
: last chunk filled timestampsrcFilledAmount
: total filled amount insrcToken
Ask
: holds the order parameters requested by the maker in theask
transactiontime
: order creation timestampdeadline
: order duration timestamp, requiredbidDelay
: minimum delay in seconds before a bid can be filled, must be>=MIN_BID_DELAY_SECONDS
fillDelay
: minimum delay in seconds between chunksmaker
: order creator (msg.sender
)exchange
: swap only on this exchange, or zero for any exchangesrcToken
: input token, requireddstToken
: output token, required. If zero address, will unwrap to native token on each chunk.srcAmount
: input total order amount insrcToken
, requiredsrcBidAmount
: input chunk size insrcToken
, requireddstMinAmount
: minimum output chunk size, indstToken
, required. Can be higher than market output (implies a limit order), or as low as 1 wei (implies a market order)
Bid
: empty until the firstbid
received. Once bids begin to be received, holds the current winning bid information:time
: bid creation timestamptaker
: the winning bidderexchange
: execute bid on this exchange, never zero on a valid biddstAmount
: output amount for this bid after fees indstToken
dstFee
: requested bytaker
for performing the bid and fill, indstToken
, may be0
data
: swap data passed to exchange, expected output =dstAmount
+dstFee
Once an order is created, it waits in the contract to be filled, when viable
The smart contract checks every bid transaction received to ensure that the following conditions are met:
- The order has not been previously cancelled, and the deadline has not passed.
- The maker granted the TWAP contract an
allowance
to swap thesrcToken
, with the approval covering an amount that is high enough to fill the next chunk. - The maker has a high enough
balance
of thesrcToken
to be swapped (for the next chunk). - The last chunk of the order was not filled too recently and there has been a sufficient delay between chunks.
- The
fillDelay
is set by the order maker, minimumMIN_FILL_DELAY_SECONDS
- The
- If the order maker specified that a particular
exchange
would be utilized for the order, only thatexchange
can be used to swap. If the value is set tozero
, any exchange can be used. - The current bid output after fees is higher than the previous winning bid.
- The current bid output after fees is higher or equal to the minimum set by the order maker.
- To avoid stale unfilled bids, a bid older than
MAX_BID_WINDOW_SECONDS
will be cleared and removed.
Any invalid constraint will revert the bid
transaction, so a successful transaction implies a win
A winning bid can be filled (executed) only after a minimum delay of a specific time period set by the maker.
This delay gives other bidders an opportunity to make a competing bid, allowing for a bidding war on the applicable chunk.
- Each succesfsul bid allows for another
MIN_BID_WINDOW_SECONDS
interval to challenge it. - If no other bid is set as the new winner, the current
taker
(winning bidder) can fill the bid by callingfill(id)
. - When receiving the
fill
transaction, theTWAP
contract performs the same verifications as when bidding. - Once verified, the
fill
transaction also utilizes the allowance previously provided by the maker to perform the actual swap on the requested exchange, resulting in the transfer of the input tokens from the maker to the exchange, and the transfer of the output tokens from the exchange back to the maker. - If
dstFee
is set>0
, the specified fee is paid out to the winning taker as part of the completion of the order, out of thedstToken
amount of that swap. - All of the above transfers are contained and set within the valid
fill
transaction.
This structure creates an incentive for takers to find a path that is advantageous for the bid, as a higher return of output tokens enables the fee to be as high as possible
But, the presence of one honest taker, who is willing to set the fee such that it takes only enough to reimburse it for the gas fees incurred, will ensure that the order will be filled to as close a price to the spot market as possible.
This is because this honest taker, by charging a lower fee, will propose a transaction containing the highest output to the maker and will therefore be selected as the winning bid when competing with those charging higher fees, all other things equal. Therefore, if the honest bidder is equally capable of locating the best path as any competing taker, the price received by the maker will be optimal.
Please note the following additional features of the TWAP contract:
- An order can be canceled any time by the maker.
- An order can be only partially filled, due to market price volatility (unfilled limit orders), or expiration.
- The maker must ensure that it has granted approval and has a sufficient balance of
srcToken
for each chunk before that chunk is available for bidding. It does not have to have the entire amount available and approved at the time of order creation. - Stale orders, as well as any orders that are invalid, can be
pruned
(canceled), by anyone.
Build Setup
- tests running on a specific block to ensure deterministic outcomes
- create free accounts on Alchemy (or similar archive node provider), etherscan and coinmarketcap
- create
.env
file with (or pass as environment variables):
NETWORK_URL_ETH="https://eth-mainnet.g.alchemy.com/v2/08***************************Kf"
NETWORK_URL_POLY="https://polygon-mainnet.g.alchemy.com/v2/a7***************************xN"
ETHERSCAN_ETH="VV***************************14"
ETHERSCAN_POLY="9H***************************WY"
COINMARKETCAP="81***************************36"
- workflow:
forge test