Skip to content

Commit b711c7c

Browse files
committed
complete sub-account transfers for okx
1 parent a107625 commit b711c7c

12 files changed

Lines changed: 211 additions & 83 deletions

File tree

cmd/oc/exchange/cmd.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ func unwrapExchangeConfig(ctx context.Context) *oc.ExchangeConfig {
3030
return ctx.Value(exchangeConfigKey).(*oc.ExchangeConfig)
3131
}
3232

33-
func unwrapAccountSecrets(ctx context.Context) *oc.MultiSecret {
34-
return ctx.Value(exchangeAccountSecretsKey).(*oc.MultiSecret)
33+
func unwrapAccountSecrets(ctx context.Context) *oc.Account {
34+
return ctx.Value(exchangeAccountSecretsKey).(*oc.Account)
3535
}
3636

37-
func unwrapAccountConfig(ctx context.Context) (*oc.ExchangeConfig, *oc.MultiSecret) {
37+
func unwrapAccountConfig(ctx context.Context) (*oc.ExchangeConfig, *oc.Account) {
3838
exchangeConfig := unwrapExchangeConfig(ctx)
3939
secrets := unwrapAccountSecrets(ctx)
4040
return exchangeConfig, secrets
@@ -66,31 +66,31 @@ func NewExchangeCmd() *cobra.Command {
6666
}
6767
slog.Info("Using exchange", "exchange", exchange)
6868
ctx = context.WithValue(ctx, exchangeConfigKey, exchangeConfig)
69-
var secrets *oc.MultiSecret
69+
var account *oc.Account
7070

7171
if subaccountId != "" {
7272
for _, subaccount := range exchangeConfig.SubAccounts {
7373
if string(subaccount.Id) == subaccountId || subaccount.Alias == subaccountId {
74-
secrets = &subaccount.MultiSecret
74+
account = subaccount.AsAccount()
7575
}
7676
}
77-
if secrets == nil {
77+
if account == nil {
7878
return fmt.Errorf("subaccount %s not found in configuration for %s", subaccountId, exchange)
7979
}
80-
err = secrets.LoadSecrets()
80+
err = account.LoadSecrets()
8181
if err != nil {
8282
return fmt.Errorf("could not load secrets for %s subaccount %s: %w", exchange, subaccountId, err)
8383
}
8484
} else {
85-
secrets = &exchangeConfig.MultiSecret
86-
err = secrets.LoadSecrets()
85+
account = exchangeConfig.AsAccount()
86+
err = account.LoadSecrets()
8787
if err != nil {
8888
return fmt.Errorf("could not load secrets for %s: %w", exchange, err)
8989
}
9090
}
9191

9292
ctx = context.WithValue(ctx, configContextKey, config)
93-
ctx = context.WithValue(ctx, exchangeAccountSecretsKey, secrets)
93+
ctx = context.WithValue(ctx, exchangeAccountSecretsKey, account)
9494
preCmd.SetContext(ctx)
9595

9696
return nil

cmd/oc/exchange/list_account_types.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,14 @@ import (
99
"github.com/spf13/cobra"
1010
)
1111

12-
var NopSecrets = &oc.MultiSecret{
13-
ApiKeyRef: secret.Secret("raw:AAA"),
14-
SecretKeyRef: secret.Secret("raw:AAA"),
15-
PassphraseRef: secret.Secret("raw:AAA"),
12+
var NopAccount = &oc.Account{
13+
Id: "",
14+
SubAccount: false,
15+
MultiSecret: oc.MultiSecret{
16+
ApiKeyRef: secret.Secret("raw:AAA"),
17+
SecretKeyRef: secret.Secret("raw:AAA"),
18+
PassphraseRef: secret.Secret("raw:AAA"),
19+
},
1620
}
1721

1822
// This just reads the configuration currently, not yet exposing an API call for this.
@@ -39,7 +43,7 @@ func NewListAccountTypesCmd() *cobra.Command {
3943
return nil
4044
},
4145
RunE: func(cmd *cobra.Command, args []string) error {
42-
cli, err := loader.NewClient(exchangeConfig, NopSecrets)
46+
cli, err := loader.NewClient(exchangeConfig, NopAccount)
4347
if err != nil {
4448
return err
4549
}

cmd/oc/exchange/list_subaccounts.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func NewListSubaccountsCmd() *cobra.Command {
3232
return nil
3333
},
3434
RunE: func(cmd *cobra.Command, args []string) error {
35-
cli, err := loader.NewClient(exchangeConfig, NopSecrets)
35+
cli, err := loader.NewClient(exchangeConfig, NopAccount)
3636
if err != nil {
3737
return err
3838
}

exchange.go

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,18 @@ type MultiSecret struct {
3636
type ExchangeClientConfig struct {
3737
ApiUrl string `yaml:"api_url"`
3838

39-
// Id of the main account, if required by the exchange.
40-
Id AccountId `yaml:"id"`
41-
4239
// The account types supported by the exchange.
4340
AccountTypes []*AccountTypeConfig `yaml:"account_types"`
4441
NoAccountTypes *bool `yaml:"no_account_types,omitempty"`
4542
}
4643

44+
type Account struct {
45+
Id AccountId
46+
Alias string
47+
SubAccount bool
48+
MultiSecret
49+
}
50+
4751
// Main configuration for an exchange
4852
type ExchangeConfig struct {
4953
ExchangeId ExchangeId `yaml:"exchange"`
@@ -54,6 +58,9 @@ type ExchangeConfig struct {
5458
// PassphraseRef secret.Secret `yaml:"passphrase"`
5559
MultiSecret `yaml:",inline"`
5660

61+
// Id of the main account, if required by the exchange.
62+
Id AccountId `yaml:"id"`
63+
5764
// Subaccounts are isolated accounts on an exchange. They have their own API keys and are
5865
// typically used for trading.
5966
SubAccounts []*SubAccount `yaml:"subaccounts"`
@@ -119,6 +126,32 @@ func (cfg *ExchangeConfig) ResolveSubAccount(idOrAlias string) (accountCfg *SubA
119126
return nil, false
120127
}
121128

129+
func (cfg *ExchangeConfig) AsAccount() *Account {
130+
return &Account{
131+
cfg.Id,
132+
"",
133+
false,
134+
cfg.MultiSecret,
135+
}
136+
}
137+
138+
func (cfg *SubAccount) AsAccount() *Account {
139+
return &Account{
140+
cfg.Id,
141+
cfg.Alias,
142+
true,
143+
cfg.MultiSecret,
144+
}
145+
}
146+
147+
func (cfg *Account) IsMain() bool {
148+
return !cfg.SubAccount
149+
}
150+
151+
func (cfg *Account) LoadSecrets() error {
152+
return cfg.MultiSecret.LoadSecrets()
153+
}
154+
122155
func (c *MultiSecret) LoadSecrets() error {
123156
if c.SecretsRef == "" {
124157
return nil

exchanges/binance/client.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ type Client struct {
1515

1616
var _ client.Client = &Client{}
1717

18-
func NewClient(config *oc.ExchangeClientConfig, secrets *oc.MultiSecret) (*Client, error) {
19-
apiKey, err := secrets.ApiKeyRef.Load()
18+
func NewClient(config *oc.ExchangeClientConfig, account *oc.Account) (*Client, error) {
19+
apiKey, err := account.ApiKeyRef.Load()
2020
if err != nil {
2121
return nil, fmt.Errorf("failed to load api key: %w", err)
2222
}
23-
secretKey, err := secrets.SecretKeyRef.Load()
23+
secretKey, err := account.SecretKeyRef.Load()
2424
if err != nil {
2525
return nil, fmt.Errorf("failed to load secret key: %w", err)
2626
}

exchanges/binanceus/client.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ type Client struct {
1515

1616
var _ client.Client = &Client{}
1717

18-
func NewClient(config *oc.ExchangeClientConfig, secrets *oc.MultiSecret) (*Client, error) {
19-
apiKey, err := secrets.ApiKeyRef.Load()
18+
func NewClient(config *oc.ExchangeClientConfig, account *oc.Account) (*Client, error) {
19+
apiKey, err := account.ApiKeyRef.Load()
2020
if err != nil {
2121
return nil, fmt.Errorf("failed to load api key: %w", err)
2222
}
23-
secretKey, err := secrets.SecretKeyRef.Load()
23+
secretKey, err := account.SecretKeyRef.Load()
2424
if err != nil {
2525
return nil, fmt.Errorf("failed to load secret key: %w", err)
2626
}

exchanges/bybit/client.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ import (
1212
)
1313

1414
type Client struct {
15-
api *api.Client
16-
cfg *oc.ExchangeClientConfig
15+
api *api.Client
16+
account *oc.Account
1717
}
1818

1919
var _ client.Client = &Client{}
2020

21-
func NewClient(config *oc.ExchangeClientConfig, secrets *oc.MultiSecret) (*Client, error) {
21+
func NewClient(config *oc.ExchangeClientConfig, secrets *oc.Account) (*Client, error) {
2222
apiKey, err := secrets.ApiKeyRef.Load()
2323
if err != nil {
2424
return nil, fmt.Errorf("could not load api key: %v", err)
@@ -36,8 +36,8 @@ func NewClient(config *oc.ExchangeClientConfig, secrets *oc.MultiSecret) (*Clien
3636
api.SetBaseURL(config.ApiUrl)
3737
}
3838
return &Client{
39-
api: api,
40-
cfg: config,
39+
api: api,
40+
account: secrets,
4141
}, nil
4242
}
4343

@@ -103,11 +103,11 @@ func (c *Client) CreateAccountTransfer(args client.AccountTransferArgs) (*client
103103

104104
if from == "" {
105105
// use the main account
106-
from = c.cfg.Id
106+
from = c.account.Id
107107
}
108108
if to == "" {
109109
// use the main account
110-
to = c.cfg.Id
110+
to = c.account.Id
111111
}
112112

113113
fromId, err := strconv.Atoi(string(from))

exchanges/okx/api/funds_transfer.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,23 @@ package api
22

33
import oc "github.com/cordialsys/offchain"
44

5+
type AccountTransferType string
6+
7+
const (
8+
SameAccount AccountTransferType = "0"
9+
MainToSub AccountTransferType = "1"
10+
SubToMainUsingMain AccountTransferType = "2"
11+
SubToMainUsingSub AccountTransferType = "3"
12+
SubToSubUsingSub AccountTransferType = "4"
13+
)
14+
515
type AccountTransferRequest struct {
6-
Currency oc.SymbolId `json:"ccy"`
7-
Amount oc.Amount `json:"amt"`
8-
From string `json:"from"`
9-
To string `json:"to"`
16+
Currency oc.SymbolId `json:"ccy"`
17+
Amount oc.Amount `json:"amt"`
18+
From oc.AccountType `json:"from"`
19+
To oc.AccountType `json:"to"`
20+
Type AccountTransferType `json:"type"`
21+
SubAccount *oc.AccountId `json:"subAcct,omitempty"`
1022
}
1123

1224
type AccountTransferResponse Response[[]AccountTransferResult]
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package api
2+
3+
import oc "github.com/cordialsys/offchain"
4+
5+
type SubaccountTransferRequest struct {
6+
Currency oc.SymbolId `json:"ccy"`
7+
Amount oc.Amount `json:"amt"`
8+
From oc.AccountType `json:"from"`
9+
To oc.AccountType `json:"to"`
10+
FromSubAccount oc.AccountId `json:"fromSubAccount"`
11+
ToSubAccount oc.AccountId `json:"toSubAccount"`
12+
LoanTrans *bool `json:"loanTrans,omitempty"`
13+
OmitPosRisk *string `json:"omitPosRisk,omitempty"`
14+
}
15+
16+
type SubaccountTransferResponse Response[[]SubaccountTransferResult]
17+
type SubaccountTransferResult struct {
18+
TransferID string `json:"transId,omitempty"`
19+
}
20+
21+
// Rate limit: 1 request per second
22+
// https://www.okx.com/docs-v5/en/#sub-account-rest-api-get-history-of-managed-sub-account-transfer
23+
func (c *Client) SubaccountTransfer(args *SubaccountTransferRequest) (*SubaccountTransferResponse, error) {
24+
var response SubaccountTransferResponse
25+
_, err := c.Request("POST", "/api/v5/asset/subaccount/transfer", args, &response, nil)
26+
if err != nil {
27+
return nil, err
28+
}
29+
return &response, nil
30+
}

0 commit comments

Comments
 (0)