Skip to content

Commit 97c28a8

Browse files
shazbertRyan O'Hara-Reidthrasher-
authored
GateIO: Update API fields due to widespread upgrade (thrasher-corp#2158)
* fix: update Gate.io websocket handling for size and amount conversions - Added "X-Gate-Size-Decimal" header in the websocket connection for spot trading. - Updated futures and options processing to convert sizes and amounts to float64 where necessary. - Ensured consistency in handling size and amount across various functions in the Gate.io exchange wrapper. * rm verbosity * docs: Update type usage guidelines for API number handling due to benchmarks below `types.Number` vs `float64` with `,string` — benchmark findings Benchmarked at count=100 on AMD Ryzen 7 5800X (Windows, amd64): | Field type | Avg ns/op | B/op | allocs/op | |---|---|---|---| | `float64` (bare number) | ~414 | 216 | 4 | | `types.Number` (quoted string) | ~403 | 216 | 4 | | `float64` + `,string` tag (quoted string) | ~506 | 264 | **7** | `types.Number` is identical in cost to a bare `float64` — the custom `UnmarshalJSON` allocates nothing of its own (the compiler eliminates the `string(data)` conversion in `strconv.ParseFloat`). By contrast, `float64` with `,string` is ~25% slower and produces 3 extra allocations and 48 extra bytes per decode due to the stdlib reflection path it takes. +**Calling `.Float64()` on a `types.Number` field is optional** — `types.Number` is defined as `type Number float64`, so a direct conversion `float64(n)` or storing it as `types.Number` and passing it where a `float64` is needed are both zero-cost. `.Float64()` is a convenience accessor and may be omitted when the call site already works with `types.Number` directly. * glorious: don't send header down the spot connection, mv it to the futures connection * crank/glorious: nits * thrasher-:nits * glorious: nits in your head * Update exchanges/gateio/gateio_test.go Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io> * Update exchanges/gateio/gateio_test.go Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io> * Update exchanges/gateio/gateio_test.go Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io> * gate f usage --------- Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io> Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>
1 parent d66d7b1 commit 97c28a8

12 files changed

+667
-592
lines changed

docs/CODING_GUIDELINES.md

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,9 @@ Refer to the [ADD_NEW_EXCHANGE.md](/docs/ADD_NEW_EXCHANGE.md) document for compr
2222
### Type Usage
2323

2424
- Use the most appropriate native Go types for struct fields:
25-
- If the API returns numbers as strings, use float64 with the `json:",string"` tag.
26-
- For timestamps, use `time.Time` if Go's JSON unmarshalling supports the format directly.
27-
- If native Go types are not supported directly, use the following built-in types:
28-
- `types.Time` for Unix timestamps that require custom unmarshalling.
29-
- `types.Number` for numerical float values where an exchange API may return either a `string` or `float64` value.
25+
- If the API always returns a number as a bare JSON number, use `float64`.
26+
- If the API returns a number as a quoted JSON string, or may return either a string or a bare number, use `types.Number`**do not** use `float64` with the `json:",string"` tag.
27+
- For timestamps, use `time.Time` if Go's JSON unmarshalling supports the format directly; otherwise use `types.Time` for Unix timestamps that require custom unmarshalling.
3028
- Always use full and descriptive field names for clarity and consistency. Avoid short API-provided aliases unless compatibility requires it.
3129
- Default to `uint64` for exchange API parameters and structs for integers where appropriate.
3230
- Avoid `int` (size varies by architecture) or `int64` (allows negatives where they don't make sense).
@@ -112,13 +110,27 @@ Use `require` and `assert` appropriately:
112110

113111
- Use when test flow depends on the result.
114112
- Messages must contain **"must"** (e.g., "response must not be nil").
115-
- Use the *f* variants when using format specifiers (e.g., `require.Equalf`).
116113

117114
#### assert
118115

119116
- Use when the test can proceed regardless of the check.
120117
- Messages must contain **"should"** (e.g., "status code should be 200").
121-
- Use `assert.Equalf`, etc., when applicable.
118+
119+
#### `f` variants (`assert.ErrorIsf`, `require.NoErrorf`, etc.)
120+
121+
- Only use `f` variants when the message contains **format verbs** (e.g., `%s`, `%d`, `%v`).
122+
- If the message is a plain string with no format verbs, use the non-`f` variant.
123+
124+
```go
125+
// Correct — format verb %s requires the f variant:
126+
assert.NoErrorf(t, err, "UpdateAccountInfo should not error for asset %s", a)
127+
128+
// Correct — plain message, no format verbs:
129+
assert.ErrorIs(t, err, errInvalidOrderSize, "validate should return expected error")
130+
131+
// Wrong — f variant used without format verbs:
132+
assert.ErrorIsf(t, err, errInvalidOrderSize, "validate should return expected error")
133+
```
122134

123135
### Test Coverage
124136

exchanges/gateio/gateio.go

Lines changed: 56 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,7 @@ func (e *Exchange) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange
933933
headers["TIMESTAMP"] = strconv.FormatInt(timestamp.Unix(), 10)
934934
headers["Accept"] = "application/json"
935935
headers["SIGN"] = sig
936+
headers["X-Gate-Size-Decimal"] = "1"
936937
urlPath = ePoint + urlPath
937938
if param != nil {
938939
urlPath = common.EncodeURLValues(urlPath, param)
@@ -993,6 +994,7 @@ func (e *Exchange) SendHTTPRequest(ctx context.Context, ep exchange.URL, epl req
993994
HTTPDebugging: e.HTTPDebugging,
994995
HTTPRecording: e.HTTPRecording,
995996
HTTPMockDataSliceLimit: e.HTTPMockDataSliceLimit,
997+
Headers: map[string]string{"X-Gate-Size-Decimal": "1"},
996998
}
997999
return e.SendPayload(ctx, epl, func() (*request.Item, error) {
9981000
return item, nil
@@ -1220,12 +1222,12 @@ func (e *Exchange) GetWithdrawalStatus(ctx context.Context, ccy currency.Code) (
12201222
}
12211223

12221224
// GetSubAccountBalances retrieve sub account balances
1223-
func (e *Exchange) GetSubAccountBalances(ctx context.Context, subAccountUserID string) ([]FuturesSubAccountBalance, error) {
1225+
func (e *Exchange) GetSubAccountBalances(ctx context.Context, subAccountUserID string) ([]SubAccountBalance, error) {
12241226
params := url.Values{}
12251227
if subAccountUserID != "" {
12261228
params.Set("sub_uid", subAccountUserID)
12271229
}
1228-
var response []FuturesSubAccountBalance
1230+
var response []SubAccountBalance
12291231
return response, e.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, walletSubAccountBalancesEPL, http.MethodGet, walletSubAccountBalance, params, nil, &response)
12301232
}
12311233

@@ -2236,17 +2238,17 @@ func (e *Exchange) UpdatePositionLeverageInDualMode(ctx context.Context, settle
22362238
// Set reduce_only to true can keep the position from changing side when reducing position size
22372239
// In single position mode, to close a position, you need to set size to 0 and close to true
22382240
// In dual position mode, to close one side position, you need to set auto_size side, reduce_only to true and size to 0
2239-
func (e *Exchange) PlaceFuturesOrder(ctx context.Context, arg *ContractOrderCreateParams) (*Order, error) {
2241+
func (e *Exchange) PlaceFuturesOrder(ctx context.Context, arg *FuturesOrderCreateParams) (*FuturesOrder, error) {
22402242
if err := arg.validate(true); err != nil {
22412243
return nil, err
22422244
}
2243-
var response *Order
2245+
var response *FuturesOrder
22442246
return response, e.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, perpetualSubmitOrderEPL, http.MethodPost, futuresPath+arg.Settle.Item.Lower+ordersPath, nil, &arg, &response)
22452247
}
22462248

22472249
// GetFuturesOrders retrieves list of futures orders
22482250
// Zero-filled order cannot be retrieved 10 minutes after order cancellation
2249-
func (e *Exchange) GetFuturesOrders(ctx context.Context, contract currency.Pair, status, lastID string, settle currency.Code, limit, offset uint64, countTotal bool) ([]Order, error) {
2251+
func (e *Exchange) GetFuturesOrders(ctx context.Context, contract currency.Pair, status, lastID string, settle currency.Code, limit, offset uint64, countTotal bool) ([]FuturesOrder, error) {
22502252
if settle.IsEmpty() {
22512253
return nil, errEmptyOrInvalidSettlementCurrency
22522254
}
@@ -2270,13 +2272,13 @@ func (e *Exchange) GetFuturesOrders(ctx context.Context, contract currency.Pair,
22702272
if countTotal && status != statusOpen {
22712273
params.Set("count_total", "1")
22722274
}
2273-
var response []Order
2275+
var response []FuturesOrder
22742276
return response, e.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, perpetualGetOrdersEPL, http.MethodGet, futuresPath+settle.Item.Lower+ordersPath, params, nil, &response)
22752277
}
22762278

22772279
// CancelMultipleFuturesOpenOrders ancel all open orders
22782280
// Zero-filled order cannot be retrieved 10 minutes after order cancellation
2279-
func (e *Exchange) CancelMultipleFuturesOpenOrders(ctx context.Context, contract currency.Pair, side string, settle currency.Code) ([]Order, error) {
2281+
func (e *Exchange) CancelMultipleFuturesOpenOrders(ctx context.Context, contract currency.Pair, side string, settle currency.Code) ([]FuturesOrder, error) {
22802282
if settle.IsEmpty() {
22812283
return nil, errEmptyOrInvalidSettlementCurrency
22822284
}
@@ -2288,7 +2290,7 @@ func (e *Exchange) CancelMultipleFuturesOpenOrders(ctx context.Context, contract
22882290
params.Set("side", side)
22892291
}
22902292
params.Set("contract", contract.String())
2291-
var response []Order
2293+
var response []FuturesOrder
22922294
return response, e.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, perpetualGetOrdersEPL, http.MethodDelete, futuresPath+settle.Item.Lower+ordersPath, params, nil, &response)
22932295
}
22942296

@@ -2300,7 +2302,7 @@ func (e *Exchange) CancelMultipleFuturesOpenOrders(ctx context.Context, contract
23002302
// In the returned result, the succeeded field of type bool indicates whether the execution was successful or not
23012303
// If the execution is successful, the normal order content is included; if the execution fails, the label field is included to indicate the cause of the error
23022304
// In the rate limiting, each order is counted individually
2303-
func (e *Exchange) PlaceBatchFuturesOrders(ctx context.Context, settle currency.Code, args []ContractOrderCreateParams) ([]Order, error) {
2305+
func (e *Exchange) PlaceBatchFuturesOrders(ctx context.Context, settle currency.Code, args []FuturesOrderCreateParams) ([]FuturesOrder, error) {
23042306
if settle.IsEmpty() {
23052307
return nil, errEmptyOrInvalidSettlementCurrency
23062308
}
@@ -2315,36 +2317,36 @@ func (e *Exchange) PlaceBatchFuturesOrders(ctx context.Context, settle currency.
23152317
return nil, fmt.Errorf("%w: %q expected %q", errEmptyOrInvalidSettlementCurrency, args[x].Settle, settle)
23162318
}
23172319
}
2318-
var response []Order
2320+
var response []FuturesOrder
23192321
return response, e.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, perpetualSubmitBatchOrdersEPL, http.MethodPost, futuresPath+settle.Item.Lower+"/batch_orders", nil, &args, &response)
23202322
}
23212323

23222324
// GetSingleFuturesOrder retrieves a single order by its identifier
2323-
func (e *Exchange) GetSingleFuturesOrder(ctx context.Context, settle currency.Code, orderID string) (*Order, error) {
2325+
func (e *Exchange) GetSingleFuturesOrder(ctx context.Context, settle currency.Code, orderID string) (*FuturesOrder, error) {
23242326
if settle.IsEmpty() {
23252327
return nil, errEmptyOrInvalidSettlementCurrency
23262328
}
23272329
if orderID == "" {
23282330
return nil, fmt.Errorf("%w, 'order_id' cannot be empty", errInvalidOrderID)
23292331
}
2330-
var response *Order
2332+
var response *FuturesOrder
23312333
return response, e.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, perpetualFetchOrderEPL, http.MethodGet, futuresPath+settle.Item.Lower+"/orders/"+orderID, nil, nil, &response)
23322334
}
23332335

23342336
// CancelSingleFuturesOrder cancel a single order
2335-
func (e *Exchange) CancelSingleFuturesOrder(ctx context.Context, settle currency.Code, orderID string) (*Order, error) {
2337+
func (e *Exchange) CancelSingleFuturesOrder(ctx context.Context, settle currency.Code, orderID string) (*FuturesOrder, error) {
23362338
if settle.IsEmpty() {
23372339
return nil, errEmptyOrInvalidSettlementCurrency
23382340
}
23392341
if orderID == "" {
23402342
return nil, fmt.Errorf("%w, 'order_id' cannot be empty", errInvalidOrderID)
23412343
}
2342-
var response *Order
2344+
var response *FuturesOrder
23432345
return response, e.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, perpetualCancelOrderEPL, http.MethodDelete, futuresPath+settle.Item.Lower+"/orders/"+orderID, nil, nil, &response)
23442346
}
23452347

23462348
// AmendFuturesOrder amends an existing futures order
2347-
func (e *Exchange) AmendFuturesOrder(ctx context.Context, settle currency.Code, orderID string, arg AmendFuturesOrderParam) (*Order, error) {
2349+
func (e *Exchange) AmendFuturesOrder(ctx context.Context, settle currency.Code, orderID string, arg AmendFuturesOrderParam) (*FuturesOrder, error) {
23482350
if settle.IsEmpty() {
23492351
return nil, errEmptyOrInvalidSettlementCurrency
23502352
}
@@ -2354,7 +2356,7 @@ func (e *Exchange) AmendFuturesOrder(ctx context.Context, settle currency.Code,
23542356
if arg.Size <= 0 && arg.Price <= 0 {
23552357
return nil, errors.New("missing update 'size' or 'price', please specify 'size' or 'price' or both information")
23562358
}
2357-
var response *Order
2359+
var response *FuturesOrder
23582360
return response, e.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, perpetualAmendOrderEPL, http.MethodPut, futuresPath+settle.Item.Lower+"/orders/"+orderID, nil, &arg, &response)
23592361
}
23602362

@@ -2756,17 +2758,17 @@ func (e *Exchange) UpdateDeliveryPositionLeverage(ctx context.Context, settle cu
27562758

27572759
// PlaceDeliveryOrder create a futures order
27582760
// Zero-filled order cannot be retrieved 10 minutes after order cancellation
2759-
func (e *Exchange) PlaceDeliveryOrder(ctx context.Context, arg *ContractOrderCreateParams) (*Order, error) {
2761+
func (e *Exchange) PlaceDeliveryOrder(ctx context.Context, arg *DeliveryOrderCreateParams) (*FuturesOrder, error) {
27602762
if err := arg.validate(true); err != nil {
27612763
return nil, err
27622764
}
2763-
var response *Order
2765+
var response *FuturesOrder
27642766
return response, e.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, deliverySubmitOrderEPL, http.MethodPost, deliveryPath+arg.Settle.Item.Lower+ordersPath, nil, &arg, &response)
27652767
}
27662768

27672769
// GetDeliveryOrders list futures orders
27682770
// Zero-filled order cannot be retrieved 10 minutes after order cancellation
2769-
func (e *Exchange) GetDeliveryOrders(ctx context.Context, contract currency.Pair, status string, settle currency.Code, lastID string, limit, offset uint64, countTotal bool) ([]Order, error) {
2771+
func (e *Exchange) GetDeliveryOrders(ctx context.Context, contract currency.Pair, status string, settle currency.Code, lastID string, limit, offset uint64, countTotal bool) ([]FuturesOrder, error) {
27702772
if settle.IsEmpty() {
27712773
return nil, errEmptyOrInvalidSettlementCurrency
27722774
}
@@ -2790,13 +2792,13 @@ func (e *Exchange) GetDeliveryOrders(ctx context.Context, contract currency.Pair
27902792
if countTotal && status != statusOpen {
27912793
params.Set("count_total", "1")
27922794
}
2793-
var response []Order
2795+
var response []FuturesOrder
27942796
return response, e.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, deliveryGetOrdersEPL, http.MethodGet, deliveryPath+settle.Item.Lower+ordersPath, params, nil, &response)
27952797
}
27962798

27972799
// CancelMultipleDeliveryOrders cancel all open orders matched
27982800
// Zero-filled order cannot be retrieved 10 minutes after order cancellation
2799-
func (e *Exchange) CancelMultipleDeliveryOrders(ctx context.Context, contract currency.Pair, side string, settle currency.Code) ([]Order, error) {
2801+
func (e *Exchange) CancelMultipleDeliveryOrders(ctx context.Context, contract currency.Pair, side string, settle currency.Code) ([]FuturesOrder, error) {
28002802
if settle.IsEmpty() {
28012803
return nil, errEmptyOrInvalidSettlementCurrency
28022804
}
@@ -2808,32 +2810,32 @@ func (e *Exchange) CancelMultipleDeliveryOrders(ctx context.Context, contract cu
28082810
params.Set("side", side)
28092811
}
28102812
params.Set("contract", contract.String())
2811-
var response []Order
2813+
var response []FuturesOrder
28122814
return response, e.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, deliveryCancelOrdersEPL, http.MethodDelete, deliveryPath+settle.Item.Lower+ordersPath, params, nil, &response)
28132815
}
28142816

28152817
// GetSingleDeliveryOrder Get a single order
28162818
// Zero-filled order cannot be retrieved 10 minutes after order cancellation
2817-
func (e *Exchange) GetSingleDeliveryOrder(ctx context.Context, settle currency.Code, orderID string) (*Order, error) {
2819+
func (e *Exchange) GetSingleDeliveryOrder(ctx context.Context, settle currency.Code, orderID string) (*FuturesOrder, error) {
28182820
if settle.IsEmpty() {
28192821
return nil, errEmptyOrInvalidSettlementCurrency
28202822
}
28212823
if orderID == "" {
28222824
return nil, fmt.Errorf("%w, 'order_id' cannot be empty", errInvalidOrderID)
28232825
}
2824-
var response *Order
2826+
var response *FuturesOrder
28252827
return response, e.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, deliveryGetOrderEPL, http.MethodGet, deliveryPath+settle.Item.Lower+"/orders/"+orderID, nil, nil, &response)
28262828
}
28272829

28282830
// CancelSingleDeliveryOrder cancel a single order
2829-
func (e *Exchange) CancelSingleDeliveryOrder(ctx context.Context, settle currency.Code, orderID string) (*Order, error) {
2831+
func (e *Exchange) CancelSingleDeliveryOrder(ctx context.Context, settle currency.Code, orderID string) (*FuturesOrder, error) {
28302832
if settle.IsEmpty() {
28312833
return nil, errEmptyOrInvalidSettlementCurrency
28322834
}
28332835
if orderID == "" {
28342836
return nil, fmt.Errorf("%w, 'order_id' cannot be empty", errInvalidOrderID)
28352837
}
2836-
var response *Order
2838+
var response *FuturesOrder
28372839
return response, e.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, deliveryCancelOrderEPL, http.MethodDelete, deliveryPath+settle.Item.Lower+"/orders/"+orderID, nil, nil, &response)
28382840
}
28392841

@@ -3571,40 +3573,53 @@ func (e *Exchange) GetUserTransactionRateLimitInfo(ctx context.Context) ([]UserT
35713573
return resp, e.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, spotAccountsEPL, http.MethodGet, "account/rate_limit", nil, nil, &resp)
35723574
}
35733575

3574-
// validate validates the ContractOrderCreateParams
3575-
func (c *ContractOrderCreateParams) validate(isRest bool) error {
3576+
// validate validates the FuturesOrderCreateParams
3577+
func (c *FuturesOrderCreateParams) validate(isRest bool) error {
35763578
if err := common.NilGuard(c); err != nil {
35773579
return err
35783580
}
3579-
if c.Contract.IsEmpty() {
3581+
return validateOrderCreateParams(c.Contract, c.Size, c.Price, c.AutoSize, c.TimeInForce, c.Text, c.Settle, isRest)
3582+
}
3583+
3584+
// validate validates the DeliveryOrderCreateParams
3585+
func (c *DeliveryOrderCreateParams) validate(isRest bool) error {
3586+
if err := common.NilGuard(c); err != nil {
3587+
return err
3588+
}
3589+
return validateOrderCreateParams(c.Contract, c.Size, c.Price, c.AutoSize, c.TimeInForce, c.Text, c.Settle, isRest)
3590+
}
3591+
3592+
// validateOrderCreateParams validates common order creation parameters shared by futures and delivery orders.
3593+
func validateOrderCreateParams(contract currency.Pair, size, price float64, autoSize, timeInForce, text string, settle currency.Code, isRest bool) error {
3594+
if contract.IsEmpty() {
35803595
return currency.ErrCurrencyPairEmpty
35813596
}
3582-
if c.Size == 0 && c.AutoSize == "" {
3597+
if size == 0 && autoSize == "" {
35833598
return errInvalidOrderSize
35843599
}
3585-
if c.TimeInForce != "" {
3586-
if _, err := timeInForceFromString(c.TimeInForce); err != nil {
3600+
if timeInForce != "" {
3601+
if _, err := timeInForceFromString(timeInForce); err != nil {
35873602
return err
35883603
}
35893604
}
3590-
if c.Price == 0 && c.TimeInForce != iocTIF && c.TimeInForce != fokTIF {
3591-
return fmt.Errorf("%w: %q; only 'ioc' and 'fok' allowed for market order", order.ErrUnsupportedTimeInForce, c.TimeInForce)
3605+
if price == 0 && timeInForce != iocTIF && timeInForce != fokTIF {
3606+
return fmt.Errorf("%w: %q; only 'ioc' and 'fok' allowed for market order", order.ErrUnsupportedTimeInForce, timeInForce)
35923607
}
3593-
if c.Text != "" && !strings.HasPrefix(c.Text, "t-") {
3608+
if text != "" && !strings.HasPrefix(text, "t-") {
35943609
return errInvalidTextPrefix
35953610
}
3596-
if c.AutoSize != "" {
3597-
if c.AutoSize != "close_long" && c.AutoSize != "close_short" {
3598-
return fmt.Errorf("%w: %q", errInvalidAutoSize, c.AutoSize)
3611+
if autoSize != "" {
3612+
if autoSize != "close_long" && autoSize != "close_short" {
3613+
return fmt.Errorf("%w: %q", errInvalidAutoSize, autoSize)
35993614
}
3600-
if c.Size != 0 {
3615+
if size != 0 {
36013616
return fmt.Errorf("%w: size needs to be zero when auto size is set", errInvalidOrderSize)
36023617
}
36033618
}
36043619
// REST requests require a settlement currency, but it can be anything
36053620
// Websocket requests may have an empty settlement currency, or it must be BTC or USDT
3606-
if (isRest && c.Settle.IsEmpty()) ||
3607-
(!isRest && !c.Settle.IsEmpty() && !c.Settle.Equal(currency.BTC) && !c.Settle.Equal(currency.USDT)) {
3621+
if (isRest && settle.IsEmpty()) ||
3622+
(!isRest && !settle.IsEmpty() && !settle.Equal(currency.BTC) && !settle.Equal(currency.USDT)) {
36083623
return errEmptyOrInvalidSettlementCurrency
36093624
}
36103625
return nil

0 commit comments

Comments
 (0)