Working with Trading and Consumption Balances
Last updated
Was this helpful?
Last updated
Was this helpful?
Was this helpful?
// View Account Balances (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';
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
};
}
}
const auth = new SignatureAuth(
process.env.GRID_TRADING_PRIVATE_KEY,
process.env.GRID_TRADING_FINGERPRINT
);
// Consumption accounts
const consumptionPath = '/api/v1/trading/consumption-accounts';
const consumptionResp = await axios.get(`${BASE_URL}${consumptionPath}?order_by=created_at`, {
headers: auth.getHeaders('GET', consumptionPath)
});
console.log('=== Consumption Accounts ===');
for (const acct of consumptionResp.data.data) {
console.log(`${acct.account_id}: ${acct.available_balance} available, ${acct.total_balance} total (instrument ${acct.instrument_id})`);
}
// Currency balance (USD)
const currencyPath = '/api/v1/trading/currency-trading-accounts';
const currencyResp = await axios.get(`${BASE_URL}${currencyPath}`, {
headers: auth.getHeaders('GET', currencyPath)
});
console.log('=== Currency Balance ===');
for (const acct of currencyResp.data.data) {
console.log(`${acct.currency.toUpperCase()}: $${acct.available_balance} available, $${acct.total_balance} total`);
}
// Trading accounts (instruments)
const tradingPath = '/api/v1/trading/trading-accounts';
const tradingResp = await axios.get(`${BASE_URL}${tradingPath}`, {
headers: auth.getHeaders('GET', tradingPath)
});
console.log('\n=== Trading Accounts ===');
for (const acct of tradingResp.data.data) {
console.log(`${acct.instrument_symbol}: ${acct.available_balance} available, ${acct.total_balance} total`);
}
package main
import (
"crypto/ed25519"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strconv"
"strings"
"time"
)
const baseURL = "https://trading.api.thegrid.ai"
type SignatureAuth struct {
privateKey ed25519.PrivateKey
fingerprint string
}
func NewSignatureAuth(privateKeyB64, fingerprint 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)
}
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,
}
}
func main() {
auth := NewSignatureAuth(
os.Getenv("GRID_TRADING_PRIVATE_KEY"),
os.Getenv("GRID_TRADING_FINGERPRINT"),
)
// Consumption accounts
consumptionPath := "/api/v1/trading/consumption-accounts"
req0, err := http.NewRequest("GET", baseURL+consumptionPath+"?order_by=created_at", nil)
if err != nil {
log.Fatal(err)
}
for k, v := range auth.GetHeaders("GET", consumptionPath, "") {
req0.Header.Set(k, v)
}
resp0, err := http.DefaultClient.Do(req0)
if err != nil {
log.Fatal(err)
}
defer resp0.Body.Close()
var consumptionResult struct {
Data []struct {
AccountID string `json:"account_id"`
InstrumentID string `json:"instrument_id"`
AvailableBalance json.Number `json:"available_balance"`
TotalBalance json.Number `json:"total_balance"`
} `json:"data"`
}
if err := json.NewDecoder(resp0.Body).Decode(&consumptionResult); err != nil {
log.Fatal(err)
}
fmt.Println("=== Consumption Accounts ===")
for _, acct := range consumptionResult.Data {
fmt.Printf("%s: %s available, %s total (instrument %s)\n", acct.AccountID, acct.AvailableBalance.String(), acct.TotalBalance.String(), acct.InstrumentID)
}
// Currency balance (USD)
currencyPath := "/api/v1/trading/currency-trading-accounts"
req, err := http.NewRequest("GET", baseURL+currencyPath, nil)
if err != nil {
log.Fatal(err)
}
for k, v := range auth.GetHeaders("GET", currencyPath, "") {
req.Header.Set(k, v)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
var currencyResult struct {
Data []struct {
Currency string `json:"currency"`
AvailableBalance string `json:"available_balance"`
TotalBalance string `json:"total_balance"`
} `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(¤cyResult); err != nil {
log.Fatal(err)
}
fmt.Println("=== Currency Balance ===")
for _, acct := range currencyResult.Data {
fmt.Printf("%s: $%s available, $%s total\n", strings.ToUpper(acct.Currency), acct.AvailableBalance, acct.TotalBalance)
}
// Trading accounts (instruments)
tradingPath := "/api/v1/trading/trading-accounts"
req2, err := http.NewRequest("GET", baseURL+tradingPath, nil)
if err != nil {
log.Fatal(err)
}
for k, v := range auth.GetHeaders("GET", tradingPath, "") {
req2.Header.Set(k, v)
}
resp2, err := http.DefaultClient.Do(req2)
if err != nil {
log.Fatal(err)
}
defer resp2.Body.Close()
var tradingResult struct {
Data []struct {
InstrumentSymbol string `json:"instrument_symbol"`
AvailableBalance string `json:"available_balance"`
TotalBalance string `json:"total_balance"`
} `json:"data"`
}
if err := json.NewDecoder(resp2.Body).Decode(&tradingResult); err != nil {
log.Fatal(err)
}
fmt.Println("\n=== Trading Accounts ===")
for _, acct := range tradingResult.Data {
fmt.Printf("%s: %s available, %s total\n", acct.InstrumentSymbol, acct.AvailableBalance, acct.TotalBalance)
}
}