Implements comprehensive SDK support for three core services across four programming languages (JavaScript/TypeScript, Python, Go, Rust). ## New SDKs ### Wallet SDK - Key management (create, import, export) - Transaction signing - Message signing and verification - Balance and UTXO queries - Stealth address support ### RPC SDK - Block and transaction queries - Chain state information - Fee estimation - Mempool information - WebSocket subscriptions for real-time updates ### Storage SDK - Content upload and download - Pinning operations - CAR file support - Directory management - Gateway URL generation ## Shared Infrastructure - JSON Schema definitions for all 11 services - Common type definitions (Address, Amount, UTXO, etc.) - Unified error handling patterns - Builder patterns for configuration ## Package Updates - JavaScript: Updated to @synor/sdk with module exports - Python: Updated to synor-sdk with websockets dependency - Go: Added gorilla/websocket dependency - Rust: Added base64, urlencoding, multipart support ## Fixes - Fixed Tensor Default trait implementation - Fixed ProcessorType enum casing
507 lines
14 KiB
Go
507 lines
14 KiB
Go
// Package wallet provides a Go SDK for Synor Wallet operations.
|
|
//
|
|
// Manage wallets, sign transactions, and query balances on the Synor blockchain.
|
|
//
|
|
// Example:
|
|
//
|
|
// client := wallet.NewClient("your-api-key")
|
|
// result, err := client.CreateWallet(ctx, wallet.Standard)
|
|
// fmt.Println("Address:", result.Wallet.Address)
|
|
// fmt.Println("Mnemonic:", result.Mnemonic) // Store securely!
|
|
package wallet
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"time"
|
|
)
|
|
|
|
// Version of the SDK.
|
|
const Version = "0.1.0"
|
|
|
|
// DefaultEndpoint is the default API endpoint.
|
|
const DefaultEndpoint = "https://wallet.synor.cc/api/v1"
|
|
|
|
// Network represents the blockchain network.
|
|
type Network string
|
|
|
|
const (
|
|
Mainnet Network = "mainnet"
|
|
Testnet Network = "testnet"
|
|
)
|
|
|
|
// WalletType represents the type of wallet.
|
|
type WalletType string
|
|
|
|
const (
|
|
Standard WalletType = "standard"
|
|
Multisig WalletType = "multisig"
|
|
Stealth WalletType = "stealth"
|
|
Hardware WalletType = "hardware"
|
|
)
|
|
|
|
// Priority represents transaction priority levels.
|
|
type Priority string
|
|
|
|
const (
|
|
Low Priority = "low"
|
|
Medium Priority = "medium"
|
|
High Priority = "high"
|
|
Urgent Priority = "urgent"
|
|
)
|
|
|
|
// Config holds client configuration.
|
|
type Config struct {
|
|
APIKey string
|
|
Endpoint string
|
|
Network Network
|
|
Timeout time.Duration
|
|
Debug bool
|
|
DerivationPath string
|
|
}
|
|
|
|
// DefaultConfig returns a default configuration.
|
|
func DefaultConfig(apiKey string) Config {
|
|
return Config{
|
|
APIKey: apiKey,
|
|
Endpoint: DefaultEndpoint,
|
|
Network: Mainnet,
|
|
Timeout: 30 * time.Second,
|
|
Debug: false,
|
|
}
|
|
}
|
|
|
|
// Client is the Synor Wallet client.
|
|
type Client struct {
|
|
config Config
|
|
httpClient *http.Client
|
|
}
|
|
|
|
// NewClient creates a new client with the given API key.
|
|
func NewClient(apiKey string) *Client {
|
|
return NewClientWithConfig(DefaultConfig(apiKey))
|
|
}
|
|
|
|
// NewClientWithConfig creates a new client with custom configuration.
|
|
func NewClientWithConfig(config Config) *Client {
|
|
return &Client{
|
|
config: config,
|
|
httpClient: &http.Client{
|
|
Timeout: config.Timeout,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Wallet represents a wallet instance.
|
|
type Wallet struct {
|
|
ID string `json:"id"`
|
|
Address string `json:"address"`
|
|
PublicKey string `json:"publicKey"`
|
|
Type WalletType `json:"type"`
|
|
CreatedAt int64 `json:"createdAt"`
|
|
}
|
|
|
|
// CreateWalletResult contains the created wallet and mnemonic.
|
|
type CreateWalletResult struct {
|
|
Wallet Wallet `json:"wallet"`
|
|
Mnemonic string `json:"mnemonic"`
|
|
}
|
|
|
|
// StealthAddress represents a stealth address for private payments.
|
|
type StealthAddress struct {
|
|
Address string `json:"address"`
|
|
ViewKey string `json:"viewKey"`
|
|
SpendKey string `json:"spendKey"`
|
|
EphemeralKey string `json:"ephemeralKey,omitempty"`
|
|
}
|
|
|
|
// TransactionInput represents a transaction input.
|
|
type TransactionInput struct {
|
|
TxID string `json:"txid"`
|
|
Vout int `json:"vout"`
|
|
Amount string `json:"amount"`
|
|
ScriptSig string `json:"scriptSig,omitempty"`
|
|
}
|
|
|
|
// TransactionOutput represents a transaction output.
|
|
type TransactionOutput struct {
|
|
Address string `json:"address"`
|
|
Amount string `json:"amount"`
|
|
ScriptPubKey string `json:"scriptPubKey,omitempty"`
|
|
}
|
|
|
|
// Transaction represents an unsigned transaction.
|
|
type Transaction struct {
|
|
Version int `json:"version"`
|
|
Inputs []TransactionInput `json:"inputs"`
|
|
Outputs []TransactionOutput `json:"outputs"`
|
|
LockTime int `json:"lockTime,omitempty"`
|
|
Fee string `json:"fee,omitempty"`
|
|
}
|
|
|
|
// SignedTransaction represents a signed transaction.
|
|
type SignedTransaction struct {
|
|
Raw string `json:"raw"`
|
|
TxID string `json:"txid"`
|
|
Size int `json:"size"`
|
|
Weight int `json:"weight,omitempty"`
|
|
}
|
|
|
|
// SignedMessage represents a signed message.
|
|
type SignedMessage struct {
|
|
Signature string `json:"signature"`
|
|
PublicKey string `json:"publicKey"`
|
|
Address string `json:"address"`
|
|
}
|
|
|
|
// UTXO represents an unspent transaction output.
|
|
type UTXO struct {
|
|
TxID string `json:"txid"`
|
|
Vout int `json:"vout"`
|
|
Amount string `json:"amount"`
|
|
Address string `json:"address"`
|
|
Confirmations int `json:"confirmations"`
|
|
ScriptPubKey string `json:"scriptPubKey,omitempty"`
|
|
}
|
|
|
|
// Balance represents balance information.
|
|
type Balance struct {
|
|
Confirmed string `json:"confirmed"`
|
|
Unconfirmed string `json:"unconfirmed"`
|
|
Total string `json:"total"`
|
|
}
|
|
|
|
// TokenBalance represents a token balance.
|
|
type TokenBalance struct {
|
|
Token string `json:"token"`
|
|
Symbol string `json:"symbol"`
|
|
Decimals int `json:"decimals"`
|
|
Balance string `json:"balance"`
|
|
}
|
|
|
|
// BalanceResponse contains native and token balances.
|
|
type BalanceResponse struct {
|
|
Native Balance `json:"native"`
|
|
Tokens []TokenBalance `json:"tokens,omitempty"`
|
|
}
|
|
|
|
// FeeEstimate represents a fee estimation result.
|
|
type FeeEstimate struct {
|
|
Priority Priority `json:"priority"`
|
|
FeeRate string `json:"feeRate"`
|
|
EstimatedBlocks int `json:"estimatedBlocks"`
|
|
}
|
|
|
|
// ImportWalletOptions contains options for importing a wallet.
|
|
type ImportWalletOptions struct {
|
|
Mnemonic string
|
|
Passphrase string
|
|
Type WalletType
|
|
}
|
|
|
|
// BuildTransactionOptions contains options for building a transaction.
|
|
type BuildTransactionOptions struct {
|
|
To string
|
|
Amount string
|
|
FeeRate float64
|
|
UTXOs []UTXO
|
|
ChangeAddress string
|
|
}
|
|
|
|
// GetUtxosOptions contains options for querying UTXOs.
|
|
type GetUtxosOptions struct {
|
|
MinConfirmations int
|
|
MinAmount string
|
|
}
|
|
|
|
// CreateWallet creates a new wallet.
|
|
func (c *Client) CreateWallet(ctx context.Context, walletType WalletType) (*CreateWalletResult, error) {
|
|
body := map[string]interface{}{
|
|
"type": walletType,
|
|
"network": c.config.Network,
|
|
}
|
|
if c.config.DerivationPath != "" {
|
|
body["derivationPath"] = c.config.DerivationPath
|
|
}
|
|
|
|
var result CreateWalletResult
|
|
if err := c.request(ctx, "POST", "/wallets", body, &result); err != nil {
|
|
return nil, err
|
|
}
|
|
return &result, nil
|
|
}
|
|
|
|
// ImportWallet imports a wallet from mnemonic phrase.
|
|
func (c *Client) ImportWallet(ctx context.Context, opts ImportWalletOptions) (*Wallet, error) {
|
|
body := map[string]interface{}{
|
|
"mnemonic": opts.Mnemonic,
|
|
"passphrase": opts.Passphrase,
|
|
"type": opts.Type,
|
|
"network": c.config.Network,
|
|
}
|
|
if c.config.DerivationPath != "" {
|
|
body["derivationPath"] = c.config.DerivationPath
|
|
}
|
|
|
|
var resp struct {
|
|
Wallet Wallet `json:"wallet"`
|
|
}
|
|
if err := c.request(ctx, "POST", "/wallets/import", body, &resp); err != nil {
|
|
return nil, err
|
|
}
|
|
return &resp.Wallet, nil
|
|
}
|
|
|
|
// GetWallet gets a wallet by ID.
|
|
func (c *Client) GetWallet(ctx context.Context, walletID string) (*Wallet, error) {
|
|
var resp struct {
|
|
Wallet Wallet `json:"wallet"`
|
|
}
|
|
if err := c.request(ctx, "GET", "/wallets/"+walletID, nil, &resp); err != nil {
|
|
return nil, err
|
|
}
|
|
return &resp.Wallet, nil
|
|
}
|
|
|
|
// ListWallets lists all wallets for this account.
|
|
func (c *Client) ListWallets(ctx context.Context) ([]Wallet, error) {
|
|
var resp struct {
|
|
Wallets []Wallet `json:"wallets"`
|
|
}
|
|
if err := c.request(ctx, "GET", "/wallets", nil, &resp); err != nil {
|
|
return nil, err
|
|
}
|
|
return resp.Wallets, nil
|
|
}
|
|
|
|
// GetAddress gets an address at a specific index for a wallet.
|
|
func (c *Client) GetAddress(ctx context.Context, walletID string, index int) (string, error) {
|
|
path := fmt.Sprintf("/wallets/%s/addresses/%d", walletID, index)
|
|
var resp struct {
|
|
Address string `json:"address"`
|
|
}
|
|
if err := c.request(ctx, "GET", path, nil, &resp); err != nil {
|
|
return "", err
|
|
}
|
|
return resp.Address, nil
|
|
}
|
|
|
|
// GetStealthAddress generates a stealth address for receiving private payments.
|
|
func (c *Client) GetStealthAddress(ctx context.Context, walletID string) (*StealthAddress, error) {
|
|
path := fmt.Sprintf("/wallets/%s/stealth", walletID)
|
|
var resp struct {
|
|
StealthAddress StealthAddress `json:"stealthAddress"`
|
|
}
|
|
if err := c.request(ctx, "POST", path, nil, &resp); err != nil {
|
|
return nil, err
|
|
}
|
|
return &resp.StealthAddress, nil
|
|
}
|
|
|
|
// SignTransaction signs a transaction.
|
|
func (c *Client) SignTransaction(ctx context.Context, walletID string, tx *Transaction) (*SignedTransaction, error) {
|
|
path := fmt.Sprintf("/wallets/%s/sign", walletID)
|
|
body := map[string]interface{}{
|
|
"transaction": tx,
|
|
}
|
|
var resp struct {
|
|
SignedTransaction SignedTransaction `json:"signedTransaction"`
|
|
}
|
|
if err := c.request(ctx, "POST", path, body, &resp); err != nil {
|
|
return nil, err
|
|
}
|
|
return &resp.SignedTransaction, nil
|
|
}
|
|
|
|
// SignMessage signs a message.
|
|
func (c *Client) SignMessage(ctx context.Context, walletID, message, format string) (*SignedMessage, error) {
|
|
if format == "" {
|
|
format = "text"
|
|
}
|
|
path := fmt.Sprintf("/wallets/%s/sign-message", walletID)
|
|
body := map[string]interface{}{
|
|
"message": message,
|
|
"format": format,
|
|
}
|
|
var result SignedMessage
|
|
if err := c.request(ctx, "POST", path, body, &result); err != nil {
|
|
return nil, err
|
|
}
|
|
return &result, nil
|
|
}
|
|
|
|
// VerifyMessage verifies a signed message.
|
|
func (c *Client) VerifyMessage(ctx context.Context, message, signature, address string) (bool, error) {
|
|
body := map[string]interface{}{
|
|
"message": message,
|
|
"signature": signature,
|
|
"address": address,
|
|
}
|
|
var resp struct {
|
|
Valid bool `json:"valid"`
|
|
}
|
|
if err := c.request(ctx, "POST", "/verify-message", body, &resp); err != nil {
|
|
return false, err
|
|
}
|
|
return resp.Valid, nil
|
|
}
|
|
|
|
// GetBalance gets the balance for an address.
|
|
func (c *Client) GetBalance(ctx context.Context, address string, includeTokens bool) (*BalanceResponse, error) {
|
|
path := fmt.Sprintf("/balances/%s?includeTokens=%t", address, includeTokens)
|
|
var result BalanceResponse
|
|
if err := c.request(ctx, "GET", path, nil, &result); err != nil {
|
|
return nil, err
|
|
}
|
|
return &result, nil
|
|
}
|
|
|
|
// GetUTXOs gets UTXOs for an address.
|
|
func (c *Client) GetUTXOs(ctx context.Context, address string, opts *GetUtxosOptions) ([]UTXO, error) {
|
|
path := "/utxos/" + address
|
|
if opts != nil {
|
|
params := url.Values{}
|
|
if opts.MinConfirmations > 0 {
|
|
params.Set("minConfirmations", fmt.Sprintf("%d", opts.MinConfirmations))
|
|
}
|
|
if opts.MinAmount != "" {
|
|
params.Set("minAmount", opts.MinAmount)
|
|
}
|
|
if len(params) > 0 {
|
|
path += "?" + params.Encode()
|
|
}
|
|
}
|
|
|
|
var resp struct {
|
|
UTXOs []UTXO `json:"utxos"`
|
|
}
|
|
if err := c.request(ctx, "GET", path, nil, &resp); err != nil {
|
|
return nil, err
|
|
}
|
|
return resp.UTXOs, nil
|
|
}
|
|
|
|
// BuildTransaction builds a transaction without signing.
|
|
func (c *Client) BuildTransaction(ctx context.Context, walletID string, opts BuildTransactionOptions) (*Transaction, error) {
|
|
path := fmt.Sprintf("/wallets/%s/build-tx", walletID)
|
|
body := map[string]interface{}{
|
|
"to": opts.To,
|
|
"amount": opts.Amount,
|
|
}
|
|
if opts.FeeRate > 0 {
|
|
body["feeRate"] = opts.FeeRate
|
|
}
|
|
if len(opts.UTXOs) > 0 {
|
|
body["utxos"] = opts.UTXOs
|
|
}
|
|
if opts.ChangeAddress != "" {
|
|
body["changeAddress"] = opts.ChangeAddress
|
|
}
|
|
|
|
var resp struct {
|
|
Transaction Transaction `json:"transaction"`
|
|
}
|
|
if err := c.request(ctx, "POST", path, body, &resp); err != nil {
|
|
return nil, err
|
|
}
|
|
return &resp.Transaction, nil
|
|
}
|
|
|
|
// SendTransaction builds and signs a transaction in one step.
|
|
func (c *Client) SendTransaction(ctx context.Context, walletID string, opts BuildTransactionOptions) (*SignedTransaction, error) {
|
|
tx, err := c.BuildTransaction(ctx, walletID, opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return c.SignTransaction(ctx, walletID, tx)
|
|
}
|
|
|
|
// EstimateFee estimates the transaction fee.
|
|
func (c *Client) EstimateFee(ctx context.Context, priority Priority) (*FeeEstimate, error) {
|
|
path := fmt.Sprintf("/fees/estimate?priority=%s", priority)
|
|
var result FeeEstimate
|
|
if err := c.request(ctx, "GET", path, nil, &result); err != nil {
|
|
return nil, err
|
|
}
|
|
return &result, nil
|
|
}
|
|
|
|
// GetAllFeeEstimates gets fee estimates for all priority levels.
|
|
func (c *Client) GetAllFeeEstimates(ctx context.Context) ([]FeeEstimate, error) {
|
|
var resp struct {
|
|
Estimates []FeeEstimate `json:"estimates"`
|
|
}
|
|
if err := c.request(ctx, "GET", "/fees/estimate/all", nil, &resp); err != nil {
|
|
return nil, err
|
|
}
|
|
return resp.Estimates, nil
|
|
}
|
|
|
|
func (c *Client) request(ctx context.Context, method, path string, body interface{}, result interface{}) error {
|
|
reqURL := c.config.Endpoint + path
|
|
|
|
var bodyReader io.Reader
|
|
if body != nil {
|
|
bodyBytes, err := json.Marshal(body)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
bodyReader = bytes.NewReader(bodyBytes)
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, method, reqURL, bodyReader)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
req.Header.Set("Authorization", "Bearer "+c.config.APIKey)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("X-Network", string(c.config.Network))
|
|
|
|
if c.config.Debug {
|
|
fmt.Printf("[SynorWallet] %s %s\n", method, path)
|
|
}
|
|
|
|
resp, err := c.httpClient.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("request failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode >= 400 {
|
|
var errResp struct {
|
|
Message string `json:"message"`
|
|
Code string `json:"code"`
|
|
}
|
|
json.NewDecoder(resp.Body).Decode(&errResp)
|
|
return &WalletError{
|
|
Message: errResp.Message,
|
|
StatusCode: resp.StatusCode,
|
|
Code: errResp.Code,
|
|
}
|
|
}
|
|
|
|
if result != nil {
|
|
if err := json.NewDecoder(resp.Body).Decode(result); err != nil {
|
|
return fmt.Errorf("failed to decode response: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// WalletError represents an API error.
|
|
type WalletError struct {
|
|
Message string
|
|
StatusCode int
|
|
Code string
|
|
}
|
|
|
|
func (e *WalletError) Error() string {
|
|
return fmt.Sprintf("wallet: %s (status %d)", e.Message, e.StatusCode)
|
|
}
|