Add Remote field to SmartContractCreateRequest and GrpcConnectionInfo struct to SmartContract model for remote smart contract execution via gRPC.
162 lines
3.9 KiB
Go
Executable File
162 lines
3.9 KiB
Go
Executable File
package client
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type Client struct {
|
|
publicID string
|
|
authKeyID string
|
|
authKey string
|
|
baseURL string
|
|
httpClient *http.Client
|
|
}
|
|
|
|
func NewClient(publicID, authKeyID, authKey, baseURL string) *Client {
|
|
return &Client{
|
|
publicID: publicID,
|
|
authKeyID: authKeyID,
|
|
authKey: authKey,
|
|
baseURL: strings.TrimSuffix(baseURL, "/"),
|
|
httpClient: &http.Client{
|
|
Timeout: 30 * time.Second,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (c *Client) generateAuthHeader(method, path, timestamp, contentType string, body []byte) string {
|
|
msgStr := c.hmacMessage(method, path, timestamp, contentType, body)
|
|
hmacMsg := c.createHmac(msgStr)
|
|
b64hmac := base64.StdEncoding.EncodeToString(hmacMsg)
|
|
return fmt.Sprintf("DC1-HMAC-SHA256 %s:%s", c.authKeyID, b64hmac)
|
|
}
|
|
|
|
func (c *Client) hmacMessage(method, path, timestamp, contentType string, body []byte) string {
|
|
h := sha256.New()
|
|
h.Write(body)
|
|
hashContent := h.Sum(nil)
|
|
b64Content := base64.StdEncoding.EncodeToString(hashContent)
|
|
|
|
return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s",
|
|
strings.ToUpper(method),
|
|
path,
|
|
c.publicID,
|
|
timestamp,
|
|
contentType,
|
|
b64Content,
|
|
)
|
|
}
|
|
|
|
func (c *Client) createHmac(msgStr string) []byte {
|
|
h := hmac.New(sha256.New, []byte(c.authKey))
|
|
h.Write([]byte(msgStr))
|
|
return h.Sum(nil)
|
|
}
|
|
|
|
func (c *Client) doRequest(ctx context.Context, method, path, contentType string, body any, response any) error {
|
|
var bodyBytes []byte
|
|
var err error
|
|
|
|
if body != nil {
|
|
if !checkByteSlice(body) {
|
|
bodyBytes, err = json.Marshal(body)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal request body: %w", err)
|
|
}
|
|
contentType = "application/json"
|
|
} else {
|
|
bodyBytes = body.([]byte)
|
|
}
|
|
}
|
|
|
|
if len(bodyBytes) == 0 {
|
|
bodyBytes = []byte("")
|
|
}
|
|
|
|
fullURL := c.baseURL + path
|
|
req, err := http.NewRequestWithContext(ctx, method, fullURL, bytes.NewBuffer(bodyBytes))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
timestamp := fmt.Sprintf("%d", time.Now().Unix())
|
|
authHeader := c.generateAuthHeader(method, path, timestamp, contentType, bodyBytes)
|
|
|
|
req.Header.Set("Authorization", authHeader)
|
|
req.Header.Set("Dragonchain", c.publicID)
|
|
req.Header.Set("Timestamp", timestamp)
|
|
if contentType != "" {
|
|
req.Header.Set("Content-Type", contentType)
|
|
}
|
|
|
|
resp, err := c.httpClient.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to execute request: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
respBody, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read response body: %w", err)
|
|
}
|
|
|
|
if resp.StatusCode >= 400 {
|
|
return fmt.Errorf("API error (status %d): %s", resp.StatusCode, strings.TrimSpace(string(respBody)))
|
|
}
|
|
|
|
if response != nil && len(respBody) > 0 {
|
|
if err := json.Unmarshal(respBody, response); err != nil {
|
|
return fmt.Errorf("failed to unmarshal response: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func checkByteSlice(body interface{}) bool {
|
|
_, ok := body.([]byte)
|
|
return ok
|
|
}
|
|
|
|
func (c *Client) InternalId() string {
|
|
return c.publicID
|
|
}
|
|
|
|
func (c *Client) PublicId() string {
|
|
return c.publicID
|
|
}
|
|
|
|
func (c *Client) AuthKeyId() string {
|
|
return c.authKeyID
|
|
}
|
|
|
|
func (c *Client) Endpoint() string {
|
|
return c.baseURL
|
|
}
|
|
|
|
func (c *Client) Get(ctx context.Context, path string, response any) error {
|
|
return c.doRequest(ctx, http.MethodGet, path, "", nil, response)
|
|
}
|
|
|
|
func (c *Client) Post(ctx context.Context, path, contentType string, body any, response any) error {
|
|
return c.doRequest(ctx, http.MethodPost, path, contentType, body, response)
|
|
}
|
|
|
|
func (c *Client) Put(ctx context.Context, path, contentType string, body any, response any) error {
|
|
return c.doRequest(ctx, http.MethodPut, path, contentType, body, response)
|
|
}
|
|
|
|
func (c *Client) Delete(ctx context.Context, path string, response any) error {
|
|
return c.doRequest(ctx, http.MethodDelete, path, "", nil, response)
|
|
}
|