decred.org/dcrdex@v1.0.5/dex/msgjson/types.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  package msgjson
     5  
     6  import (
     7  	"encoding/binary"
     8  	"encoding/json"
     9  	"fmt"
    10  	"time"
    11  
    12  	"decred.org/dcrdex/dex"
    13  	"decred.org/dcrdex/server/account"
    14  )
    15  
    16  // Error codes
    17  const (
    18  	RPCErrorUnspecified           = iota // 0
    19  	RPCParseError                        // 1
    20  	RPCUnknownRoute                      // 2
    21  	RPCInternal                          // 3
    22  	RPCQuarantineClient                  // 4
    23  	RPCVersionUnsupported                // 5
    24  	RPCUnknownMatch                      // 6
    25  	RPCInternalError                     // 7
    26  	RPCInitError                         // 8
    27  	RPCLoginError                        // 9
    28  	RPCLogoutError                       // 10
    29  	RPCCreateWalletError                 // 11
    30  	RPCOpenWalletError                   // 12
    31  	RPCWalletExistsError                 // 13
    32  	RPCCloseWalletError                  // 14
    33  	RPCGetFeeError                       // 15, obsolete, kept for order
    34  	RPCRegisterError                     // 16
    35  	RPCArgumentsError                    // 17
    36  	RPCTradeError                        // 18
    37  	RPCCancelError                       // 19
    38  	RPCFundTransferError                 // 20
    39  	RPCOrderBookError                    // 21
    40  	SignatureError                       // 22
    41  	SerializationError                   // 23
    42  	TransactionUndiscovered              // 24
    43  	ContractError                        // 25
    44  	SettlementSequenceError              // 26
    45  	ResultLengthError                    // 27
    46  	IDMismatchError                      // 28
    47  	RedemptionError                      // 29
    48  	IDTypeError                          // 30
    49  	AckCountError                        // 31
    50  	UnknownResponseID                    // 32
    51  	OrderParameterError                  // 33
    52  	UnknownMarketError                   // 34
    53  	ClockRangeError                      // 35
    54  	FundingError                         // 36
    55  	CoinAuthError                        // 37
    56  	UnknownMarket                        // 38
    57  	NotSubscribedError                   // 39
    58  	UnauthorizedConnection               // 40
    59  	AuthenticationError                  // 41
    60  	PubKeyParseError                     // 42
    61  	FeeError                             // 43
    62  	InvalidPreimage                      // 44
    63  	PreimageCommitmentMismatch           // 45
    64  	UnknownMessageType                   // 46
    65  	AccountClosedError                   // 47
    66  	MarketNotRunningError                // 48
    67  	TryAgainLaterError                   // 49
    68  	AccountNotFoundError                 // 50
    69  	UnpaidAccountError                   // 51
    70  	InvalidRequestError                  // 52
    71  	OrderQuantityTooHigh                 // 53
    72  	HTTPRouteError                       // 54
    73  	RouteUnavailableError                // 55
    74  	AccountExistsError                   // 56
    75  	AccountSuspendedError                // 57 deprecated, kept for order
    76  	RPCExportSeedError                   // 58
    77  	TooManyRequestsError                 // 59
    78  	RPCGetDEXConfigError                 // 60
    79  	RPCDiscoverAcctError                 // 61
    80  	RPCWalletRescanError                 // 62
    81  	RPCDeleteArchivedRecordsError        // 63
    82  	DuplicateRequestError                // 64
    83  	RPCToggleWalletStatusError           // 65
    84  	BondError                            // 66
    85  	BondAlreadyConfirmingError           // 67
    86  	RPCWalletPeersError                  // 68
    87  	RPCNotificationsError                // 69
    88  	RPCPostBondError                     // 70
    89  	RPCWalletDefinitionError             // 71
    90  	RPCStartMarketMakingError            // 72
    91  	RPCStopMarketMakingError             // 73
    92  	RPCSetVSPError                       // 74
    93  	RPCPurchaseTicketsError              // 75
    94  	RPCStakeStatusError                  // 76
    95  	RPCSetVotingPreferencesError         // 77
    96  	RPCTxHistoryError                    // 78
    97  	RPCMMAvailableBalancesError          // 79
    98  	RPCUpdateRunningBotCfgError          // 80
    99  	RPCUpdateRunningBotInvError          // 81
   100  	RPCMMStatusError                     // 82
   101  )
   102  
   103  // Routes are destinations for a "payload" of data. The type of data being
   104  // delivered, and what kind of action is expected from the receiving party, is
   105  // completely dependent on the route. The route designation is a string sent as
   106  // the "route" parameter of a JSON-encoded Message.
   107  const (
   108  	// MatchRoute is the route of a DEX-originating request-type message notifying
   109  	// the client of a match and initiating swap negotiation.
   110  	MatchRoute = "match"
   111  	// NoMatchRoute is the route of a DEX-originating notification-type message
   112  	// notifying the client that an order did not match during its match cycle.
   113  	NoMatchRoute = "nomatch"
   114  	// MatchStatusRoute is the route of a client-originating request-type
   115  	// message to retrieve match data from the DEX.
   116  	MatchStatusRoute = "match_status"
   117  	// OrderStatusRoute is the route of a client-originating request-type
   118  	// message to retrieve order data from the DEX.
   119  	OrderStatusRoute = "order_status"
   120  	// InitRoute is the route of a client-originating request-type message
   121  	// notifying the DEX, and subsequently the match counter-party, of the details
   122  	// of a swap contract.
   123  	InitRoute = "init"
   124  	// AuditRoute is the route of a DEX-originating request-type message relaying
   125  	// swap contract details (from InitRoute) from one client to the other.
   126  	AuditRoute = "audit"
   127  	// RedeemRoute is the route of a client-originating request-type message
   128  	// notifying the DEX, and subsequently the match counter-party, of the details
   129  	// of a redemption transaction.
   130  	RedeemRoute = "redeem"
   131  	// RedemptionRoute is the route of a DEX-originating request-type message
   132  	// relaying redemption transaction (from RedeemRoute) details from one client
   133  	// to the other.
   134  	RedemptionRoute = "redemption"
   135  	// RevokeMatchRoute is a DEX-originating notification-type message informing
   136  	// a client that a match has been revoked.
   137  	RevokeMatchRoute = "revoke_match"
   138  	// RevokeOrderRoute is a DEX-originating notification-type message informing
   139  	// a client that an order has been revoked.
   140  	RevokeOrderRoute = "revoke_order"
   141  	// LimitRoute is the client-originating request-type message placing a limit
   142  	// order.
   143  	LimitRoute = "limit"
   144  	// MarketRoute is the client-originating request-type message placing a market
   145  	// order.
   146  	MarketRoute = "market"
   147  	// CancelRoute is the client-originating request-type message placing a cancel
   148  	// order.
   149  	CancelRoute = "cancel"
   150  	// OrderBookRoute is the client-originating request-type message subscribing
   151  	// to an order book update notification feed.
   152  	OrderBookRoute = "orderbook"
   153  	// UnsubOrderBookRoute is client-originating request-type message cancelling
   154  	// an order book subscription.
   155  	UnsubOrderBookRoute = "unsub_orderbook"
   156  	// BookOrderRoute is the DEX-originating notification-type message informing
   157  	// the client to add the order to the order book.
   158  	BookOrderRoute = "book_order"
   159  	// UnbookOrderRoute is the DEX-originating notification-type message informing
   160  	// the client to remove an order from the order book.
   161  	UnbookOrderRoute = "unbook_order"
   162  	// EpochOrderRoute is the DEX-originating notification-type message informing
   163  	// the client about an order added to the epoch queue.
   164  	EpochOrderRoute = "epoch_order"
   165  	// UpdateRemainingRoute is the DEX-originating notification-type message that
   166  	// updates the remaining amount of unfilled quantity on a standing limit order.
   167  	UpdateRemainingRoute = "update_remaining"
   168  	// EpochReportRoute is the DEX-originating notification-type message that
   169  	// indicates the end of an epoch's book updates and provides stats for
   170  	// maintaining a candlestick cache.
   171  	EpochReportRoute = "epoch_report"
   172  	// ConnectRoute is a client-originating request-type message seeking
   173  	// authentication so that the connection can be used for trading.
   174  	ConnectRoute = "connect"
   175  	// PostBondRoute is the client-originating request used to post a new
   176  	// fidelity bond. This can create a new account or it can add bond to an
   177  	// existing account.
   178  	PostBondRoute = "postbond"
   179  	// PreValidateBondRoute is the client-originating request used to
   180  	// pre-validate a fidelity bond transaction before broadcasting it (and
   181  	// locking funds for months).
   182  	PreValidateBondRoute = "prevalidatebond"
   183  	// BondExpiredRoute is a server-originating notification when a bond expires
   184  	// according to the configure bond expiry duration and the bond's lock time.
   185  	BondExpiredRoute = "bondexpired"
   186  	// TierChangeRoute is a server-originating notification sent to a connected
   187  	// user whose tier changes for any reason.
   188  	TierChangeRoute = "tierchange" // (TODO: use in many auth mgr events)
   189  	// ScoreChangeRoute is a server-originating notificdation sent to a
   190  	// connected user when their score changes.
   191  	ScoreChangeRoute = "scorechanged"
   192  	// ConfigRoute is the client-originating request-type message requesting the
   193  	// DEX configuration information.
   194  	ConfigRoute = "config"
   195  	// HealthRoute is the client-originating request-type message requesting the
   196  	// DEX's health status.
   197  	HealthRoute = "healthy"
   198  	// MatchProofRoute is the DEX-originating notification-type message
   199  	// delivering match cycle results to the client.
   200  	MatchProofRoute = "match_proof"
   201  	// PreimageRoute is the DEX-originating request-type message requesting the
   202  	// preimages for the client's epoch orders.
   203  	PreimageRoute = "preimage"
   204  	// SuspensionRoute is the DEX-originating request-type message informing the
   205  	// client of an upcoming trade suspension. This is part of the
   206  	// subscription-based orderbook notification feed.
   207  	SuspensionRoute = "suspension"
   208  	// ResumptionRoute is the DEX-originating request-type message informing the
   209  	// client of an upcoming trade resumption. This is part of the
   210  	// subscription-based orderbook notification feed.
   211  	ResumptionRoute = "resumption"
   212  	// NotifyRoute is the DEX-originating notification-type message
   213  	// delivering text messages from the operator.
   214  	NotifyRoute = "notify"
   215  	// PenaltyRoute is the DEX-originating notification-type message
   216  	// informing of a broken rule and the resulting penalty.
   217  	PenaltyRoute = "penalty"
   218  	// SpotsRoute is the client-originating HTTP or WebSocket request to get the
   219  	// spot price and volume for the DEX's markets.
   220  	SpotsRoute = "spots"
   221  	// FeeRateRoute is the client-originating request asking for the most
   222  	// recently recorded transaction fee estimate for an asset.
   223  	FeeRateRoute = "fee_rate"
   224  	// PriceFeedRoute is the client-originating request subscribing to the
   225  	// market overview feed.
   226  	PriceFeedRoute = "price_feed"
   227  	// PriceUpdateRoute is a dex-originating notification updating the current
   228  	// spot price for a market.
   229  	PriceUpdateRoute = "price_update"
   230  	// CandlesRoute is the HTTP request to get the set of candlesticks
   231  	// representing market activity history.
   232  	CandlesRoute = "candles"
   233  )
   234  
   235  const errNullRespPayload = dex.ErrorKind("null response payload")
   236  
   237  type Bytes = dex.Bytes
   238  
   239  // Signable allows for serialization and signing.
   240  type Signable interface {
   241  	Serialize() []byte
   242  	SetSig([]byte)
   243  	SigBytes() []byte
   244  }
   245  
   246  // Signature partially implements Signable, and can be embedded by types intended
   247  // to satisfy Signable, which must themselves implement the Serialize method.
   248  type Signature struct {
   249  	Sig Bytes `json:"sig"`
   250  }
   251  
   252  // SetSig sets the Sig field.
   253  func (s *Signature) SetSig(b []byte) {
   254  	s.Sig = b
   255  }
   256  
   257  // SigBytes returns the signature as a []byte.
   258  func (s *Signature) SigBytes() []byte {
   259  	return s.Sig
   260  }
   261  
   262  // Stampable is an interface that supports timestamping and signing.
   263  type Stampable interface {
   264  	Signable
   265  	Stamp(serverTime uint64)
   266  }
   267  
   268  // Acknowledgement is the 'result' field in a response to a request that
   269  // requires an acknowledgement. It is typically a signature of some serialized
   270  // data associated with the request.
   271  type Acknowledgement struct {
   272  	MatchID Bytes `json:"matchid"`
   273  	Sig     Bytes `json:"sig"`
   274  }
   275  
   276  // Error is returned as part of the Response to indicate that an error
   277  // occurred during method execution.
   278  type Error struct {
   279  	Code    int    `json:"code"`
   280  	Message string `json:"message"`
   281  }
   282  
   283  // Error returns the error message. Satisfies the error interface.
   284  func (e *Error) Error() string {
   285  	return e.String()
   286  }
   287  
   288  // String satisfies the Stringer interface for pretty printing.
   289  func (e Error) String() string {
   290  	return fmt.Sprintf("error code %d: %s", e.Code, e.Message)
   291  }
   292  
   293  // NewError is a constructor for an Error.
   294  func NewError(code int, format string, a ...any) *Error {
   295  	return &Error{
   296  		Code:    code,
   297  		Message: fmt.Sprintf(format, a...),
   298  	}
   299  }
   300  
   301  // ResponsePayload is the payload for a Response-type Message.
   302  type ResponsePayload struct {
   303  	// Result is the payload, if successful, else nil.
   304  	Result json.RawMessage `json:"result,omitempty"`
   305  	// Error is the error, or nil if none was encountered.
   306  	Error *Error `json:"error,omitempty"`
   307  }
   308  
   309  // MessageType indicates the type of message. MessageType is typically the first
   310  // switch checked when examining a message, and how the rest of the message is
   311  // decoded depends on its MessageType.
   312  type MessageType uint8
   313  
   314  // There are presently three recognized message types: request, response, and
   315  // notification.
   316  const (
   317  	InvalidMessageType MessageType = iota // 0
   318  	Request                               // 1
   319  	Response                              // 2
   320  	Notification                          // 3
   321  )
   322  
   323  // String satisfies the Stringer interface for translating the MessageType code
   324  // into a description, primarily for logging.
   325  func (mt MessageType) String() string {
   326  	switch mt {
   327  	case Request:
   328  		return "request"
   329  	case Response:
   330  		return "response"
   331  	case Notification:
   332  		return "notification"
   333  	default:
   334  		return "unknown MessageType"
   335  	}
   336  }
   337  
   338  // Message is the primary messaging type for websocket communications.
   339  type Message struct {
   340  	// Type is the message type.
   341  	Type MessageType `json:"type"`
   342  	// Route is used for requests and notifications, and specifies a handler for
   343  	// the message.
   344  	Route string `json:"route,omitempty"`
   345  	// ID is a unique number that is used to link a response to a request.
   346  	ID uint64 `json:"id,omitempty"`
   347  	// Payload is any data attached to the message. How Payload is decoded
   348  	// depends on the Route.
   349  	Payload json.RawMessage `json:"payload,omitempty"`
   350  	// Sig is a signature of the message. This is the new-style signature
   351  	// scheme. The old way was to sign individual payloads. Which is used
   352  	// depends on the route.
   353  	Sig dex.Bytes `json:"sig"`
   354  }
   355  
   356  // DecodeMessage decodes a *Message from JSON-formatted bytes. Note that
   357  // *Message may be nil even if error is nil, when the message is JSON null,
   358  // []byte("null").
   359  func DecodeMessage(b []byte) (msg *Message, _ error) {
   360  	err := json.Unmarshal(b, &msg)
   361  	if err != nil {
   362  		return nil, err
   363  	}
   364  	return msg, nil
   365  }
   366  
   367  // NewRequest is the constructor for a Request-type *Message.
   368  func NewRequest(id uint64, route string, payload any) (*Message, error) {
   369  	if id == 0 {
   370  		return nil, fmt.Errorf("id = 0 not allowed for a request-type message")
   371  	}
   372  	if route == "" {
   373  		return nil, fmt.Errorf("empty string not allowed for route of request-type message")
   374  	}
   375  	encoded, err := json.Marshal(payload)
   376  	if err != nil {
   377  		return nil, err
   378  	}
   379  	return &Message{
   380  		Type:    Request,
   381  		Payload: json.RawMessage(encoded),
   382  		Route:   route,
   383  		ID:      id,
   384  	}, nil
   385  }
   386  
   387  // NewResponse encodes the result and creates a Response-type *Message.
   388  func NewResponse(id uint64, result any, rpcErr *Error) (*Message, error) {
   389  	if id == 0 {
   390  		return nil, fmt.Errorf("id = 0 not allowed for response-type message")
   391  	}
   392  	encResult, err := json.Marshal(result)
   393  	if err != nil {
   394  		return nil, err
   395  	}
   396  	resp := &ResponsePayload{
   397  		Result: encResult,
   398  		Error:  rpcErr,
   399  	}
   400  	encResp, err := json.Marshal(resp)
   401  	if err != nil {
   402  		return nil, err
   403  	}
   404  	return &Message{
   405  		Type:    Response,
   406  		Payload: json.RawMessage(encResp),
   407  		ID:      id,
   408  	}, nil
   409  }
   410  
   411  // Response attempts to decode the payload to a *ResponsePayload. Response will
   412  // return an error if the Type is not Response. It is an error if the Message's
   413  // Payload is []byte("null").
   414  func (msg *Message) Response() (*ResponsePayload, error) {
   415  	if msg.Type != Response {
   416  		return nil, fmt.Errorf("invalid type %d for ResponsePayload", msg.Type)
   417  	}
   418  	resp := new(ResponsePayload)
   419  	err := json.Unmarshal(msg.Payload, &resp)
   420  	if err != nil {
   421  		return nil, err
   422  	}
   423  	if resp == nil /* null JSON */ {
   424  		return nil, errNullRespPayload
   425  	}
   426  	return resp, nil
   427  }
   428  
   429  // NewNotification encodes the payload and creates a Notification-type *Message.
   430  func NewNotification(route string, payload any) (*Message, error) {
   431  	if route == "" {
   432  		return nil, fmt.Errorf("empty string not allowed for route of notification-type message")
   433  	}
   434  	encPayload, err := json.Marshal(payload)
   435  	if err != nil {
   436  		return nil, err
   437  	}
   438  	return &Message{
   439  		Type:    Notification,
   440  		Route:   route,
   441  		Payload: json.RawMessage(encPayload),
   442  	}, nil
   443  }
   444  
   445  // Unmarshal unmarshals the Payload field into the provided interface. Note that
   446  // the payload interface must contain a pointer. If it is a pointer to a
   447  // pointer, it may become nil for a Message.Payload of []byte("null").
   448  func (msg *Message) Unmarshal(payload any) error {
   449  	return json.Unmarshal(msg.Payload, payload)
   450  }
   451  
   452  // UnmarshalResult is a convenience method for decoding the Result field of a
   453  // ResponsePayload.
   454  func (msg *Message) UnmarshalResult(result any) error {
   455  	resp, err := msg.Response()
   456  	if err != nil {
   457  		return err
   458  	}
   459  	if resp.Error != nil {
   460  		return fmt.Errorf("rpc error: %w", resp.Error)
   461  	}
   462  	return json.Unmarshal(resp.Result, result)
   463  }
   464  
   465  // String prints the message as a JSON-encoded string.
   466  func (msg *Message) String() string {
   467  	b, err := json.Marshal(msg)
   468  	if err != nil {
   469  		return "[Message decode error]"
   470  	}
   471  	return string(b)
   472  }
   473  
   474  // Match is the params for a DEX-originating MatchRoute request.
   475  type Match struct {
   476  	Signature
   477  	OrderID      Bytes  `json:"orderid"`
   478  	MatchID      Bytes  `json:"matchid"`
   479  	Quantity     uint64 `json:"qty"`
   480  	Rate         uint64 `json:"rate"`
   481  	ServerTime   uint64 `json:"tserver"`
   482  	Address      string `json:"address"`
   483  	FeeRateBase  uint64 `json:"feeratebase"`
   484  	FeeRateQuote uint64 `json:"feeratequote"`
   485  	// Status and Side are provided for convenience and are not part of the
   486  	// match serialization.
   487  	Status uint8 `json:"status"`
   488  	Side   uint8 `json:"side"`
   489  }
   490  
   491  var _ Signable = (*Match)(nil)
   492  
   493  // Serialize serializes the Match data.
   494  func (m *Match) Serialize() []byte {
   495  	// Match serialization is orderid (32) + matchid (32) + quantity (8) + rate (8)
   496  	// + server time (8) + address (variable, guess 35) + base fee rate (8) +
   497  	// quote fee rate (8) = 139
   498  	s := make([]byte, 0, 139)
   499  	s = append(s, m.OrderID...)
   500  	s = append(s, m.MatchID...)
   501  	s = append(s, uint64Bytes(m.Quantity)...)
   502  	s = append(s, uint64Bytes(m.Rate)...)
   503  	s = append(s, uint64Bytes(m.ServerTime)...)
   504  	s = append(s, []byte(m.Address)...)
   505  	s = append(s, uint64Bytes(m.FeeRateBase)...)
   506  	return append(s, uint64Bytes(m.FeeRateQuote)...)
   507  }
   508  
   509  // NoMatch is the payload for a server-originating NoMatchRoute notification.
   510  type NoMatch struct {
   511  	OrderID Bytes `json:"orderid"`
   512  }
   513  
   514  // MatchRequest details a match for the MatchStatusRoute request. The actual
   515  // payload is a []MatchRequest.
   516  type MatchRequest struct {
   517  	Base    uint32 `json:"base"`
   518  	Quote   uint32 `json:"quote"`
   519  	MatchID Bytes  `json:"matchid"`
   520  }
   521  
   522  // MatchStatusResult is the successful result for the MatchStatusRoute request.
   523  type MatchStatusResult struct {
   524  	MatchID       Bytes `json:"matchid"`
   525  	Status        uint8 `json:"status"`
   526  	MakerContract Bytes `json:"makercontract,omitempty"`
   527  	TakerContract Bytes `json:"takercontract,omitempty"`
   528  	MakerSwap     Bytes `json:"makerswap,omitempty"`
   529  	TakerSwap     Bytes `json:"takerswap,omitempty"`
   530  	MakerRedeem   Bytes `json:"makerredeem,omitempty"`
   531  	TakerRedeem   Bytes `json:"takerredeem,omitempty"`
   532  	Secret        Bytes `json:"secret,omitempty"`
   533  	Active        bool  `json:"active"`
   534  	// MakerTxData and TakerTxData will only be populated by the server when the
   535  	// match status is MakerSwapCast and TakerSwapCast, respectively.
   536  	MakerTxData Bytes `json:"makertx,omitempty"`
   537  	TakerTxData Bytes `json:"takertx,omitempty"`
   538  }
   539  
   540  // OrderStatusRequest details an order for the OrderStatusRoute request. The
   541  // actual payload is a []OrderStatusRequest.
   542  type OrderStatusRequest struct {
   543  	Base    uint32 `json:"base"`
   544  	Quote   uint32 `json:"quote"`
   545  	OrderID Bytes  `json:"orderid"`
   546  }
   547  
   548  // OrderStatus is the current status of an order.
   549  type OrderStatus struct {
   550  	ID     Bytes  `json:"id"`
   551  	Status uint16 `json:"status"`
   552  }
   553  
   554  // Init is the payload for a client-originating InitRoute request.
   555  type Init struct {
   556  	Signature
   557  	OrderID  Bytes `json:"orderid"`
   558  	MatchID  Bytes `json:"matchid"`
   559  	CoinID   Bytes `json:"coinid"`
   560  	Contract Bytes `json:"contract"`
   561  }
   562  
   563  var _ Signable = (*Init)(nil)
   564  
   565  // Serialize serializes the Init data.
   566  func (init *Init) Serialize() []byte {
   567  	// Init serialization is orderid (32) + matchid (32) + coinid (probably 36)
   568  	// + contract (97 ish). Sum = 197
   569  	s := make([]byte, 0, 197)
   570  	s = append(s, init.OrderID...)
   571  	s = append(s, init.MatchID...)
   572  	s = append(s, init.CoinID...)
   573  	return append(s, init.Contract...)
   574  }
   575  
   576  // Audit is the payload for a DEX-originating AuditRoute request.
   577  type Audit struct {
   578  	Signature
   579  	OrderID  Bytes  `json:"orderid"`
   580  	MatchID  Bytes  `json:"matchid"`
   581  	Time     uint64 `json:"timestamp"`
   582  	CoinID   Bytes  `json:"coinid"`
   583  	Contract Bytes  `json:"contract"`
   584  	TxData   Bytes  `json:"txdata"`
   585  }
   586  
   587  var _ Signable = (*Audit)(nil)
   588  
   589  // Serialize serializes the Audit data.
   590  func (audit *Audit) Serialize() []byte {
   591  	// Audit serialization is orderid (32) + matchid (32) + time (8) +
   592  	// coin ID (36) + contract (97 ish) = 205
   593  	s := make([]byte, 0, 205)
   594  	s = append(s, audit.OrderID...)
   595  	s = append(s, audit.MatchID...)
   596  	s = append(s, uint64Bytes(audit.Time)...)
   597  	s = append(s, audit.CoinID...)
   598  	return append(s, audit.Contract...)
   599  }
   600  
   601  // RevokeOrder are the params for a DEX-originating RevokeOrderRoute notification.
   602  type RevokeOrder struct {
   603  	Signature
   604  	OrderID Bytes `json:"orderid"`
   605  }
   606  
   607  var _ Signable = (*RevokeMatch)(nil)
   608  
   609  // Serialize serializes the RevokeOrder data.
   610  func (rev *RevokeOrder) Serialize() []byte {
   611  	// RevokeMatch serialization is order id (32) = 32 bytes
   612  	s := make([]byte, 64)
   613  	copy(s, rev.OrderID)
   614  	return s
   615  }
   616  
   617  // RevokeMatch are the params for a DEX-originating RevokeMatchRoute request.
   618  type RevokeMatch struct {
   619  	Signature
   620  	OrderID Bytes `json:"orderid"`
   621  	MatchID Bytes `json:"matchid"`
   622  }
   623  
   624  var _ Signable = (*RevokeMatch)(nil)
   625  
   626  // Serialize serializes the RevokeMatch data.
   627  func (rev *RevokeMatch) Serialize() []byte {
   628  	// RevokeMatch serialization is order id (32) + match id (32) = 64 bytes
   629  	s := make([]byte, 0, 64)
   630  	s = append(s, rev.OrderID...)
   631  	return append(s, rev.MatchID...)
   632  }
   633  
   634  // Redeem are the params for a client-originating RedeemRoute request.
   635  type Redeem struct {
   636  	Signature
   637  	OrderID Bytes `json:"orderid"`
   638  	MatchID Bytes `json:"matchid"`
   639  	CoinID  Bytes `json:"coinid"`
   640  	Secret  Bytes `json:"secret"`
   641  }
   642  
   643  var _ Signable = (*Redeem)(nil)
   644  
   645  // Serialize serializes the Redeem data.
   646  func (redeem *Redeem) Serialize() []byte {
   647  	// Redeem serialization is orderid (32) + matchid (32) + coin ID (36) +
   648  	// secret (32) = 132
   649  	s := make([]byte, 0, 132)
   650  	s = append(s, redeem.OrderID...)
   651  	s = append(s, redeem.MatchID...)
   652  	s = append(s, redeem.CoinID...)
   653  	return append(s, redeem.Secret...)
   654  }
   655  
   656  // Redemption is the payload for a DEX-originating RedemptionRoute request.
   657  type Redemption struct {
   658  	Redeem
   659  	Time uint64 `json:"timestamp"`
   660  }
   661  
   662  // Serialize serializes the Redemption data.
   663  func (r *Redemption) Serialize() []byte {
   664  	// Redemption serialization is Redeem (100) + timestamp (8) = 108
   665  	s := r.Redeem.Serialize()
   666  	return append(s, uint64Bytes(r.Time)...)
   667  }
   668  
   669  // Certain order properties are specified with the following constants. These
   670  // properties include buy/sell (side), standing/immediate (force),
   671  // limit/market/cancel (order type).
   672  const (
   673  	BuyOrderNum       = 1
   674  	SellOrderNum      = 2
   675  	StandingOrderNum  = 1
   676  	ImmediateOrderNum = 2
   677  	LimitOrderNum     = 1
   678  	MarketOrderNum    = 2
   679  	CancelOrderNum    = 3
   680  )
   681  
   682  // Coin is information for validating funding coins. Some number of
   683  // Coins must be included with both Limit and Market payloads.
   684  type Coin struct {
   685  	ID      Bytes   `json:"coinid"`
   686  	PubKeys []Bytes `json:"pubkeys"`
   687  	Sigs    []Bytes `json:"sigs"`
   688  	Redeem  Bytes   `json:"redeem"`
   689  }
   690  
   691  // Prefix is a common structure shared among order type payloads.
   692  type Prefix struct {
   693  	Signature
   694  	AccountID  Bytes  `json:"accountid"`
   695  	Base       uint32 `json:"base"`
   696  	Quote      uint32 `json:"quote"`
   697  	OrderType  uint8  `json:"ordertype"`
   698  	ClientTime uint64 `json:"tclient"`
   699  	ServerTime uint64 `json:"tserver"`
   700  	Commit     Bytes  `json:"com"`
   701  }
   702  
   703  // Stamp sets the server timestamp and epoch ID. Partially satisfies the
   704  // Stampable interface.
   705  func (p *Prefix) Stamp(t uint64) {
   706  	p.ServerTime = t
   707  }
   708  
   709  // Serialize serializes the Prefix data.
   710  func (p *Prefix) Serialize() []byte {
   711  	// serialization: account ID (32) + base asset (4) + quote asset (4) +
   712  	// order type (1) + client time (8) + server time (8) + commitment (32)
   713  	// = 89 bytes
   714  	b := make([]byte, 0, 89)
   715  	b = append(b, p.AccountID...)
   716  	b = append(b, uint32Bytes(p.Base)...)
   717  	b = append(b, uint32Bytes(p.Quote)...)
   718  	b = append(b, p.OrderType)
   719  	b = append(b, uint64Bytes(p.ClientTime)...)
   720  	// Note: ServerTime is zero for the client's signature message, but non-zero
   721  	// for the server's. This is in contrast to an order.Order which cannot
   722  	// even be serialized without the server's timestamp.
   723  	b = append(b, uint64Bytes(p.ServerTime)...)
   724  	return append(b, p.Commit...)
   725  }
   726  
   727  // Trade is common to Limit and Market Payloads.
   728  type Trade struct {
   729  	Side      uint8      `json:"side"`
   730  	Quantity  uint64     `json:"ordersize"`
   731  	Coins     []*Coin    `json:"coins"`
   732  	Address   string     `json:"address"`
   733  	RedeemSig *RedeemSig `json:"redeemsig,omitempty"` // account-based assets only. not serialized.
   734  }
   735  
   736  // Serialize serializes the Trade data.
   737  func (t *Trade) Serialize() []byte {
   738  	// serialization: coin count (1), coin data (36*count), side (1), qty (8)
   739  	// = 10 + 36*count
   740  	// Address is not serialized as part of the trade.
   741  	coinCount := len(t.Coins)
   742  	b := make([]byte, 0, 10+36*coinCount)
   743  	b = append(b, byte(coinCount))
   744  	for _, coin := range t.Coins {
   745  		b = append(b, coin.ID...)
   746  	}
   747  	b = append(b, t.Side)
   748  	return append(b, uint64Bytes(t.Quantity)...)
   749  	// Note that Address is part of LimitOrder and MarketOrder serialization.
   750  }
   751  
   752  // LimitOrder is the payload for the LimitRoute, which places a limit order.
   753  type LimitOrder struct {
   754  	Prefix
   755  	Trade
   756  	Rate uint64 `json:"rate"`
   757  	TiF  uint8  `json:"timeinforce"`
   758  }
   759  
   760  // Serialize serializes the Limit data.
   761  func (l *LimitOrder) Serialize() []byte {
   762  	// serialization: prefix (89) + trade (variable) + rate (8)
   763  	// + time-in-force (1) + address (~35) = 133 + len(trade)
   764  	trade := l.Trade.Serialize()
   765  	b := make([]byte, 0, 133+len(trade))
   766  	b = append(b, l.Prefix.Serialize()...)
   767  	b = append(b, trade...)
   768  	b = append(b, uint64Bytes(l.Rate)...)
   769  	b = append(b, l.TiF)
   770  	return append(b, []byte(l.Trade.Address)...)
   771  }
   772  
   773  // MarketOrder is the payload for the MarketRoute, which places a market order.
   774  type MarketOrder struct {
   775  	Prefix
   776  	Trade
   777  }
   778  
   779  // Serialize serializes the MarketOrder data.
   780  func (m *MarketOrder) Serialize() []byte {
   781  	// serialization: prefix (89) + trade (varies) + address (35 ish)
   782  	b := append(m.Prefix.Serialize(), m.Trade.Serialize()...)
   783  	return append(b, []byte(m.Trade.Address)...)
   784  }
   785  
   786  // CancelOrder is the payload for the CancelRoute, which places a cancel order.
   787  type CancelOrder struct {
   788  	Prefix
   789  	TargetID Bytes `json:"targetid"`
   790  }
   791  
   792  // Serialize serializes the CancelOrder data.
   793  func (c *CancelOrder) Serialize() []byte {
   794  	// serialization: prefix (89) + target id (32) = 121
   795  	return append(c.Prefix.Serialize(), c.TargetID...)
   796  }
   797  
   798  // RedeemSig is a signature proving ownership of the redeeming address. This is
   799  // only necessary as part of a Trade if the asset received is account-based.
   800  type RedeemSig struct {
   801  	PubKey dex.Bytes `json:"pubkey"`
   802  	Sig    dex.Bytes `json:"sig"`
   803  }
   804  
   805  // OrderResult is returned from the order-placing routes.
   806  type OrderResult struct {
   807  	Sig        Bytes  `json:"sig"`
   808  	OrderID    Bytes  `json:"orderid"`
   809  	ServerTime uint64 `json:"tserver"`
   810  }
   811  
   812  // OrderBookSubscription is the payload for a client-originating request to the
   813  // OrderBookRoute, intializing an order book feed.
   814  type OrderBookSubscription struct {
   815  	Base  uint32 `json:"base"`
   816  	Quote uint32 `json:"quote"`
   817  }
   818  
   819  // UnsubOrderBook is the payload for a client-originating request to the
   820  // UnsubOrderBookRoute, terminating an order book subscription.
   821  type UnsubOrderBook struct {
   822  	MarketID string `json:"marketid"`
   823  }
   824  
   825  // orderbook subscription notification payloads include: BookOrderNote,
   826  // UnbookOrderNote, EpochOrderNote, and MatchProofNote.
   827  
   828  // OrderNote is part of a notification about any type of order.
   829  type OrderNote struct {
   830  	Seq      uint64 `json:"seq,omitempty"`      // May be empty when part of an OrderBook.
   831  	MarketID string `json:"marketid,omitempty"` // May be empty when part of an OrderBook.
   832  	OrderID  Bytes  `json:"oid"`
   833  }
   834  
   835  // TradeNote is part of a notification that includes information about a
   836  // limit or market order.
   837  type TradeNote struct {
   838  	Side     uint8  `json:"side,omitempty"`
   839  	Quantity uint64 `json:"qty,omitempty"`
   840  	Rate     uint64 `json:"rate,omitempty"`
   841  	TiF      uint8  `json:"tif,omitempty"`
   842  	Time     uint64 `json:"time,omitempty"`
   843  }
   844  
   845  // BookOrderNote is the payload for a DEX-originating notification-type message
   846  // informing the client to add the order to the order book.
   847  type BookOrderNote struct {
   848  	OrderNote
   849  	TradeNote
   850  }
   851  
   852  // UnbookOrderNote is the DEX-originating notification-type message informing
   853  // the client to remove an order from the order book.
   854  type UnbookOrderNote OrderNote
   855  
   856  // EpochOrderNote is the DEX-originating notification-type message informing the
   857  // client about an order added to the epoch queue.
   858  type EpochOrderNote struct {
   859  	BookOrderNote
   860  	Commit    Bytes  `json:"com"`
   861  	OrderType uint8  `json:"otype"`
   862  	Epoch     uint64 `json:"epoch"`
   863  	TargetID  Bytes  `json:"target,omitempty"` // omit for cancel orders
   864  }
   865  
   866  // UpdateRemainingNote is the DEX-originating notification-type message
   867  // informing the client about an update to a booked order's remaining quantity.
   868  type UpdateRemainingNote struct {
   869  	OrderNote
   870  	Remaining uint64 `json:"remaining"`
   871  }
   872  
   873  // OrderBook is the response to a successful OrderBookSubscription.
   874  type OrderBook struct {
   875  	MarketID string `json:"marketid"`
   876  	Seq      uint64 `json:"seq"`
   877  	Epoch    uint64 `json:"epoch"`
   878  	// MarketStatus `json:"status"`// maybe
   879  	// DRAFT NOTE: We might want to use a different structure for bulk updates.
   880  	// Sending a struct of arrays rather than an array of structs could
   881  	// potentially cut the encoding effort and encoded size substantially.
   882  	Orders       []*BookOrderNote `json:"orders"`
   883  	BaseFeeRate  uint64           `json:"baseFeeRate"`
   884  	QuoteFeeRate uint64           `json:"quoteFeeRate"`
   885  	// RecentMatches is [rate, qty, timestamp]. Quantity is signed.
   886  	// Negative means that the maker was a sell order.
   887  	RecentMatches [][3]int64 `json:"recentMatches"`
   888  }
   889  
   890  // MatchProofNote is the match_proof notification payload.
   891  type MatchProofNote struct {
   892  	MarketID  string  `json:"marketid"`
   893  	Epoch     uint64  `json:"epoch"`
   894  	Preimages []Bytes `json:"preimages"`
   895  	Misses    []Bytes `json:"misses"`
   896  	CSum      Bytes   `json:"csum"`
   897  	Seed      Bytes   `json:"seed"`
   898  }
   899  
   900  // TradeSuspension is the SuspensionRoute notification payload. It is part of
   901  // the orderbook subscription.
   902  type TradeSuspension struct {
   903  	MarketID    string `json:"marketid"`
   904  	Seq         uint64 `json:"seq,omitempty"`         // only set at suspend time and if Persist==false
   905  	SuspendTime uint64 `json:"suspendtime,omitempty"` // only set in advance of suspend
   906  	FinalEpoch  uint64 `json:"finalepoch"`
   907  	Persist     bool   `json:"persistbook"`
   908  }
   909  
   910  // TradeResumption is the ResumptionRoute notification payload. It is part of
   911  // the orderbook subscription.
   912  type TradeResumption struct {
   913  	MarketID   string `json:"marketid"`
   914  	ResumeTime uint64 `json:"resumetime,omitempty"` // only set in advance of resume
   915  	StartEpoch uint64 `json:"startepoch"`
   916  	// TODO: ConfigChange bool or entire Config Market here.
   917  }
   918  
   919  // PreimageRequest is the server-originating preimage request payload.
   920  type PreimageRequest struct {
   921  	OrderID        Bytes `json:"orderid"`
   922  	Commitment     Bytes `json:"commit"`
   923  	CommitChecksum Bytes `json:"csum"`
   924  }
   925  
   926  // PreimageResponse is the client-originating preimage response payload.
   927  type PreimageResponse struct {
   928  	Preimage Bytes `json:"pimg"`
   929  }
   930  
   931  // Connect is the payload for a client-originating ConnectRoute request.
   932  type Connect struct {
   933  	Signature
   934  	AccountID  Bytes  `json:"accountid"`
   935  	APIVersion uint16 `json:"apiver"`
   936  	Time       uint64 `json:"timestamp"`
   937  }
   938  
   939  // Serialize serializes the Connect data.
   940  func (c *Connect) Serialize() []byte {
   941  	// serialization: account ID (32) + api version (2) + timestamp (8) = 42 bytes
   942  	s := make([]byte, 0, 42)
   943  	s = append(s, c.AccountID...)
   944  	s = append(s, uint16Bytes(c.APIVersion)...)
   945  	return append(s, uint64Bytes(c.Time)...)
   946  }
   947  
   948  // Bond is information on a fidelity bond. This is part of the ConnectResult and
   949  // PostBondResult payloads.
   950  type Bond struct {
   951  	Version  uint16 `json:"version"`
   952  	Amount   uint64 `json:"amount"`
   953  	Expiry   uint64 `json:"expiry"` // when it expires, not the lock time
   954  	CoinID   Bytes  `json:"coinID"` // NOTE: ID capitalization not consistent with other payloads, but internally consistent with assetID
   955  	AssetID  uint32 `json:"assetID"`
   956  	Strength uint32 `json:"strength"`
   957  }
   958  
   959  // ConnectResult is the result for the ConnectRoute request.
   960  //
   961  // TODO: Include penalty data as specified in the spec.
   962  type ConnectResult struct {
   963  	Sig                 Bytes               `json:"sig"`
   964  	ActiveOrderStatuses []*OrderStatus      `json:"activeorderstatuses"`
   965  	ActiveMatches       []*Match            `json:"activematches"`
   966  	Score               int32               `json:"score"`
   967  	ActiveBonds         []*Bond             `json:"activeBonds"`
   968  	Reputation          *account.Reputation `json:"reputation"`
   969  }
   970  
   971  // TierChangedNotification is the dex-originating notification sent when the
   972  // user's tier changes as a result of account conduct violations. Tier change
   973  // due to bond expiry is communicated with a BondExpiredNotification.
   974  type TierChangedNotification struct {
   975  	Signature
   976  	// AccountID Bytes  `json:"accountID"`
   977  	Tier       int64               `json:"tier"`
   978  	Reputation *account.Reputation `json:"reputation"` // replaces Tier field
   979  	Reason     string              `json:"reason"`
   980  }
   981  
   982  // Serialize serializes the TierChangedNotification data.
   983  func (tc *TierChangedNotification) Serialize() []byte {
   984  	// serialization: tier (8) + reason (variable string)
   985  	b := make([]byte, 0, 8+len(tc.Reason))
   986  	b = append(b, uint64Bytes(uint64(tc.Tier))...)
   987  	return append(b, []byte(tc.Reason)...)
   988  }
   989  
   990  // ScoreChangedNotification is the dex-originating notification sent when the
   991  // user's score changes.
   992  type ScoreChangedNotification struct {
   993  	Signature
   994  	Reputation account.Reputation `json:"reputation"`
   995  }
   996  
   997  // Serialize serializes the ScoreChangedNotification data.
   998  func (tc *ScoreChangedNotification) Serialize() []byte {
   999  	// serialization: bondedTier 8 + penalties 2 + score 4
  1000  	b := make([]byte, 0, 10)
  1001  	b = append(b, uint64Bytes(uint64(tc.Reputation.BondedTier))...)
  1002  	b = append(b, uint16Bytes(tc.Reputation.Penalties)...)
  1003  	return append(b, uint32Bytes(uint32(tc.Reputation.Score))...)
  1004  }
  1005  
  1006  // PenaltyNote is the payload of a Penalty notification.
  1007  type PenaltyNote struct {
  1008  	Signature
  1009  	Penalty *Penalty `json:"penalty"`
  1010  }
  1011  
  1012  // Penalty is part of the payload for a dex-originating Penalty notification
  1013  // and part of the connect response.
  1014  type Penalty struct {
  1015  	Rule    account.Rule `json:"rule"`
  1016  	Time    uint64       `json:"timestamp"`
  1017  	Details string       `json:"details"`
  1018  }
  1019  
  1020  // Serialize serializes the PenaltyNote data.
  1021  func (n *PenaltyNote) Serialize() []byte {
  1022  	p := n.Penalty
  1023  	// serialization: rule(1) + time (8) +
  1024  	// details (variable, ~100) = 109 bytes
  1025  	b := make([]byte, 0, 109)
  1026  	b = append(b, byte(p.Rule))
  1027  	b = append(b, uint64Bytes(p.Time)...)
  1028  	return append(b, []byte(p.Details)...)
  1029  }
  1030  
  1031  // Client should send bond info when their bond tx is fully-confirmed. Server
  1032  // should start waiting for required confs when it receives the 'postbond'
  1033  // request if the txn is found. Client is responsible for submitting 'postbond'
  1034  // for their bond txns when they reach required confs. Implementation note: the
  1035  // client should also check on startup for stored bonds that are neither
  1036  // accepted nor expired yet (also maybe if not listed in the 'connect'
  1037  // response), and post those.
  1038  
  1039  // PreValidateBond may provide the unsigned bond transaction for validation
  1040  // prior to broadcasting the signed transaction. If they skip pre-validation,
  1041  // and the broadcasted transaction is rejected, the client would have needlessly
  1042  // locked funds.
  1043  type PreValidateBond struct {
  1044  	Signature
  1045  	AcctPubKey Bytes  `json:"acctPubKey"` // acctID = blake256(blake256(acctPubKey))
  1046  	AssetID    uint32 `json:"assetID"`
  1047  	Version    uint16 `json:"version"`
  1048  	RawTx      Bytes  `json:"tx"`
  1049  	// Data       Bytes  `json:"data"` // needed for some assets? e.g. redeem script or contract key
  1050  }
  1051  
  1052  // Serialize serializes the PreValidateBond data for the signature.
  1053  func (pb *PreValidateBond) Serialize() []byte {
  1054  	// serialization: client pubkey (33) + asset ID (4) + bond version (2) +
  1055  	// raw tx (variable)
  1056  	sz := len(pb.AcctPubKey) + 4 + 2 + len(pb.RawTx) // + len(pb.Data)
  1057  	b := make([]byte, 0, sz)
  1058  	b = append(b, pb.AcctPubKey...)
  1059  	b = append(b, uint32Bytes(pb.AssetID)...)
  1060  	b = append(b, uint16Bytes(pb.Version)...)
  1061  	return append(b, pb.RawTx...)
  1062  	// return append(b, pb.Data...)
  1063  }
  1064  
  1065  // PreValidateBondResult is the response to the client's PreValidateBond
  1066  // request.
  1067  type PreValidateBondResult struct {
  1068  	// Signature is the result of signing the serialized PreValidateBondResult
  1069  	// concatenated with the RawTx.
  1070  	Signature
  1071  	AccountID Bytes  `json:"accountID"`
  1072  	AssetID   uint32 `json:"assetID"`
  1073  	Amount    uint64 `json:"amount"`
  1074  	Expiry    uint64 `json:"expiry"` // not locktime, but time when bond expires for dex
  1075  }
  1076  
  1077  // Serialize serializes the PreValidateBondResult data for the signature.
  1078  func (pbr *PreValidateBondResult) Serialize() []byte {
  1079  	sz := len(pbr.AccountID) + 4 + 8 + 8
  1080  	b := make([]byte, 0, sz)
  1081  	b = append(b, pbr.AccountID...)
  1082  	b = append(b, uint32Bytes(pbr.AssetID)...)
  1083  	b = append(b, uint64Bytes(pbr.Amount)...)
  1084  	return append(b, uint64Bytes(pbr.Expiry)...)
  1085  }
  1086  
  1087  // PostBond requests that server accept a confirmed bond payment, specified by
  1088  // the provided CoinID, for a certain account.
  1089  type PostBond struct {
  1090  	Signature
  1091  	AcctPubKey Bytes  `json:"acctPubKey"` // acctID = blake256(blake256(acctPubKey))
  1092  	AssetID    uint32 `json:"assetID"`
  1093  	Version    uint16 `json:"version"`
  1094  	CoinID     Bytes  `json:"coinid"`
  1095  	// For an account-based asset where there is a central bond contract implied
  1096  	// by Version, do we use AcctPubKey to lookup bonded amount, and CoinID to
  1097  	// wait for confs of this latest bond addition?
  1098  	// Data Bytes `json:"data"`
  1099  }
  1100  
  1101  // Serialize serializes the PostBond data for the signature.
  1102  func (pb *PostBond) Serialize() []byte {
  1103  	// serialization: client pubkey (33) + asset ID (4) + bond version (2) +
  1104  	// coin ID (variable)
  1105  	sz := len(pb.AcctPubKey) + 4 + 2 + len(pb.CoinID)
  1106  	b := make([]byte, 0, sz)
  1107  	b = append(b, pb.AcctPubKey...)
  1108  	b = append(b, uint32Bytes(pb.AssetID)...)
  1109  	b = append(b, uint16Bytes(pb.Version)...)
  1110  	return append(b, pb.CoinID...)
  1111  }
  1112  
  1113  // PostBondResult is the response to the client's PostBond request. If Active is
  1114  // true, the bond was applied to the account; if false it is not confirmed, but
  1115  // was otherwise validated.
  1116  type PostBondResult struct {
  1117  	Signature                      // message is BondID | AccountID
  1118  	AccountID  Bytes               `json:"accountID"`
  1119  	AssetID    uint32              `json:"assetID"`
  1120  	Amount     uint64              `json:"amount"`
  1121  	Expiry     uint64              `json:"expiry"` // not locktime, but time when bond expires for dex
  1122  	Strength   uint32              `json:"strength"`
  1123  	BondID     Bytes               `json:"bondID"`
  1124  	Reputation *account.Reputation `json:"reputation"`
  1125  }
  1126  
  1127  // Serialize serializes the PostBondResult data for the signature.
  1128  func (pbr *PostBondResult) Serialize() []byte {
  1129  	sz := len(pbr.AccountID) + len(pbr.BondID)
  1130  	b := make([]byte, 0, sz)
  1131  	b = append(b, pbr.AccountID...)
  1132  	return append(b, pbr.BondID...)
  1133  }
  1134  
  1135  // BondExpiredNotification is a notification from a server when a bond tx
  1136  // expires.
  1137  type BondExpiredNotification struct {
  1138  	Signature
  1139  	AccountID  Bytes               `json:"accountID"`
  1140  	AssetID    uint32              `json:"assetid"`
  1141  	BondCoinID Bytes               `json:"coinid"`
  1142  	Tier       int64               `json:"tier"`
  1143  	Reputation *account.Reputation `json:"reputation"`
  1144  }
  1145  
  1146  // Serialize serializes the BondExpiredNotification data.
  1147  func (bc *BondExpiredNotification) Serialize() []byte {
  1148  	sz := 4 + len(bc.AccountID) + 4 + len(bc.BondCoinID) + 8
  1149  	b := make([]byte, 0, sz)
  1150  	b = append(b, bc.AccountID...)
  1151  	b = append(b, uint32Bytes(bc.AssetID)...)
  1152  	b = append(b, bc.BondCoinID...)
  1153  	return append(b, uint64Bytes(uint64(bc.Tier))...) // correct bytes for int64 (signed)?
  1154  }
  1155  
  1156  // Register is the payload for the RegisterRoute request.
  1157  type Register struct {
  1158  	Signature
  1159  	PubKey Bytes   `json:"pubkey"`
  1160  	Time   uint64  `json:"timestamp"`
  1161  	Asset  *uint32 `json:"feeAsset,omitempty"` // default to 42 if not set by client
  1162  }
  1163  
  1164  // Serialize serializes the Register data.
  1165  func (r *Register) Serialize() []byte {
  1166  	// serialization: pubkey (33) + time (8) + asset (4 if set) = 45
  1167  	s := make([]byte, 0, 45)
  1168  	s = append(s, r.PubKey...)
  1169  	s = append(s, uint64Bytes(r.Time)...)
  1170  	if r.Asset != nil {
  1171  		s = append(s, uint32Bytes(*r.Asset)...)
  1172  	}
  1173  	return s
  1174  }
  1175  
  1176  // NotifyFee is the payload for a client-originating NotifyFeeRoute request.
  1177  type NotifyFee struct {
  1178  	Signature
  1179  	AccountID Bytes  `json:"accountid"`
  1180  	CoinID    Bytes  `json:"coinid"`
  1181  	Time      uint64 `json:"timestamp"`
  1182  }
  1183  
  1184  // Serialize serializes the NotifyFee data.
  1185  func (n *NotifyFee) Serialize() []byte {
  1186  	// serialization: account id (32) + coinID (variable, ~36) + time (8) = 76
  1187  	b := make([]byte, 0, 76)
  1188  	b = append(b, n.AccountID...)
  1189  	b = append(b, n.CoinID...)
  1190  	return append(b, uint64Bytes(n.Time)...)
  1191  }
  1192  
  1193  // Stamp satisfies the Stampable interface.
  1194  func (n *NotifyFee) Stamp(t uint64) {
  1195  	n.Time = t
  1196  }
  1197  
  1198  // NotifyFeeResult is the result for the response to NotifyFee. Though it embeds
  1199  // Signature, it does not satisfy the Signable interface, as it has no need for
  1200  // serialization.
  1201  type NotifyFeeResult struct {
  1202  	Signature
  1203  }
  1204  
  1205  // MarketStatus describes the status of the market, where StartEpoch is when the
  1206  // market started or will start. FinalEpoch is a when the market will suspend
  1207  // if it is running, or when the market suspended if it is presently stopped.
  1208  type MarketStatus struct {
  1209  	StartEpoch uint64 `json:"startepoch"`
  1210  	FinalEpoch uint64 `json:"finalepoch,omitempty"`
  1211  	Persist    *bool  `json:"persistbook,omitempty"` // nil and omitted when finalepoch is omitted
  1212  }
  1213  
  1214  // Market describes a market and its variables, and is returned as part of a
  1215  // ConfigResult. The market's status (running, start epoch, and any planned
  1216  // final epoch before suspend) are also provided.
  1217  type Market struct {
  1218  	Name            string  `json:"name"`
  1219  	Base            uint32  `json:"base"`
  1220  	Quote           uint32  `json:"quote"`
  1221  	EpochLen        uint64  `json:"epochlen"`
  1222  	LotSize         uint64  `json:"lotsize"`
  1223  	RateStep        uint64  `json:"ratestep"`
  1224  	MarketBuyBuffer float64 `json:"buybuffer"`
  1225  	ParcelSize      uint32  `json:"parcelSize"`
  1226  	MarketStatus    `json:"status"`
  1227  }
  1228  
  1229  // Running indicates if the market should be running given the known StartEpoch,
  1230  // EpochLen, and FinalEpoch (if set).
  1231  func (m *Market) Running() bool {
  1232  	dur := m.EpochLen
  1233  	now := uint64(time.Now().UnixMilli())
  1234  	start := m.StartEpoch * dur
  1235  	end := m.FinalEpoch * dur
  1236  	return now >= start && (now < end || end < start) // end < start detects obsolete end
  1237  }
  1238  
  1239  // Asset describes an asset and its variables, and is returned as part of a
  1240  // ConfigResult.
  1241  type Asset struct {
  1242  	Symbol     string       `json:"symbol"`
  1243  	ID         uint32       `json:"id"`
  1244  	Version    uint32       `json:"version"`
  1245  	MaxFeeRate uint64       `json:"maxfeerate"`
  1246  	SwapConf   uint16       `json:"swapconf"`
  1247  	UnitInfo   dex.UnitInfo `json:"unitinfo"`
  1248  }
  1249  
  1250  // BondAsset describes an asset for which fidelity bonds are supported.
  1251  type BondAsset struct {
  1252  	Version uint16 `json:"version"` // latest version supported
  1253  	ID      uint32 `json:"id"`
  1254  	Confs   uint32 `json:"confs"`
  1255  	Amt     uint64 `json:"amount"` // to be implied by bond version?
  1256  }
  1257  
  1258  // ConfigResult is the successful result for the ConfigRoute.
  1259  type ConfigResult struct {
  1260  	// APIVersion is the server's communications API version, but we may
  1261  	// consider APIVersions []uint16, with versioned routes e.g. "initV2".
  1262  	// APIVersions []uint16 `json:"apivers"`
  1263  	APIVersion       uint16    `json:"apiver"`
  1264  	DEXPubKey        dex.Bytes `json:"pubkey"`
  1265  	CancelMax        float64   `json:"cancelmax"`
  1266  	BroadcastTimeout uint64    `json:"btimeout"`
  1267  	Assets           []*Asset  `json:"assets"`
  1268  	Markets          []*Market `json:"markets"`
  1269  	BinSizes         []string  `json:"binSizes"` // Just apidata.BinSizes for now.
  1270  
  1271  	BondAssets map[string]*BondAsset `json:"bondAssets"`
  1272  	// BondExpiry defines the duration of time remaining until lockTime below
  1273  	// which a bond is considered expired. As such, bonds should be created with
  1274  	// a considerably longer lockTime. NOTE: BondExpiry in the config response
  1275  	// is temporary, removed when APIVersion reaches BondAPIVersion and we have
  1276  	// codified the expiries for each network (main,test,sim). Until then, the
  1277  	// value will be considered variable, and we will communicate to the clients
  1278  	// what we expect at any given time. BondAsset.Amt may also become implied
  1279  	// by bond version.
  1280  	BondExpiry uint64 `json:"DEV_bondExpiry"`
  1281  
  1282  	PenaltyThreshold uint32 `json:"penaltyThreshold"`
  1283  	MaxScore         uint32 `json:"maxScore"`
  1284  }
  1285  
  1286  // Spot is a snapshot of a market at the end of a match cycle. A slice of Spot
  1287  // are sent as the response to the SpotsRoute request.
  1288  type Spot struct {
  1289  	Stamp      uint64  `json:"stamp"`
  1290  	BaseID     uint32  `json:"baseID"`
  1291  	QuoteID    uint32  `json:"quoteID"`
  1292  	Rate       uint64  `json:"rate"`
  1293  	BookVolume uint64  `json:"bookVolume"`
  1294  	Change24   float64 `json:"change24"`
  1295  	Vol24      uint64  `json:"vol24"`
  1296  	High24     uint64  `json:"high24"`
  1297  	Low24      uint64  `json:"low24"`
  1298  }
  1299  
  1300  // CandlesRequest is a data API request for market history.
  1301  type CandlesRequest struct {
  1302  	BaseID     uint32 `json:"baseID"`
  1303  	QuoteID    uint32 `json:"quoteID"`
  1304  	BinSize    string `json:"binSize"`
  1305  	NumCandles int    `json:"numCandles,omitempty"` // default and max defined in apidata.
  1306  }
  1307  
  1308  // Candle is a statistical history of a specified period of market activity.
  1309  type Candle struct {
  1310  	StartStamp  uint64 `json:"startStamp"`
  1311  	EndStamp    uint64 `json:"endStamp"`
  1312  	MatchVolume uint64 `json:"matchVolume"`
  1313  	QuoteVolume uint64 `json:"quoteVolume"`
  1314  	HighRate    uint64 `json:"highRate"`
  1315  	LowRate     uint64 `json:"lowRate"`
  1316  	StartRate   uint64 `json:"startRate"`
  1317  	EndRate     uint64 `json:"endRate"`
  1318  }
  1319  
  1320  // WireCandles are Candles encoded as a series of integer arrays, as opposed to
  1321  // an array of candles. WireCandles encode smaller than []Candle, since the
  1322  // property names are not repeated for each candle.
  1323  type WireCandles struct {
  1324  	StartStamps  []uint64 `json:"startStamps"`
  1325  	EndStamps    []uint64 `json:"endStamps"`
  1326  	MatchVolumes []uint64 `json:"matchVolumes"`
  1327  	QuoteVolumes []uint64 `json:"quoteVolumes"`
  1328  	HighRates    []uint64 `json:"highRates"`
  1329  	LowRates     []uint64 `json:"lowRates"`
  1330  	StartRates   []uint64 `json:"startRates"`
  1331  	EndRates     []uint64 `json:"endRates"`
  1332  }
  1333  
  1334  // NewWireCandles prepares a *WireCandles with slices of capacity n.
  1335  func NewWireCandles(n int) *WireCandles {
  1336  	return &WireCandles{
  1337  		StartStamps:  make([]uint64, 0, n),
  1338  		EndStamps:    make([]uint64, 0, n),
  1339  		MatchVolumes: make([]uint64, 0, n),
  1340  		QuoteVolumes: make([]uint64, 0, n),
  1341  		HighRates:    make([]uint64, 0, n),
  1342  		LowRates:     make([]uint64, 0, n),
  1343  		StartRates:   make([]uint64, 0, n),
  1344  		EndRates:     make([]uint64, 0, n),
  1345  	}
  1346  }
  1347  
  1348  // Candles converts the WireCandles to []*Candle.
  1349  func (wc *WireCandles) Candles() []*Candle {
  1350  	candles := make([]*Candle, 0, len(wc.StartStamps))
  1351  	for i := range wc.StartStamps {
  1352  		candles = append(candles, &Candle{
  1353  			StartStamp:  wc.StartStamps[i],
  1354  			EndStamp:    wc.EndStamps[i],
  1355  			MatchVolume: wc.MatchVolumes[i],
  1356  			QuoteVolume: wc.QuoteVolumes[i],
  1357  			HighRate:    wc.HighRates[i],
  1358  			LowRate:     wc.LowRates[i],
  1359  			StartRate:   wc.StartRates[i],
  1360  			EndRate:     wc.EndRates[i],
  1361  		})
  1362  	}
  1363  	return candles
  1364  }
  1365  
  1366  // EpochReportNote is a report about an epoch sent after all of the epoch's book
  1367  // updates. Like TradeResumption, and TradeSuspension when Persist is true, Seq
  1368  // is omitted since it doesn't modify the book.
  1369  type EpochReportNote struct {
  1370  	MarketID     string `json:"marketid"`
  1371  	Epoch        uint64 `json:"epoch"`
  1372  	BaseFeeRate  uint64 `json:"baseFeeRate"`
  1373  	QuoteFeeRate uint64 `json:"quoteFeeRate"`
  1374  	// MatchSummary: [rate, quantity]. Quantity is signed. Negative means that
  1375  	// the maker was a sell order.
  1376  	MatchSummary [][2]int64 `json:"matchSummary"`
  1377  	Candle
  1378  }
  1379  
  1380  // Convert uint64 to 8 bytes.
  1381  func uint64Bytes(i uint64) []byte {
  1382  	b := make([]byte, 8)
  1383  	binary.BigEndian.PutUint64(b, i)
  1384  	return b
  1385  }
  1386  
  1387  // Convert uint32 to 4 bytes.
  1388  func uint32Bytes(i uint32) []byte {
  1389  	b := make([]byte, 4)
  1390  	binary.BigEndian.PutUint32(b, i)
  1391  	return b
  1392  }
  1393  
  1394  // Convert uint32 to 4 bytes.
  1395  func uint16Bytes(i uint16) []byte {
  1396  	b := make([]byte, 2)
  1397  	binary.BigEndian.PutUint16(b, i)
  1398  	return b
  1399  }