max: add restful api endpoint

This commit is contained in:
c9s 2020-10-02 10:10:59 +08:00
parent 73d81a4e98
commit 59f27cfe2e
8 changed files with 936 additions and 24 deletions

View File

@ -0,0 +1,110 @@
package max
type AccountService struct {
client *RestClient
}
// Account is for max rest api v2, Balance and Type will be conflict with types.PrivateBalanceUpdate
type Account struct {
Currency string `json:"currency"`
Balance string `json:"balance"`
Locked string `json:"locked"`
Type string `json:"type"`
}
// Balance is for kingfisher
type Balance struct {
Currency string
Available int64
Locked int64
Total int64
}
type UserBank struct {
Branch string `json:"branch"`
Name string `json:"name"`
Account string `json:"account"`
State string `json:"state"`
}
type UserInfo struct {
Sn string `json:"sn"`
Name string `json:"name"`
Type string `json:"member_type"`
Level int `json:"level"`
Email string `json:"email"`
Accounts []Account `json:"accounts"`
Bank *UserBank `json:"bank,omitempty"`
IsFrozen bool `json:"is_frozen"`
IsActivated bool `json:"is_activated"`
KycApproved bool `json:"kyc_approved"`
KycState string `json:"kyc_state"`
PhoneSet bool `json:"phone_set"`
PhoneNumber string `json:"phone_number"`
ProfileVerified bool `json:"profile_verified"`
CountryCode string `json:"country_code"`
IdentityNumber string `json:"identity_number"`
WithDrawable bool `json:"withdrawable"`
ReferralCode string `json:"referral_code"`
}
func (s *AccountService) Account(currency string) (*Account, error) {
req, err := s.client.newAuthenticatedRequest("GET", "v2/members/accounts/"+currency, nil)
if err != nil {
return nil, err
}
response, err := s.client.sendRequest(req)
if err != nil {
return nil, err
}
var account Account
err = response.DecodeJSON(&account)
if err != nil {
return nil, err
}
return &account, nil
}
func (s *AccountService) Accounts() ([]Account, error) {
req, err := s.client.newAuthenticatedRequest("GET", "v2/members/accounts", nil)
if err != nil {
return nil, err
}
response, err := s.client.sendRequest(req)
if err != nil {
return nil, err
}
var accounts []Account
err = response.DecodeJSON(&accounts)
if err != nil {
return nil, err
}
return accounts, nil
}
// Me returns the current user info by the current used MAX key and secret
func (s *AccountService) Me() (*UserInfo, error) {
req, err := s.client.newAuthenticatedRequest("GET", "v2/members/me", nil)
if err != nil {
return nil, err
}
response, err := s.client.sendRequest(req)
if err != nil {
return nil, err
}
var m = UserInfo{}
err = response.DecodeJSON(&m)
if err != nil {
return nil, err
}
return &m, nil
}

View File

@ -1,11 +1,5 @@
package max
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
)
type AuthMessage struct {
Action string `json:"action"`
APIKey string `json:"apiKey"`
@ -19,13 +13,3 @@ type AuthEvent struct {
ID string
Timestamp int64
}
func signPayload(payload string, secret string) string {
var sig = hmac.New(sha256.New, []byte(secret))
_, err := sig.Write([]byte(payload))
if err != nil {
return ""
}
return hex.EncodeToString(sig.Sum(nil))
}

View File

@ -0,0 +1,201 @@
package max
import (
"strconv"
"time"
"github.com/pkg/errors"
)
type OrderStateToQuery int
const (
All = iota
Active
Closed
)
type OrderType string
// Order types that the API can return.
const (
OrderTypeMarket = OrderType("market")
OrderTypeLimit = OrderType("limit")
)
// OrderService manages the Order endpoint.
type OrderService struct {
client *RestClient
}
// Order represents one returned order (POST order/GET order/GET orders) on the max platform.
type Order struct {
ID uint64 `json:"id,omitempty" db:"exchange_id"`
Side string `json:"side" db:"side"`
OrderType string `json:"ord_type" db:"order_type"`
Price string `json:"price" db:"price"`
AveragePrice string `json:"avg_price,omitempty" db:"average_price"`
State string `json:"state,omitempty" db:"state"`
Market string `json:"market" db:"market"`
Volume string `json:"volume" db:"volume"`
RemainingVolume string `json:"remaining_volume,omitempty" db:"remaining_volume"`
ExecutedVolume string `json:"executed_volume,omitempty" db:"executed_volume"`
TradesCount int64 `json:"trades_count,omitempty" db:"trades_count"`
GroupID int64 `json:"group_id,omitempty" db:"group_id"`
ClientOID string `json:"client_oid,omitempty" db:"client_oid"`
CreatedAt time.Time `db:"created_at"`
CreatedAtMs int64 `json:"created_at_in_ms,omitempty"`
InsertedAt time.Time `db:"inserted_at"`
}
// All returns all orders for the authenticated account.
func (s *OrderService) All(market string, limit, page int, state OrderStateToQuery) ([]Order, error) {
var states []string
switch state {
case All:
states = []string{"done", "cancel", "wait", "convert"}
case Active:
states = []string{"wait", "convert"}
case Closed:
states = []string{"done", "cancel"}
default:
states = []string{"wait", "convert"}
}
payload := map[string]interface{}{
"market": market,
"limit": limit,
"page": page,
"state": states,
"order_by": "desc",
}
req, err := s.client.newAuthenticatedRequest("GET", "v2/orders", payload)
if err != nil {
return nil, err
}
response, err := s.client.sendRequest(req)
if err != nil {
return nil, err
}
var orders []Order
if err := response.DecodeJSON(&orders); err != nil {
return nil, err
}
return orders, nil
}
// CancelAll active orders for the authenticated account.
func (s *OrderService) CancelAll(side string, market string) error {
payload := map[string]interface{}{}
if side == "buy" || side == "sell" {
payload["side"] = side
}
if market != "all" {
payload["market"] = market
}
req, err := s.client.newAuthenticatedRequest("POST", "v2/orders/clear", payload)
if err != nil {
return err
}
_, err = s.client.sendRequest(req)
if err != nil {
return err
}
return nil
}
// Options carry the option fields for REST API
type Options map[string]interface{}
// Create a new order.
func (s *OrderService) Create(market string, side string, volume float64, price float64, orderType string, options Options) (*Order, error) {
options["market"] = market
options["volume"] = strconv.FormatFloat(volume, 'f', -1, 64)
options["price"] = strconv.FormatFloat(price, 'f', -1, 64)
options["side"] = side
options["ord_type"] = orderType
response, err := s.client.sendAuthenticatedRequest("POST", "v2/orders", options)
if err != nil {
return nil, err
}
var order = Order{}
if err := response.DecodeJSON(&order); err != nil {
return nil, err
}
return &order, nil
}
// Cancel the order with id `orderID`.
func (s *OrderService) Cancel(orderID uint64) error {
payload := map[string]interface{}{
"id": orderID,
}
req, err := s.client.newAuthenticatedRequest("POST", "v2/order/delete", payload)
if err != nil {
return err
}
_, err = s.client.sendRequest(req)
if err != nil {
return err
}
return nil
}
// Status retrieves the given order from the API.
func (s *OrderService) Get(orderID uint64) (*Order, error) {
payload := map[string]interface{}{
"id": orderID,
}
req, err := s.client.newAuthenticatedRequest("GET", "v2/order", payload)
if err != nil {
return &Order{}, err
}
response, err := s.client.sendRequest(req)
if err != nil {
return nil, err
}
var order = Order{}
if err := response.DecodeJSON(&order); err != nil {
return nil, err
}
return &order, nil
}
// Create multiple order in a single request
func (s *OrderService) CreateMulti(market string, orders []Order) ([]Order, error) {
var returnOrders []Order
req, err := s.client.newAuthenticatedRequest("POST", "v2/orders/multi", map[string]interface{}{
"market": market,
"orders": orders,
})
if err != nil {
return returnOrders, errors.Wrapf(err, "failed to create %s orders", market)
}
response, err := s.client.sendRequest(req)
if err != nil {
return returnOrders, err
}
if errJson := response.DecodeJSON(&returnOrders); errJson != nil {
return returnOrders, errJson
}
return returnOrders, err
}

View File

@ -0,0 +1,142 @@
package max
import (
"net/url"
"time"
"github.com/valyala/fastjson"
)
type PublicService struct {
client *RestClient
}
type Market struct {
ID string `json:"id"`
Name string `json:"name"`
BaseUnit string `json:"base_unit"`
BaseUnitPrecision int `json:"base_unit_precision"`
QuoteUnit string `json:"quote_unit"`
QuoteUnitPrecision int `json:"quote_unit_precision"`
}
type Ticker struct {
Time time.Time
At int64 `json:"at"`
Buy string `json:"buy"`
Sell string `json:"sell"`
Open string `json:"open"`
High string `json:"high"`
Low string `json:"low"`
Last string `json:"last"`
Volume string `json:"vol"`
VolumeInBTC string `json:"vol_in_btc"`
}
func (s *PublicService) Timestamp() (serverTimestamp int64, err error) {
// sync timestamp with server
req, err := s.client.newRequest("GET", "v2/timestamp", nil, nil)
if err != nil {
return 0, err
}
response, err := s.client.sendRequest(req)
if err != nil {
return 0, err
}
err = response.DecodeJSON(&serverTimestamp)
if err != nil {
return 0, err
}
return serverTimestamp, nil
}
func (s *PublicService) Markets() ([]Market, error) {
req, err := s.client.newRequest("GET", "v2/markets", url.Values{}, nil)
if err != nil {
return nil, err
}
response, err := s.client.sendRequest(req)
if err != nil {
return nil, err
}
var m []Market
if err := response.DecodeJSON(&m); err != nil {
return nil, err
}
return m, nil
}
func (s *PublicService) Tickers() (map[string]Ticker, error) {
var endPoint = "v2/tickers"
req, err := s.client.newRequest("GET", endPoint, url.Values{}, nil)
if err != nil {
return nil, err
}
response, err := s.client.sendRequest(req)
if err != nil {
return nil, err
}
v, err := fastjson.ParseBytes(response.Body)
if err != nil {
return nil, err
}
o, err := v.Object()
if err != nil {
return nil, err
}
var tickers = make(map[string]Ticker)
o.Visit(func(key []byte, v *fastjson.Value) {
var ticker = mustParseTicker(v)
tickers[string(key)] = ticker
})
return tickers, nil
}
func (s *PublicService) Ticker(market string) (*Ticker, error) {
var endPoint = "v2/tickers/" + market
req, err := s.client.newRequest("GET", endPoint, url.Values{}, nil)
if err != nil {
return nil, err
}
response, err := s.client.sendRequest(req)
if err != nil {
return nil, err
}
v, err := fastjson.ParseBytes(response.Body)
if err != nil {
return nil, err
}
var ticker = mustParseTicker(v)
return &ticker, nil
}
func mustParseTicker(v *fastjson.Value) Ticker {
var at = v.GetInt64("at")
return Ticker{
Time: time.Unix(at, 0),
At: at,
Buy: string(v.GetStringBytes("buy")),
Sell: string(v.GetStringBytes("sell")),
Volume: string(v.GetStringBytes("vol")),
VolumeInBTC: string(v.GetStringBytes("vol_in_btc")),
Last: string(v.GetStringBytes("last")),
Open: string(v.GetStringBytes("open")),
High: string(v.GetStringBytes("high")),
Low: string(v.GetStringBytes("low")),
}
}

View File

@ -9,13 +9,10 @@ import (
"github.com/gorilla/websocket"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
var ErrMessageTypeNotSupported = errors.New("message type currently not supported")
var logger = log.WithField("exchange", "max")
// Subscription is used for presenting the subscription metadata.
// This is used for sending subscribe and unsubscribe requests
type Subscription struct {

View File

@ -0,0 +1,347 @@
package max
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"math"
"net/http"
"net/url"
"regexp"
"strconv"
"sync/atomic"
"time"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
const (
// ProductionAPIURL is the official MAX API v2 Endpoint
ProductionAPIURL = "https://max-api.maicoin.com/api/v2"
UserAgent = "bbgo/1.0"
defaultHTTPTimeout = time.Second * 15
)
var logger = log.WithField("exchange", "max")
var htmlTagPattern = regexp.MustCompile("<[/]?[a-zA-Z-]+.*?>")
// The following variables are used for nonce.
// timeOffset is used for nonce
var timeOffset int64 = 0
// serverTimestamp is used for storing the server timestamp, default to Now
var serverTimestamp = time.Now().Unix()
// reqCount is used for nonce, this variable counts the API request count.
var reqCount int64 = 0
// Response is wrapper for standard http.Response and provides
// more methods.
type Response struct {
*http.Response
// Body overrides the composited Body field.
Body []byte
}
// newResponse is a wrapper of the http.Response instance, it reads the response body and close the file.
func newResponse(r *http.Response) (response *Response, err error) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return nil, err
}
err = r.Body.Close()
response = &Response{Response: r, Body: body}
return response, err
}
// String converts response body to string.
// An empty string will be returned if error.
func (r *Response) String() string {
return string(r.Body)
}
func (r *Response) DecodeJSON(o interface{}) error {
return json.Unmarshal(r.Body, o)
}
type RestClient struct {
client *http.Client
BaseURL *url.URL
// Authentication
APIKey string
APISecret string
AccountService *AccountService
PublicService *PublicService
TradeService *TradeService
OrderService *OrderService
// OrderBookService *OrderBookService
// MaxTokenService *MaxTokenService
// MaxKLineService *KLineService
// CreditService *CreditService
}
func NewRestClientWithHttpClient(baseURL string, httpClient *http.Client) *RestClient {
u, err := url.Parse(baseURL)
if err != nil {
panic(err)
}
var client = &RestClient{
client: httpClient,
BaseURL: u,
}
client.AccountService = &AccountService{client}
client.TradeService = &TradeService{client}
client.PublicService = &PublicService{client}
client.OrderService = &OrderService{client}
// client.OrderBookService = &OrderBookService{client}
// client.MaxTokenService = &MaxTokenService{client}
// client.MaxKLineService = &KLineService{client}
// client.CreditService = &CreditService{client}
client.initNonce()
return client
}
func NewRestClient(baseURL string) *RestClient {
return NewRestClientWithHttpClient(baseURL, &http.Client{
Timeout: defaultHTTPTimeout,
})
}
// Auth sets api key and secret for usage is requests that requires authentication.
func (c *RestClient) Auth(key string, secret string) *RestClient {
c.APIKey = key
c.APISecret = secret
return c
}
func (c *RestClient) initNonce() {
var clientTime = time.Now()
var err error
serverTimestamp, err = c.PublicService.Timestamp()
if err != nil {
logger.WithError(err).Panic("failed to sync timestamp with Max")
}
// 1 is for the request count mod 0.000 to 0.999
timeOffset = serverTimestamp - clientTime.Unix() - 1
}
func (c *RestClient) getNonce() int64 {
var seconds = time.Now().Unix()
var rc = atomic.AddInt64(&reqCount, 1)
return (seconds+timeOffset)*1000 + int64(math.Mod(float64(rc), 1000.0))
}
// NewRequest create new API request. Relative url can be provided in refURL.
func (c *RestClient) newRequest(method string, refURL string, params url.Values, body []byte) (*http.Request, error) {
rel, err := url.Parse(refURL)
if err != nil {
return nil, err
}
if params != nil {
rel.RawQuery = params.Encode()
}
var req *http.Request
u := c.BaseURL.ResolveReference(rel)
req, err = http.NewRequest(method, u.String(), bytes.NewReader(body))
if err != nil {
return nil, err
}
return req, nil
}
// newAuthenticatedRequest creates new http request for authenticated routes.
func (c *RestClient) newAuthenticatedRequest(m string, refURL string, data map[string]interface{}) (*http.Request, error) {
rel, err := url.Parse(refURL)
if err != nil {
return nil, err
}
payload := map[string]interface{}{
"nonce": c.getNonce(),
"path": c.BaseURL.ResolveReference(rel).Path,
}
for k, v := range data {
payload[k] = v
}
p, err := json.Marshal(payload)
if err != nil {
return nil, err
}
req, err := c.newRequest(m, refURL, nil, p)
if err != nil {
return nil, err
}
encoded := base64.StdEncoding.EncodeToString(p)
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "application/json")
req.Header.Add("X-MAX-ACCESSKEY", c.APIKey)
req.Header.Add("X-MAX-PAYLOAD", encoded)
req.Header.Add("X-MAX-SIGNATURE", signPayload(encoded, c.APISecret))
return req, nil
}
func signPayload(payload string, secret string) string {
var sig = hmac.New(sha256.New, []byte(secret))
_, err := sig.Write([]byte(payload))
if err != nil {
return ""
}
return hex.EncodeToString(sig.Sum(nil))
}
func (c *RestClient) Do(req *http.Request) (resp *http.Response, err error) {
req.Header.Set("User-Agent", UserAgent)
return c.client.Do(req)
}
// sendRequest sends the request to the API server and handle the response
func (c *RestClient) sendRequest(req *http.Request) (*Response, error) {
resp, err := c.Do(req)
if err != nil {
return nil, err
}
// newResponse reads the response body and return a new Response object
response, err := newResponse(resp)
if err != nil {
return response, err
}
// Check error, if there is an error, return the ErrorResponse struct type
if isError(response) {
errorResponse, err := toErrorResponse(response)
if err != nil {
return response, err
}
return response, errorResponse
}
return response, nil
}
func (c *RestClient) sendAuthenticatedRequest(m string, refURL string, data map[string]interface{}) (*Response, error) {
req, err := c.newAuthenticatedRequest(m, refURL, data)
if err != nil {
return nil, err
}
response, err := c.sendRequest(req)
if err != nil {
return nil, err
}
return response, err
}
// FIXME: should deprecate the polling usage from the websocket struct
func (c *RestClient) GetTrades(market string, lastTradeID int64) ([]byte, error) {
params := url.Values{}
params.Add("market", market)
if lastTradeID > 0 {
params.Add("from", strconv.Itoa(int(lastTradeID)))
}
return c.get("/trades", params)
}
// get sends GET http request to the api endpoint, the urlPath must start with a slash '/'
func (c *RestClient) get(urlPath string, values url.Values) ([]byte, error) {
var reqURL = c.BaseURL.String() + urlPath
// Create request
req, err := http.NewRequest("GET", reqURL, nil)
if err != nil {
return nil, fmt.Errorf("could not init request: %s", err.Error())
}
req.URL.RawQuery = values.Encode()
req.Header.Add("User-Agent", UserAgent)
// Execute request
resp, err := c.client.Do(req)
if err != nil {
return nil, fmt.Errorf("could not execute request: %s", err.Error())
}
defer resp.Body.Close()
// Load request
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("could not read response: %s", err.Error())
}
return body, nil
}
// ErrorResponse is the custom error type that is returned if the API returns an
// error.
type ErrorField struct {
Code int `json:"code"`
Message string `json:"message"`
}
type ErrorResponse struct {
*Response
Err ErrorField `json:"error"`
}
func (r *ErrorResponse) Error() string {
return fmt.Sprintf("%s %s: %d %d %s",
r.Response.Response.Request.Method,
r.Response.Response.Request.URL.String(),
r.Response.Response.StatusCode,
r.Err.Code,
r.Err.Message,
)
}
// isError check the response status code so see if a response is an error.
func isError(response *Response) bool {
var c = response.StatusCode
return c < 200 || c > 299
}
// toErrorResponse tries to convert/parse the server response to the standard Error interface object
func toErrorResponse(response *Response) (errorResponse *ErrorResponse, err error) {
errorResponse = &ErrorResponse{Response: response}
contentType := response.Header.Get("content-type")
switch contentType {
case "text/json", "application/json", "application/json; charset=utf-8":
var err = response.DecodeJSON(errorResponse)
if err != nil {
return errorResponse, errors.Wrapf(err, "failed to decode json for response: %d %s", response.StatusCode, string(response.Body))
}
return errorResponse, nil
case "text/html":
// convert 5xx error from the HTML page to the ErrorResponse
errorResponse.Err.Message = htmlTagPattern.ReplaceAllLiteralString(string(response.Body), "")
return errorResponse, nil
}
return errorResponse, fmt.Errorf("unexpected response content type %s", contentType)
}

View File

@ -0,0 +1,131 @@
package max
import (
"net/url"
"strconv"
"time"
)
// Trade represents one returned trade on the max platform.
type Trade struct {
ID uint64 `json:"id" db:"exchange_id"`
Price string `json:"price" db:"price"`
Volume string `json:"volume" db:"volume"`
Funds string `json:"funds"`
Market string `json:"market" db:"market"`
MarketName string `json:"market_name"`
CreatedAt int64 `json:"created_at"`
CreatedAtMilliSeconds int64 `json:"created_at_in_ms"`
Side string `json:"side" db:"side"`
OrderID uint64 `json:"order_id" db:"order_id"`
Fee string `json:"fee" db:"fee"` // float number as string
FeeCurrency string `json:"fee_currency" db:"fee_currency"`
CreatedAtInDB time.Time `db:"created_at"`
InsertedAt time.Time `db:"inserted_at"`
}
type QueryTradeOptions struct {
Market string `json:"market"`
Timestamp int64 `json:"timestamp,omitempty"`
From int64 `json:"from,omitempty"`
To int64 `json:"to,omitempty"`
OrderBy string `json:"order_by,omitempty"`
Page int `json:"page,omitempty"`
Offset int `json:"offset,omitempty"`
Limit int64 `json:"limit,omitempty"`
}
type TradeService struct {
client *RestClient
}
func (options *QueryTradeOptions) Map() map[string]interface{} {
var data = map[string]interface{}{}
data["market"] = options.Market
if options.Limit > 0 {
data["limit"] = options.Limit
}
if options.Timestamp > 0 {
data["timestamp"] = options.Timestamp
}
if options.From >= 0 {
data["from"] = options.From
}
if options.To > options.From {
data["to"] = options.To
}
if len(options.OrderBy) > 0 {
// could be "asc" or "desc"
data["order_by"] = options.OrderBy
}
return data
}
func (options *QueryTradeOptions) Params() url.Values {
var params = url.Values{}
params.Add("market", options.Market)
if options.Limit > 0 {
params.Add("limit", strconv.FormatInt(options.Limit, 10))
}
if options.Timestamp > 0 {
params.Add("timestamp", strconv.FormatInt(options.Timestamp, 10))
}
if options.From >= 0 {
params.Add("from", strconv.FormatInt(options.From, 10))
}
if options.To > options.From {
params.Add("to", strconv.FormatInt(options.To, 10))
}
if len(options.OrderBy) > 0 {
// could be "asc" or "desc"
params.Add("order_by", options.OrderBy)
}
return params
}
func (s *TradeService) MyTrades(options QueryTradeOptions) ([]Trade, error) {
req, err := s.client.newAuthenticatedRequest("GET", "v2/trades/my", options.Map())
if err != nil {
return nil, err
}
response, err := s.client.sendRequest(req)
if err != nil {
return nil, err
}
var v []Trade
if err := response.DecodeJSON(&v); err != nil {
return nil, err
}
return v, nil
}
func (s *TradeService) Trades(options QueryTradeOptions) ([]Trade, error) {
var params = options.Params()
req, err := s.client.newRequest("GET", "v2/trades", params, nil)
if err != nil {
return nil, err
}
response, err := s.client.sendRequest(req)
if err != nil {
return nil, err
}
var v []Trade
if err := response.DecodeJSON(&v); err != nil {
return nil, err
}
return v, nil
}

View File

@ -155,14 +155,14 @@ func parseTradeSnapshotEvent(v *fastjson.Value) (e TradeSnapshotEvent) {
return e
}
type Balance struct {
type BalanceMessage struct {
Currency string `json:"cu"`
Available string `json:"av"`
Locked string `json:"l"`
}
func parseBalance(v *fastjson.Value) Balance {
return Balance{
func parseBalance(v *fastjson.Value) BalanceMessage {
return BalanceMessage{
Currency: string(v.GetStringBytes("cu")),
Available: string(v.GetStringBytes("av")),
Locked: string(v.GetStringBytes("l")),
@ -172,7 +172,7 @@ func parseBalance(v *fastjson.Value) Balance {
type AccountUpdateEvent struct {
BaseEvent
Balances []Balance `json:"B"`
Balances []BalanceMessage `json:"B"`
}
func parserAccountUpdateEvent(v *fastjson.Value) (e AccountUpdateEvent) {
@ -188,7 +188,7 @@ func parserAccountUpdateEvent(v *fastjson.Value) (e AccountUpdateEvent) {
type AccountSnapshotEvent struct {
BaseEvent
Balances []Balance `json:"B"`
Balances []BalanceMessage `json:"B"`
}
func parserAccountSnapshotEvent(v *fastjson.Value) (e AccountSnapshotEvent) {