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 }