// 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) }