Retries and Error Handling
Last updated
Was this helpful?
Last updated
Was this helpful?
Was this helpful?
// Buy with Retries and Error Handling (Trading API)
import axios from 'axios';
import nacl from 'tweetnacl';
import util from 'tweetnacl-util';
function decodePrivateKey(privateKeyBase64) {
const raw = util.decodeBase64(privateKeyBase64);
if (raw.length === nacl.sign.seedLength) {
return nacl.sign.keyPair.fromSeed(raw).secretKey;
}
if (raw.length === nacl.sign.secretKeyLength) {
return raw;
}
throw new Error('Invalid private key length; expected 32-byte seed or 64-byte secret key');
}
const BASE_URL = 'https://trading.api.thegrid.ai';
const MAX_RETRIES = 3;
const RETRY_DELAY_MS = 1000;
class SignatureAuth {
constructor(privateKeyBase64, fingerprint) {
this.privateKey = decodePrivateKey(privateKeyBase64);
this.fingerprint = fingerprint;
}
getHeaders(method, path, body = '') {
const timestamp = Math.floor(Date.now() / 1000).toString();
const message = `${timestamp}${method.toUpperCase()}${path}${body}`;
const messageBytes = util.decodeUTF8(message);
const signatureBytes = nacl.sign.detached(messageBytes, this.privateKey);
return {
'x-thegrid-signature': util.encodeBase64(signatureBytes),
'x-thegrid-timestamp': timestamp,
'x-thegrid-fingerprint': this.fingerprint
};
}
}
async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function buyWithRetry(auth, marketId, quantity, maxPrice) {
const orderData = {
market_id: marketId,
side: 'buy',
type: 'limit',
quantity: quantity,
price: maxPrice.toString(),
time_in_force: 'gtc',
client_order_id: `buy-${Date.now()}`
};
const path = '/api/v1/trading/orders';
const body = JSON.stringify(orderData);
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
// Generate fresh signature for each attempt (timestamp changes)
const headers = {
'Content-Type': 'application/json',
...auth.getHeaders('POST', path, body)
};
const response = await axios.post(`${BASE_URL}${path}`, body, { headers });
return { success: true, orderId: response.data.data.order_id };
} catch (error) {
const status = error.response?.status;
const message = error.response?.data?.errors?.detail || error.message;
console.log(`Attempt ${attempt}/${MAX_RETRIES} failed: ${message}`);
// Don't retry on client errors (4xx) except rate limiting (429)
if (status && status >= 400 && status < 500 && status !== 429) {
return { success: false, error: message, retryable: false };
}
// Wait before retrying (exponential backoff)
if (attempt < MAX_RETRIES) {
const delay = RETRY_DELAY_MS * Math.pow(2, attempt - 1);
console.log(`Retrying in ${delay}ms...`);
await sleep(delay);
}
}
}
return { success: false, error: 'Max retries exceeded', retryable: true };
}
// Main
const auth = new SignatureAuth(
process.env.GRID_TRADING_PRIVATE_KEY,
process.env.GRID_TRADING_FINGERPRINT
);
const result = await buyWithRetry(
auth,
'market_788dcbd5-ac68-4c61-acf1-4443beaf2a1c',
1,
2.50
);
if (result.success) {
console.log(`Order placed: ${result.orderId}`);
} else {
console.log(`Order failed: ${result.error}`);
process.exit(1);
}
package main
import (
"bytes"
"crypto/ed25519"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
"time"
)
const (
baseURL = "https://trading.api.thegrid.ai"
maxRetries = 3
retryDelaySec = 1
)
type SignatureAuth struct {
privateKey ed25519.PrivateKey
fingerprint string
}
func NewSignatureAuth(privateKeyB64, publicKeyB64 string) *SignatureAuth {
privateKeyBytes, _ := base64.StdEncoding.DecodeString(privateKeyB64)
var privateKey ed25519.PrivateKey
if len(privateKeyBytes) == ed25519.SeedSize {
privateKey = ed25519.NewKeyFromSeed(privateKeyBytes)
} else {
privateKey = ed25519.PrivateKey(privateKeyBytes)
}
publicKeyBytes, _ := base64.StdEncoding.DecodeString(publicKeyB64)
hash := sha256.Sum256(publicKeyBytes)
fingerprint := strings.TrimRight(base64.StdEncoding.EncodeToString(hash[:]), "=")
return &SignatureAuth{privateKey: privateKey, fingerprint: fingerprint}
}
func (sa *SignatureAuth) GetHeaders(method, path, body string) map[string]string {
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
message := timestamp + method + path + body
signature := ed25519.Sign(sa.privateKey, []byte(message))
return map[string]string{
"x-thegrid-signature": base64.StdEncoding.EncodeToString(signature),
"x-thegrid-timestamp": timestamp,
"x-thegrid-fingerprint": sa.fingerprint,
}
}
type BuyResult struct {
Success bool
OrderID string
Error string
Retryable bool
}
func buyWithRetry(auth *SignatureAuth, marketID string, quantity int, maxPrice float64) BuyResult {
orderData := map[string]any{
"market_id": marketID,
"side": "buy",
"type": "limit",
"quantity": quantity,
"price": fmt.Sprintf("%.2f", maxPrice),
"time_in_force": "gtc",
"client_order_id": fmt.Sprintf("buy-%d", time.Now().UnixMilli()),
}
orderJSON, _ := json.Marshal(orderData)
path := "/api/v1/trading/orders"
for attempt := 1; attempt <= maxRetries; attempt++ {
// Generate fresh signature for each attempt (timestamp changes)
req, _ := http.NewRequest("POST", baseURL+path, bytes.NewBuffer(orderJSON))
req.Header.Set("Content-Type", "application/json")
for k, v := range auth.GetHeaders("POST", path, string(orderJSON)) {
req.Header.Set(k, v)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Printf("Attempt %d/%d failed: %v\n", attempt, maxRetries, err)
if attempt < maxRetries {
delay := retryDelaySec * (1 << (attempt - 1))
fmt.Printf("Retrying in %ds...\n", delay)
time.Sleep(time.Duration(delay) * time.Second)
}
continue
}
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
var result struct {
Data struct {
OrderID string `json:"order_id"`
} `json:"data"`
}
json.Unmarshal(body, &result)
return BuyResult{Success: true, OrderID: result.Data.OrderID}
}
// Parse error message
var errResp struct {
Errors struct {
Detail string `json:"detail"`
} `json:"errors"`
}
json.Unmarshal(body, &errResp)
errMsg := errResp.Errors.Detail
if errMsg == "" {
errMsg = string(body)
}
fmt.Printf("Attempt %d/%d failed: %s\n", attempt, maxRetries, errMsg)
// Don't retry on client errors (4xx) except rate limiting (429)
if resp.StatusCode >= 400 && resp.StatusCode < 500 && resp.StatusCode != 429 {
return BuyResult{Success: false, Error: errMsg, Retryable: false}
}
// Wait before retrying (exponential backoff)
if attempt < maxRetries {
delay := retryDelaySec * (1 << (attempt - 1))
fmt.Printf("Retrying in %ds...\n", delay)
time.Sleep(time.Duration(delay) * time.Second)
}
}
return BuyResult{Success: false, Error: "Max retries exceeded", Retryable: true}
}
func main() {
auth := NewSignatureAuth(
os.Getenv("GRID_TRADING_PRIVATE_KEY"),
os.Getenv("GRID_TRADING_PUBLIC_KEY"),
)
result := buyWithRetry(
auth,
"market_788dcbd5-ac68-4c61-acf1-4443beaf2a1c",
1,
2.50,
)
if result.Success {
fmt.Printf("Order placed: %s\n", result.OrderID)
} else {
fmt.Printf("Order failed: %s\n", result.Error)
os.Exit(1)
}
}