Documentation ¶
Index ¶
- Constants
- func DisableLog()
- func OrderToMsgOrder(ord order.Order, mkt string) (*msgjson.BookOrderNote, error)
- func UseLogger(logger slog.Logger)
- type AuthManager
- type Balancer
- type BookRouter
- type BookSource
- type Config
- type DEXBalancer
- type DataCollector
- type EpochQueue
- type Error
- type FeeFetcher
- type FeeSource
- type Market
- func (m *Market) AccountPending(acctAddr string, assetID uint32) (qty, lots uint64, redeems int)
- func (m *Market) Base() uint32
- func (m *Market) Book() (epoch int64, buys, sells []*order.LimitOrder)
- func (m *Market) Cancelable(oid order.OrderID) bool
- func (m *Market) CancelableBy(oid order.OrderID, aid account.AccountID) (bool, time.Time, error)
- func (m *Market) CheckUnfilled(assetID uint32, user account.AccountID) (unbooked []*order.LimitOrder)
- func (m *Market) CoinLocked(asset uint32, coin coinlock.CoinID) bool
- func (m *Market) EpochDuration() uint64
- func (m *Market) FeedDone(feed <-chan *updateSignal) bool
- func (m *Market) LotSize() uint64
- func (m *Market) MarketBuyBuffer() float64
- func (m *Market) MidGap() uint64
- func (m *Market) OrderFeed() <-chan *updateSignal
- func (m *Market) ParcelSize() uint32
- func (m *Market) Parcels(user account.AccountID, settlingQty uint64) float64
- func (m *Market) PurgeBook()
- func (m *Market) Quote() uint32
- func (m *Market) RateStep() uint64
- func (m *Market) ResumeEpoch(asSoonAs time.Time) (startEpochIdx int64)
- func (m *Market) Run(ctx context.Context)
- func (m *Market) Running() bool
- func (m *Market) ScaleFeeRate(assetID uint32, feeRate uint64) uint64
- func (m *Market) SetFeeRateScale(assetID uint32, scale float64)
- func (m *Market) SetStartEpochIdx(startEpochIdx int64)
- func (m *Market) Start(ctx context.Context, startEpochIdx int64)
- func (m *Market) Status() *Status
- func (m *Market) SubmitOrder(rec *orderRecord) error
- func (m *Market) SubmitOrderAsync(rec *orderRecord) <-chan error
- func (m *Market) Suspend(asSoonAs time.Time, persistBook bool) (finalEpochIdx int64, finalEpochEnd time.Time)
- func (m *Market) SuspendASAP(persistBook bool) (finalEpochIdx int64, finalEpochEnd time.Time)
- func (m *Market) SwapDone(ord order.Order, match *order.Match, fail bool)
- func (m *Market) Unbook(lo *order.LimitOrder) bool
- func (m *Market) UnbookUserOrders(user account.AccountID)
- type MarketParcelCalculator
- type MarketTunnel
- type MatchNegotiator
- type MatchSwapper
- type OrderRouter
- func (r *OrderRouter) CheckParcelLimit(user account.AccountID, targetMarketName string, ...) bool
- func (r *OrderRouter) Run(ctx context.Context)
- func (r *OrderRouter) Suspend(asSoonAs time.Time, persistBooks bool) map[string]*SuspendEpoch
- func (r *OrderRouter) SuspendMarket(mktName string, asSoonAs time.Time, persistBooks bool) *SuspendEpoch
- type OrderRouterConfig
- type PendingAccounter
- type Status
- type Storage
- type SuspendEpoch
- type Swapper
Constants ¶
const ( ErrMarketNotRunning = Error("market not running") ErrInvalidOrder = Error("order failed validation") ErrInvalidRate = Error("limit order rate too low") ErrInvalidCommitment = Error("order commitment invalid") ErrEpochMissed = Error("order unexpectedly missed its intended epoch") ErrDuplicateOrder = Error("order already in epoch") // maybe remove since this is ill defined ErrQuantityTooHigh = Error("order quantity exceeds user limit") ErrDuplicateCancelOrder = Error("equivalent cancel order already in epoch") ErrTooManyCancelOrders = Error("too many cancel orders in current epoch") ErrCancelNotPermitted = Error("cancel order account does not match targeted order account") ErrTargetNotActive = Error("target order not active on this market") ErrTargetNotCancelable = Error("targeted order is not a limit order with standing time-in-force") ErrSuspendedAccount = Error("suspended account") ErrMalformedOrderResponse = Error("malformed order response") ErrInternalServer = Error("internal server error") )
const ( // ZeroConfFeeRateThreshold is multiplied by the last known fee rate for an // asset to attain a minimum fee rate acceptable for zero-conf funding // coins. ZeroConfFeeRateThreshold = 0.9 )
Variables ¶
This section is empty.
Functions ¶
func DisableLog ¶
func DisableLog()
DisableLog disables all library log output. Logging output is disabled by default until UseLogger is called.
func OrderToMsgOrder ¶ added in v0.2.0
OrderToMsgOrder converts an order.Order into a *msgjson.BookOrderNote.
Types ¶
type AuthManager ¶
type AuthManager interface { Route(route string, handler func(account.AccountID, *msgjson.Message) *msgjson.Error) Auth(user account.AccountID, msg, sig []byte) error AcctStatus(user account.AccountID) (connected bool, tier int64) Sign(...msgjson.Signable) Send(account.AccountID, *msgjson.Message) error Request(account.AccountID, *msgjson.Message, func(comms.Link, *msgjson.Message)) error RequestWithTimeout(account.AccountID, *msgjson.Message, func(comms.Link, *msgjson.Message), time.Duration, func()) error PreimageSuccess(user account.AccountID, refTime time.Time, oid order.OrderID) MissedPreimage(user account.AccountID, refTime time.Time, oid order.OrderID) RecordCancel(user account.AccountID, oid, target order.OrderID, epochGap int32, t time.Time) RecordCompletedOrder(user account.AccountID, oid order.OrderID, t time.Time) UserReputation(user account.AccountID) (tier int64, score, maxScore int32, err error) }
The AuthManager handles client-related actions, including authorization and communications.
type Balancer ¶ added in v0.5.0
type Balancer interface { // CheckBalance checks that the address's account has sufficient balance to // trade the outgoing number of lots (totaling qty) and incoming number of // redeems. CheckBalance(acctAddr string, assetID, redeemAssetID uint32, qty, lots uint64, redeems int) bool }
Balancer provides a method to check that an account on an account-based asset has sufficient balance.
type BookRouter ¶
type BookRouter struct {
// contains filtered or unexported fields
}
BookRouter handles order book subscriptions, syncing the market with a group of subscribers, and maintaining an intermediate copy of the orderbook in message payload format for quick, full-book syncing.
func NewBookRouter ¶
func NewBookRouter(sources map[string]BookSource, feeSource FeeSource, route func(route string, handler comms.MsgHandler)) *BookRouter
NewBookRouter is a constructor for a BookRouter. Routes are registered with comms and a monitoring goroutine is started for each BookSource specified. The input sources is a mapping of market names to sources for order and epoch queue information.
func (*BookRouter) Book ¶ added in v0.2.0
func (r *BookRouter) Book(mktName string) (*msgjson.OrderBook, error)
Book creates a copy of the book as a *msgjson.OrderBook.
func (*BookRouter) Run ¶
func (r *BookRouter) Run(ctx context.Context)
Run implements dex.Runner, and is blocking.
type BookSource ¶
type BookSource interface { Book() (epoch int64, buys []*order.LimitOrder, sells []*order.LimitOrder) OrderFeed() <-chan *updateSignal Base() uint32 Quote() uint32 }
BookSource is a source of a market's order book and a feed of updates to the order book and epoch queue.
type Config ¶ added in v0.2.0
type Config struct { MarketInfo *dex.MarketInfo Storage Storage Swapper Swapper AuthManager AuthManager FeeFetcherBase FeeFetcher CoinLockerBase coinlock.CoinLocker FeeFetcherQuote FeeFetcher CoinLockerQuote coinlock.CoinLocker DataCollector DataCollector Balancer Balancer CheckParcelLimit func(user account.AccountID, calcParcels MarketParcelCalculator) bool MinimumRate uint64 }
Config is the Market configuration.
type DEXBalancer ¶ added in v0.5.0
type DEXBalancer struct {
// contains filtered or unexported fields
}
BackedBalancer is an asset manager that is capable of querying the entire DEX for the balance required to fulfill new + existing orders and outstanding redemptions.
func NewDEXBalancer ¶ added in v0.5.0
func NewDEXBalancer(tunnels map[string]PendingAccounter, assets map[uint32]*asset.BackedAsset, matchNegotiator MatchNegotiator) (*DEXBalancer, error)
NewDEXBalancer is a constructor for a DEXBalancer. Provided assets will be filtered for those that are account-based. The matchNegotiator is satisfied by the *Swapper.
func (*DEXBalancer) CheckBalance ¶ added in v0.5.0
func (b *DEXBalancer) CheckBalance(acctAddr string, assetID, redeemAssetID uint32, qty, lots uint64, redeems int) bool
CheckBalance checks if there is sufficient balance to support the specified new funding and redemptions, given the existing orders throughout DEX that fund from or redeem to the specified account address for the account-based asset. It is an internally logged error to call CheckBalance for a non-account-based asset or an asset that was not provided to the constructor. Because these assets may have a base chain as well as degenerate tokens, we need to consider outstanding orders and matches across all "fee family" assets. It is acceptable to call CheckBalance with qty = 0, lots = 0, redeems > 0 and assetID = redeemAssetID, as might be the case when checking that a user has sufficient balance to redeem an order's matches.
type DataCollector ¶ added in v0.2.0
type EpochQueue ¶
type EpochQueue struct { // Epoch is the epoch index. Epoch int64 Duration int64 // Start and End define the time range of the epoch as [Start,End). Start, End time.Time // Orders holds the epoch queue orders in a map for quick lookups. Orders map[order.OrderID]order.Order // UserCancels counts the number of cancel orders per user. UserCancels map[account.AccountID]uint32 // CancelTargets maps known targeted order IDs with the CancelOrder CancelTargets map[order.OrderID]*order.CancelOrder }
EpochQueue represents an epoch order queue. The methods are NOT thread safe by design.
func NewEpoch ¶
func NewEpoch(idx int64, duration int64) *EpochQueue
NewEpoch creates an epoch with the given index and duration in milliseconds.
func (*EpochQueue) IncludesTime ¶
func (eq *EpochQueue) IncludesTime(t time.Time) bool
IncludesTime checks if the given time falls in the epoch.
func (*EpochQueue) Insert ¶
func (eq *EpochQueue) Insert(ord order.Order)
Stores an order in the Order slice, overwriting and pre-existing order.
func (*EpochQueue) OrderSlice ¶
func (eq *EpochQueue) OrderSlice() []order.Order
OrderSlice extracts the orders in a slice. The slice ordering is random.
type FeeFetcher ¶ added in v0.2.0
type FeeFetcher interface { FeeRate(context.Context) uint64 SwapFeeRate(context.Context) uint64 LastRate() uint64 MaxFeeRate() uint64 }
FeeFetcher is a fee fetcher for fetching fees. Fees are fickle, so fetch fees with FeeFetcher fairly frequently.
type FeeSource ¶ added in v0.2.0
FeeSource is a source of the last reported tx fee rate estimate for an asset.
type Market ¶
type Market struct {
// contains filtered or unexported fields
}
Market is the market manager. It should not be overly involved with details of accounts and authentication. Via the account package it should request account status with new orders, verification of order signatures. The Market should also perform various account package callbacks such as order status updates so that the account package code can keep various data up-to-date, including order status, history, cancellation statistics, etc.
The Market performs the following:
- Receive and validate new order data (amounts vs. lot size, check fees, utxos, sufficient market buy buffer, etc.).
- Put incoming orders into the current epoch queue.
- Maintain an order book, which must also implement matcher.Booker.
- Initiate order matching with matcher.Match(book, currentQueue)
- During and/or after matching: * update the book (remove orders, add new standing orders, etc.) * retire/archive the epoch queue * publish the matches (and order book changes?) * initiate swaps for each match (possibly groups of related matches)
- Cycle the epochs.
- Record all events with the archivist.
func NewMarket ¶
NewMarket creates a new Market for the provided base and quote assets, with an epoch cycling at given duration in milliseconds.
func (*Market) AccountPending ¶ added in v0.5.0
AccountPending sums the orders quantities that pay to or from the specified account address.
func (*Market) Book ¶
func (m *Market) Book() (epoch int64, buys, sells []*order.LimitOrder)
Book retrieves the market's current order book and the current epoch index. If the Market is not yet running or the start epoch has not yet begun, the epoch index will be zero.
func (*Market) Cancelable ¶
Cancelable determines if an order is a limit order with time-in-force standing that is in either the epoch queue or in the order book.
func (*Market) CancelableBy ¶
CancelableBy determines if an order is cancelable by a certain account. This means: (1) an order in the book or epoch queue, (2) type limit with time-in-force standing (implied for book orders), and (3) AccountID field matching the provided account ID.
func (*Market) CheckUnfilled ¶
func (m *Market) CheckUnfilled(assetID uint32, user account.AccountID) (unbooked []*order.LimitOrder)
CheckUnfilled checks unfilled book orders belonging to a user and funded by coins for a given asset to ensure that their funding coins are not spent. If any of an order's funding coins are spent, the order is unbooked (removed from the in-memory book, revoked in the DB, a cancellation marked against the user, coins unlocked, and orderbook subscribers notified). See Unbook for details.
func (*Market) CoinLocked ¶
CoinLocked checks if a coin is locked. The asset is specified since we should not assume that a CoinID for one asset cannot be made to match another asset's CoinID.
func (*Market) EpochDuration ¶
EpochDuration returns the Market's epoch duration in milliseconds.
func (*Market) FeedDone ¶
FeedDone informs the market that the caller is finished receiving from the given channel, which should have been obtained from OrderFeed. If the channel was a registered order feed channel from OrderFeed, it is closed and removed so that no further signals will be send on the channel.
func (*Market) LotSize ¶ added in v0.4.0
LotSize returns the market's lot size in units of the base asset.
func (*Market) MarketBuyBuffer ¶
MarketBuyBuffer returns the Market's market-buy buffer.
func (*Market) MidGap ¶
MidGap returns the mid-gap market rate, which is ths rate halfway between the best buy order and the best sell order in the order book. If one side has no orders, the best order rate on other side is returned. If both sides have no orders, 0 is returned.
func (*Market) OrderFeed ¶
func (m *Market) OrderFeed() <-chan *updateSignal
OrderFeed provides a new order book update channel. Channels provided before the market starts and while a market is running are both valid. When the market stops, channels are closed (invalidated), and new channels should be requested if the market starts again.
func (*Market) ParcelSize ¶ added in v1.0.0
ParcelSize returns market's configured parcel size.
func (*Market) Parcels ¶ added in v1.0.0
Parcels calculates the total parcels for the market with the specified settling quantity. Parcels is used as part of order validation for global parcel limits. Parcels is not called for the market for which the order is for, which will use m.checkParcelLimit to validate in processOrder.
func (*Market) PurgeBook ¶
func (m *Market) PurgeBook()
PurgeBook flushes all booked orders from the in-memory book and persistent storage. In terms of storage, this means changing orders with status booked to status revoked.
func (*Market) RateStep ¶ added in v0.4.0
RateStep returns the market's rate step in units of the quote asset.
func (*Market) ResumeEpoch ¶
ResumeEpoch gets the next available resume epoch index for the currently configured epoch duration for the market and the provided earliest allowable start time. The market must be running, otherwise the zero index is returned.
func (*Market) Run ¶
Run is the main order processing loop, which takes new orders, notifies book subscribers, and cycles the epochs. The caller should cancel the provided Context to stop the market. The outgoing order feed channels persist after Run returns for possible Market resume, and for Swapper's unbook callback to function using sendToFeeds.
func (*Market) Running ¶
Running indicates is the market is accepting new orders. This will return false when suspended, but false does not necessarily mean Run has stopped since a start epoch may be set. Note that this method is of limited use and communicating subsystems shouldn't rely on the result for correct operation since a market could start or stop. Rather, they should infer or be informed of market status rather than rely on this.
TODO: Instead of using Running in OrderRouter and DEX, these types should track statuses (known suspend times).
func (*Market) ScaleFeeRate ¶ added in v0.2.0
ScaleFeeRate scales the provided fee rate with the given asset's swap fee rate scale factor, which is 1.0 by default.
func (*Market) SetFeeRateScale ¶ added in v0.2.0
SetFeeRateScale sets a swap fee scale factor for the given asset. SetFeeRateScale should be called regardless of whether the Market is suspended.
func (*Market) SetStartEpochIdx ¶
SetStartEpochIdx sets the starting epoch index. This should generally be called before Run, or Start used to specify the index at the same time.
func (*Market) Start ¶
Start begins order processing with a starting epoch index. See also SetStartEpochIdx and Run. Stop the Market by cancelling the context.
func (*Market) SubmitOrder ¶
SubmitOrder submits a new order for inclusion into the current epoch. This is the synchronous version of SubmitOrderAsync.
func (*Market) SubmitOrderAsync ¶
SubmitOrderAsync submits a new order for inclusion into the current epoch. When submission is completed, an error value will be sent on the channel. This is the asynchronous version of SubmitOrder.
func (*Market) Suspend ¶
func (m *Market) Suspend(asSoonAs time.Time, persistBook bool) (finalEpochIdx int64, finalEpochEnd time.Time)
Suspend requests the market to gracefully suspend epoch cycling as soon as the given time, always allowing the epoch including that time to complete. If the time is before the current epoch, the current epoch will be the last.
func (*Market) SuspendASAP ¶
SuspendASAP suspends requests the market to gracefully suspend epoch cycling as soon as possible, always allowing an active epoch to close. See also Suspend.
func (*Market) SwapDone ¶ added in v0.2.0
SwapDone registers a match for a given order as being finished. Whether the match was a successful or failed swap is indicated by fail. This is used to (1) register completed orders for cancellation rate purposes, and (2) to unbook at-fault limit orders.
Implementation note: Orders that have failed a swap or were canceled (see processReadyEpoch) are removed from the settling map regardless of any amount still setting for such orders.
func (*Market) Unbook ¶
func (m *Market) Unbook(lo *order.LimitOrder) bool
Unbook allows the DEX manager to remove a booked order. This does: (1) remove the order from the in-memory book, (2) unlock funding order coins, (3) set the order's status in the DB to "revoked", (4) inform the auth manager of the action for cancellation ratio accounting, and (5) send an 'unbook' notification to subscribers of this market's order book. Note that this presently treats the user as at-fault by counting the revocation in the user's cancellation statistics.
func (*Market) UnbookUserOrders ¶
UnbookUserOrders unbooks all orders belonging to a user, unlocks the coins that were used to fund the unbooked orders, changes the orders' statuses to revoked in the DB, and notifies orderbook subscribers.
type MarketParcelCalculator ¶ added in v1.0.0
type MarketTunnel ¶
type MarketTunnel interface { // SubmitOrder submits the order to the market for insertion into the epoch // queue. SubmitOrder(*orderRecord) error // MidGap returns the mid-gap market rate, which is ths rate halfway between // the best buy order and the best sell order in the order book. MidGap() uint64 // MarketBuyBuffer is a coefficient that when multiplied by the market's lot // size specifies the minimum required amount for a market buy order. MarketBuyBuffer() float64 // LotSize is the market's lot size in units of the base asset. LotSize() uint64 // RateStep is the market's rate step in units of the quote asset. RateStep() uint64 // CoinLocked should return true if the CoinID is currently a funding Coin // for an active DEX order. This is required for Coin validation to prevent // a user from submitting multiple orders spending the same Coin. This // method will likely need to check all orders currently in the epoch queue, // the order book, and the swap monitor, since UTXOs will still be unspent // according to the asset backends until the client broadcasts their // initialization transaction. // // DRAFT NOTE: This function could also potentially be handled by persistent // storage, since active orders and active matches are tracked there. CoinLocked(assetID uint32, coinID order.CoinID) bool // Cancelable determines whether an order is cancelable. A cancelable order // is a limit order with time-in-force standing either in the epoch queue or // in the order book. Cancelable(order.OrderID) bool // Suspend suspends the market as soon as a given time, returning the final // epoch index and and time at which that epoch closes. Suspend(asSoonAs time.Time, persistBook bool) (finalEpochIdx int64, finalEpochEnd time.Time) // Running indicates is the market is accepting new orders. This will return // false when suspended, but false does not necessarily mean Run has stopped // since a start epoch may be set. Running() bool // CheckUnfilled checks a user's unfilled book orders that are funded by // coins for a given asset to ensure that their funding coins are not spent. // If any of an unfilled order's funding coins are spent, the order is // unbooked (removed from the in-memory book, revoked in the DB, a // cancellation marked against the user, coins unlocked, and orderbook // subscribers notified). See Unbook for details. CheckUnfilled(assetID uint32, user account.AccountID) (unbooked []*order.LimitOrder) // Parcels calculates the number of active parcels for the market. Parcels(user account.AccountID, settlingQty uint64) float64 }
MarketTunnel is a connection to a market.
type MatchNegotiator ¶ added in v0.5.0
type MatchNegotiator interface { // AccountStats collects stats about pending matches for account's address // on an account-based asset. qty is the total pending outgoing quantity, // swaps is the number matches with outstanding swaps funded by the account, // and redeem is the number of matches with outstanding redemptions that pay // to the account. AccountStats(acctAddr string, assetID uint32) (qty, swaps uint64, redeems int) }
MatchNegotiator can view match-reserved funds for an account-based asset's address. MatchNegotiator is satisfied by *Swapper.
type MatchSwapper ¶ added in v1.0.0
MatchSwapper is a source for information about settling matches.
type OrderRouter ¶
type OrderRouter struct {
// contains filtered or unexported fields
}
OrderRouter handles the 'limit', 'market', and 'cancel' DEX routes. These are authenticated routes used for placing and canceling orders.
func NewOrderRouter ¶
func NewOrderRouter(cfg *OrderRouterConfig) *OrderRouter
NewOrderRouter is a constructor for an OrderRouter.
func (*OrderRouter) CheckParcelLimit ¶ added in v1.0.0
func (r *OrderRouter) CheckParcelLimit(user account.AccountID, targetMarketName string, calcParcels MarketParcelCalculator) bool
CheckParcelLimit checks that the user does not exceed their parcel limit. The calcParcels function must be provided by the order's targeted Market, and calculate the number of parcels from that market when quantity from settling matches is taken into consideration. CheckParcelLimit checks the global parcel limit, based on the users tier and score and active orders for ALL markets.
func (*OrderRouter) Run ¶
func (r *OrderRouter) Run(ctx context.Context)
func (*OrderRouter) Suspend ¶
func (r *OrderRouter) Suspend(asSoonAs time.Time, persistBooks bool) map[string]*SuspendEpoch
Suspend is like SuspendMarket, but for all known markets.
func (*OrderRouter) SuspendMarket ¶
func (r *OrderRouter) SuspendMarket(mktName string, asSoonAs time.Time, persistBooks bool) *SuspendEpoch
SuspendMarket schedules a suspension of a given market, with the option to persist the orders on the book (or purge the book automatically on market shutdown). The scheduled final epoch and suspend time are returned. Note that OrderRouter is a proxy for this request to the ultimate Market. This is done because OrderRouter is the entry point for new orders into the market. TODO: track running, suspended, and scheduled-suspended markets, appropriately blocking order submission according to the schedule rather than just checking Market.Running prior to submitting incoming orders to the Market.
type OrderRouterConfig ¶
type OrderRouterConfig struct { AuthManager AuthManager Assets map[uint32]*asset.BackedAsset Markets map[string]MarketTunnel FeeSource FeeSource DEXBalancer *DEXBalancer MatchSwapper MatchSwapper }
OrderRouterConfig is the configuration settings for an OrderRouter.
type PendingAccounter ¶ added in v0.5.0
type PendingAccounter interface { // AccountPending retrieves the total pending order-reserved quantity for // the asset, as well as the number of possible pending redemptions // (a.k.a. ordered lots). AccountPending(acctAddr string, assetID uint32) (qty, lots uint64, redeems int) // Base is the base asset ID. Base() uint32 // Quote is the quote asset ID. Quote() uint32 }
PendingAccounter can view order-reserved funds for an account-based asset's address. PendingAccounter is satisfied by *Market.
type Status ¶
type Status struct { Running bool EpochDuration uint64 // to compute times from epoch inds ActiveEpoch int64 StartEpoch int64 SuspendEpoch int64 PersistBook bool Base, Quote uint32 }
Status describes the operation state of the Market.
type Storage ¶ added in v0.2.0
type Storage interface { db.OrderArchiver LastErr() error Fatal() <-chan struct{} Close() error InsertEpoch(ed *db.EpochResults) error LastEpochRate(base, quote uint32) (uint64, error) MarketMatches(base, quote uint32) ([]*db.MatchDataWithCoins, error) InsertMatch(match *order.Match) error }
Storage is the DB interface required by Market.
type SuspendEpoch ¶
SuspendEpoch holds the index and end time of final epoch marking the suspension of a market.