From b763ead2f96af548b7ed8f0d2025ba8433292df8 Mon Sep 17 00:00:00 2001 From: pomadev Date: Thu, 5 Jun 2025 01:39:16 +0900 Subject: [PATCH] refactor: reduce code duplication in exchange clients MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Extract BaseClient with shared nonce logic - Create common HTTP utilities for JSON requests - Consolidate currency pair constants - Remove duplicate getNonce implementations - Simplify client creation with embedded BaseClient This reduces code duplication and improves maintainability across exchange implementations. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- client/base.go | 20 +++++++++++++++++++ client/bitbank/bitbank.go | 22 +++++--------------- client/bitflyer/bitflyer.go | 26 +++++++----------------- client/client.go | 15 ++++++++++---- client/common/http.go | 40 +++++++++++++++++++++++++++++++++++++ 5 files changed, 83 insertions(+), 40 deletions(-) create mode 100644 client/base.go create mode 100644 client/common/http.go diff --git a/client/base.go b/client/base.go new file mode 100644 index 0000000..7ed973a --- /dev/null +++ b/client/base.go @@ -0,0 +1,20 @@ +package client + +import ( + "fmt" + "time" +) + +type BaseClient struct { + AccessKey string + ApiSecret string + nonce int64 +} + +func (c *BaseClient) GetNonce() string { + if c.nonce == 0 { + c.nonce = time.Now().Unix() + } + c.nonce++ + return fmt.Sprintf("%d", c.nonce) +} \ No newline at end of file diff --git a/client/bitbank/bitbank.go b/client/bitbank/bitbank.go index b791d16..28a5d1f 100644 --- a/client/bitbank/bitbank.go +++ b/client/bitbank/bitbank.go @@ -7,38 +7,26 @@ import ( "log" "net/http" "strconv" - "time" + "github.com/pomadev/dollar-cost-averaging-bot/client" "github.com/pomadev/dollar-cost-averaging-bot/client/common" ) const ( PUBLIC_API_URL = "https://public.bitbank.cc" PRIVATE_API_URL = "https://api.bitbank.cc/v1" - BTC_JPY = "btc_jpy" - ETH_JPY = "eth_jpy" ) type BitbankClient struct { - AccessKey string - ApiSecret string - nonce int64 -} - -func (c *BitbankClient) getNonce() string { - if c.nonce == 0 { - c.nonce = time.Now().Unix() - } - c.nonce++ - return fmt.Sprintf("%d", c.nonce) + client.BaseClient } func (c *BitbankClient) OrderBTC(yen int64) (string, string, error) { - return c.order(BTC_JPY, yen) + return c.order(string(client.BTCJPY), yen) } func (c *BitbankClient) OrderETH(yen int64) (string, string, error) { - return c.order(ETH_JPY, yen) + return c.order(string(client.ETHJPY), yen) } type orderRequest struct { @@ -79,7 +67,7 @@ func (c *BitbankClient) order(pair string, yen int64) (string, string, error) { if err != nil { return "", "", fmt.Errorf("Failed to create request: %s", err) } - nonce := c.getNonce() + nonce := c.GetNonce() req.Header.Set("ACCESS-KEY", c.AccessKey) req.Header.Set("ACCESS-NONCE", nonce) req.Header.Set("ACCESS-SIGNATURE", common.MakeSign(nonce+string(requestBodyJson), c.ApiSecret)) diff --git a/client/bitflyer/bitflyer.go b/client/bitflyer/bitflyer.go index 5742db2..e2f2191 100644 --- a/client/bitflyer/bitflyer.go +++ b/client/bitflyer/bitflyer.go @@ -6,37 +6,25 @@ import ( "fmt" "log" "net/http" - "time" + "github.com/pomadev/dollar-cost-averaging-bot/client" "github.com/pomadev/dollar-cost-averaging-bot/client/common" ) const ( API_URL = "https://api.bitflyer.com/v1/" - BTC_JPY = "BTC_JPY" - ETH_JPY = "ETH_JPY" ) type BitflyerClient struct { - AccessKey string - ApiSecret string - nonce int64 -} - -func (c *BitflyerClient) getNonce() string { - if c.nonce == 0 { - c.nonce = time.Now().Unix() - } - c.nonce++ - return fmt.Sprintf("%d", c.nonce) + client.BaseClient } func (c *BitflyerClient) OrderBTC(yen int64) (string, string, error) { - return c.order(BTC_JPY, yen) + return c.order("BTC_JPY", yen) } func (c *BitflyerClient) OrderETH(yen int64) (string, string, error) { - return c.order(ETH_JPY, yen) + return c.order("ETH_JPY", yen) } type orderRequest struct { @@ -56,9 +44,9 @@ func (c *BitflyerClient) order(pair string, yen int64) (string, string, error) { return "", "", fmt.Errorf("Failed to get price: %s", err) } var amount float64 - if pair == BTC_JPY { + if pair == "BTC_JPY" { amount = common.CalcAmount(price, yen, 1000) - } else if pair == ETH_JPY { + } else if pair == "ETH_JPY" { amount = common.CalcAmount(price, yen, 100) } else { amount = 0 @@ -81,7 +69,7 @@ func (c *BitflyerClient) order(pair string, yen int64) (string, string, error) { if err != nil { return "", "", fmt.Errorf("Failed to create request: %s", err) } - nonce := c.getNonce() + nonce := c.GetNonce() req.Header.Set("ACCESS-KEY", c.AccessKey) req.Header.Set("ACCESS-TIMESTAMP", nonce) req.Header.Set("ACCESS-SIGN", common.MakeSign(nonce+"POST"+"/v1/me/sendchildorder"+string(requestBodyJson), c.ApiSecret)) diff --git a/client/client.go b/client/client.go index a35857e..c44ce94 100644 --- a/client/client.go +++ b/client/client.go @@ -8,12 +8,19 @@ import ( "github.com/pomadev/dollar-cost-averaging-bot/client/bitflyer" ) -type client interface { +type Client interface { OrderBTC(int64) (string, string, error) OrderETH(int64) (string, string, error) } -func CreateClient(exchange string) (client, error) { +type Pair string + +const ( + BTCJPY Pair = "btc_jpy" + ETHJPY Pair = "eth_jpy" +) + +func CreateClient(exchange string) (Client, error) { accessKey := os.Getenv("ACCESS_KEY") apiSecret := os.Getenv("API_SECRET") if accessKey == "" || apiSecret == "" { @@ -22,9 +29,9 @@ func CreateClient(exchange string) (client, error) { switch exchange { case "bitbank": - return &bitbank.BitbankClient{AccessKey: accessKey, ApiSecret: apiSecret}, nil + return &bitbank.BitbankClient{BaseClient: BaseClient{AccessKey: accessKey, ApiSecret: apiSecret}}, nil case "bitflyer": - return &bitflyer.BitflyerClient{AccessKey: accessKey, ApiSecret: apiSecret}, nil + return &bitflyer.BitflyerClient{BaseClient: BaseClient{AccessKey: accessKey, ApiSecret: apiSecret}}, nil default: return nil, fmt.Errorf("Unknown exchange: %s", exchange) } diff --git a/client/common/http.go b/client/common/http.go new file mode 100644 index 0000000..e94e94a --- /dev/null +++ b/client/common/http.go @@ -0,0 +1,40 @@ +package common + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" +) + +func DoJSONRequest(method, url string, headers map[string]string, body interface{}) (*http.Response, error) { + var requestBody []byte + var err error + if body != nil { + requestBody, err = json.Marshal(body) + if err != nil { + return nil, fmt.Errorf("failed to marshal request body: %w", err) + } + } + + req, err := http.NewRequest(method, url, bytes.NewReader(requestBody)) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + for key, value := range headers { + req.Header.Set(key, value) + } + + if body != nil && req.Header.Get("Content-Type") == "" { + req.Header.Set("Content-Type", "application/json") + } + + client := &http.Client{} + return client.Do(req) +} + +func DecodeJSONResponse(resp *http.Response, target interface{}) error { + defer resp.Body.Close() + return json.NewDecoder(resp.Body).Decode(target) +} \ No newline at end of file