feat(sdk): implement Phase 1 SDKs for Wallet, RPC, and Storage

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
This commit is contained in:
Gulshan Yadav 2026-01-27 00:46:24 +05:30
parent 7785dbe8f8
commit 59a7123535
52 changed files with 10946 additions and 13 deletions

View file

@ -4,4 +4,7 @@ go 1.21
require ( require (
github.com/goccy/go-json v0.10.2 github.com/goccy/go-json v0.10.2
github.com/gorilla/websocket v1.5.1
) )
require golang.org/x/net v0.17.0 // indirect

480
sdk/go/rpc/rpc.go Normal file
View file

@ -0,0 +1,480 @@
// Package rpc provides a Go SDK for Synor blockchain RPC operations.
//
// Query blocks, transactions, and chain state with WebSocket subscription support.
//
// Example:
//
// client := rpc.NewClient("your-api-key")
// block, err := client.GetLatestBlock(ctx)
// fmt.Println("Height:", block.Height)
package rpc
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"
"github.com/gorilla/websocket"
)
// Version of the SDK.
const Version = "0.1.0"
// Default endpoints.
const (
DefaultEndpoint = "https://rpc.synor.cc/api/v1"
DefaultWSEndpoint = "wss://rpc.synor.cc/ws"
)
// Network type.
type Network string
const (
Mainnet Network = "mainnet"
Testnet Network = "testnet"
)
// Priority levels.
type Priority string
const (
Low Priority = "low"
Medium Priority = "medium"
High Priority = "high"
Urgent Priority = "urgent"
)
// TransactionStatus represents transaction states.
type TransactionStatus string
const (
Pending TransactionStatus = "pending"
Confirmed TransactionStatus = "confirmed"
Failed TransactionStatus = "failed"
Replaced TransactionStatus = "replaced"
)
// Config holds client configuration.
type Config struct {
APIKey string
Endpoint string
WSEndpoint string
Network Network
Timeout time.Duration
Debug bool
}
// DefaultConfig returns a default configuration.
func DefaultConfig(apiKey string) Config {
return Config{
APIKey: apiKey,
Endpoint: DefaultEndpoint,
WSEndpoint: DefaultWSEndpoint,
Network: Mainnet,
Timeout: 30 * time.Second,
Debug: false,
}
}
// Client is the Synor RPC client.
type Client struct {
config Config
httpClient *http.Client
wsConn *websocket.Conn
subs map[string]func(json.RawMessage)
}
// 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,
},
subs: make(map[string]func(json.RawMessage)),
}
}
// BlockHeader represents a block header.
type BlockHeader struct {
Hash string `json:"hash"`
Height int64 `json:"height"`
Version int `json:"version"`
PreviousHash string `json:"previousHash"`
MerkleRoot string `json:"merkleRoot"`
Timestamp int64 `json:"timestamp"`
Difficulty string `json:"difficulty"`
Nonce uint64 `json:"nonce"`
}
// Block represents a full block.
type Block struct {
BlockHeader
Transactions []string `json:"transactions"`
Size int `json:"size"`
Weight int `json:"weight"`
TxCount int `json:"txCount"`
}
// Transaction represents a transaction.
type Transaction struct {
TxID string `json:"txid"`
BlockHash string `json:"blockHash,omitempty"`
BlockHeight int64 `json:"blockHeight,omitempty"`
Confirmations int `json:"confirmations"`
Timestamp int64 `json:"timestamp,omitempty"`
Status TransactionStatus `json:"status"`
Raw string `json:"raw,omitempty"`
Size int `json:"size"`
Fee string `json:"fee"`
}
// FeeEstimate represents a fee estimation.
type FeeEstimate struct {
Priority Priority `json:"priority"`
FeeRate string `json:"feeRate"`
EstimatedBlocks int `json:"estimatedBlocks"`
}
// ChainInfo represents chain information.
type ChainInfo struct {
Chain string `json:"chain"`
Network string `json:"network"`
Height int64 `json:"height"`
BestBlockHash string `json:"bestBlockHash"`
Difficulty string `json:"difficulty"`
MedianTime int64 `json:"medianTime"`
ChainWork string `json:"chainWork"`
Syncing bool `json:"syncing"`
SyncProgress float64 `json:"syncProgress"`
}
// MempoolInfo represents mempool information.
type MempoolInfo struct {
Size int `json:"size"`
Bytes int `json:"bytes"`
Usage int `json:"usage"`
MaxMempool int `json:"maxMempool"`
MinFee string `json:"minFee"`
}
// 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"`
}
// GetBlock gets a block by hash or height.
func (c *Client) GetBlock(ctx context.Context, hashOrHeight interface{}) (*Block, error) {
var path string
switch v := hashOrHeight.(type) {
case int, int64:
path = fmt.Sprintf("/blocks/height/%d", v)
case string:
path = fmt.Sprintf("/blocks/%s", v)
default:
return nil, fmt.Errorf("invalid hashOrHeight type")
}
var block Block
if err := c.request(ctx, "GET", path, nil, &block); err != nil {
return nil, err
}
return &block, nil
}
// GetLatestBlock gets the latest block.
func (c *Client) GetLatestBlock(ctx context.Context) (*Block, error) {
var block Block
if err := c.request(ctx, "GET", "/blocks/latest", nil, &block); err != nil {
return nil, err
}
return &block, nil
}
// GetBlockHeader gets a block header.
func (c *Client) GetBlockHeader(ctx context.Context, hashOrHeight interface{}) (*BlockHeader, error) {
var path string
switch v := hashOrHeight.(type) {
case int, int64:
path = fmt.Sprintf("/blocks/height/%d/header", v)
case string:
path = fmt.Sprintf("/blocks/%s/header", v)
default:
return nil, fmt.Errorf("invalid hashOrHeight type")
}
var header BlockHeader
if err := c.request(ctx, "GET", path, nil, &header); err != nil {
return nil, err
}
return &header, nil
}
// GetTransaction gets a transaction by hash.
func (c *Client) GetTransaction(ctx context.Context, txid string) (*Transaction, error) {
var tx Transaction
if err := c.request(ctx, "GET", "/transactions/"+txid, nil, &tx); err != nil {
return nil, err
}
return &tx, nil
}
// SendRawTransaction sends a raw transaction.
func (c *Client) SendRawTransaction(ctx context.Context, rawTx string) (string, error) {
var resp struct {
TxID string `json:"txid"`
}
if err := c.request(ctx, "POST", "/transactions", map[string]string{"raw": rawTx}, &resp); err != nil {
return "", err
}
return resp.TxID, nil
}
// 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 estimate FeeEstimate
if err := c.request(ctx, "GET", path, nil, &estimate); err != nil {
return nil, err
}
return &estimate, nil
}
// GetChainInfo gets chain information.
func (c *Client) GetChainInfo(ctx context.Context) (*ChainInfo, error) {
var info ChainInfo
if err := c.request(ctx, "GET", "/chain", nil, &info); err != nil {
return nil, err
}
return &info, nil
}
// GetMempoolInfo gets mempool information.
func (c *Client) GetMempoolInfo(ctx context.Context) (*MempoolInfo, error) {
var info MempoolInfo
if err := c.request(ctx, "GET", "/mempool", nil, &info); err != nil {
return nil, err
}
return &info, nil
}
// GetUTXOs gets UTXOs for an address.
func (c *Client) GetUTXOs(ctx context.Context, address string) ([]UTXO, error) {
var resp struct {
UTXOs []UTXO `json:"utxos"`
}
if err := c.request(ctx, "GET", "/addresses/"+address+"/utxos", nil, &resp); err != nil {
return nil, err
}
return resp.UTXOs, nil
}
// GetBalance gets the balance for an address.
func (c *Client) GetBalance(ctx context.Context, address string) (*Balance, error) {
var balance Balance
if err := c.request(ctx, "GET", "/addresses/"+address+"/balance", nil, &balance); err != nil {
return nil, err
}
return &balance, nil
}
// Subscription represents a WebSocket subscription.
type Subscription struct {
ID string
Type string
CreatedAt int64
Unsubscribe func()
}
// SubscribeBlocks subscribes to new blocks.
func (c *Client) SubscribeBlocks(ctx context.Context, callback func(*Block)) (*Subscription, error) {
return c.subscribe(ctx, "blocks", nil, func(data json.RawMessage) {
var resp struct {
Block Block `json:"block"`
}
if err := json.Unmarshal(data, &resp); err == nil {
callback(&resp.Block)
}
})
}
// SubscribeAddress subscribes to address transactions.
func (c *Client) SubscribeAddress(ctx context.Context, address string, callback func(*Transaction)) (*Subscription, error) {
return c.subscribe(ctx, "address", map[string]string{"address": address}, func(data json.RawMessage) {
var resp struct {
Transaction Transaction `json:"transaction"`
}
if err := json.Unmarshal(data, &resp); err == nil {
callback(&resp.Transaction)
}
})
}
func (c *Client) subscribe(ctx context.Context, subType string, params map[string]string, callback func(json.RawMessage)) (*Subscription, error) {
if err := c.ensureWebSocket(ctx); err != nil {
return nil, err
}
subID := fmt.Sprintf("%s_%d", subType, time.Now().UnixMilli())
c.subs[subID] = callback
msg := map[string]interface{}{
"type": "subscribe",
"id": subID,
"subscription": subType,
}
for k, v := range params {
msg[k] = v
}
if err := c.wsConn.WriteJSON(msg); err != nil {
return nil, err
}
return &Subscription{
ID: subID,
Type: subType,
CreatedAt: time.Now().UnixMilli(),
Unsubscribe: func() {
delete(c.subs, subID)
c.wsConn.WriteJSON(map[string]string{"type": "unsubscribe", "id": subID})
},
}, nil
}
func (c *Client) ensureWebSocket(ctx context.Context) error {
if c.wsConn != nil {
return nil
}
u, _ := url.Parse(c.config.WSEndpoint)
q := u.Query()
q.Set("apiKey", c.config.APIKey)
q.Set("network", string(c.config.Network))
u.RawQuery = q.Encode()
conn, _, err := websocket.DefaultDialer.DialContext(ctx, u.String(), nil)
if err != nil {
return fmt.Errorf("websocket connection failed: %w", err)
}
c.wsConn = conn
go c.wsListener()
return nil
}
func (c *Client) wsListener() {
for {
_, message, err := c.wsConn.ReadMessage()
if err != nil {
return
}
var msg struct {
SubscriptionID string `json:"subscriptionId"`
Data json.RawMessage `json:"data"`
}
if err := json.Unmarshal(message, &msg); err != nil {
continue
}
if callback, ok := c.subs[msg.SubscriptionID]; ok {
callback(msg.Data)
}
}
}
// Close closes the client.
func (c *Client) Close() {
if c.wsConn != nil {
c.wsConn.Close()
}
}
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("[SynorRpc] %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 &RpcError{
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
}
// RpcError represents an API error.
type RpcError struct {
Message string
StatusCode int
Code string
}
func (e *RpcError) Error() string {
return fmt.Sprintf("rpc: %s (status %d)", e.Message, e.StatusCode)
}

673
sdk/go/storage/storage.go Normal file
View file

@ -0,0 +1,673 @@
// Package storage provides a Go SDK for Synor Storage operations.
//
// Decentralized storage, pinning, and content retrieval on the Synor network.
//
// Example:
//
// client := storage.NewClient("your-api-key")
// result, err := client.Upload(ctx, []byte("Hello, World!"), nil)
// fmt.Println("CID:", result.CID)
//
// // Get gateway URL
// gateway := client.GetGatewayURL(result.CID, "")
// fmt.Println("URL:", gateway.URL)
package storage
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/url"
"time"
)
// Version of the SDK.
const Version = "0.1.0"
// Default endpoints.
const (
DefaultEndpoint = "https://storage.synor.cc/api/v1"
DefaultGateway = "https://gateway.synor.cc"
)
// PinStatus represents the status of a pin.
type PinStatus string
const (
Queued PinStatus = "queued"
Pinning PinStatus = "pinning"
Pinned PinStatus = "pinned"
Failed PinStatus = "failed"
Unpinned PinStatus = "unpinned"
)
// HashAlgorithm represents a hash algorithm.
type HashAlgorithm string
const (
SHA256 HashAlgorithm = "sha2-256"
BLAKE3 HashAlgorithm = "blake3"
)
// EntryType represents a directory entry type.
type EntryType string
const (
FileType EntryType = "file"
DirectoryType EntryType = "directory"
)
// MatchType represents a pin matching type.
type MatchType string
const (
Exact MatchType = "exact"
IExact MatchType = "iexact"
Partial MatchType = "partial"
IPartial MatchType = "ipartial"
)
// Config holds client configuration.
type Config struct {
APIKey string
Endpoint string
Gateway string
PinningService string
ChunkSize int
Timeout time.Duration
Debug bool
}
// DefaultConfig returns a default configuration.
func DefaultConfig(apiKey string) Config {
return Config{
APIKey: apiKey,
Endpoint: DefaultEndpoint,
Gateway: DefaultGateway,
ChunkSize: 262144, // 256KB
Timeout: 30 * time.Second,
Debug: false,
}
}
// Client is the Synor Storage 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,
},
}
}
// UploadOptions contains options for uploading content.
type UploadOptions struct {
Pin bool
WrapWithDirectory bool
CIDVersion int
HashAlgorithm HashAlgorithm
}
// UploadResponse contains the result of an upload.
type UploadResponse struct {
CID string `json:"cid"`
Size int64 `json:"size"`
Name string `json:"name,omitempty"`
Hash string `json:"hash,omitempty"`
}
// DownloadOptions contains options for downloading content.
type DownloadOptions struct {
Offset int64
Length int64
}
// Pin represents pin information.
type Pin struct {
CID string `json:"cid"`
Status PinStatus `json:"status"`
Name string `json:"name,omitempty"`
Size int64 `json:"size,omitempty"`
CreatedAt int64 `json:"createdAt,omitempty"`
ExpiresAt int64 `json:"expiresAt,omitempty"`
Delegates []string `json:"delegates,omitempty"`
}
// PinRequest contains options for pinning content.
type PinRequest struct {
CID string `json:"cid"`
Name string `json:"name,omitempty"`
Duration int64 `json:"duration,omitempty"`
Origins []string `json:"origins,omitempty"`
}
// ListPinsOptions contains options for listing pins.
type ListPinsOptions struct {
Status []PinStatus
Match MatchType
Name string
Limit int
Offset int
}
// ListPinsResponse contains the result of listing pins.
type ListPinsResponse struct {
Pins []Pin `json:"pins"`
Total int `json:"total"`
HasMore bool `json:"hasMore"`
}
// GatewayURL represents a gateway URL.
type GatewayURL struct {
URL string
CID string
Path string
}
// CarBlock represents a CAR block.
type CarBlock struct {
CID string `json:"cid"`
Data string `json:"data"`
Size int64 `json:"size,omitempty"`
}
// CarFile represents a CAR file.
type CarFile struct {
Version int `json:"version"`
Roots []string `json:"roots"`
Blocks []CarBlock `json:"blocks,omitempty"`
Size int64 `json:"size,omitempty"`
}
// FileEntry represents a file entry for directory creation.
type FileEntry struct {
Name string
Content []byte
CID string
}
// DirectoryEntry represents a directory entry.
type DirectoryEntry struct {
Name string `json:"name"`
CID string `json:"cid"`
Size int64 `json:"size,omitempty"`
Type EntryType `json:"type"`
}
// ImportCarResponse contains the result of importing a CAR file.
type ImportCarResponse struct {
Roots []string `json:"roots"`
BlocksImported int `json:"blocksImported"`
}
// StorageStats represents storage statistics.
type StorageStats struct {
TotalSize int64 `json:"totalSize"`
PinCount int `json:"pinCount"`
Bandwidth struct {
Upload int64 `json:"upload"`
Download int64 `json:"download"`
} `json:"bandwidth,omitempty"`
}
// Upload uploads content to storage.
func (c *Client) Upload(ctx context.Context, data []byte, opts *UploadOptions) (*UploadResponse, error) {
var buf bytes.Buffer
writer := multipart.NewWriter(&buf)
part, err := writer.CreateFormFile("file", "file")
if err != nil {
return nil, fmt.Errorf("failed to create form file: %w", err)
}
if _, err := part.Write(data); err != nil {
return nil, fmt.Errorf("failed to write data: %w", err)
}
if err := writer.Close(); err != nil {
return nil, fmt.Errorf("failed to close writer: %w", err)
}
params := url.Values{}
if opts != nil {
if opts.Pin {
params.Set("pin", "true")
}
if opts.WrapWithDirectory {
params.Set("wrapWithDirectory", "true")
}
if opts.CIDVersion != 0 {
params.Set("cidVersion", fmt.Sprintf("%d", opts.CIDVersion))
}
if opts.HashAlgorithm != "" {
params.Set("hashAlgorithm", string(opts.HashAlgorithm))
}
}
path := "/upload"
if len(params) > 0 {
path += "?" + params.Encode()
}
reqURL := c.config.Endpoint + path
req, err := http.NewRequestWithContext(ctx, "POST", reqURL, &buf)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+c.config.APIKey)
req.Header.Set("Content-Type", writer.FormDataContentType())
if c.config.Debug {
fmt.Printf("[SynorStorage] POST %s\n", path)
}
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return nil, c.parseError(resp)
}
var result UploadResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
return &result, nil
}
// Download downloads content by CID.
func (c *Client) Download(ctx context.Context, cid string, opts *DownloadOptions) ([]byte, error) {
params := url.Values{}
if opts != nil {
if opts.Offset > 0 {
params.Set("offset", fmt.Sprintf("%d", opts.Offset))
}
if opts.Length > 0 {
params.Set("length", fmt.Sprintf("%d", opts.Length))
}
}
path := fmt.Sprintf("/content/%s", cid)
if len(params) > 0 {
path += "?" + params.Encode()
}
reqURL := c.config.Endpoint + path
req, err := http.NewRequestWithContext(ctx, "GET", reqURL, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+c.config.APIKey)
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return nil, c.parseError(resp)
}
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
return data, nil
}
// DownloadReader returns a reader for downloading content.
func (c *Client) DownloadReader(ctx context.Context, cid string, opts *DownloadOptions) (io.ReadCloser, error) {
params := url.Values{}
if opts != nil {
if opts.Offset > 0 {
params.Set("offset", fmt.Sprintf("%d", opts.Offset))
}
if opts.Length > 0 {
params.Set("length", fmt.Sprintf("%d", opts.Length))
}
}
path := fmt.Sprintf("/content/%s/stream", cid)
if len(params) > 0 {
path += "?" + params.Encode()
}
reqURL := c.config.Endpoint + path
req, err := http.NewRequestWithContext(ctx, "GET", reqURL, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+c.config.APIKey)
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
if resp.StatusCode >= 400 {
defer resp.Body.Close()
return nil, c.parseError(resp)
}
return resp.Body, nil
}
// Pin pins content by CID.
func (c *Client) Pin(ctx context.Context, req PinRequest) (*Pin, error) {
var result Pin
if err := c.request(ctx, "POST", "/pins", req, &result); err != nil {
return nil, err
}
return &result, nil
}
// Unpin unpins content by CID.
func (c *Client) Unpin(ctx context.Context, cid string) error {
return c.request(ctx, "DELETE", "/pins/"+cid, nil, nil)
}
// GetPinStatus gets the pin status for a CID.
func (c *Client) GetPinStatus(ctx context.Context, cid string) (*Pin, error) {
var result Pin
if err := c.request(ctx, "GET", "/pins/"+cid, nil, &result); err != nil {
return nil, err
}
return &result, nil
}
// ListPins lists pins.
func (c *Client) ListPins(ctx context.Context, opts *ListPinsOptions) (*ListPinsResponse, error) {
path := "/pins"
if opts != nil {
params := url.Values{}
if len(opts.Status) > 0 {
var statuses []string
for _, s := range opts.Status {
statuses = append(statuses, string(s))
}
params.Set("status", joinStrings(statuses, ","))
}
if opts.Match != "" {
params.Set("match", string(opts.Match))
}
if opts.Name != "" {
params.Set("name", opts.Name)
}
if opts.Limit > 0 {
params.Set("limit", fmt.Sprintf("%d", opts.Limit))
}
if opts.Offset > 0 {
params.Set("offset", fmt.Sprintf("%d", opts.Offset))
}
if len(params) > 0 {
path += "?" + params.Encode()
}
}
var result ListPinsResponse
if err := c.request(ctx, "GET", path, nil, &result); err != nil {
return nil, err
}
return &result, nil
}
// GetGatewayURL returns the gateway URL for content.
func (c *Client) GetGatewayURL(cid string, path string) GatewayURL {
fullPath := "/" + cid
if path != "" {
fullPath += "/" + path
}
return GatewayURL{
URL: c.config.Gateway + "/ipfs" + fullPath,
CID: cid,
Path: path,
}
}
// CreateCar creates a CAR file from files.
func (c *Client) CreateCar(ctx context.Context, files []FileEntry) (*CarFile, error) {
type fileData struct {
Name string `json:"name"`
Content string `json:"content,omitempty"`
CID string `json:"cid,omitempty"`
}
var entries []fileData
for _, f := range files {
entry := fileData{Name: f.Name}
if len(f.Content) > 0 {
entry.Content = base64.StdEncoding.EncodeToString(f.Content)
}
if f.CID != "" {
entry.CID = f.CID
}
entries = append(entries, entry)
}
var result CarFile
if err := c.request(ctx, "POST", "/car/create", map[string]interface{}{"files": entries}, &result); err != nil {
return nil, err
}
return &result, nil
}
// ImportCar imports a CAR file.
func (c *Client) ImportCar(ctx context.Context, carData []byte, pin bool) (*ImportCarResponse, error) {
encoded := base64.StdEncoding.EncodeToString(carData)
body := map[string]interface{}{
"car": encoded,
"pin": pin,
}
var result ImportCarResponse
if err := c.request(ctx, "POST", "/car/import", body, &result); err != nil {
return nil, err
}
return &result, nil
}
// ExportCar exports content as a CAR file.
func (c *Client) ExportCar(ctx context.Context, cid string) ([]byte, error) {
reqURL := c.config.Endpoint + "/car/export/" + cid
req, err := http.NewRequestWithContext(ctx, "GET", reqURL, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+c.config.APIKey)
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return nil, c.parseError(resp)
}
return io.ReadAll(resp.Body)
}
// CreateDirectory creates a directory from files.
func (c *Client) CreateDirectory(ctx context.Context, files []FileEntry) (*UploadResponse, error) {
type fileData struct {
Name string `json:"name"`
Content string `json:"content,omitempty"`
CID string `json:"cid,omitempty"`
}
var entries []fileData
for _, f := range files {
entry := fileData{Name: f.Name}
if len(f.Content) > 0 {
entry.Content = base64.StdEncoding.EncodeToString(f.Content)
}
if f.CID != "" {
entry.CID = f.CID
}
entries = append(entries, entry)
}
var result UploadResponse
if err := c.request(ctx, "POST", "/directory", map[string]interface{}{"files": entries}, &result); err != nil {
return nil, err
}
return &result, nil
}
// ListDirectory lists directory contents.
func (c *Client) ListDirectory(ctx context.Context, cid string, path string) ([]DirectoryEntry, error) {
apiPath := fmt.Sprintf("/directory/%s", cid)
if path != "" {
apiPath += "?path=" + url.QueryEscape(path)
}
var resp struct {
Entries []DirectoryEntry `json:"entries"`
}
if err := c.request(ctx, "GET", apiPath, nil, &resp); err != nil {
return nil, err
}
return resp.Entries, nil
}
// GetStats gets storage statistics.
func (c *Client) GetStats(ctx context.Context) (*StorageStats, error) {
var result StorageStats
if err := c.request(ctx, "GET", "/stats", nil, &result); err != nil {
return nil, err
}
return &result, nil
}
// Exists checks if content exists.
func (c *Client) Exists(ctx context.Context, cid string) (bool, error) {
reqURL := c.config.Endpoint + "/content/" + cid
req, err := http.NewRequestWithContext(ctx, "HEAD", reqURL, nil)
if err != nil {
return false, err
}
req.Header.Set("Authorization", "Bearer "+c.config.APIKey)
resp, err := c.httpClient.Do(req)
if err != nil {
return false, nil
}
defer resp.Body.Close()
return resp.StatusCode == 200, nil
}
// GetMetadata gets content metadata.
func (c *Client) GetMetadata(ctx context.Context, cid string) (map[string]interface{}, error) {
var result map[string]interface{}
if err := c.request(ctx, "GET", "/content/"+cid+"/metadata", nil, &result); err != nil {
return nil, err
}
return result, 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")
if c.config.Debug {
fmt.Printf("[SynorStorage] %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 {
return c.parseError(resp)
}
if result != nil && resp.StatusCode != 204 {
if err := json.NewDecoder(resp.Body).Decode(result); err != nil {
return fmt.Errorf("failed to decode response: %w", err)
}
}
return nil
}
func (c *Client) parseError(resp *http.Response) error {
var errResp struct {
Message string `json:"message"`
Code string `json:"code"`
}
json.NewDecoder(resp.Body).Decode(&errResp)
return &StorageError{
Message: errResp.Message,
StatusCode: resp.StatusCode,
Code: errResp.Code,
}
}
func joinStrings(strs []string, sep string) string {
if len(strs) == 0 {
return ""
}
result := strs[0]
for i := 1; i < len(strs); i++ {
result += sep + strs[i]
}
return result
}
// StorageError represents an API error.
type StorageError struct {
Message string
StatusCode int
Code string
}
func (e *StorageError) Error() string {
return fmt.Sprintf("storage: %s (status %d)", e.Message, e.StatusCode)
}

507
sdk/go/wallet/wallet.go Normal file
View file

@ -0,0 +1,507 @@
// 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)
}

View file

@ -1,7 +1,7 @@
{ {
"name": "@synor/compute-sdk", "name": "@synor/sdk",
"version": "0.1.0", "version": "0.1.0",
"description": "Synor Compute SDK for browser and Node.js - Access distributed GPU/TPU/NPU compute", "description": "Synor SDK for browser and Node.js - Complete blockchain SDK including Compute, Wallet, RPC, and Storage",
"main": "dist/index.js", "main": "dist/index.js",
"module": "dist/index.mjs", "module": "dist/index.mjs",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
@ -10,28 +10,52 @@
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"import": "./dist/index.mjs", "import": "./dist/index.mjs",
"require": "./dist/index.js" "require": "./dist/index.js"
},
"./compute": {
"types": "./dist/compute/index.d.ts",
"import": "./dist/compute/index.mjs",
"require": "./dist/compute/index.js"
},
"./wallet": {
"types": "./dist/wallet/index.d.ts",
"import": "./dist/wallet/index.mjs",
"require": "./dist/wallet/index.js"
},
"./rpc": {
"types": "./dist/rpc/index.d.ts",
"import": "./dist/rpc/index.mjs",
"require": "./dist/rpc/index.js"
},
"./storage": {
"types": "./dist/storage/index.d.ts",
"import": "./dist/storage/index.mjs",
"require": "./dist/storage/index.js"
} }
}, },
"files": [ "files": [
"dist" "dist"
], ],
"scripts": { "scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts", "build": "tsup src/index.ts src/compute/index.ts src/wallet/index.ts src/rpc/index.ts src/storage/index.ts --format cjs,esm --dts",
"dev": "tsup src/index.ts --format cjs,esm --dts --watch", "dev": "tsup src/index.ts src/compute/index.ts src/wallet/index.ts src/rpc/index.ts src/storage/index.ts --format cjs,esm --dts --watch",
"test": "vitest", "test": "vitest",
"lint": "biome check src/", "lint": "biome check src/",
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"keywords": [ "keywords": [
"synor", "synor",
"blockchain",
"compute", "compute",
"wallet",
"rpc",
"storage",
"ipfs",
"gpu", "gpu",
"tpu", "tpu",
"npu", "npu",
"ai", "ai",
"ml", "ml",
"distributed-computing", "distributed-computing"
"heterogeneous-compute"
], ],
"author": "Synor Team", "author": "Synor Team",
"license": "MIT", "license": "MIT",

403
sdk/js/src/rpc/client.ts Normal file
View file

@ -0,0 +1,403 @@
/**
* Synor RPC Client
*/
import type {
RpcConfig,
Block,
BlockHeader,
Transaction,
FeeEstimate,
ChainInfo,
MempoolInfo,
UTXO,
Subscription,
SubscriptionType,
Priority,
Notification,
} from './types';
const DEFAULT_ENDPOINT = 'https://rpc.synor.cc/api/v1';
const DEFAULT_WS_ENDPOINT = 'wss://rpc.synor.cc/ws';
/**
* Synor RPC SDK error.
*/
export class RpcError extends Error {
constructor(
message: string,
public statusCode?: number,
public code?: string
) {
super(message);
this.name = 'RpcError';
}
}
/**
* Main Synor RPC client.
*
* @example
* ```ts
* const rpc = new SynorRpc({ apiKey: 'sk_...' });
*
* // Get latest block
* const block = await rpc.getLatestBlock();
* console.log('Height:', block.height);
*
* // Subscribe to new blocks
* const sub = await rpc.subscribeBlocks((block) => {
* console.log('New block:', block.height);
* });
* ```
*/
export class SynorRpc {
private config: Required<RpcConfig>;
private ws: WebSocket | null = null;
private subscriptions: Map<string, (data: Notification) => void> = new Map();
private reconnectAttempts = 0;
private maxReconnectAttempts = 5;
constructor(config: RpcConfig) {
this.config = {
apiKey: config.apiKey,
endpoint: config.endpoint ?? DEFAULT_ENDPOINT,
wsEndpoint: config.wsEndpoint ?? DEFAULT_WS_ENDPOINT,
network: config.network ?? 'mainnet',
timeout: config.timeout ?? 30000,
debug: config.debug ?? false,
};
}
// ==================== Block Operations ====================
/**
* Get block by hash or height.
*/
async getBlock(hashOrHeight: string | number): Promise<Block> {
const path = typeof hashOrHeight === 'number'
? `/blocks/height/${hashOrHeight}`
: `/blocks/${hashOrHeight}`;
return this.request('GET', path);
}
/**
* Get latest block.
*/
async getLatestBlock(): Promise<Block> {
return this.request('GET', '/blocks/latest');
}
/**
* Get block header only.
*/
async getBlockHeader(hashOrHeight: string | number): Promise<BlockHeader> {
const path = typeof hashOrHeight === 'number'
? `/blocks/height/${hashOrHeight}/header`
: `/blocks/${hashOrHeight}/header`;
return this.request('GET', path);
}
/**
* Get blocks in a range.
*/
async getBlocks(startHeight: number, endHeight: number): Promise<Block[]> {
const response = await this.request('GET', '/blocks', undefined, {
start: startHeight.toString(),
end: endHeight.toString(),
});
return response.blocks;
}
// ==================== Transaction Operations ====================
/**
* Get transaction by hash.
*/
async getTransaction(txid: string): Promise<Transaction> {
return this.request('GET', `/transactions/${txid}`);
}
/**
* Get raw transaction hex.
*/
async getRawTransaction(txid: string): Promise<string> {
const response = await this.request('GET', `/transactions/${txid}/raw`);
return response.raw;
}
/**
* Send a raw transaction.
*/
async sendRawTransaction(rawTx: string): Promise<string> {
const response = await this.request('POST', '/transactions', { raw: rawTx });
return response.txid;
}
/**
* Get transactions for an address.
*/
async getAddressTransactions(
address: string,
options?: { limit?: number; offset?: number }
): Promise<Transaction[]> {
const params: Record<string, string> = {};
if (options?.limit) params.limit = options.limit.toString();
if (options?.offset) params.offset = options.offset.toString();
const response = await this.request(
'GET',
`/addresses/${address}/transactions`,
undefined,
params
);
return response.transactions;
}
// ==================== Fee Estimation ====================
/**
* Estimate fee for a priority level.
*/
async estimateFee(priority: Priority = 'medium'): Promise<FeeEstimate> {
return this.request('GET', '/fees/estimate', undefined, { priority });
}
/**
* Get all fee estimates.
*/
async getAllFeeEstimates(): Promise<FeeEstimate[]> {
const response = await this.request('GET', '/fees/estimate/all');
return response.estimates;
}
// ==================== Chain Information ====================
/**
* Get chain information.
*/
async getChainInfo(): Promise<ChainInfo> {
return this.request('GET', '/chain');
}
/**
* Get mempool information.
*/
async getMempoolInfo(): Promise<MempoolInfo> {
return this.request('GET', '/mempool');
}
/**
* Get mempool transactions.
*/
async getMempoolTransactions(limit: number = 100): Promise<string[]> {
const response = await this.request('GET', '/mempool/transactions', undefined, {
limit: limit.toString(),
});
return response.txids;
}
// ==================== UTXO Operations ====================
/**
* Get UTXOs for an address.
*/
async getUtxos(address: string): Promise<UTXO[]> {
const response = await this.request('GET', `/addresses/${address}/utxos`);
return response.utxos;
}
/**
* Get balance for an address.
*/
async getBalance(address: string): Promise<{ confirmed: string; unconfirmed: string; total: string }> {
return this.request('GET', `/addresses/${address}/balance`);
}
// ==================== Subscriptions ====================
/**
* Subscribe to new blocks.
*/
async subscribeBlocks(callback: (block: Block) => void): Promise<Subscription> {
return this.subscribe('blocks', (notification) => {
if (notification.type === 'block') {
callback(notification.block);
}
});
}
/**
* Subscribe to address transactions.
*/
async subscribeAddress(
address: string,
callback: (tx: Transaction) => void
): Promise<Subscription> {
return this.subscribe('address', (notification) => {
if (notification.type === 'transaction') {
callback(notification.transaction);
}
}, { address });
}
/**
* Subscribe to mempool transactions.
*/
async subscribeMempool(callback: (tx: Transaction) => void): Promise<Subscription> {
return this.subscribe('mempool', (notification) => {
if (notification.type === 'transaction') {
callback(notification.transaction);
}
});
}
private async subscribe(
type: SubscriptionType,
callback: (notification: Notification) => void,
params?: Record<string, string>
): Promise<Subscription> {
await this.ensureWebSocket();
const id = `${type}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
this.subscriptions.set(id, callback);
this.ws!.send(JSON.stringify({
type: 'subscribe',
id,
subscription: type,
...params,
}));
return {
id,
type,
createdAt: Date.now(),
unsubscribe: () => this.unsubscribe(id),
};
}
private unsubscribe(id: string): void {
this.subscriptions.delete(id);
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({
type: 'unsubscribe',
id,
}));
}
}
private async ensureWebSocket(): Promise<void> {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
return;
}
return new Promise((resolve, reject) => {
const wsUrl = `${this.config.wsEndpoint}?apiKey=${this.config.apiKey}&network=${this.config.network}`;
this.ws = new WebSocket(wsUrl);
this.ws.onopen = () => {
this.reconnectAttempts = 0;
if (this.config.debug) {
console.log('[SynorRpc] WebSocket connected');
}
resolve();
};
this.ws.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
if (message.subscriptionId && this.subscriptions.has(message.subscriptionId)) {
this.subscriptions.get(message.subscriptionId)!(message.data);
}
} catch {
console.error('[SynorRpc] Failed to parse WebSocket message');
}
};
this.ws.onerror = (error) => {
console.error('[SynorRpc] WebSocket error:', error);
reject(new RpcError('WebSocket connection failed'));
};
this.ws.onclose = () => {
if (this.config.debug) {
console.log('[SynorRpc] WebSocket closed');
}
this.attemptReconnect();
};
});
}
private attemptReconnect(): void {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error('[SynorRpc] Max reconnect attempts reached');
return;
}
this.reconnectAttempts++;
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
setTimeout(() => {
if (this.subscriptions.size > 0) {
this.ensureWebSocket().catch(console.error);
}
}, delay);
}
/**
* Close the client and all connections.
*/
close(): void {
if (this.ws) {
this.ws.close();
this.ws = null;
}
this.subscriptions.clear();
}
// ==================== HTTP Request ====================
private async request(
method: string,
path: string,
body?: unknown,
params?: Record<string, string>
): Promise<any> {
let url = `${this.config.endpoint}${path}`;
if (params && Object.keys(params).length > 0) {
const searchParams = new URLSearchParams(params);
url += `?${searchParams.toString()}`;
}
if (this.config.debug) {
console.log(`[SynorRpc] ${method} ${url}`);
}
const response = await fetch(url, {
method,
headers: {
'Authorization': `Bearer ${this.config.apiKey}`,
'Content-Type': 'application/json',
'X-Network': this.config.network,
},
body: body ? JSON.stringify(body) : undefined,
signal: AbortSignal.timeout(this.config.timeout),
});
if (!response.ok) {
const error = await response.json().catch(() => ({ message: response.statusText }));
throw new RpcError(
error.message || 'Request failed',
response.status,
error.code
);
}
return response.json();
}
}

26
sdk/js/src/rpc/index.ts Normal file
View file

@ -0,0 +1,26 @@
/**
* Synor RPC SDK
*
* @packageDocumentation
*/
export { SynorRpc, RpcError } from './client';
export type {
RpcConfig,
TransactionStatus,
Priority,
SubscriptionType,
BlockHeader,
Block,
TxInput,
TxOutput,
Transaction,
FeeEstimate,
ChainInfo,
MempoolInfo,
UTXO,
Subscription,
BlockNotification,
TransactionNotification,
Notification,
} from './types';

203
sdk/js/src/rpc/types.ts Normal file
View file

@ -0,0 +1,203 @@
/**
* Synor RPC SDK Types
*/
/** RPC SDK configuration */
export interface RpcConfig {
/** API key for authentication */
apiKey: string;
/** HTTP API endpoint */
endpoint?: string;
/** WebSocket endpoint for subscriptions */
wsEndpoint?: string;
/** Network type */
network?: 'mainnet' | 'testnet';
/** Request timeout in milliseconds */
timeout?: number;
/** Enable debug logging */
debug?: boolean;
}
/** Transaction status */
export type TransactionStatus = 'pending' | 'confirmed' | 'failed' | 'replaced';
/** Transaction priority */
export type Priority = 'low' | 'medium' | 'high' | 'urgent';
/** Subscription type */
export type SubscriptionType = 'blocks' | 'transactions' | 'address' | 'mempool';
/** Block header */
export interface BlockHeader {
/** Block hash */
hash: string;
/** Block height */
height: number;
/** Block version */
version: number;
/** Previous block hash */
previousHash: string;
/** Merkle root */
merkleRoot: string;
/** Block timestamp */
timestamp: number;
/** Difficulty target */
difficulty: string;
/** Nonce */
nonce: number;
}
/** Full block */
export interface Block extends BlockHeader {
/** Transaction hashes */
transactions: string[];
/** Block size in bytes */
size: number;
/** Block weight */
weight: number;
/** Transaction count */
txCount: number;
}
/** Transaction input */
export interface TxInput {
/** Previous transaction hash */
txid: string;
/** Output index */
vout: number;
/** Script signature */
scriptSig: string;
/** Sequence number */
sequence: number;
}
/** Transaction output */
export interface TxOutput {
/** Amount */
value: string;
/** Output index */
n: number;
/** Script pubkey */
scriptPubKey: {
asm: string;
hex: string;
type: string;
addresses?: string[];
};
}
/** Transaction */
export interface Transaction {
/** Transaction hash */
txid: string;
/** Block hash (if confirmed) */
blockHash?: string;
/** Block height (if confirmed) */
blockHeight?: number;
/** Number of confirmations */
confirmations: number;
/** Block timestamp */
timestamp?: number;
/** Transaction status */
status: TransactionStatus;
/** Raw transaction hex */
raw?: string;
/** Transaction size */
size: number;
/** Transaction fee */
fee: string;
/** Inputs */
inputs: TxInput[];
/** Outputs */
outputs: TxOutput[];
}
/** Fee estimate */
export interface FeeEstimate {
/** Priority level */
priority: Priority;
/** Fee rate (satoshis per byte) */
feeRate: string;
/** Estimated blocks until confirmation */
estimatedBlocks: number;
}
/** Chain information */
export interface ChainInfo {
/** Chain name */
chain: string;
/** Network name */
network: string;
/** Current block height */
height: number;
/** Best block hash */
bestBlockHash: string;
/** Current difficulty */
difficulty: string;
/** Median time */
medianTime: number;
/** Chain work */
chainWork: string;
/** Syncing status */
syncing: boolean;
/** Sync progress (0-1) */
syncProgress: number;
}
/** Mempool information */
export interface MempoolInfo {
/** Number of transactions */
size: number;
/** Total size in bytes */
bytes: number;
/** Memory usage */
usage: number;
/** Maximum mempool size */
maxMempool: number;
/** Minimum fee for relay */
minFee: string;
}
/** UTXO */
export interface UTXO {
/** Transaction hash */
txid: string;
/** Output index */
vout: number;
/** Amount */
amount: string;
/** Address */
address: string;
/** Confirmations */
confirmations: number;
/** Script pubkey */
scriptPubKey: string;
}
/** Subscription */
export interface Subscription {
/** Subscription ID */
id: string;
/** Subscription type */
type: SubscriptionType;
/** Creation timestamp */
createdAt: number;
/** Unsubscribe function */
unsubscribe: () => void;
}
/** Block notification */
export interface BlockNotification {
type: 'block';
block: Block;
}
/** Transaction notification */
export interface TransactionNotification {
type: 'transaction';
transaction: Transaction;
address?: string;
}
/** Notification types */
export type Notification = BlockNotification | TransactionNotification;

View file

@ -0,0 +1,365 @@
/**
* Synor Storage SDK Client
*
* Decentralized storage, pinning, and content retrieval.
*
* @example
* ```typescript
* import { SynorStorage } from '@synor/storage';
*
* const storage = new SynorStorage({ apiKey: 'your-api-key' });
*
* // Upload a file
* const result = await storage.upload(Buffer.from('Hello, World!'));
* console.log('CID:', result.cid);
*
* // Download content
* const data = await storage.download(result.cid);
* console.log('Content:', data.toString());
* ```
*/
import type {
StorageConfig,
UploadOptions,
UploadResponse,
DownloadOptions,
Pin,
PinRequest,
ListPinsOptions,
ListPinsResponse,
GatewayUrl,
CarFile,
FileEntry,
DirectoryEntry,
ImportCarResponse,
StorageStats,
StorageError,
} from './types';
const DEFAULT_ENDPOINT = 'https://storage.synor.cc/api/v1';
const DEFAULT_GATEWAY = 'https://gateway.synor.cc';
const DEFAULT_CHUNK_SIZE = 262144; // 256KB
const DEFAULT_TIMEOUT = 30000;
export class SynorStorage {
private config: Required<Omit<StorageConfig, 'pinningService'>> & Pick<StorageConfig, 'pinningService'>;
constructor(config: StorageConfig) {
this.config = {
apiKey: config.apiKey,
endpoint: config.endpoint || DEFAULT_ENDPOINT,
gateway: config.gateway || DEFAULT_GATEWAY,
pinningService: config.pinningService,
chunkSize: config.chunkSize || DEFAULT_CHUNK_SIZE,
timeout: config.timeout || DEFAULT_TIMEOUT,
debug: config.debug || false,
};
}
/**
* Upload content to storage.
*/
async upload(
data: Buffer | Uint8Array | string,
options: UploadOptions = {}
): Promise<UploadResponse> {
const formData = new FormData();
const blob = new Blob([data]);
formData.append('file', blob);
const params = new URLSearchParams();
if (options.pin !== undefined) params.set('pin', String(options.pin));
if (options.wrapWithDirectory) params.set('wrapWithDirectory', 'true');
if (options.cidVersion !== undefined) params.set('cidVersion', String(options.cidVersion));
if (options.hashAlgorithm) params.set('hashAlgorithm', options.hashAlgorithm);
const url = `${this.config.endpoint}/upload${params.toString() ? `?${params}` : ''}`;
const response = await this.fetchWithAuth(url, {
method: 'POST',
body: formData,
});
return response as UploadResponse;
}
/**
* Upload a file from a path (Node.js only).
*/
async uploadFile(
filePath: string,
options: UploadOptions = {}
): Promise<UploadResponse> {
// This would need fs module in Node.js
// For browser, use upload() with File object
const response = await this.request('POST', '/upload/file', {
filePath,
...options,
});
return response as UploadResponse;
}
/**
* Download content by CID.
*/
async download(cid: string, options: DownloadOptions = {}): Promise<Buffer> {
const params = new URLSearchParams();
if (options.offset !== undefined) params.set('offset', String(options.offset));
if (options.length !== undefined) params.set('length', String(options.length));
const url = `${this.config.endpoint}/content/${cid}${params.toString() ? `?${params}` : ''}`;
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${this.config.apiKey}`,
},
});
if (!response.ok) {
await this.handleError(response);
}
const arrayBuffer = await response.arrayBuffer();
return Buffer.from(arrayBuffer);
}
/**
* Download content as a stream.
*/
async downloadStream(cid: string, options: DownloadOptions = {}): Promise<ReadableStream<Uint8Array>> {
const params = new URLSearchParams();
if (options.offset !== undefined) params.set('offset', String(options.offset));
if (options.length !== undefined) params.set('length', String(options.length));
const url = `${this.config.endpoint}/content/${cid}/stream${params.toString() ? `?${params}` : ''}`;
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${this.config.apiKey}`,
},
});
if (!response.ok) {
await this.handleError(response);
}
if (!response.body) {
throw new Error('No response body');
}
return response.body;
}
/**
* Pin content by CID.
*/
async pin(request: PinRequest): Promise<Pin> {
const response = await this.request('POST', '/pins', request);
return response as Pin;
}
/**
* Unpin content by CID.
*/
async unpin(cid: string): Promise<void> {
await this.request('DELETE', `/pins/${cid}`);
}
/**
* Get pin status by CID.
*/
async getPinStatus(cid: string): Promise<Pin> {
const response = await this.request('GET', `/pins/${cid}`);
return response as Pin;
}
/**
* List pins.
*/
async listPins(options: ListPinsOptions = {}): Promise<ListPinsResponse> {
const params = new URLSearchParams();
if (options.status) params.set('status', options.status.join(','));
if (options.match) params.set('match', options.match);
if (options.name) params.set('name', options.name);
if (options.limit !== undefined) params.set('limit', String(options.limit));
if (options.offset !== undefined) params.set('offset', String(options.offset));
const response = await this.request('GET', `/pins?${params}`);
return response as ListPinsResponse;
}
/**
* Get gateway URL for content.
*/
getGatewayUrl(cid: string, path?: string): GatewayUrl {
const fullPath = path ? `/${cid}/${path}` : `/${cid}`;
return {
url: `${this.config.gateway}/ipfs${fullPath}`,
cid,
path,
};
}
/**
* Create a CAR file from files.
*/
async createCar(files: FileEntry[]): Promise<CarFile> {
const response = await this.request('POST', '/car/create', { files });
return response as CarFile;
}
/**
* Import a CAR file.
*/
async importCar(carData: Buffer | string, pin = true): Promise<ImportCarResponse> {
const base64 = typeof carData === 'string' ? carData : carData.toString('base64');
const response = await this.request('POST', '/car/import', {
car: base64,
pin,
});
return response as ImportCarResponse;
}
/**
* Export content as a CAR file.
*/
async exportCar(cid: string): Promise<Buffer> {
const url = `${this.config.endpoint}/car/export/${cid}`;
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${this.config.apiKey}`,
},
});
if (!response.ok) {
await this.handleError(response);
}
const arrayBuffer = await response.arrayBuffer();
return Buffer.from(arrayBuffer);
}
/**
* Create a directory from files.
*/
async createDirectory(files: FileEntry[]): Promise<UploadResponse> {
const response = await this.request('POST', '/directory', { files });
return response as UploadResponse;
}
/**
* List directory contents.
*/
async listDirectory(cid: string, path?: string): Promise<DirectoryEntry[]> {
const params = path ? `?path=${encodeURIComponent(path)}` : '';
const response = await this.request('GET', `/directory/${cid}${params}`);
return (response as { entries: DirectoryEntry[] }).entries;
}
/**
* Get storage statistics.
*/
async getStats(): Promise<StorageStats> {
const response = await this.request('GET', '/stats');
return response as StorageStats;
}
/**
* Check if content exists.
*/
async exists(cid: string): Promise<boolean> {
try {
await this.request('HEAD', `/content/${cid}`);
return true;
} catch {
return false;
}
}
/**
* Get content metadata.
*/
async getMetadata(cid: string): Promise<{ size: number; type: string }> {
const response = await this.request('GET', `/content/${cid}/metadata`);
return response as { size: number; type: string };
}
private async request(
method: string,
path: string,
body?: unknown
): Promise<unknown> {
const url = `${this.config.endpoint}${path}`;
if (this.config.debug) {
console.log(`[SynorStorage] ${method} ${path}`);
}
const response = await this.fetchWithAuth(url, {
method,
body: body ? JSON.stringify(body) : undefined,
});
return response;
}
private async fetchWithAuth(url: string, options: RequestInit): Promise<unknown> {
const headers: Record<string, string> = {
Authorization: `Bearer ${this.config.apiKey}`,
};
if (options.body && typeof options.body === 'string') {
headers['Content-Type'] = 'application/json';
}
const response = await fetch(url, {
...options,
headers: {
...headers,
...(options.headers as Record<string, string>),
},
signal: AbortSignal.timeout(this.config.timeout),
});
if (!response.ok) {
await this.handleError(response);
}
if (response.status === 204 || options.method === 'HEAD') {
return {};
}
return response.json();
}
private async handleError(response: Response): Promise<never> {
let message = 'Unknown error';
let code: string | undefined;
try {
const errorBody = await response.json();
message = errorBody.message || message;
code = errorBody.code;
} catch {
message = response.statusText;
}
const error = new Error(message) as StorageError;
error.statusCode = response.status;
error.code = code;
throw error;
}
}
export class StorageApiError extends Error implements StorageError {
constructor(
message: string,
public statusCode: number,
public code?: string
) {
super(message);
this.name = 'StorageApiError';
}
}

View file

@ -0,0 +1,51 @@
/**
* Synor Storage SDK
*
* Decentralized storage, pinning, and content retrieval.
*
* @example
* ```typescript
* import { SynorStorage } from '@synor/storage';
*
* const storage = new SynorStorage({ apiKey: 'your-api-key' });
*
* // Upload content
* const result = await storage.upload(Buffer.from('Hello, World!'));
* console.log('CID:', result.cid);
*
* // Pin content
* await storage.pin({ cid: result.cid, name: 'my-file' });
*
* // Get gateway URL
* const gateway = storage.getGatewayUrl(result.cid);
* console.log('URL:', gateway.url);
* ```
*
* @packageDocumentation
*/
export { SynorStorage, StorageApiError } from './client';
export type {
StorageConfig,
PinStatus,
HashAlgorithm,
EntryType,
MatchType,
UploadOptions,
UploadProgress,
UploadResponse,
DownloadOptions,
DownloadProgress,
Pin,
PinRequest,
ListPinsOptions,
ListPinsResponse,
GatewayUrl,
CarBlock,
CarFile,
FileEntry,
DirectoryEntry,
ImportCarResponse,
StorageStats,
StorageError,
} from './types';

158
sdk/js/src/storage/types.ts Normal file
View file

@ -0,0 +1,158 @@
/**
* Synor Storage SDK Types
*/
/** Pin status */
export type PinStatus = 'queued' | 'pinning' | 'pinned' | 'failed' | 'unpinned';
/** Hash algorithm */
export type HashAlgorithm = 'sha2-256' | 'blake3';
/** Entry type */
export type EntryType = 'file' | 'directory';
/** Match type for listing pins */
export type MatchType = 'exact' | 'iexact' | 'partial' | 'ipartial';
/** Storage SDK configuration */
export interface StorageConfig {
apiKey: string;
endpoint?: string;
gateway?: string;
pinningService?: string;
chunkSize?: number;
timeout?: number;
debug?: boolean;
}
/** Upload options */
export interface UploadOptions {
pin?: boolean;
wrapWithDirectory?: boolean;
cidVersion?: 0 | 1;
hashAlgorithm?: HashAlgorithm;
onProgress?: (progress: UploadProgress) => void;
}
/** Upload progress */
export interface UploadProgress {
bytesUploaded: number;
totalBytes: number;
percentage: number;
}
/** Upload response */
export interface UploadResponse {
cid: string;
size: number;
name?: string;
hash?: string;
}
/** Download options */
export interface DownloadOptions {
offset?: number;
length?: number;
onProgress?: (progress: DownloadProgress) => void;
}
/** Download progress */
export interface DownloadProgress {
bytesDownloaded: number;
totalBytes: number;
percentage: number;
}
/** Pin information */
export interface Pin {
cid: string;
status: PinStatus;
name?: string;
size?: number;
createdAt?: number;
expiresAt?: number;
delegates?: string[];
}
/** Pin request */
export interface PinRequest {
cid: string;
name?: string;
duration?: number;
origins?: string[];
}
/** List pins options */
export interface ListPinsOptions {
status?: PinStatus[];
match?: MatchType;
name?: string;
limit?: number;
offset?: number;
}
/** List pins response */
export interface ListPinsResponse {
pins: Pin[];
total: number;
hasMore: boolean;
}
/** Gateway URL */
export interface GatewayUrl {
url: string;
cid: string;
path?: string;
}
/** CAR block */
export interface CarBlock {
cid: string;
data: string; // Base64-encoded
size?: number;
}
/** CAR file */
export interface CarFile {
version: 1 | 2;
roots: string[];
blocks?: CarBlock[];
size?: number;
}
/** File entry for directory creation */
export interface FileEntry {
name: string;
content?: string | Buffer;
cid?: string;
}
/** Directory entry */
export interface DirectoryEntry {
name: string;
cid: string;
size?: number;
type: EntryType;
}
/** Import CAR response */
export interface ImportCarResponse {
roots: string[];
blocksImported: number;
}
/** Storage statistics */
export interface StorageStats {
totalSize: number;
pinCount: number;
bandwidth?: {
upload: number;
download: number;
};
}
/** API error response */
export interface StorageError extends Error {
statusCode: number;
code?: string;
}

334
sdk/js/src/wallet/client.ts Normal file
View file

@ -0,0 +1,334 @@
/**
* Synor Wallet Client
*/
import type {
WalletConfig,
Wallet,
WalletType,
CreateWalletResult,
StealthAddress,
Transaction,
SignedTransaction,
SignMessageOptions,
SignedMessage,
UTXO,
Balance,
BalanceResponse,
GetUtxosOptions,
ImportWalletOptions,
BuildTransactionOptions,
FeeEstimate,
Priority,
} from './types';
const DEFAULT_ENDPOINT = 'https://wallet.synor.cc/api/v1';
/**
* Synor Wallet SDK error.
*/
export class WalletError extends Error {
constructor(
message: string,
public statusCode?: number,
public code?: string
) {
super(message);
this.name = 'WalletError';
}
}
/**
* Main Synor Wallet client.
*
* @example
* ```ts
* const wallet = new SynorWallet({ apiKey: 'sk_...' });
*
* // Create a new wallet
* const { wallet: w, mnemonic } = await wallet.createWallet();
* console.log('Address:', w.address);
* console.log('Mnemonic:', mnemonic); // Store securely!
*
* // Get balance
* const balance = await wallet.getBalance(w.address);
* console.log('Balance:', balance.native.total);
* ```
*/
export class SynorWallet {
private config: Required<Omit<WalletConfig, 'derivationPath'>> & { derivationPath?: string };
constructor(config: WalletConfig) {
this.config = {
apiKey: config.apiKey,
endpoint: config.endpoint ?? DEFAULT_ENDPOINT,
network: config.network ?? 'mainnet',
timeout: config.timeout ?? 30000,
debug: config.debug ?? false,
derivationPath: config.derivationPath,
};
}
/**
* Create a new wallet.
*
* @param type - Wallet type (standard, stealth)
* @returns Created wallet and mnemonic phrase
*/
async createWallet(type: WalletType = 'standard'): Promise<CreateWalletResult> {
const response = await this.request('POST', '/wallets', {
type,
network: this.config.network,
derivationPath: this.config.derivationPath,
});
return {
wallet: response.wallet,
mnemonic: response.mnemonic,
};
}
/**
* Import a wallet from mnemonic phrase.
*
* @param options - Import options including mnemonic
* @returns Imported wallet
*/
async importWallet(options: ImportWalletOptions): Promise<Wallet> {
const response = await this.request('POST', '/wallets/import', {
mnemonic: options.mnemonic,
passphrase: options.passphrase,
type: options.type ?? 'standard',
network: this.config.network,
derivationPath: this.config.derivationPath,
});
return response.wallet;
}
/**
* Get wallet by ID.
*
* @param walletId - Wallet ID
* @returns Wallet details
*/
async getWallet(walletId: string): Promise<Wallet> {
const response = await this.request('GET', `/wallets/${walletId}`);
return response.wallet;
}
/**
* List all wallets for this account.
*
* @returns List of wallets
*/
async listWallets(): Promise<Wallet[]> {
const response = await this.request('GET', '/wallets');
return response.wallets;
}
/**
* Get address at a specific index for a wallet.
*
* @param walletId - Wallet ID
* @param index - Derivation index
* @returns Address at the index
*/
async getAddress(walletId: string, index: number = 0): Promise<string> {
const response = await this.request('GET', `/wallets/${walletId}/addresses/${index}`);
return response.address;
}
/**
* Generate a stealth address for receiving private payments.
*
* @param walletId - Wallet ID
* @returns Stealth address details
*/
async getStealthAddress(walletId: string): Promise<StealthAddress> {
const response = await this.request('POST', `/wallets/${walletId}/stealth`);
return response.stealthAddress;
}
/**
* Sign a transaction.
*
* @param walletId - Wallet ID
* @param transaction - Transaction to sign
* @returns Signed transaction
*/
async signTransaction(walletId: string, transaction: Transaction): Promise<SignedTransaction> {
const response = await this.request('POST', `/wallets/${walletId}/sign`, {
transaction,
});
return response.signedTransaction;
}
/**
* Sign a message.
*
* @param walletId - Wallet ID
* @param options - Message signing options
* @returns Signed message
*/
async signMessage(walletId: string, options: SignMessageOptions): Promise<SignedMessage> {
const response = await this.request('POST', `/wallets/${walletId}/sign-message`, {
message: options.message,
format: options.format ?? 'text',
});
return response;
}
/**
* Verify a signed message.
*
* @param message - Original message
* @param signature - Signature to verify
* @param address - Expected signer address
* @returns True if signature is valid
*/
async verifyMessage(message: string, signature: string, address: string): Promise<boolean> {
const response = await this.request('POST', '/verify-message', {
message,
signature,
address,
});
return response.valid;
}
/**
* Get balance for an address.
*
* @param address - Address to check
* @param includeTokens - Include token balances
* @returns Balance information
*/
async getBalance(address: string, includeTokens: boolean = false): Promise<BalanceResponse> {
const response = await this.request('GET', `/balances/${address}`, undefined, {
includeTokens: includeTokens.toString(),
});
return response;
}
/**
* Get UTXOs for an address.
*
* @param address - Address to query
* @param options - Query options
* @returns List of UTXOs
*/
async getUtxos(address: string, options?: GetUtxosOptions): Promise<UTXO[]> {
const params: Record<string, string> = {};
if (options?.minConfirmations) {
params.minConfirmations = options.minConfirmations.toString();
}
if (options?.minAmount) {
params.minAmount = options.minAmount;
}
const response = await this.request('GET', `/utxos/${address}`, undefined, params);
return response.utxos;
}
/**
* Build a transaction (without signing).
*
* @param walletId - Wallet ID
* @param options - Transaction building options
* @returns Unsigned transaction
*/
async buildTransaction(walletId: string, options: BuildTransactionOptions): Promise<Transaction> {
const response = await this.request('POST', `/wallets/${walletId}/build-tx`, {
to: options.to,
amount: options.amount,
feeRate: options.feeRate,
utxos: options.utxos,
changeAddress: options.changeAddress,
});
return response.transaction;
}
/**
* Build and sign a transaction in one step.
*
* @param walletId - Wallet ID
* @param options - Transaction building options
* @returns Signed transaction
*/
async sendTransaction(walletId: string, options: BuildTransactionOptions): Promise<SignedTransaction> {
const tx = await this.buildTransaction(walletId, options);
return this.signTransaction(walletId, tx);
}
/**
* Estimate transaction fee.
*
* @param priority - Priority level
* @returns Fee estimate
*/
async estimateFee(priority: Priority = 'medium'): Promise<FeeEstimate> {
const response = await this.request('GET', '/fees/estimate', undefined, {
priority,
});
return response;
}
/**
* Get all fee estimates.
*
* @returns Fee estimates for all priority levels
*/
async getAllFeeEstimates(): Promise<FeeEstimate[]> {
const response = await this.request('GET', '/fees/estimate/all');
return response.estimates;
}
/**
* Make an API request.
*/
private async request(
method: string,
path: string,
body?: unknown,
params?: Record<string, string>
): Promise<any> {
let url = `${this.config.endpoint}${path}`;
if (params && Object.keys(params).length > 0) {
const searchParams = new URLSearchParams(params);
url += `?${searchParams.toString()}`;
}
if (this.config.debug) {
console.log(`[SynorWallet] ${method} ${url}`);
}
const response = await fetch(url, {
method,
headers: {
'Authorization': `Bearer ${this.config.apiKey}`,
'Content-Type': 'application/json',
'X-Network': this.config.network,
},
body: body ? JSON.stringify(body) : undefined,
signal: AbortSignal.timeout(this.config.timeout),
});
if (!response.ok) {
const error = await response.json().catch(() => ({ message: response.statusText }));
throw new WalletError(
error.message || 'Request failed',
response.status,
error.code
);
}
return response.json();
}
}

View file

@ -0,0 +1,29 @@
/**
* Synor Wallet SDK
*
* @packageDocumentation
*/
export { SynorWallet, WalletError } from './client';
export type {
WalletConfig,
WalletType,
Wallet,
CreateWalletResult,
StealthAddress,
TransactionInput,
TransactionOutput,
Transaction,
SignedTransaction,
SignMessageOptions,
SignedMessage,
UTXO,
Balance,
TokenBalance,
BalanceResponse,
GetUtxosOptions,
ImportWalletOptions,
BuildTransactionOptions,
Priority,
FeeEstimate,
} from './types';

213
sdk/js/src/wallet/types.ts Normal file
View file

@ -0,0 +1,213 @@
/**
* Synor Wallet SDK Types
*/
/** Wallet SDK configuration */
export interface WalletConfig {
/** API key for authentication */
apiKey: string;
/** API endpoint (defaults to production) */
endpoint?: string;
/** Network type */
network?: 'mainnet' | 'testnet';
/** Request timeout in milliseconds */
timeout?: number;
/** Enable debug logging */
debug?: boolean;
/** BIP44 derivation path */
derivationPath?: string;
}
/** Wallet type */
export type WalletType = 'standard' | 'multisig' | 'stealth' | 'hardware';
/** Wallet instance */
export interface Wallet {
/** Unique wallet ID */
id: string;
/** Primary address */
address: string;
/** Compressed public key (hex) */
publicKey: string;
/** Wallet type */
type: WalletType;
/** Creation timestamp */
createdAt: number;
}
/** Wallet creation result */
export interface CreateWalletResult {
/** Created wallet */
wallet: Wallet;
/** BIP39 mnemonic phrase (24 words) - SENSITIVE */
mnemonic: string;
}
/** Stealth address */
export interface StealthAddress {
/** One-time address */
address: string;
/** View public key */
viewKey: string;
/** Spend public key */
spendKey: string;
/** Ephemeral public key */
ephemeralKey?: string;
}
/** Transaction input */
export interface TransactionInput {
/** Previous transaction hash */
txid: string;
/** Output index */
vout: number;
/** Amount */
amount: string;
/** Script signature (for signed inputs) */
scriptSig?: string;
}
/** Transaction output */
export interface TransactionOutput {
/** Recipient address */
address: string;
/** Amount */
amount: string;
/** Script pubkey */
scriptPubKey?: string;
}
/** Unsigned transaction */
export interface Transaction {
/** Version */
version: number;
/** Inputs */
inputs: TransactionInput[];
/** Outputs */
outputs: TransactionOutput[];
/** Lock time */
lockTime?: number;
/** Transaction fee */
fee?: string;
}
/** Signed transaction */
export interface SignedTransaction {
/** Raw hex-encoded transaction */
raw: string;
/** Transaction hash */
txid: string;
/** Transaction size in bytes */
size: number;
/** Transaction weight */
weight?: number;
}
/** Message signing options */
export interface SignMessageOptions {
/** Message to sign */
message: string;
/** Message format */
format?: 'text' | 'hex' | 'base64';
}
/** Signed message result */
export interface SignedMessage {
/** Signature (hex) */
signature: string;
/** Public key used for signing */
publicKey: string;
/** Address that signed */
address: string;
}
/** UTXO (Unspent Transaction Output) */
export interface UTXO {
/** Transaction hash */
txid: string;
/** Output index */
vout: number;
/** Amount */
amount: string;
/** Address */
address: string;
/** Number of confirmations */
confirmations: number;
/** Script pubkey */
scriptPubKey?: string;
}
/** Balance information */
export interface Balance {
/** Confirmed balance */
confirmed: string;
/** Unconfirmed balance */
unconfirmed: string;
/** Total balance */
total: string;
}
/** Token balance */
export interface TokenBalance {
/** Token contract address */
token: string;
/** Token symbol */
symbol: string;
/** Token decimals */
decimals: number;
/** Balance */
balance: string;
}
/** Full balance response */
export interface BalanceResponse {
/** Native token balance */
native: Balance;
/** Token balances */
tokens?: TokenBalance[];
}
/** UTXO query options */
export interface GetUtxosOptions {
/** Minimum confirmations */
minConfirmations?: number;
/** Minimum amount */
minAmount?: string;
}
/** Import wallet options */
export interface ImportWalletOptions {
/** BIP39 mnemonic phrase */
mnemonic: string;
/** Optional passphrase */
passphrase?: string;
/** Wallet type */
type?: WalletType;
}
/** Transaction building options */
export interface BuildTransactionOptions {
/** Recipient address */
to: string;
/** Amount to send */
amount: string;
/** Fee rate (satoshis per byte) */
feeRate?: number;
/** Specific UTXOs to use */
utxos?: UTXO[];
/** Change address (defaults to sender) */
changeAddress?: string;
}
/** Transaction priority */
export type Priority = 'low' | 'medium' | 'high' | 'urgent';
/** Fee estimation result */
export interface FeeEstimate {
/** Priority level */
priority: Priority;
/** Fee rate */
feeRate: string;
/** Estimated blocks until confirmation */
estimatedBlocks: number;
}

View file

@ -1,13 +1,13 @@
[project] [project]
name = "synor-compute" name = "synor-sdk"
version = "0.1.0" version = "0.1.0"
description = "Synor Compute SDK for Python - Access distributed GPU/TPU/NPU compute" description = "Synor SDK for Python - Complete blockchain SDK including Compute, Wallet, RPC, and Storage"
readme = "README.md" readme = "README.md"
license = { text = "MIT" } license = { text = "MIT" }
authors = [ authors = [
{ name = "Synor Team", email = "dev@synor.cc" } { name = "Synor Team", email = "dev@synor.cc" }
] ]
keywords = ["synor", "compute", "gpu", "tpu", "npu", "ai", "ml", "distributed-computing"] keywords = ["synor", "blockchain", "compute", "wallet", "rpc", "storage", "ipfs", "gpu", "tpu", "npu", "ai", "ml", "distributed-computing"]
classifiers = [ classifiers = [
"Development Status :: 3 - Alpha", "Development Status :: 3 - Alpha",
"Intended Audience :: Developers", "Intended Audience :: Developers",
@ -17,12 +17,14 @@ classifiers = [
"Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.12",
"Topic :: Scientific/Engineering :: Artificial Intelligence", "Topic :: Scientific/Engineering :: Artificial Intelligence",
"Topic :: Software Development :: Libraries :: Python Modules",
] ]
requires-python = ">=3.10" requires-python = ">=3.10"
dependencies = [ dependencies = [
"httpx>=0.25.0", "httpx>=0.25.0",
"numpy>=1.24.0", "numpy>=1.24.0",
"pydantic>=2.0.0", "pydantic>=2.0.0",
"websockets>=12.0",
] ]
[project.optional-dependencies] [project.optional-dependencies]
@ -35,8 +37,9 @@ dev = [
[project.urls] [project.urls]
Homepage = "https://synor.cc" Homepage = "https://synor.cc"
Documentation = "https://docs.synor.cc/compute" Documentation = "https://docs.synor.cc/sdk"
Repository = "https://github.com/synor/synor" Repository = "https://github.com/synor/synor"
Changelog = "https://github.com/synor/synor/blob/main/CHANGELOG.md"
[build-system] [build-system]
requires = ["hatchling"] requires = ["hatchling"]

View file

@ -0,0 +1,57 @@
"""
Synor RPC SDK
A Python SDK for querying blocks, transactions, and chain state
on the Synor blockchain with WebSocket subscription support.
Example:
>>> from synor_rpc import SynorRpc
>>> async with SynorRpc(api_key="sk_...") as rpc:
... block = await rpc.get_latest_block()
... print(f"Height: {block.height}")
"""
from .client import SynorRpc, RpcError
from .types import (
RpcConfig,
Network,
Priority,
TransactionStatus,
SubscriptionType,
BlockHeader,
Block,
TxInput,
TxOutput,
Transaction,
FeeEstimate,
ChainInfo,
MempoolInfo,
UTXO,
Subscription,
)
__all__ = [
# Client
"SynorRpc",
"RpcError",
# Config
"RpcConfig",
# Enums
"Network",
"Priority",
"TransactionStatus",
"SubscriptionType",
# Types
"BlockHeader",
"Block",
"TxInput",
"TxOutput",
"Transaction",
"FeeEstimate",
"ChainInfo",
"MempoolInfo",
"UTXO",
"Subscription",
]
__version__ = "0.1.0"

View file

@ -0,0 +1,454 @@
"""Synor RPC Client."""
import asyncio
import json
import time
from typing import Any, Callable, Optional
import httpx
import websockets
from .types import (
RpcConfig,
Network,
Priority,
TransactionStatus,
SubscriptionType,
BlockHeader,
Block,
TxInput,
TxOutput,
ScriptPubKey,
Transaction,
FeeEstimate,
ChainInfo,
MempoolInfo,
UTXO,
Balance,
Subscription,
)
class RpcError(Exception):
"""Synor RPC SDK error."""
def __init__(
self,
message: str,
status_code: Optional[int] = None,
code: Optional[str] = None,
):
super().__init__(message)
self.status_code = status_code
self.code = code
class SynorRpc:
"""
Synor RPC SDK client.
Example:
>>> async with SynorRpc(api_key="sk_...") as rpc:
... block = await rpc.get_latest_block()
... print(f"Height: {block.height}")
...
... # Subscribe to new blocks
... async def on_block(block):
... print(f"New block: {block.height}")
... sub = await rpc.subscribe_blocks(on_block)
"""
def __init__(
self,
api_key: str,
endpoint: str = "https://rpc.synor.cc/api/v1",
ws_endpoint: str = "wss://rpc.synor.cc/ws",
network: Network = Network.MAINNET,
timeout: float = 30.0,
debug: bool = False,
):
self.config = RpcConfig(
api_key=api_key,
endpoint=endpoint,
ws_endpoint=ws_endpoint,
network=network,
timeout=timeout,
debug=debug,
)
self._client = httpx.AsyncClient(
base_url=endpoint,
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"X-Network": network.value,
},
timeout=timeout,
)
self._ws: Optional[websockets.WebSocketClientProtocol] = None
self._subscriptions: dict[str, Callable] = {}
self._ws_task: Optional[asyncio.Task] = None
async def __aenter__(self) -> "SynorRpc":
return self
async def __aexit__(self, *args: Any) -> None:
await self.close()
async def close(self) -> None:
"""Close the client."""
await self._client.aclose()
if self._ws:
await self._ws.close()
if self._ws_task:
self._ws_task.cancel()
# ==================== Block Operations ====================
async def get_block(self, hash_or_height: str | int) -> Block:
"""Get block by hash or height."""
if isinstance(hash_or_height, int):
path = f"/blocks/height/{hash_or_height}"
else:
path = f"/blocks/{hash_or_height}"
data = await self._request("GET", path)
return self._parse_block(data)
async def get_latest_block(self) -> Block:
"""Get the latest block."""
data = await self._request("GET", "/blocks/latest")
return self._parse_block(data)
async def get_block_header(self, hash_or_height: str | int) -> BlockHeader:
"""Get block header only."""
if isinstance(hash_or_height, int):
path = f"/blocks/height/{hash_or_height}/header"
else:
path = f"/blocks/{hash_or_height}/header"
data = await self._request("GET", path)
return self._parse_block_header(data)
async def get_blocks(self, start_height: int, end_height: int) -> list[Block]:
"""Get blocks in a range."""
data = await self._request(
"GET",
"/blocks",
params={"start": str(start_height), "end": str(end_height)},
)
return [self._parse_block(b) for b in data.get("blocks", [])]
# ==================== Transaction Operations ====================
async def get_transaction(self, txid: str) -> Transaction:
"""Get transaction by hash."""
data = await self._request("GET", f"/transactions/{txid}")
return self._parse_transaction(data)
async def get_raw_transaction(self, txid: str) -> str:
"""Get raw transaction hex."""
data = await self._request("GET", f"/transactions/{txid}/raw")
return data["raw"]
async def send_raw_transaction(self, raw_tx: str) -> str:
"""Send a raw transaction."""
data = await self._request("POST", "/transactions", {"raw": raw_tx})
return data["txid"]
async def get_address_transactions(
self,
address: str,
limit: int = 20,
offset: int = 0,
) -> list[Transaction]:
"""Get transactions for an address."""
data = await self._request(
"GET",
f"/addresses/{address}/transactions",
params={"limit": str(limit), "offset": str(offset)},
)
return [self._parse_transaction(tx) for tx in data.get("transactions", [])]
# ==================== Fee Estimation ====================
async def estimate_fee(self, priority: Priority = Priority.MEDIUM) -> FeeEstimate:
"""Estimate fee for a priority level."""
data = await self._request(
"GET", "/fees/estimate", params={"priority": priority.value}
)
return FeeEstimate(
priority=Priority(data["priority"]),
fee_rate=data["feeRate"],
estimated_blocks=data["estimatedBlocks"],
)
async def get_all_fee_estimates(self) -> list[FeeEstimate]:
"""Get all fee estimates."""
data = await self._request("GET", "/fees/estimate/all")
return [
FeeEstimate(
priority=Priority(e["priority"]),
fee_rate=e["feeRate"],
estimated_blocks=e["estimatedBlocks"],
)
for e in data.get("estimates", [])
]
# ==================== Chain Information ====================
async def get_chain_info(self) -> ChainInfo:
"""Get chain information."""
data = await self._request("GET", "/chain")
return ChainInfo(
chain=data["chain"],
network=data["network"],
height=data["height"],
best_block_hash=data["bestBlockHash"],
difficulty=data["difficulty"],
median_time=data["medianTime"],
chain_work=data["chainWork"],
syncing=data["syncing"],
sync_progress=data["syncProgress"],
)
async def get_mempool_info(self) -> MempoolInfo:
"""Get mempool information."""
data = await self._request("GET", "/mempool")
return MempoolInfo(
size=data["size"],
bytes=data["bytes"],
usage=data["usage"],
max_mempool=data["maxMempool"],
min_fee=data["minFee"],
)
async def get_mempool_transactions(self, limit: int = 100) -> list[str]:
"""Get mempool transaction IDs."""
data = await self._request(
"GET", "/mempool/transactions", params={"limit": str(limit)}
)
return data.get("txids", [])
# ==================== UTXO Operations ====================
async def get_utxos(self, address: str) -> list[UTXO]:
"""Get UTXOs for an address."""
data = await self._request("GET", f"/addresses/{address}/utxos")
return [
UTXO(
txid=u["txid"],
vout=u["vout"],
amount=u["amount"],
address=u["address"],
confirmations=u["confirmations"],
script_pub_key=u.get("scriptPubKey"),
)
for u in data.get("utxos", [])
]
async def get_balance(self, address: str) -> Balance:
"""Get balance for an address."""
data = await self._request("GET", f"/addresses/{address}/balance")
return Balance(
confirmed=data["confirmed"],
unconfirmed=data["unconfirmed"],
total=data["total"],
)
# ==================== Subscriptions ====================
async def subscribe_blocks(
self, callback: Callable[[Block], None]
) -> Subscription:
"""Subscribe to new blocks."""
return await self._subscribe(
SubscriptionType.BLOCKS,
lambda data: callback(self._parse_block(data["block"])),
)
async def subscribe_address(
self, address: str, callback: Callable[[Transaction], None]
) -> Subscription:
"""Subscribe to address transactions."""
return await self._subscribe(
SubscriptionType.ADDRESS,
lambda data: callback(self._parse_transaction(data["transaction"])),
{"address": address},
)
async def subscribe_mempool(
self, callback: Callable[[Transaction], None]
) -> Subscription:
"""Subscribe to mempool transactions."""
return await self._subscribe(
SubscriptionType.MEMPOOL,
lambda data: callback(self._parse_transaction(data["transaction"])),
)
async def _subscribe(
self,
sub_type: SubscriptionType,
callback: Callable,
params: Optional[dict] = None,
) -> Subscription:
"""Create a subscription."""
await self._ensure_websocket()
sub_id = f"{sub_type.value}_{int(time.time() * 1000)}"
self._subscriptions[sub_id] = callback
message = {
"type": "subscribe",
"id": sub_id,
"subscription": sub_type.value,
**(params or {}),
}
await self._ws.send(json.dumps(message))
def unsubscribe():
self._subscriptions.pop(sub_id, None)
if self._ws:
asyncio.create_task(
self._ws.send(json.dumps({"type": "unsubscribe", "id": sub_id}))
)
return Subscription(
id=sub_id,
type=sub_type,
created_at=int(time.time() * 1000),
unsubscribe=unsubscribe,
)
async def _ensure_websocket(self) -> None:
"""Ensure WebSocket connection is established."""
if self._ws and self._ws.open:
return
ws_url = (
f"{self.config.ws_endpoint}"
f"?apiKey={self.config.api_key}"
f"&network={self.config.network.value}"
)
self._ws = await websockets.connect(ws_url)
self._ws_task = asyncio.create_task(self._ws_listener())
async def _ws_listener(self) -> None:
"""Listen for WebSocket messages."""
try:
async for message in self._ws:
try:
data = json.loads(message)
sub_id = data.get("subscriptionId")
if sub_id and sub_id in self._subscriptions:
self._subscriptions[sub_id](data.get("data", {}))
except json.JSONDecodeError:
pass
except websockets.ConnectionClosed:
if self.config.debug:
print("[SynorRpc] WebSocket closed")
# ==================== HTTP Request ====================
async def _request(
self,
method: str,
path: str,
data: Optional[dict[str, Any]] = None,
params: Optional[dict[str, str]] = None,
) -> dict[str, Any]:
"""Make an API request."""
if self.config.debug:
print(f"[SynorRpc] {method} {path}")
response = await self._client.request(
method,
path,
json=data,
params=params,
)
if response.status_code >= 400:
error = (
response.json()
if response.content
else {"message": response.reason_phrase}
)
raise RpcError(
error.get("message", "Request failed"),
response.status_code,
error.get("code"),
)
return response.json()
# ==================== Parsers ====================
def _parse_block_header(self, data: dict) -> BlockHeader:
"""Parse block header from API response."""
return BlockHeader(
hash=data["hash"],
height=data["height"],
version=data["version"],
previous_hash=data["previousHash"],
merkle_root=data["merkleRoot"],
timestamp=data["timestamp"],
difficulty=data["difficulty"],
nonce=data["nonce"],
)
def _parse_block(self, data: dict) -> Block:
"""Parse block from API response."""
return Block(
hash=data["hash"],
height=data["height"],
version=data["version"],
previous_hash=data["previousHash"],
merkle_root=data["merkleRoot"],
timestamp=data["timestamp"],
difficulty=data["difficulty"],
nonce=data["nonce"],
transactions=data.get("transactions", []),
size=data.get("size", 0),
weight=data.get("weight", 0),
tx_count=data.get("txCount", 0),
)
def _parse_transaction(self, data: dict) -> Transaction:
"""Parse transaction from API response."""
inputs = [
TxInput(
txid=i["txid"],
vout=i["vout"],
script_sig=i["scriptSig"],
sequence=i["sequence"],
)
for i in data.get("inputs", [])
]
outputs = [
TxOutput(
value=o["value"],
n=o["n"],
script_pub_key=ScriptPubKey(
asm=o["scriptPubKey"]["asm"],
hex=o["scriptPubKey"]["hex"],
type=o["scriptPubKey"]["type"],
addresses=o["scriptPubKey"].get("addresses", []),
),
)
for o in data.get("outputs", [])
]
return Transaction(
txid=data["txid"],
confirmations=data.get("confirmations", 0),
status=TransactionStatus(data.get("status", "pending")),
size=data.get("size", 0),
fee=data.get("fee", "0"),
inputs=inputs,
outputs=outputs,
block_hash=data.get("blockHash"),
block_height=data.get("blockHeight"),
timestamp=data.get("timestamp"),
raw=data.get("raw"),
)

View file

@ -0,0 +1,170 @@
"""Synor RPC SDK Types."""
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional, Callable
class Network(str, Enum):
"""Network type."""
MAINNET = "mainnet"
TESTNET = "testnet"
class Priority(str, Enum):
"""Transaction priority."""
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
URGENT = "urgent"
class TransactionStatus(str, Enum):
"""Transaction status."""
PENDING = "pending"
CONFIRMED = "confirmed"
FAILED = "failed"
REPLACED = "replaced"
class SubscriptionType(str, Enum):
"""Subscription type."""
BLOCKS = "blocks"
TRANSACTIONS = "transactions"
ADDRESS = "address"
MEMPOOL = "mempool"
@dataclass
class RpcConfig:
"""RPC SDK configuration."""
api_key: str
endpoint: str = "https://rpc.synor.cc/api/v1"
ws_endpoint: str = "wss://rpc.synor.cc/ws"
network: Network = Network.MAINNET
timeout: float = 30.0
debug: bool = False
@dataclass
class BlockHeader:
"""Block header."""
hash: str
height: int
version: int
previous_hash: str
merkle_root: str
timestamp: int
difficulty: str
nonce: int
@dataclass
class Block(BlockHeader):
"""Full block with transactions."""
transactions: list[str] = field(default_factory=list)
size: int = 0
weight: int = 0
tx_count: int = 0
@dataclass
class TxInput:
"""Transaction input."""
txid: str
vout: int
script_sig: str
sequence: int
@dataclass
class ScriptPubKey:
"""Script pubkey."""
asm: str
hex: str
type: str
addresses: list[str] = field(default_factory=list)
@dataclass
class TxOutput:
"""Transaction output."""
value: str
n: int
script_pub_key: ScriptPubKey
@dataclass
class Transaction:
"""Transaction."""
txid: str
confirmations: int
status: TransactionStatus
size: int
fee: str
inputs: list[TxInput] = field(default_factory=list)
outputs: list[TxOutput] = field(default_factory=list)
block_hash: Optional[str] = None
block_height: Optional[int] = None
timestamp: Optional[int] = None
raw: Optional[str] = None
@dataclass
class FeeEstimate:
"""Fee estimation result."""
priority: Priority
fee_rate: str
estimated_blocks: int
@dataclass
class ChainInfo:
"""Chain information."""
chain: str
network: str
height: int
best_block_hash: str
difficulty: str
median_time: int
chain_work: str
syncing: bool
sync_progress: float
@dataclass
class MempoolInfo:
"""Mempool information."""
size: int
bytes: int
usage: int
max_mempool: int
min_fee: str
@dataclass
class UTXO:
"""Unspent Transaction Output."""
txid: str
vout: int
amount: str
address: str
confirmations: int
script_pub_key: Optional[str] = None
@dataclass
class Balance:
"""Balance information."""
confirmed: str
unconfirmed: str
total: str
@dataclass
class Subscription:
"""WebSocket subscription."""
id: str
type: SubscriptionType
created_at: int
unsubscribe: Callable[[], None]

View file

@ -0,0 +1,72 @@
"""
Synor Storage SDK
Decentralized storage, pinning, and content retrieval on the Synor network.
Example:
>>> from synor_storage import SynorStorage
>>> async with SynorStorage(api_key="sk_...") as storage:
... result = await storage.upload(b"Hello, World!")
... print(f"CID: {result.cid}")
...
... # Get gateway URL
... gateway = storage.get_gateway_url(result.cid)
... print(f"URL: {gateway.url}")
"""
from .client import SynorStorage, StorageError
from .types import (
StorageConfig,
PinStatus,
HashAlgorithm,
EntryType,
MatchType,
UploadOptions,
UploadProgress,
UploadResponse,
DownloadOptions,
DownloadProgress,
Pin,
PinRequest,
ListPinsOptions,
ListPinsResponse,
GatewayUrl,
CarBlock,
CarFile,
FileEntry,
DirectoryEntry,
ImportCarResponse,
StorageStats,
)
__all__ = [
# Client
"SynorStorage",
"StorageError",
# Config
"StorageConfig",
# Enums
"PinStatus",
"HashAlgorithm",
"EntryType",
"MatchType",
# Types
"UploadOptions",
"UploadProgress",
"UploadResponse",
"DownloadOptions",
"DownloadProgress",
"Pin",
"PinRequest",
"ListPinsOptions",
"ListPinsResponse",
"GatewayUrl",
"CarBlock",
"CarFile",
"FileEntry",
"DirectoryEntry",
"ImportCarResponse",
"StorageStats",
]
__version__ = "0.1.0"

View file

@ -0,0 +1,524 @@
"""Synor Storage SDK Client.
Decentralized storage, pinning, and content retrieval.
Example:
>>> from synor_storage import SynorStorage
>>> async with SynorStorage(api_key="sk_...") as storage:
... result = await storage.upload(b"Hello, World!")
... print(f"CID: {result.cid}")
"""
import base64
from typing import Optional, List, AsyncIterator, Union
from dataclasses import asdict
import httpx
from .types import (
StorageConfig,
UploadOptions,
UploadResponse,
DownloadOptions,
Pin,
PinRequest,
ListPinsOptions,
ListPinsResponse,
GatewayUrl,
CarFile,
CarBlock,
FileEntry,
DirectoryEntry,
ImportCarResponse,
StorageStats,
PinStatus,
)
class StorageError(Exception):
"""Storage API error."""
def __init__(self, message: str, status_code: int, code: Optional[str] = None):
super().__init__(message)
self.message = message
self.status_code = status_code
self.code = code
class SynorStorage:
"""Synor Storage SDK client."""
def __init__(
self,
api_key: Optional[str] = None,
config: Optional[StorageConfig] = None,
):
"""Initialize the storage client.
Args:
api_key: API key for authentication.
config: Full configuration object (overrides api_key).
"""
if config:
self.config = config
elif api_key:
self.config = StorageConfig(api_key=api_key)
else:
raise ValueError("Either api_key or config must be provided")
self._client: Optional[httpx.AsyncClient] = None
async def __aenter__(self) -> "SynorStorage":
"""Enter async context manager."""
self._client = httpx.AsyncClient(
timeout=self.config.timeout,
headers={
"Authorization": f"Bearer {self.config.api_key}",
},
)
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
"""Exit async context manager."""
if self._client:
await self._client.aclose()
self._client = None
@property
def client(self) -> httpx.AsyncClient:
"""Get the HTTP client."""
if not self._client:
raise RuntimeError("Client not initialized. Use 'async with' context manager.")
return self._client
async def upload(
self,
data: Union[bytes, str],
options: Optional[UploadOptions] = None,
) -> UploadResponse:
"""Upload content to storage.
Args:
data: Content to upload (bytes or string).
options: Upload options.
Returns:
Upload response with CID and size.
"""
opts = options or UploadOptions()
if isinstance(data, str):
data = data.encode()
params = {}
if opts.pin is not None:
params["pin"] = str(opts.pin).lower()
if opts.wrap_with_directory:
params["wrapWithDirectory"] = "true"
if opts.cid_version is not None:
params["cidVersion"] = str(opts.cid_version)
if opts.hash_algorithm:
params["hashAlgorithm"] = opts.hash_algorithm.value
files = {"file": ("file", data)}
response = await self.client.post(
f"{self.config.endpoint}/upload",
files=files,
params=params,
)
self._check_response(response)
result = response.json()
return UploadResponse(
cid=result["cid"],
size=result["size"],
name=result.get("name"),
hash=result.get("hash"),
)
async def download(
self,
cid: str,
options: Optional[DownloadOptions] = None,
) -> bytes:
"""Download content by CID.
Args:
cid: Content ID.
options: Download options.
Returns:
Content as bytes.
"""
opts = options or DownloadOptions()
params = {}
if opts.offset is not None:
params["offset"] = opts.offset
if opts.length is not None:
params["length"] = opts.length
response = await self.client.get(
f"{self.config.endpoint}/content/{cid}",
params=params,
)
self._check_response(response)
return response.content
async def download_stream(
self,
cid: str,
options: Optional[DownloadOptions] = None,
) -> AsyncIterator[bytes]:
"""Download content as a stream.
Args:
cid: Content ID.
options: Download options.
Yields:
Content chunks as bytes.
"""
opts = options or DownloadOptions()
params = {}
if opts.offset is not None:
params["offset"] = opts.offset
if opts.length is not None:
params["length"] = opts.length
async with self.client.stream(
"GET",
f"{self.config.endpoint}/content/{cid}/stream",
params=params,
) as response:
self._check_response(response)
async for chunk in response.aiter_bytes():
yield chunk
async def pin(self, request: PinRequest) -> Pin:
"""Pin content by CID.
Args:
request: Pin request.
Returns:
Pin information.
"""
body = {"cid": request.cid}
if request.name:
body["name"] = request.name
if request.duration:
body["duration"] = request.duration
if request.origins:
body["origins"] = request.origins
response = await self._request("POST", "/pins", body)
return self._parse_pin(response)
async def unpin(self, cid: str) -> None:
"""Unpin content by CID.
Args:
cid: Content ID.
"""
await self._request("DELETE", f"/pins/{cid}")
async def get_pin_status(self, cid: str) -> Pin:
"""Get pin status by CID.
Args:
cid: Content ID.
Returns:
Pin information.
"""
response = await self._request("GET", f"/pins/{cid}")
return self._parse_pin(response)
async def list_pins(
self,
options: Optional[ListPinsOptions] = None,
) -> ListPinsResponse:
"""List pins.
Args:
options: List options.
Returns:
List of pins with pagination info.
"""
opts = options or ListPinsOptions()
params = {}
if opts.status:
params["status"] = ",".join(s.value for s in opts.status)
if opts.match:
params["match"] = opts.match.value
if opts.name:
params["name"] = opts.name
if opts.limit is not None:
params["limit"] = opts.limit
if opts.offset is not None:
params["offset"] = opts.offset
response = await self._request("GET", "/pins", params=params)
pins = [self._parse_pin(p) for p in response.get("pins", [])]
return ListPinsResponse(
pins=pins,
total=response.get("total", len(pins)),
has_more=response.get("hasMore", False),
)
def get_gateway_url(self, cid: str, path: Optional[str] = None) -> GatewayUrl:
"""Get gateway URL for content.
Args:
cid: Content ID.
path: Optional path within content.
Returns:
Gateway URL information.
"""
full_path = f"/{cid}/{path}" if path else f"/{cid}"
return GatewayUrl(
url=f"{self.config.gateway}/ipfs{full_path}",
cid=cid,
path=path,
)
async def create_car(self, files: List[FileEntry]) -> CarFile:
"""Create a CAR file from files.
Args:
files: Files to include in the CAR.
Returns:
CAR file information.
"""
file_data = []
for f in files:
entry = {"name": f.name}
if f.content:
entry["content"] = base64.b64encode(f.content).decode()
if f.cid:
entry["cid"] = f.cid
file_data.append(entry)
response = await self._request("POST", "/car/create", {"files": file_data})
return CarFile(
version=response["version"],
roots=response["roots"],
blocks=[
CarBlock(
cid=b["cid"],
data=b["data"],
size=b.get("size"),
)
for b in response.get("blocks", [])
],
size=response.get("size"),
)
async def import_car(self, car_data: bytes, pin: bool = True) -> ImportCarResponse:
"""Import a CAR file.
Args:
car_data: CAR file data.
pin: Whether to pin imported content.
Returns:
Import result.
"""
encoded = base64.b64encode(car_data).decode()
response = await self._request("POST", "/car/import", {
"car": encoded,
"pin": pin,
})
return ImportCarResponse(
roots=response["roots"],
blocks_imported=response["blocksImported"],
)
async def export_car(self, cid: str) -> bytes:
"""Export content as a CAR file.
Args:
cid: Content ID.
Returns:
CAR file data.
"""
response = await self.client.get(
f"{self.config.endpoint}/car/export/{cid}"
)
self._check_response(response)
return response.content
async def create_directory(self, files: List[FileEntry]) -> UploadResponse:
"""Create a directory from files.
Args:
files: Files to include in the directory.
Returns:
Upload response with directory CID.
"""
file_data = []
for f in files:
entry = {"name": f.name}
if f.content:
entry["content"] = base64.b64encode(f.content).decode()
if f.cid:
entry["cid"] = f.cid
file_data.append(entry)
response = await self._request("POST", "/directory", {"files": file_data})
return UploadResponse(
cid=response["cid"],
size=response["size"],
name=response.get("name"),
)
async def list_directory(
self,
cid: str,
path: Optional[str] = None,
) -> List[DirectoryEntry]:
"""List directory contents.
Args:
cid: Directory CID.
path: Optional path within directory.
Returns:
List of directory entries.
"""
params = {"path": path} if path else {}
response = await self._request("GET", f"/directory/{cid}", params=params)
from .types import EntryType
return [
DirectoryEntry(
name=e["name"],
cid=e["cid"],
type=EntryType(e["type"]),
size=e.get("size"),
)
for e in response.get("entries", [])
]
async def get_stats(self) -> StorageStats:
"""Get storage statistics.
Returns:
Storage statistics.
"""
response = await self._request("GET", "/stats")
bandwidth = response.get("bandwidth", {})
return StorageStats(
total_size=response["totalSize"],
pin_count=response["pinCount"],
upload_bandwidth=bandwidth.get("upload"),
download_bandwidth=bandwidth.get("download"),
)
async def exists(self, cid: str) -> bool:
"""Check if content exists.
Args:
cid: Content ID.
Returns:
True if content exists.
"""
try:
response = await self.client.head(
f"{self.config.endpoint}/content/{cid}"
)
return response.status_code == 200
except Exception:
return False
async def get_metadata(self, cid: str) -> dict:
"""Get content metadata.
Args:
cid: Content ID.
Returns:
Metadata dictionary.
"""
return await self._request("GET", f"/content/{cid}/metadata")
async def _request(
self,
method: str,
path: str,
body: Optional[dict] = None,
params: Optional[dict] = None,
) -> dict:
"""Make an API request.
Args:
method: HTTP method.
path: API path.
body: Request body.
params: Query parameters.
Returns:
Response data.
"""
url = f"{self.config.endpoint}{path}"
if self.config.debug:
print(f"[SynorStorage] {method} {path}")
kwargs = {}
if body:
kwargs["json"] = body
if params:
kwargs["params"] = params
response = await self.client.request(method, url, **kwargs)
self._check_response(response)
if response.status_code == 204:
return {}
return response.json()
def _check_response(self, response: httpx.Response) -> None:
"""Check response for errors.
Args:
response: HTTP response.
Raises:
StorageError: If response indicates an error.
"""
if response.status_code >= 400:
try:
error = response.json()
message = error.get("message", "Unknown error")
code = error.get("code")
except Exception:
message = response.text or "Unknown error"
code = None
raise StorageError(message, response.status_code, code)
def _parse_pin(self, data: dict) -> Pin:
"""Parse pin from API response.
Args:
data: API response data.
Returns:
Pin object.
"""
return Pin(
cid=data["cid"],
status=PinStatus(data["status"]),
name=data.get("name"),
size=data.get("size"),
created_at=data.get("createdAt"),
expires_at=data.get("expiresAt"),
delegates=data.get("delegates", []),
)

View file

@ -0,0 +1,186 @@
"""Synor Storage SDK Types."""
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional, Callable, List
class PinStatus(str, Enum):
"""Pin status."""
QUEUED = "queued"
PINNING = "pinning"
PINNED = "pinned"
FAILED = "failed"
UNPINNED = "unpinned"
class HashAlgorithm(str, Enum):
"""Hash algorithm."""
SHA2_256 = "sha2-256"
BLAKE3 = "blake3"
class EntryType(str, Enum):
"""Entry type."""
FILE = "file"
DIRECTORY = "directory"
class MatchType(str, Enum):
"""Match type for listing pins."""
EXACT = "exact"
IEXACT = "iexact"
PARTIAL = "partial"
IPARTIAL = "ipartial"
@dataclass
class StorageConfig:
"""Storage SDK configuration."""
api_key: str
endpoint: str = "https://storage.synor.cc/api/v1"
gateway: str = "https://gateway.synor.cc"
pinning_service: Optional[str] = None
chunk_size: int = 262144 # 256KB
timeout: float = 30.0
debug: bool = False
@dataclass
class UploadProgress:
"""Upload progress."""
bytes_uploaded: int
total_bytes: int
percentage: float
@dataclass
class UploadOptions:
"""Upload options."""
pin: bool = True
wrap_with_directory: bool = False
cid_version: int = 1
hash_algorithm: HashAlgorithm = HashAlgorithm.SHA2_256
on_progress: Optional[Callable[[UploadProgress], None]] = None
@dataclass
class UploadResponse:
"""Upload response."""
cid: str
size: int
name: Optional[str] = None
hash: Optional[str] = None
@dataclass
class DownloadProgress:
"""Download progress."""
bytes_downloaded: int
total_bytes: int
percentage: float
@dataclass
class DownloadOptions:
"""Download options."""
offset: Optional[int] = None
length: Optional[int] = None
on_progress: Optional[Callable[[DownloadProgress], None]] = None
@dataclass
class Pin:
"""Pin information."""
cid: str
status: PinStatus
name: Optional[str] = None
size: Optional[int] = None
created_at: Optional[int] = None
expires_at: Optional[int] = None
delegates: List[str] = field(default_factory=list)
@dataclass
class PinRequest:
"""Pin request."""
cid: str
name: Optional[str] = None
duration: Optional[int] = None
origins: List[str] = field(default_factory=list)
@dataclass
class ListPinsOptions:
"""List pins options."""
status: Optional[List[PinStatus]] = None
match: Optional[MatchType] = None
name: Optional[str] = None
limit: Optional[int] = None
offset: Optional[int] = None
@dataclass
class ListPinsResponse:
"""List pins response."""
pins: List[Pin]
total: int
has_more: bool
@dataclass
class GatewayUrl:
"""Gateway URL."""
url: str
cid: str
path: Optional[str] = None
@dataclass
class CarBlock:
"""CAR block."""
cid: str
data: str # Base64-encoded
size: Optional[int] = None
@dataclass
class CarFile:
"""CAR file."""
version: int
roots: List[str]
blocks: List[CarBlock] = field(default_factory=list)
size: Optional[int] = None
@dataclass
class FileEntry:
"""File entry for directory creation."""
name: str
content: Optional[bytes] = None
cid: Optional[str] = None
@dataclass
class DirectoryEntry:
"""Directory entry."""
name: str
cid: str
type: EntryType
size: Optional[int] = None
@dataclass
class ImportCarResponse:
"""Import CAR response."""
roots: List[str]
blocks_imported: int
@dataclass
class StorageStats:
"""Storage statistics."""
total_size: int
pin_count: int
upload_bandwidth: Optional[int] = None
download_bandwidth: Optional[int] = None

View file

@ -0,0 +1,68 @@
"""
Synor Wallet SDK
A Python SDK for wallet management, key operations, and transaction signing
on the Synor blockchain.
Example:
>>> from synor_wallet import SynorWallet
>>> async with SynorWallet(api_key="sk_...") as wallet:
... result = await wallet.create_wallet()
... print(f"Address: {result.wallet.address}")
"""
from .client import SynorWallet, WalletError
from .types import (
WalletConfig,
Network,
WalletType,
Priority,
Wallet,
CreateWalletResult,
StealthAddress,
TransactionInput,
TransactionOutput,
Transaction,
SignedTransaction,
SignedMessage,
UTXO,
Balance,
TokenBalance,
BalanceResponse,
FeeEstimate,
GetUtxosOptions,
ImportWalletOptions,
BuildTransactionOptions,
)
__all__ = [
# Client
"SynorWallet",
"WalletError",
# Config
"WalletConfig",
# Enums
"Network",
"WalletType",
"Priority",
# Types
"Wallet",
"CreateWalletResult",
"StealthAddress",
"TransactionInput",
"TransactionOutput",
"Transaction",
"SignedTransaction",
"SignedMessage",
"UTXO",
"Balance",
"TokenBalance",
"BalanceResponse",
"FeeEstimate",
# Options
"GetUtxosOptions",
"ImportWalletOptions",
"BuildTransactionOptions",
]
__version__ = "0.1.0"

View file

@ -0,0 +1,547 @@
"""Synor Wallet Client."""
from typing import Any, Optional
import httpx
from .types import (
WalletConfig,
Network,
WalletType,
Wallet,
CreateWalletResult,
StealthAddress,
Transaction,
TransactionInput,
TransactionOutput,
SignedTransaction,
SignedMessage,
UTXO,
Balance,
TokenBalance,
BalanceResponse,
FeeEstimate,
Priority,
GetUtxosOptions,
ImportWalletOptions,
BuildTransactionOptions,
)
class WalletError(Exception):
"""Synor Wallet SDK error."""
def __init__(
self,
message: str,
status_code: Optional[int] = None,
code: Optional[str] = None,
):
super().__init__(message)
self.status_code = status_code
self.code = code
class SynorWallet:
"""
Synor Wallet SDK client.
Example:
>>> async with SynorWallet(api_key="sk_...") as wallet:
... result = await wallet.create_wallet()
... print(f"Address: {result.wallet.address}")
... print(f"Mnemonic: {result.mnemonic}") # Store securely!
...
... balance = await wallet.get_balance(result.wallet.address)
... print(f"Balance: {balance.native.total}")
"""
def __init__(
self,
api_key: str,
endpoint: str = "https://wallet.synor.cc/api/v1",
network: Network = Network.MAINNET,
timeout: float = 30.0,
debug: bool = False,
derivation_path: Optional[str] = None,
):
self.config = WalletConfig(
api_key=api_key,
endpoint=endpoint,
network=network,
timeout=timeout,
debug=debug,
derivation_path=derivation_path,
)
self._client = httpx.AsyncClient(
base_url=endpoint,
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"X-Network": network.value,
},
timeout=timeout,
)
async def __aenter__(self) -> "SynorWallet":
return self
async def __aexit__(self, *args: Any) -> None:
await self.close()
async def close(self) -> None:
"""Close the client."""
await self._client.aclose()
async def create_wallet(
self,
wallet_type: WalletType = WalletType.STANDARD,
) -> CreateWalletResult:
"""
Create a new wallet.
Args:
wallet_type: Type of wallet to create
Returns:
Created wallet and mnemonic phrase
"""
response = await self._request(
"POST",
"/wallets",
{
"type": wallet_type.value,
"network": self.config.network.value,
"derivationPath": self.config.derivation_path,
},
)
wallet = self._parse_wallet(response["wallet"])
return CreateWalletResult(wallet=wallet, mnemonic=response["mnemonic"])
async def import_wallet(self, options: ImportWalletOptions) -> Wallet:
"""
Import a wallet from mnemonic phrase.
Args:
options: Import options including mnemonic
Returns:
Imported wallet
"""
response = await self._request(
"POST",
"/wallets/import",
{
"mnemonic": options.mnemonic,
"passphrase": options.passphrase,
"type": options.type.value,
"network": self.config.network.value,
"derivationPath": self.config.derivation_path,
},
)
return self._parse_wallet(response["wallet"])
async def get_wallet(self, wallet_id: str) -> Wallet:
"""
Get wallet by ID.
Args:
wallet_id: Wallet ID
Returns:
Wallet details
"""
response = await self._request("GET", f"/wallets/{wallet_id}")
return self._parse_wallet(response["wallet"])
async def list_wallets(self) -> list[Wallet]:
"""
List all wallets for this account.
Returns:
List of wallets
"""
response = await self._request("GET", "/wallets")
return [self._parse_wallet(w) for w in response["wallets"]]
async def get_address(self, wallet_id: str, index: int = 0) -> str:
"""
Get address at a specific index for a wallet.
Args:
wallet_id: Wallet ID
index: Derivation index
Returns:
Address at the index
"""
response = await self._request(
"GET", f"/wallets/{wallet_id}/addresses/{index}"
)
return response["address"]
async def get_stealth_address(self, wallet_id: str) -> StealthAddress:
"""
Generate a stealth address for receiving private payments.
Args:
wallet_id: Wallet ID
Returns:
Stealth address details
"""
response = await self._request("POST", f"/wallets/{wallet_id}/stealth")
sa = response["stealthAddress"]
return StealthAddress(
address=sa["address"],
view_key=sa["viewKey"],
spend_key=sa["spendKey"],
ephemeral_key=sa.get("ephemeralKey"),
)
async def sign_transaction(
self,
wallet_id: str,
transaction: Transaction,
) -> SignedTransaction:
"""
Sign a transaction.
Args:
wallet_id: Wallet ID
transaction: Transaction to sign
Returns:
Signed transaction
"""
response = await self._request(
"POST",
f"/wallets/{wallet_id}/sign",
{"transaction": self._serialize_transaction(transaction)},
)
st = response["signedTransaction"]
return SignedTransaction(
raw=st["raw"],
txid=st["txid"],
size=st["size"],
weight=st.get("weight"),
)
async def sign_message(
self,
wallet_id: str,
message: str,
format: str = "text",
) -> SignedMessage:
"""
Sign a message.
Args:
wallet_id: Wallet ID
message: Message to sign
format: Message format (text, hex, base64)
Returns:
Signed message
"""
response = await self._request(
"POST",
f"/wallets/{wallet_id}/sign-message",
{"message": message, "format": format},
)
return SignedMessage(
signature=response["signature"],
public_key=response["publicKey"],
address=response["address"],
)
async def verify_message(
self,
message: str,
signature: str,
address: str,
) -> bool:
"""
Verify a signed message.
Args:
message: Original message
signature: Signature to verify
address: Expected signer address
Returns:
True if signature is valid
"""
response = await self._request(
"POST",
"/verify-message",
{"message": message, "signature": signature, "address": address},
)
return response["valid"]
async def get_balance(
self,
address: str,
include_tokens: bool = False,
) -> BalanceResponse:
"""
Get balance for an address.
Args:
address: Address to check
include_tokens: Include token balances
Returns:
Balance information
"""
params = {"includeTokens": str(include_tokens).lower()}
response = await self._request(
"GET", f"/balances/{address}", params=params
)
native = Balance(
confirmed=response["native"]["confirmed"],
unconfirmed=response["native"]["unconfirmed"],
total=response["native"]["total"],
)
tokens = []
if "tokens" in response:
tokens = [
TokenBalance(
token=t["token"],
symbol=t["symbol"],
decimals=t["decimals"],
balance=t["balance"],
)
for t in response["tokens"]
]
return BalanceResponse(native=native, tokens=tokens)
async def get_utxos(
self,
address: str,
options: Optional[GetUtxosOptions] = None,
) -> list[UTXO]:
"""
Get UTXOs for an address.
Args:
address: Address to query
options: Query options
Returns:
List of UTXOs
"""
params: dict[str, str] = {}
if options:
if options.min_confirmations:
params["minConfirmations"] = str(options.min_confirmations)
if options.min_amount:
params["minAmount"] = options.min_amount
response = await self._request(
"GET", f"/utxos/{address}", params=params
)
return [self._parse_utxo(u) for u in response["utxos"]]
async def build_transaction(
self,
wallet_id: str,
options: BuildTransactionOptions,
) -> Transaction:
"""
Build a transaction (without signing).
Args:
wallet_id: Wallet ID
options: Transaction building options
Returns:
Unsigned transaction
"""
data: dict[str, Any] = {
"to": options.to,
"amount": options.amount,
}
if options.fee_rate is not None:
data["feeRate"] = options.fee_rate
if options.utxos:
data["utxos"] = [
{"txid": u.txid, "vout": u.vout, "amount": u.amount}
for u in options.utxos
]
if options.change_address:
data["changeAddress"] = options.change_address
response = await self._request(
"POST", f"/wallets/{wallet_id}/build-tx", data
)
return self._parse_transaction(response["transaction"])
async def send_transaction(
self,
wallet_id: str,
options: BuildTransactionOptions,
) -> SignedTransaction:
"""
Build and sign a transaction in one step.
Args:
wallet_id: Wallet ID
options: Transaction building options
Returns:
Signed transaction
"""
tx = await self.build_transaction(wallet_id, options)
return await self.sign_transaction(wallet_id, tx)
async def estimate_fee(
self,
priority: Priority = Priority.MEDIUM,
) -> FeeEstimate:
"""
Estimate transaction fee.
Args:
priority: Priority level
Returns:
Fee estimate
"""
response = await self._request(
"GET", "/fees/estimate", params={"priority": priority.value}
)
return FeeEstimate(
priority=Priority(response["priority"]),
fee_rate=response["feeRate"],
estimated_blocks=response["estimatedBlocks"],
)
async def get_all_fee_estimates(self) -> list[FeeEstimate]:
"""
Get all fee estimates.
Returns:
Fee estimates for all priority levels
"""
response = await self._request("GET", "/fees/estimate/all")
return [
FeeEstimate(
priority=Priority(e["priority"]),
fee_rate=e["feeRate"],
estimated_blocks=e["estimatedBlocks"],
)
for e in response["estimates"]
]
async def _request(
self,
method: str,
path: str,
data: Optional[dict[str, Any]] = None,
params: Optional[dict[str, str]] = None,
) -> dict[str, Any]:
"""Make an API request."""
if self.config.debug:
print(f"[SynorWallet] {method} {path}")
response = await self._client.request(
method,
path,
json=data,
params=params,
)
if response.status_code >= 400:
error = (
response.json()
if response.content
else {"message": response.reason_phrase}
)
raise WalletError(
error.get("message", "Request failed"),
response.status_code,
error.get("code"),
)
return response.json()
def _parse_wallet(self, data: dict[str, Any]) -> Wallet:
"""Parse wallet from API response."""
return Wallet(
id=data["id"],
address=data["address"],
public_key=data["publicKey"],
type=WalletType(data["type"]),
created_at=data["createdAt"],
)
def _parse_utxo(self, data: dict[str, Any]) -> UTXO:
"""Parse UTXO from API response."""
return UTXO(
txid=data["txid"],
vout=data["vout"],
amount=data["amount"],
address=data["address"],
confirmations=data["confirmations"],
script_pub_key=data.get("scriptPubKey"),
)
def _parse_transaction(self, data: dict[str, Any]) -> Transaction:
"""Parse transaction from API response."""
inputs = [
TransactionInput(
txid=i["txid"],
vout=i["vout"],
amount=i["amount"],
script_sig=i.get("scriptSig"),
)
for i in data["inputs"]
]
outputs = [
TransactionOutput(
address=o["address"],
amount=o["amount"],
script_pub_key=o.get("scriptPubKey"),
)
for o in data["outputs"]
]
return Transaction(
version=data["version"],
inputs=inputs,
outputs=outputs,
lock_time=data.get("lockTime", 0),
fee=data.get("fee"),
)
def _serialize_transaction(self, tx: Transaction) -> dict[str, Any]:
"""Serialize transaction for API request."""
return {
"version": tx.version,
"inputs": [
{
"txid": i.txid,
"vout": i.vout,
"amount": i.amount,
}
for i in tx.inputs
],
"outputs": [
{
"address": o.address,
"amount": o.amount,
}
for o in tx.outputs
],
"lockTime": tx.lock_time,
}

View file

@ -0,0 +1,176 @@
"""Synor Wallet SDK Types."""
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
class Network(str, Enum):
"""Network type."""
MAINNET = "mainnet"
TESTNET = "testnet"
class WalletType(str, Enum):
"""Wallet type."""
STANDARD = "standard"
MULTISIG = "multisig"
STEALTH = "stealth"
HARDWARE = "hardware"
class Priority(str, Enum):
"""Transaction priority."""
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
URGENT = "urgent"
@dataclass
class WalletConfig:
"""Wallet SDK configuration."""
api_key: str
endpoint: str = "https://wallet.synor.cc/api/v1"
network: Network = Network.MAINNET
timeout: float = 30.0
debug: bool = False
derivation_path: Optional[str] = None
@dataclass
class Wallet:
"""Wallet instance."""
id: str
address: str
public_key: str
type: WalletType
created_at: int
@dataclass
class CreateWalletResult:
"""Wallet creation result."""
wallet: Wallet
mnemonic: str
@dataclass
class StealthAddress:
"""Stealth address for private payments."""
address: str
view_key: str
spend_key: str
ephemeral_key: Optional[str] = None
@dataclass
class TransactionInput:
"""Transaction input."""
txid: str
vout: int
amount: str
script_sig: Optional[str] = None
@dataclass
class TransactionOutput:
"""Transaction output."""
address: str
amount: str
script_pub_key: Optional[str] = None
@dataclass
class Transaction:
"""Unsigned transaction."""
version: int
inputs: list[TransactionInput]
outputs: list[TransactionOutput]
lock_time: int = 0
fee: Optional[str] = None
@dataclass
class SignedTransaction:
"""Signed transaction."""
raw: str
txid: str
size: int
weight: Optional[int] = None
@dataclass
class SignedMessage:
"""Signed message."""
signature: str
public_key: str
address: str
@dataclass
class UTXO:
"""Unspent Transaction Output."""
txid: str
vout: int
amount: str
address: str
confirmations: int
script_pub_key: Optional[str] = None
@dataclass
class Balance:
"""Balance information."""
confirmed: str
unconfirmed: str
total: str
@dataclass
class TokenBalance:
"""Token balance."""
token: str
symbol: str
decimals: int
balance: str
@dataclass
class BalanceResponse:
"""Full balance response."""
native: Balance
tokens: list[TokenBalance] = field(default_factory=list)
@dataclass
class FeeEstimate:
"""Fee estimation result."""
priority: Priority
fee_rate: str
estimated_blocks: int
@dataclass
class GetUtxosOptions:
"""UTXO query options."""
min_confirmations: int = 1
min_amount: Optional[str] = None
@dataclass
class ImportWalletOptions:
"""Import wallet options."""
mnemonic: str
passphrase: Optional[str] = None
type: WalletType = WalletType.STANDARD
@dataclass
class BuildTransactionOptions:
"""Transaction building options."""
to: str
amount: str
fee_rate: Optional[float] = None
utxos: Optional[list[UTXO]] = None
change_address: Optional[str] = None

View file

@ -1,3 +1,5 @@
[workspace]
[package] [package]
name = "synor-compute" name = "synor-compute"
version = "0.1.0" version = "0.1.0"
@ -10,7 +12,7 @@ keywords = ["compute", "gpu", "ai", "ml", "distributed"]
categories = ["api-bindings", "asynchronous"] categories = ["api-bindings", "asynchronous"]
[dependencies] [dependencies]
reqwest = { version = "0.11", features = ["json", "stream"] } reqwest = { version = "0.11", features = ["json", "stream", "multipart"] }
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
tokio-stream = "0.1" tokio-stream = "0.1"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
@ -19,6 +21,8 @@ thiserror = "1"
async-trait = "0.1" async-trait = "0.1"
futures = "0.3" futures = "0.3"
rand = "0.8" rand = "0.8"
base64 = "0.21"
urlencoding = "2"
[dev-dependencies] [dev-dependencies]
tokio-test = "0.4" tokio-test = "0.4"

View file

@ -48,6 +48,10 @@ mod tensor;
mod client; mod client;
mod error; mod error;
pub mod wallet;
pub mod rpc;
pub mod storage;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;

298
sdk/rust/src/rpc/client.rs Normal file
View file

@ -0,0 +1,298 @@
//! Synor RPC SDK Client.
use std::time::Duration;
use reqwest::{Client, header};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use super::error::{RpcError, Result};
use super::types::*;
/// Synor RPC client.
#[derive(Debug, Clone)]
pub struct SynorRpc {
config: RpcConfig,
client: Client,
}
#[derive(Debug, Deserialize)]
struct ApiError {
message: String,
code: Option<String>,
}
#[derive(Debug, Deserialize)]
struct BlockResponse {
block: Block,
}
#[derive(Debug, Deserialize)]
struct BlockHeaderResponse {
header: BlockHeader,
}
#[derive(Debug, Deserialize)]
struct TransactionResponse {
transaction: Transaction,
}
#[derive(Debug, Deserialize)]
struct SendTxResponse {
txid: String,
}
#[derive(Debug, Deserialize)]
struct UtxosResponse {
utxos: Vec<Utxo>,
}
impl SynorRpc {
/// Create a new RPC client with the given API key.
pub fn new(api_key: impl Into<String>) -> Self {
Self::with_config(RpcConfig::new(api_key))
}
/// Create a new RPC client with custom configuration.
pub fn with_config(config: RpcConfig) -> Self {
let client = Client::builder()
.timeout(Duration::from_secs(config.timeout_secs))
.build()
.expect("Failed to create HTTP client");
Self { config, client }
}
/// Get a block by hash.
pub async fn get_block_by_hash(&self, hash: &str) -> Result<Block> {
let path = format!("/blocks/{}", hash);
self.request("GET", &path, Option::<&()>::None).await
}
/// Get a block by height.
pub async fn get_block_by_height(&self, height: i64) -> Result<Block> {
let path = format!("/blocks/height/{}", height);
self.request("GET", &path, Option::<&()>::None).await
}
/// Get the latest block.
pub async fn get_latest_block(&self) -> Result<Block> {
self.request("GET", "/blocks/latest", Option::<&()>::None)
.await
}
/// Get a block header by hash.
pub async fn get_block_header_by_hash(&self, hash: &str) -> Result<BlockHeader> {
let path = format!("/blocks/{}/header", hash);
let resp: BlockHeaderResponse = self.request("GET", &path, Option::<&()>::None).await?;
Ok(resp.header)
}
/// Get a block header by height.
pub async fn get_block_header_by_height(&self, height: i64) -> Result<BlockHeader> {
let path = format!("/blocks/height/{}/header", height);
let resp: BlockHeaderResponse = self.request("GET", &path, Option::<&()>::None).await?;
Ok(resp.header)
}
/// Get a transaction by ID.
pub async fn get_transaction(&self, txid: &str) -> Result<Transaction> {
let path = format!("/transactions/{}", txid);
self.request("GET", &path, Option::<&()>::None).await
}
/// Send a raw transaction.
pub async fn send_raw_transaction(&self, raw_tx: &str) -> Result<String> {
#[derive(Serialize)]
struct SendTxRequest<'a> {
raw: &'a str,
}
let resp: SendTxResponse = self
.request("POST", "/transactions", Some(&SendTxRequest { raw: raw_tx }))
.await?;
Ok(resp.txid)
}
/// Estimate transaction fee.
pub async fn estimate_fee(&self, priority: Priority) -> Result<FeeEstimate> {
let priority_str = match priority {
Priority::Low => "low",
Priority::Medium => "medium",
Priority::High => "high",
Priority::Urgent => "urgent",
};
let path = format!("/fees/estimate?priority={}", priority_str);
self.request("GET", &path, Option::<&()>::None).await
}
/// Get all fee estimates.
pub async fn get_all_fee_estimates(&self) -> Result<Vec<FeeEstimate>> {
#[derive(Deserialize)]
struct EstimatesResponse {
estimates: Vec<FeeEstimate>,
}
let resp: EstimatesResponse = self
.request("GET", "/fees/estimate/all", Option::<&()>::None)
.await?;
Ok(resp.estimates)
}
/// Get chain information.
pub async fn get_chain_info(&self) -> Result<ChainInfo> {
self.request("GET", "/chain", Option::<&()>::None).await
}
/// Get mempool information.
pub async fn get_mempool_info(&self) -> Result<MempoolInfo> {
self.request("GET", "/mempool", Option::<&()>::None).await
}
/// Get UTXOs for an address.
pub async fn get_utxos(&self, address: &str) -> Result<Vec<Utxo>> {
let path = format!("/addresses/{}/utxos", address);
let resp: UtxosResponse = self.request("GET", &path, Option::<&()>::None).await?;
Ok(resp.utxos)
}
/// Get balance for an address.
pub async fn get_balance(&self, address: &str) -> Result<Balance> {
let path = format!("/addresses/{}/balance", address);
self.request("GET", &path, Option::<&()>::None).await
}
/// Get blocks in a range.
pub async fn get_blocks(&self, start_height: i64, end_height: i64) -> Result<Vec<Block>> {
#[derive(Deserialize)]
struct BlocksResponse {
blocks: Vec<Block>,
}
let path = format!("/blocks?start={}&end={}", start_height, end_height);
let resp: BlocksResponse = self.request("GET", &path, Option::<&()>::None).await?;
Ok(resp.blocks)
}
/// Get transactions for an address.
pub async fn get_address_transactions(
&self,
address: &str,
limit: Option<i32>,
offset: Option<i32>,
) -> Result<Vec<Transaction>> {
#[derive(Deserialize)]
struct TxsResponse {
transactions: Vec<Transaction>,
}
let mut path = format!("/addresses/{}/transactions", address);
let mut params = Vec::new();
if let Some(l) = limit {
params.push(format!("limit={}", l));
}
if let Some(o) = offset {
params.push(format!("offset={}", o));
}
if !params.is_empty() {
path = format!("{}?{}", path, params.join("&"));
}
let resp: TxsResponse = self.request("GET", &path, Option::<&()>::None).await?;
Ok(resp.transactions)
}
/// Get mempool transactions.
pub async fn get_mempool_transactions(&self, limit: Option<i32>) -> Result<Vec<String>> {
#[derive(Deserialize)]
struct MempoolTxsResponse {
transactions: Vec<String>,
}
let path = match limit {
Some(l) => format!("/mempool/transactions?limit={}", l),
None => "/mempool/transactions".to_string(),
};
let resp: MempoolTxsResponse = self.request("GET", &path, Option::<&()>::None).await?;
Ok(resp.transactions)
}
/// Internal HTTP request method.
async fn request<T, B>(&self, method: &str, path: &str, body: Option<&B>) -> Result<T>
where
T: DeserializeOwned,
B: Serialize,
{
let url = format!("{}{}", self.config.endpoint, path);
if self.config.debug {
println!("[SynorRpc] {} {}", method, path);
}
let network_str = match self.config.network {
Network::Mainnet => "mainnet",
Network::Testnet => "testnet",
};
let mut request = match method {
"GET" => self.client.get(&url),
"POST" => self.client.post(&url),
"PUT" => self.client.put(&url),
"DELETE" => self.client.delete(&url),
_ => return Err(RpcError::Validation(format!("Invalid method: {}", method))),
};
request = request
.header(header::AUTHORIZATION, format!("Bearer {}", self.config.api_key))
.header(header::CONTENT_TYPE, "application/json")
.header("X-Network", network_str);
if let Some(b) = body {
request = request.json(b);
}
let response = request.send().await?;
let status = response.status();
if status.is_client_error() || status.is_server_error() {
let error: ApiError = response.json().await.unwrap_or(ApiError {
message: "Unknown error".to_string(),
code: None,
});
return Err(RpcError::Api {
message: error.message,
status_code: status.as_u16(),
code: error.code,
});
}
let result: T = response.json().await?;
Ok(result)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_builder() {
let config = RpcConfig::new("test-key")
.endpoint("https://custom.endpoint")
.network(Network::Testnet)
.timeout(60)
.debug(true);
assert_eq!(config.api_key, "test-key");
assert_eq!(config.endpoint, "https://custom.endpoint");
assert_eq!(config.network, Network::Testnet);
assert_eq!(config.timeout_secs, 60);
assert!(config.debug);
}
#[test]
fn test_client_creation() {
let client = SynorRpc::new("test-key");
assert_eq!(client.config.api_key, "test-key");
}
}

68
sdk/rust/src/rpc/error.rs Normal file
View file

@ -0,0 +1,68 @@
//! Synor RPC SDK Error Types.
use std::fmt;
/// RPC SDK error type.
#[derive(Debug)]
pub enum RpcError {
/// HTTP request failed.
Request(String),
/// API returned an error.
Api {
message: String,
status_code: u16,
code: Option<String>,
},
/// JSON serialization/deserialization failed.
Serialization(String),
/// WebSocket connection error.
WebSocket(String),
/// Invalid input provided.
Validation(String),
/// Request timed out.
Timeout,
}
impl fmt::Display for RpcError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RpcError::Request(msg) => write!(f, "Request error: {}", msg),
RpcError::Api {
message,
status_code,
code,
} => {
if let Some(c) = code {
write!(f, "API error [{}]: {} (status {})", c, message, status_code)
} else {
write!(f, "API error: {} (status {})", message, status_code)
}
}
RpcError::Serialization(msg) => write!(f, "Serialization error: {}", msg),
RpcError::WebSocket(msg) => write!(f, "WebSocket error: {}", msg),
RpcError::Validation(msg) => write!(f, "Validation error: {}", msg),
RpcError::Timeout => write!(f, "Request timed out"),
}
}
}
impl std::error::Error for RpcError {}
impl From<reqwest::Error> for RpcError {
fn from(err: reqwest::Error) -> Self {
if err.is_timeout() {
RpcError::Timeout
} else {
RpcError::Request(err.to_string())
}
}
}
impl From<serde_json::Error> for RpcError {
fn from(err: serde_json::Error) -> Self {
RpcError::Serialization(err.to_string())
}
}
/// Result type alias for RPC operations.
pub type Result<T> = std::result::Result<T, RpcError>;

36
sdk/rust/src/rpc/mod.rs Normal file
View file

@ -0,0 +1,36 @@
//! Synor RPC SDK
//!
//! Query blocks, transactions, and chain state with WebSocket subscription support.
//!
//! # Quick Start
//!
//! ```rust,no_run
//! use synor_compute::rpc::{SynorRpc, Priority};
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let client = SynorRpc::new("your-api-key");
//!
//! // Get latest block
//! let block = client.get_latest_block().await?;
//! println!("Height: {}", block.height);
//!
//! // Get transaction
//! let tx = client.get_transaction("txid...").await?;
//! println!("Status: {:?}", tx.status);
//!
//! // Estimate fee
//! let fee = client.estimate_fee(Priority::Medium).await?;
//! println!("Fee rate: {}", fee.fee_rate);
//!
//! Ok(())
//! }
//! ```
mod types;
mod error;
mod client;
pub use types::*;
pub use error::{RpcError, Result};
pub use client::SynorRpc;

244
sdk/rust/src/rpc/types.rs Normal file
View file

@ -0,0 +1,244 @@
//! Synor RPC SDK Types.
use serde::{Deserialize, Serialize};
/// Network type.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum Network {
#[default]
Mainnet,
Testnet,
}
/// Transaction priority levels.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum Priority {
Low,
#[default]
Medium,
High,
Urgent,
}
/// Transaction status.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum TransactionStatus {
Pending,
Confirmed,
Failed,
Replaced,
}
/// Subscription type.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SubscriptionType {
Blocks,
Transactions,
Address,
Mempool,
}
/// RPC client configuration.
#[derive(Debug, Clone)]
pub struct RpcConfig {
pub api_key: String,
pub endpoint: String,
pub ws_endpoint: String,
pub network: Network,
pub timeout_secs: u64,
pub debug: bool,
}
impl RpcConfig {
/// Create a new configuration with the given API key.
pub fn new(api_key: impl Into<String>) -> Self {
Self {
api_key: api_key.into(),
endpoint: "https://rpc.synor.cc/api/v1".to_string(),
ws_endpoint: "wss://rpc.synor.cc/ws".to_string(),
network: Network::default(),
timeout_secs: 30,
debug: false,
}
}
/// Set the endpoint.
pub fn endpoint(mut self, endpoint: impl Into<String>) -> Self {
self.endpoint = endpoint.into();
self
}
/// Set the WebSocket endpoint.
pub fn ws_endpoint(mut self, ws_endpoint: impl Into<String>) -> Self {
self.ws_endpoint = ws_endpoint.into();
self
}
/// Set the network.
pub fn network(mut self, network: Network) -> Self {
self.network = network;
self
}
/// Set the timeout in seconds.
pub fn timeout(mut self, secs: u64) -> Self {
self.timeout_secs = secs;
self
}
/// Enable debug mode.
pub fn debug(mut self, debug: bool) -> Self {
self.debug = debug;
self
}
}
/// Block header.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BlockHeader {
pub hash: String,
pub height: i64,
pub version: i32,
pub previous_hash: String,
pub merkle_root: String,
pub timestamp: i64,
pub difficulty: String,
pub nonce: u64,
}
/// Full block with transactions.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Block {
pub hash: String,
pub height: i64,
pub version: i32,
pub previous_hash: String,
pub merkle_root: String,
pub timestamp: i64,
pub difficulty: String,
pub nonce: u64,
pub transactions: Vec<String>,
pub size: i32,
pub weight: i32,
pub tx_count: i32,
}
/// Transaction input.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TxInput {
pub txid: String,
pub vout: i32,
pub script_sig: String,
pub sequence: u32,
}
/// Script pubkey.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ScriptPubKey {
pub asm: String,
pub hex: String,
#[serde(rename = "type")]
pub script_type: String,
#[serde(default)]
pub addresses: Vec<String>,
}
/// Transaction output.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TxOutput {
pub value: String,
pub n: i32,
pub script_pub_key: ScriptPubKey,
}
/// Transaction.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Transaction {
pub txid: String,
pub confirmations: i32,
pub status: TransactionStatus,
pub size: i32,
pub fee: String,
#[serde(default)]
pub inputs: Vec<TxInput>,
#[serde(default)]
pub outputs: Vec<TxOutput>,
pub block_hash: Option<String>,
pub block_height: Option<i64>,
pub timestamp: Option<i64>,
pub raw: Option<String>,
}
/// Fee estimation result.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FeeEstimate {
pub priority: Priority,
pub fee_rate: String,
pub estimated_blocks: i32,
}
/// Chain information.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChainInfo {
pub chain: String,
pub network: String,
pub height: i64,
pub best_block_hash: String,
pub difficulty: String,
pub median_time: i64,
pub chain_work: String,
pub syncing: bool,
pub sync_progress: f64,
}
/// Mempool information.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MempoolInfo {
pub size: i32,
pub bytes: i64,
pub usage: i64,
pub max_mempool: i64,
pub min_fee: String,
}
/// Unspent transaction output.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Utxo {
pub txid: String,
pub vout: i32,
pub amount: String,
pub address: String,
pub confirmations: i32,
pub script_pub_key: Option<String>,
}
/// Balance information.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Balance {
pub confirmed: String,
pub unconfirmed: String,
pub total: String,
}
/// WebSocket subscription.
#[derive(Debug, Clone)]
pub struct Subscription {
pub id: String,
pub subscription_type: SubscriptionType,
pub created_at: i64,
}

View file

@ -0,0 +1,464 @@
//! Synor Storage SDK Client.
use std::time::Duration;
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use reqwest::{header, multipart, Client};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use super::error::{Result, StorageError};
use super::types::*;
/// Synor Storage client.
#[derive(Debug, Clone)]
pub struct SynorStorage {
config: StorageConfig,
client: Client,
}
#[derive(Debug, Deserialize)]
struct ApiError {
message: String,
code: Option<String>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct FileData {
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
cid: Option<String>,
}
#[derive(Debug, Deserialize)]
struct DirectoryResponse {
entries: Vec<DirectoryEntry>,
}
impl SynorStorage {
/// Create a new Storage client with the given API key.
pub fn new(api_key: impl Into<String>) -> Self {
Self::with_config(StorageConfig::new(api_key))
}
/// Create a new Storage client with custom configuration.
pub fn with_config(config: StorageConfig) -> Self {
let client = Client::builder()
.timeout(Duration::from_secs(config.timeout_secs))
.build()
.expect("Failed to create HTTP client");
Self { config, client }
}
/// Upload content to storage.
pub async fn upload(&self, data: &[u8], options: Option<UploadOptions>) -> Result<UploadResponse> {
let opts = options.unwrap_or_default();
let part = multipart::Part::bytes(data.to_vec()).file_name("file");
let form = multipart::Form::new().part("file", part);
let mut url = format!("{}/upload", self.config.endpoint);
let mut params = Vec::new();
if opts.pin {
params.push("pin=true".to_string());
}
if opts.wrap_with_directory {
params.push("wrapWithDirectory=true".to_string());
}
if opts.cid_version != 0 {
params.push(format!("cidVersion={}", opts.cid_version));
}
let algo = match opts.hash_algorithm {
HashAlgorithm::Sha256 => "sha2-256",
HashAlgorithm::Blake3 => "blake3",
};
params.push(format!("hashAlgorithm={}", algo));
if !params.is_empty() {
url = format!("{}?{}", url, params.join("&"));
}
if self.config.debug {
println!("[SynorStorage] POST /upload");
}
let response = self
.client
.post(&url)
.header(header::AUTHORIZATION, format!("Bearer {}", self.config.api_key))
.multipart(form)
.send()
.await?;
let status = response.status();
if status.is_client_error() || status.is_server_error() {
let error: ApiError = response.json().await.unwrap_or(ApiError {
message: "Unknown error".to_string(),
code: None,
});
return Err(StorageError::Api {
message: error.message,
status_code: status.as_u16(),
code: error.code,
});
}
let result: UploadResponse = response.json().await?;
Ok(result)
}
/// Download content by CID.
pub async fn download(&self, cid: &str, options: Option<DownloadOptions>) -> Result<Vec<u8>> {
let opts = options.unwrap_or_default();
let mut url = format!("{}/content/{}", self.config.endpoint, cid);
let mut params = Vec::new();
if let Some(offset) = opts.offset {
params.push(format!("offset={}", offset));
}
if let Some(length) = opts.length {
params.push(format!("length={}", length));
}
if !params.is_empty() {
url = format!("{}?{}", url, params.join("&"));
}
if self.config.debug {
println!("[SynorStorage] GET /content/{}", cid);
}
let response = self
.client
.get(&url)
.header(header::AUTHORIZATION, format!("Bearer {}", self.config.api_key))
.send()
.await?;
let status = response.status();
if status.is_client_error() || status.is_server_error() {
let error: ApiError = response.json().await.unwrap_or(ApiError {
message: "Unknown error".to_string(),
code: None,
});
return Err(StorageError::Api {
message: error.message,
status_code: status.as_u16(),
code: error.code,
});
}
let bytes = response.bytes().await?;
Ok(bytes.to_vec())
}
/// Pin content by CID.
pub async fn pin(&self, request: PinRequest) -> Result<Pin> {
self.request("POST", "/pins", Some(&request)).await
}
/// Unpin content by CID.
pub async fn unpin(&self, cid: &str) -> Result<()> {
let path = format!("/pins/{}", cid);
self.request::<(), _>("DELETE", &path, Option::<&()>::None)
.await?;
Ok(())
}
/// Get pin status by CID.
pub async fn get_pin_status(&self, cid: &str) -> Result<Pin> {
let path = format!("/pins/{}", cid);
self.request("GET", &path, Option::<&()>::None).await
}
/// List pins.
pub async fn list_pins(&self, options: Option<ListPinsOptions>) -> Result<ListPinsResponse> {
let mut path = "/pins".to_string();
let mut params = Vec::new();
if let Some(opts) = options {
if !opts.status.is_empty() {
let statuses: Vec<&str> = opts
.status
.iter()
.map(|s| match s {
PinStatus::Queued => "queued",
PinStatus::Pinning => "pinning",
PinStatus::Pinned => "pinned",
PinStatus::Failed => "failed",
PinStatus::Unpinned => "unpinned",
})
.collect();
params.push(format!("status={}", statuses.join(",")));
}
if let Some(match_type) = opts.match_type {
let m = match match_type {
MatchType::Exact => "exact",
MatchType::IExact => "iexact",
MatchType::Partial => "partial",
MatchType::IPartial => "ipartial",
};
params.push(format!("match={}", m));
}
if let Some(name) = opts.name {
params.push(format!("name={}", name));
}
if let Some(limit) = opts.limit {
params.push(format!("limit={}", limit));
}
if let Some(offset) = opts.offset {
params.push(format!("offset={}", offset));
}
}
if !params.is_empty() {
path = format!("{}?{}", path, params.join("&"));
}
self.request("GET", &path, Option::<&()>::None).await
}
/// Get gateway URL for content.
pub fn get_gateway_url(&self, cid: &str, path: Option<&str>) -> GatewayUrl {
let full_path = match path {
Some(p) => format!("/{}/{}", cid, p),
None => format!("/{}", cid),
};
GatewayUrl {
url: format!("{}/ipfs{}", self.config.gateway, full_path),
cid: cid.to_string(),
path: path.map(|s| s.to_string()),
}
}
/// Create a CAR file from files.
pub async fn create_car(&self, files: Vec<FileEntry>) -> Result<CarFile> {
let file_data: Vec<FileData> = files
.into_iter()
.map(|f| FileData {
name: f.name,
content: f.content.map(|c| BASE64.encode(c)),
cid: f.cid,
})
.collect();
#[derive(Serialize)]
struct CreateCarRequest {
files: Vec<FileData>,
}
self.request("POST", "/car/create", Some(&CreateCarRequest { files: file_data }))
.await
}
/// Import a CAR file.
pub async fn import_car(&self, car_data: &[u8], pin: bool) -> Result<ImportCarResponse> {
#[derive(Serialize)]
struct ImportCarRequest {
car: String,
pin: bool,
}
let request = ImportCarRequest {
car: BASE64.encode(car_data),
pin,
};
self.request("POST", "/car/import", Some(&request)).await
}
/// Export content as a CAR file.
pub async fn export_car(&self, cid: &str) -> Result<Vec<u8>> {
let url = format!("{}/car/export/{}", self.config.endpoint, cid);
let response = self
.client
.get(&url)
.header(header::AUTHORIZATION, format!("Bearer {}", self.config.api_key))
.send()
.await?;
let status = response.status();
if status.is_client_error() || status.is_server_error() {
let error: ApiError = response.json().await.unwrap_or(ApiError {
message: "Unknown error".to_string(),
code: None,
});
return Err(StorageError::Api {
message: error.message,
status_code: status.as_u16(),
code: error.code,
});
}
let bytes = response.bytes().await?;
Ok(bytes.to_vec())
}
/// Create a directory from files.
pub async fn create_directory(&self, files: Vec<FileEntry>) -> Result<UploadResponse> {
let file_data: Vec<FileData> = files
.into_iter()
.map(|f| FileData {
name: f.name,
content: f.content.map(|c| BASE64.encode(c)),
cid: f.cid,
})
.collect();
#[derive(Serialize)]
struct CreateDirRequest {
files: Vec<FileData>,
}
self.request("POST", "/directory", Some(&CreateDirRequest { files: file_data }))
.await
}
/// List directory contents.
pub async fn list_directory(&self, cid: &str, path: Option<&str>) -> Result<Vec<DirectoryEntry>> {
let mut api_path = format!("/directory/{}", cid);
if let Some(p) = path {
api_path = format!("{}?path={}", api_path, urlencoding::encode(p));
}
let response: DirectoryResponse = self.request("GET", &api_path, Option::<&()>::None).await?;
Ok(response.entries)
}
/// Get storage statistics.
pub async fn get_stats(&self) -> Result<StorageStats> {
self.request("GET", "/stats", Option::<&()>::None).await
}
/// Check if content exists.
pub async fn exists(&self, cid: &str) -> Result<bool> {
let url = format!("{}/content/{}", self.config.endpoint, cid);
let response = self
.client
.head(&url)
.header(header::AUTHORIZATION, format!("Bearer {}", self.config.api_key))
.send()
.await;
match response {
Ok(resp) => Ok(resp.status().is_success()),
Err(_) => Ok(false),
}
}
/// Get content metadata.
pub async fn get_metadata(&self, cid: &str) -> Result<serde_json::Value> {
let path = format!("/content/{}/metadata", cid);
self.request("GET", &path, Option::<&()>::None).await
}
/// Internal HTTP request method.
async fn request<T, B>(&self, method: &str, path: &str, body: Option<&B>) -> Result<T>
where
T: DeserializeOwned,
B: Serialize,
{
let url = format!("{}{}", self.config.endpoint, path);
if self.config.debug {
println!("[SynorStorage] {} {}", method, path);
}
let mut request = match method {
"GET" => self.client.get(&url),
"POST" => self.client.post(&url),
"PUT" => self.client.put(&url),
"DELETE" => self.client.delete(&url),
"HEAD" => self.client.head(&url),
_ => {
return Err(StorageError::Validation(format!(
"Invalid method: {}",
method
)))
}
};
request = request
.header(header::AUTHORIZATION, format!("Bearer {}", self.config.api_key))
.header(header::CONTENT_TYPE, "application/json");
if let Some(b) = body {
request = request.json(b);
}
let response = request.send().await?;
let status = response.status();
if status.is_client_error() || status.is_server_error() {
let error: ApiError = response.json().await.unwrap_or(ApiError {
message: "Unknown error".to_string(),
code: None,
});
return Err(StorageError::Api {
message: error.message,
status_code: status.as_u16(),
code: error.code,
});
}
if status == reqwest::StatusCode::NO_CONTENT {
// For DELETE operations that return 204
return serde_json::from_str("{}").map_err(StorageError::from);
}
let result: T = response.json().await?;
Ok(result)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_builder() {
let config = StorageConfig::new("test-key")
.endpoint("https://custom.endpoint")
.gateway("https://custom.gateway")
.chunk_size(512000)
.timeout(60)
.debug(true);
assert_eq!(config.api_key, "test-key");
assert_eq!(config.endpoint, "https://custom.endpoint");
assert_eq!(config.gateway, "https://custom.gateway");
assert_eq!(config.chunk_size, 512000);
assert_eq!(config.timeout_secs, 60);
assert!(config.debug);
}
#[test]
fn test_client_creation() {
let client = SynorStorage::new("test-key");
assert_eq!(client.config.api_key, "test-key");
}
#[test]
fn test_gateway_url() {
let client = SynorStorage::new("test-key");
let url = client.get_gateway_url("QmTest123", None);
assert_eq!(url.url, "https://gateway.synor.cc/ipfs/QmTest123");
assert_eq!(url.cid, "QmTest123");
assert!(url.path.is_none());
let url_with_path = client.get_gateway_url("QmTest123", Some("subdir/file.txt"));
assert_eq!(
url_with_path.url,
"https://gateway.synor.cc/ipfs/QmTest123/subdir/file.txt"
);
assert_eq!(url_with_path.path, Some("subdir/file.txt".to_string()));
}
}

View file

@ -0,0 +1,74 @@
//! Synor Storage SDK Error Types.
use std::fmt;
/// Storage SDK error type.
#[derive(Debug)]
pub enum StorageError {
/// HTTP request failed.
Request(String),
/// API returned an error.
Api {
message: String,
status_code: u16,
code: Option<String>,
},
/// JSON serialization/deserialization failed.
Serialization(String),
/// Invalid input provided.
Validation(String),
/// Request timed out.
Timeout,
/// IO error.
Io(String),
}
impl fmt::Display for StorageError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
StorageError::Request(msg) => write!(f, "Request error: {}", msg),
StorageError::Api {
message,
status_code,
code,
} => {
if let Some(c) = code {
write!(f, "API error [{}]: {} (status {})", c, message, status_code)
} else {
write!(f, "API error: {} (status {})", message, status_code)
}
}
StorageError::Serialization(msg) => write!(f, "Serialization error: {}", msg),
StorageError::Validation(msg) => write!(f, "Validation error: {}", msg),
StorageError::Timeout => write!(f, "Request timed out"),
StorageError::Io(msg) => write!(f, "IO error: {}", msg),
}
}
}
impl std::error::Error for StorageError {}
impl From<reqwest::Error> for StorageError {
fn from(err: reqwest::Error) -> Self {
if err.is_timeout() {
StorageError::Timeout
} else {
StorageError::Request(err.to_string())
}
}
}
impl From<serde_json::Error> for StorageError {
fn from(err: serde_json::Error) -> Self {
StorageError::Serialization(err.to_string())
}
}
impl From<std::io::Error> for StorageError {
fn from(err: std::io::Error) -> Self {
StorageError::Io(err.to_string())
}
}
/// Result type alias for Storage operations.
pub type Result<T> = std::result::Result<T, StorageError>;

View file

@ -0,0 +1,40 @@
//! Synor Storage SDK
//!
//! Decentralized storage, pinning, and content retrieval on the Synor network.
//!
//! # Quick Start
//!
//! ```rust,no_run
//! use synor_compute::storage::{SynorStorage, UploadOptions, PinRequest};
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let client = SynorStorage::new("your-api-key");
//!
//! // Upload content
//! let result = client.upload(b"Hello, World!", None).await?;
//! println!("CID: {}", result.cid);
//!
//! // Pin content
//! let pin = client.pin(PinRequest::new(&result.cid).name("my-file")).await?;
//! println!("Pin status: {:?}", pin.status);
//!
//! // Get gateway URL
//! let gateway = client.get_gateway_url(&result.cid, None);
//! println!("URL: {}", gateway.url);
//!
//! // Download content
//! let data = client.download(&result.cid, None).await?;
//! println!("Content: {}", String::from_utf8_lossy(&data));
//!
//! Ok(())
//! }
//! ```
mod types;
mod error;
mod client;
pub use types::*;
pub use error::{StorageError, Result};
pub use client::SynorStorage;

View file

@ -0,0 +1,356 @@
//! Synor Storage SDK Types.
use serde::{Deserialize, Serialize};
/// Pin status.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum PinStatus {
Queued,
Pinning,
Pinned,
Failed,
Unpinned,
}
/// Hash algorithm.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum HashAlgorithm {
#[default]
#[serde(rename = "sha2-256")]
Sha256,
#[serde(rename = "blake3")]
Blake3,
}
/// Entry type.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum EntryType {
File,
Directory,
}
/// Match type for listing pins.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum MatchType {
Exact,
IExact,
Partial,
IPartial,
}
/// Storage SDK configuration.
#[derive(Debug, Clone)]
pub struct StorageConfig {
pub api_key: String,
pub endpoint: String,
pub gateway: String,
pub pinning_service: Option<String>,
pub chunk_size: usize,
pub timeout_secs: u64,
pub debug: bool,
}
impl StorageConfig {
/// Create a new configuration with the given API key.
pub fn new(api_key: impl Into<String>) -> Self {
Self {
api_key: api_key.into(),
endpoint: "https://storage.synor.cc/api/v1".to_string(),
gateway: "https://gateway.synor.cc".to_string(),
pinning_service: None,
chunk_size: 262144, // 256KB
timeout_secs: 30,
debug: false,
}
}
/// Set the endpoint.
pub fn endpoint(mut self, endpoint: impl Into<String>) -> Self {
self.endpoint = endpoint.into();
self
}
/// Set the gateway.
pub fn gateway(mut self, gateway: impl Into<String>) -> Self {
self.gateway = gateway.into();
self
}
/// Set the pinning service.
pub fn pinning_service(mut self, service: impl Into<String>) -> Self {
self.pinning_service = Some(service.into());
self
}
/// Set the chunk size.
pub fn chunk_size(mut self, size: usize) -> Self {
self.chunk_size = size;
self
}
/// Set the timeout in seconds.
pub fn timeout(mut self, secs: u64) -> Self {
self.timeout_secs = secs;
self
}
/// Enable debug mode.
pub fn debug(mut self, debug: bool) -> Self {
self.debug = debug;
self
}
}
/// Upload options.
#[derive(Debug, Clone, Default)]
pub struct UploadOptions {
pub pin: bool,
pub wrap_with_directory: bool,
pub cid_version: u8,
pub hash_algorithm: HashAlgorithm,
}
impl UploadOptions {
/// Create new upload options with pinning enabled.
pub fn new() -> Self {
Self {
pin: true,
cid_version: 1,
..Default::default()
}
}
/// Set whether to pin content.
pub fn pin(mut self, pin: bool) -> Self {
self.pin = pin;
self
}
/// Set whether to wrap with directory.
pub fn wrap_with_directory(mut self, wrap: bool) -> Self {
self.wrap_with_directory = wrap;
self
}
/// Set the CID version.
pub fn cid_version(mut self, version: u8) -> Self {
self.cid_version = version;
self
}
/// Set the hash algorithm.
pub fn hash_algorithm(mut self, algorithm: HashAlgorithm) -> Self {
self.hash_algorithm = algorithm;
self
}
}
/// Upload response.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UploadResponse {
pub cid: String,
pub size: i64,
pub name: Option<String>,
pub hash: Option<String>,
}
/// Download options.
#[derive(Debug, Clone, Default)]
pub struct DownloadOptions {
pub offset: Option<i64>,
pub length: Option<i64>,
}
impl DownloadOptions {
/// Create new download options.
pub fn new() -> Self {
Self::default()
}
/// Set the offset.
pub fn offset(mut self, offset: i64) -> Self {
self.offset = Some(offset);
self
}
/// Set the length.
pub fn length(mut self, length: i64) -> Self {
self.length = Some(length);
self
}
}
/// Pin information.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Pin {
pub cid: String,
pub status: PinStatus,
pub name: Option<String>,
pub size: Option<i64>,
pub created_at: Option<i64>,
pub expires_at: Option<i64>,
#[serde(default)]
pub delegates: Vec<String>,
}
/// Pin request.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PinRequest {
pub cid: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub duration: Option<i64>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub origins: Vec<String>,
}
impl PinRequest {
/// Create a new pin request.
pub fn new(cid: impl Into<String>) -> Self {
Self {
cid: cid.into(),
name: None,
duration: None,
origins: Vec::new(),
}
}
/// Set the name.
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
/// Set the duration.
pub fn duration(mut self, duration: i64) -> Self {
self.duration = Some(duration);
self
}
/// Set the origins.
pub fn origins(mut self, origins: Vec<String>) -> Self {
self.origins = origins;
self
}
}
/// List pins options.
#[derive(Debug, Clone, Default)]
pub struct ListPinsOptions {
pub status: Vec<PinStatus>,
pub match_type: Option<MatchType>,
pub name: Option<String>,
pub limit: Option<i32>,
pub offset: Option<i32>,
}
/// List pins response.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ListPinsResponse {
pub pins: Vec<Pin>,
pub total: i32,
pub has_more: bool,
}
/// Gateway URL.
#[derive(Debug, Clone)]
pub struct GatewayUrl {
pub url: String,
pub cid: String,
pub path: Option<String>,
}
/// CAR block.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CarBlock {
pub cid: String,
pub data: String, // Base64-encoded
pub size: Option<i64>,
}
/// CAR file.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CarFile {
pub version: i32,
pub roots: Vec<String>,
#[serde(default)]
pub blocks: Vec<CarBlock>,
pub size: Option<i64>,
}
/// File entry for directory creation.
#[derive(Debug, Clone)]
pub struct FileEntry {
pub name: String,
pub content: Option<Vec<u8>>,
pub cid: Option<String>,
}
impl FileEntry {
/// Create a new file entry.
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
content: None,
cid: None,
}
}
/// Set the content.
pub fn content(mut self, content: Vec<u8>) -> Self {
self.content = Some(content);
self
}
/// Set the CID.
pub fn cid(mut self, cid: impl Into<String>) -> Self {
self.cid = Some(cid.into());
self
}
}
/// Directory entry.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DirectoryEntry {
pub name: String,
pub cid: String,
pub size: Option<i64>,
#[serde(rename = "type")]
pub entry_type: EntryType,
}
/// Import CAR response.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ImportCarResponse {
pub roots: Vec<String>,
pub blocks_imported: i32,
}
/// Storage statistics.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StorageStats {
pub total_size: i64,
pub pin_count: i32,
pub bandwidth: Option<BandwidthStats>,
}
/// Bandwidth statistics.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BandwidthStats {
pub upload: i64,
pub download: i64,
}

View file

@ -22,10 +22,11 @@ use std::f64::consts::PI;
/// let mean = random.mean(); /// let mean = random.mean();
/// let transposed = matrix.transpose(); /// let transposed = matrix.transpose();
/// ``` /// ```
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct Tensor { pub struct Tensor {
shape: Vec<usize>, shape: Vec<usize>,
data: Vec<f64>, data: Vec<f64>,
#[serde(default)]
dtype: Precision, dtype: Precision,
} }

View file

@ -196,7 +196,7 @@ impl Default for AttentionOptions {
num_heads: 8, num_heads: 8,
flash: true, flash: true,
precision: Precision::FP16, precision: Precision::FP16,
processor: ProcessorType::GPU, processor: ProcessorType::Gpu,
} }
} }
} }

View file

@ -0,0 +1,379 @@
//! Synor Wallet client implementation.
use crate::wallet::error::{Result, WalletError};
use crate::wallet::types::*;
use reqwest::Client;
use serde_json::{json, Value};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
/// Synor Wallet SDK client.
pub struct SynorWallet {
config: WalletConfig,
client: Client,
closed: Arc<AtomicBool>,
}
impl SynorWallet {
/// Create a new client with an API key.
pub fn new(api_key: impl Into<String>) -> Self {
Self::with_config(WalletConfig::new(api_key))
}
/// Create a new client with configuration.
pub fn with_config(config: WalletConfig) -> Self {
let client = Client::builder()
.timeout(std::time::Duration::from_secs(config.timeout_secs))
.build()
.expect("Failed to create HTTP client");
Self {
config,
client,
closed: Arc::new(AtomicBool::new(false)),
}
}
// ==================== Wallet Management ====================
/// Create a new wallet.
pub async fn create_wallet(&self, wallet_type: WalletType) -> Result<CreateWalletResult> {
self.check_closed()?;
let mut body = json!({
"type": wallet_type,
"network": self.config.network,
});
if let Some(ref path) = self.config.derivation_path {
body["derivationPath"] = json!(path);
}
self.post("/wallets", body).await
}
/// Import a wallet from mnemonic phrase.
pub async fn import_wallet(&self, options: ImportWalletOptions) -> Result<Wallet> {
self.check_closed()?;
let mut body = json!({
"mnemonic": options.mnemonic,
"type": options.wallet_type,
"network": self.config.network,
});
if let Some(passphrase) = options.passphrase {
body["passphrase"] = json!(passphrase);
}
if let Some(ref path) = self.config.derivation_path {
body["derivationPath"] = json!(path);
}
let resp: Value = self.post("/wallets/import", body).await?;
Ok(serde_json::from_value(resp["wallet"].clone())?)
}
/// Get a wallet by ID.
pub async fn get_wallet(&self, wallet_id: &str) -> Result<Wallet> {
self.check_closed()?;
let resp: Value = self.get(&format!("/wallets/{}", wallet_id)).await?;
Ok(serde_json::from_value(resp["wallet"].clone())?)
}
/// List all wallets for this account.
pub async fn list_wallets(&self) -> Result<Vec<Wallet>> {
self.check_closed()?;
let resp: Value = self.get("/wallets").await?;
let wallets = resp["wallets"]
.as_array()
.unwrap_or(&vec![])
.iter()
.filter_map(|w| serde_json::from_value(w.clone()).ok())
.collect();
Ok(wallets)
}
/// Get address at a specific index for a wallet.
pub async fn get_address(&self, wallet_id: &str, index: u32) -> Result<String> {
self.check_closed()?;
let resp: Value = self
.get(&format!("/wallets/{}/addresses/{}", wallet_id, index))
.await?;
Ok(resp["address"].as_str().unwrap_or_default().to_string())
}
/// Generate a stealth address for receiving private payments.
pub async fn get_stealth_address(&self, wallet_id: &str) -> Result<StealthAddress> {
self.check_closed()?;
let resp: Value = self
.post(&format!("/wallets/{}/stealth", wallet_id), json!({}))
.await?;
Ok(serde_json::from_value(resp["stealthAddress"].clone())?)
}
// ==================== Transaction Operations ====================
/// Sign a transaction.
pub async fn sign_transaction(
&self,
wallet_id: &str,
transaction: &Transaction,
) -> Result<SignedTransaction> {
self.check_closed()?;
let body = json!({
"transaction": transaction,
});
let resp: Value = self
.post(&format!("/wallets/{}/sign", wallet_id), body)
.await?;
Ok(serde_json::from_value(resp["signedTransaction"].clone())?)
}
/// Sign a message.
pub async fn sign_message(
&self,
wallet_id: &str,
message: &str,
format: Option<&str>,
) -> Result<SignedMessage> {
self.check_closed()?;
let body = json!({
"message": message,
"format": format.unwrap_or("text"),
});
self.post(&format!("/wallets/{}/sign-message", wallet_id), body)
.await
}
/// Verify a signed message.
pub async fn verify_message(
&self,
message: &str,
signature: &str,
address: &str,
) -> Result<bool> {
self.check_closed()?;
let body = json!({
"message": message,
"signature": signature,
"address": address,
});
let resp: Value = self.post("/verify-message", body).await?;
Ok(resp["valid"].as_bool().unwrap_or(false))
}
// ==================== Balance & UTXOs ====================
/// Get balance for an address.
pub async fn get_balance(&self, address: &str, include_tokens: bool) -> Result<BalanceResponse> {
self.check_closed()?;
let url = format!("/balances/{}?includeTokens={}", address, include_tokens);
self.get(&url).await
}
/// Get UTXOs for an address.
pub async fn get_utxos(&self, address: &str, options: Option<GetUtxosOptions>) -> Result<Vec<Utxo>> {
self.check_closed()?;
let mut url = format!("/utxos/{}", address);
if let Some(opts) = options {
let mut params = Vec::new();
if let Some(min_conf) = opts.min_confirmations {
params.push(format!("minConfirmations={}", min_conf));
}
if let Some(min_amount) = opts.min_amount {
params.push(format!("minAmount={}", min_amount));
}
if !params.is_empty() {
url.push('?');
url.push_str(&params.join("&"));
}
}
let resp: Value = self.get(&url).await?;
let utxos = resp["utxos"]
.as_array()
.unwrap_or(&vec![])
.iter()
.filter_map(|u| serde_json::from_value(u.clone()).ok())
.collect();
Ok(utxos)
}
// ==================== Transaction Building ====================
/// Build a transaction without signing.
pub async fn build_transaction(
&self,
wallet_id: &str,
options: BuildTransactionOptions,
) -> Result<Transaction> {
self.check_closed()?;
let mut body = json!({
"to": options.to,
"amount": options.amount,
});
if let Some(fee_rate) = options.fee_rate {
body["feeRate"] = json!(fee_rate);
}
if let Some(utxos) = options.utxos {
body["utxos"] = json!(utxos);
}
if let Some(change_address) = options.change_address {
body["changeAddress"] = json!(change_address);
}
let resp: Value = self
.post(&format!("/wallets/{}/build-tx", wallet_id), body)
.await?;
Ok(serde_json::from_value(resp["transaction"].clone())?)
}
/// Build and sign a transaction in one step.
pub async fn send_transaction(
&self,
wallet_id: &str,
options: BuildTransactionOptions,
) -> Result<SignedTransaction> {
let tx = self.build_transaction(wallet_id, options).await?;
self.sign_transaction(wallet_id, &tx).await
}
// ==================== Fee Estimation ====================
/// Estimate transaction fee.
pub async fn estimate_fee(&self, priority: Priority) -> Result<FeeEstimate> {
self.check_closed()?;
let url = format!("/fees/estimate?priority={:?}", priority).to_lowercase();
self.get(&url).await
}
/// Get all fee estimates.
pub async fn get_all_fee_estimates(&self) -> Result<Vec<FeeEstimate>> {
self.check_closed()?;
let resp: Value = self.get("/fees/estimate/all").await?;
let estimates = resp["estimates"]
.as_array()
.unwrap_or(&vec![])
.iter()
.filter_map(|e| serde_json::from_value(e.clone()).ok())
.collect();
Ok(estimates)
}
// ==================== Lifecycle ====================
/// Close the client.
pub fn close(&self) {
self.closed.store(true, Ordering::SeqCst);
}
/// Check if the client is closed.
pub fn is_closed(&self) -> bool {
self.closed.load(Ordering::SeqCst)
}
// ==================== Internal Methods ====================
fn check_closed(&self) -> Result<()> {
if self.is_closed() {
Err(WalletError::ClientClosed)
} else {
Ok(())
}
}
async fn get<T: serde::de::DeserializeOwned>(&self, path: &str) -> Result<T> {
let url = format!("{}{}", self.config.endpoint, path);
if self.config.debug {
println!("[SynorWallet] GET {}", path);
}
let response = self
.client
.get(&url)
.header("Authorization", format!("Bearer {}", self.config.api_key))
.header("X-Network", format!("{:?}", self.config.network).to_lowercase())
.send()
.await?;
if !response.status().is_success() {
let status_code = response.status().as_u16();
let body: Value = response.json().await.unwrap_or_default();
return Err(WalletError::Api {
status_code,
message: body["message"]
.as_str()
.unwrap_or("Request failed")
.to_string(),
code: body["code"].as_str().map(String::from),
});
}
Ok(response.json().await?)
}
async fn post<T: serde::de::DeserializeOwned>(&self, path: &str, body: Value) -> Result<T> {
let url = format!("{}{}", self.config.endpoint, path);
if self.config.debug {
println!("[SynorWallet] POST {}", path);
}
let response = self
.client
.post(&url)
.header("Authorization", format!("Bearer {}", self.config.api_key))
.header("X-Network", format!("{:?}", self.config.network).to_lowercase())
.json(&body)
.send()
.await?;
if !response.status().is_success() {
let status_code = response.status().as_u16();
let body: Value = response.json().await.unwrap_or_default();
return Err(WalletError::Api {
status_code,
message: body["message"]
.as_str()
.unwrap_or("Request failed")
.to_string(),
code: body["code"].as_str().map(String::from),
});
}
Ok(response.json().await?)
}
}

View file

@ -0,0 +1,72 @@
//! Synor Wallet SDK error types.
use std::fmt;
/// Result type for wallet operations.
pub type Result<T> = std::result::Result<T, WalletError>;
/// Wallet SDK error.
#[derive(Debug)]
pub enum WalletError {
/// HTTP request error.
Http(reqwest::Error),
/// API error response.
Api {
/// HTTP status code.
status_code: u16,
/// Error message.
message: String,
/// Error code.
code: Option<String>,
},
/// Client is closed.
ClientClosed,
/// Invalid configuration.
InvalidConfig(String),
/// Serialization error.
Serialization(serde_json::Error),
}
impl fmt::Display for WalletError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
WalletError::Http(e) => write!(f, "HTTP error: {}", e),
WalletError::Api {
status_code,
message,
code,
} => {
if let Some(code) = code {
write!(f, "API error [{}]: {} (status {})", code, message, status_code)
} else {
write!(f, "API error: {} (status {})", message, status_code)
}
}
WalletError::ClientClosed => write!(f, "Client is closed"),
WalletError::InvalidConfig(msg) => write!(f, "Invalid configuration: {}", msg),
WalletError::Serialization(e) => write!(f, "Serialization error: {}", e),
}
}
}
impl std::error::Error for WalletError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
WalletError::Http(e) => Some(e),
WalletError::Serialization(e) => Some(e),
_ => None,
}
}
}
impl From<reqwest::Error> for WalletError {
fn from(error: reqwest::Error) -> Self {
WalletError::Http(error)
}
}
impl From<serde_json::Error> for WalletError {
fn from(error: serde_json::Error) -> Self {
WalletError::Serialization(error)
}
}

View file

@ -0,0 +1,34 @@
//! Synor Wallet SDK
//!
//! A Rust SDK for wallet management, key operations, and transaction signing
//! on the Synor blockchain.
//!
//! # Example
//!
//! ```no_run
//! use synor::wallet::{SynorWallet, WalletType};
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let client = SynorWallet::new("your-api-key");
//!
//! // Create a new wallet
//! let result = client.create_wallet(WalletType::Standard).await?;
//! println!("Address: {}", result.wallet.address);
//! println!("Mnemonic: {}", result.mnemonic); // Store securely!
//!
//! // Get balance
//! let balance = client.get_balance(&result.wallet.address, false).await?;
//! println!("Balance: {}", balance.native.total);
//!
//! Ok(())
//! }
//! ```
mod client;
mod error;
mod types;
pub use client::SynorWallet;
pub use error::{Result, WalletError};
pub use types::*;

View file

@ -0,0 +1,386 @@
//! Synor Wallet SDK types.
use serde::{Deserialize, Serialize};
/// Network type.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Network {
Mainnet,
Testnet,
}
impl Default for Network {
fn default() -> Self {
Self::Mainnet
}
}
/// Wallet type.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum WalletType {
Standard,
Multisig,
Stealth,
Hardware,
}
impl Default for WalletType {
fn default() -> Self {
Self::Standard
}
}
/// Transaction priority.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Priority {
Low,
Medium,
High,
Urgent,
}
impl Default for Priority {
fn default() -> Self {
Self::Medium
}
}
/// Wallet SDK configuration.
#[derive(Debug, Clone)]
pub struct WalletConfig {
/// API key for authentication.
pub api_key: String,
/// API endpoint.
pub endpoint: String,
/// Network type.
pub network: Network,
/// Request timeout in seconds.
pub timeout_secs: u64,
/// Enable debug logging.
pub debug: bool,
/// BIP44 derivation path.
pub derivation_path: Option<String>,
}
impl WalletConfig {
/// Create a new configuration with an API key.
pub fn new(api_key: impl Into<String>) -> Self {
Self {
api_key: api_key.into(),
endpoint: "https://wallet.synor.cc/api/v1".to_string(),
network: Network::default(),
timeout_secs: 30,
debug: false,
derivation_path: None,
}
}
/// Set the API endpoint.
pub fn endpoint(mut self, endpoint: impl Into<String>) -> Self {
self.endpoint = endpoint.into();
self
}
/// Set the network.
pub fn network(mut self, network: Network) -> Self {
self.network = network;
self
}
/// Set the timeout in seconds.
pub fn timeout(mut self, secs: u64) -> Self {
self.timeout_secs = secs;
self
}
/// Enable debug mode.
pub fn debug(mut self, debug: bool) -> Self {
self.debug = debug;
self
}
/// Set the derivation path.
pub fn derivation_path(mut self, path: impl Into<String>) -> Self {
self.derivation_path = Some(path.into());
self
}
}
/// Wallet instance.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Wallet {
/// Unique wallet ID.
pub id: String,
/// Primary address.
pub address: String,
/// Compressed public key (hex).
pub public_key: String,
/// Wallet type.
#[serde(rename = "type")]
pub wallet_type: WalletType,
/// Creation timestamp.
pub created_at: i64,
}
/// Wallet creation result.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateWalletResult {
/// Created wallet.
pub wallet: Wallet,
/// BIP39 mnemonic phrase (24 words).
pub mnemonic: String,
}
/// Stealth address for private payments.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StealthAddress {
/// One-time address.
pub address: String,
/// View public key.
pub view_key: String,
/// Spend public key.
pub spend_key: String,
/// Ephemeral public key.
pub ephemeral_key: Option<String>,
}
/// Transaction input.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionInput {
/// Previous transaction hash.
pub txid: String,
/// Output index.
pub vout: u32,
/// Amount.
pub amount: String,
/// Script signature.
pub script_sig: Option<String>,
}
/// Transaction output.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionOutput {
/// Recipient address.
pub address: String,
/// Amount.
pub amount: String,
/// Script pubkey.
pub script_pub_key: Option<String>,
}
/// Unsigned transaction.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Transaction {
/// Version.
pub version: u32,
/// Inputs.
pub inputs: Vec<TransactionInput>,
/// Outputs.
pub outputs: Vec<TransactionOutput>,
/// Lock time.
#[serde(default)]
pub lock_time: u32,
/// Fee.
pub fee: Option<String>,
}
/// Signed transaction.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SignedTransaction {
/// Raw hex-encoded transaction.
pub raw: String,
/// Transaction hash.
pub txid: String,
/// Transaction size in bytes.
pub size: u32,
/// Transaction weight.
pub weight: Option<u32>,
}
/// Signed message.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SignedMessage {
/// Signature (hex).
pub signature: String,
/// Public key used for signing.
pub public_key: String,
/// Address that signed.
pub address: String,
}
/// Unspent Transaction Output.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Utxo {
/// Transaction hash.
pub txid: String,
/// Output index.
pub vout: u32,
/// Amount.
pub amount: String,
/// Address.
pub address: String,
/// Number of confirmations.
pub confirmations: u32,
/// Script pubkey.
pub script_pub_key: Option<String>,
}
/// Balance information.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Balance {
/// Confirmed balance.
pub confirmed: String,
/// Unconfirmed balance.
pub unconfirmed: String,
/// Total balance.
pub total: String,
}
/// Token balance.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenBalance {
/// Token contract address.
pub token: String,
/// Token symbol.
pub symbol: String,
/// Token decimals.
pub decimals: u8,
/// Balance.
pub balance: String,
}
/// Full balance response.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BalanceResponse {
/// Native token balance.
pub native: Balance,
/// Token balances.
#[serde(default)]
pub tokens: Vec<TokenBalance>,
}
/// Fee estimation result.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FeeEstimate {
/// Priority level.
pub priority: Priority,
/// Fee rate.
pub fee_rate: String,
/// Estimated blocks until confirmation.
pub estimated_blocks: u32,
}
/// Options for importing a wallet.
#[derive(Debug, Clone, Default)]
pub struct ImportWalletOptions {
/// BIP39 mnemonic phrase.
pub mnemonic: String,
/// Optional passphrase.
pub passphrase: Option<String>,
/// Wallet type.
pub wallet_type: WalletType,
}
impl ImportWalletOptions {
/// Create new import options with a mnemonic.
pub fn new(mnemonic: impl Into<String>) -> Self {
Self {
mnemonic: mnemonic.into(),
passphrase: None,
wallet_type: WalletType::default(),
}
}
/// Set the passphrase.
pub fn passphrase(mut self, passphrase: impl Into<String>) -> Self {
self.passphrase = Some(passphrase.into());
self
}
/// Set the wallet type.
pub fn wallet_type(mut self, wallet_type: WalletType) -> Self {
self.wallet_type = wallet_type;
self
}
}
/// Options for building a transaction.
#[derive(Debug, Clone, Default)]
pub struct BuildTransactionOptions {
/// Recipient address.
pub to: String,
/// Amount to send.
pub amount: String,
/// Fee rate (satoshis per byte).
pub fee_rate: Option<f64>,
/// Specific UTXOs to use.
pub utxos: Option<Vec<Utxo>>,
/// Change address.
pub change_address: Option<String>,
}
impl BuildTransactionOptions {
/// Create new build options.
pub fn new(to: impl Into<String>, amount: impl Into<String>) -> Self {
Self {
to: to.into(),
amount: amount.into(),
fee_rate: None,
utxos: None,
change_address: None,
}
}
/// Set the fee rate.
pub fn fee_rate(mut self, fee_rate: f64) -> Self {
self.fee_rate = Some(fee_rate);
self
}
/// Set specific UTXOs to use.
pub fn utxos(mut self, utxos: Vec<Utxo>) -> Self {
self.utxos = Some(utxos);
self
}
/// Set the change address.
pub fn change_address(mut self, address: impl Into<String>) -> Self {
self.change_address = Some(address.into());
self
}
}
/// Options for querying UTXOs.
#[derive(Debug, Clone, Default)]
pub struct GetUtxosOptions {
/// Minimum confirmations.
pub min_confirmations: Option<u32>,
/// Minimum amount.
pub min_amount: Option<String>,
}
impl GetUtxosOptions {
/// Set minimum confirmations.
pub fn min_confirmations(mut self, confirmations: u32) -> Self {
self.min_confirmations = Some(confirmations);
self
}
/// Set minimum amount.
pub fn min_amount(mut self, amount: impl Into<String>) -> Self {
self.min_amount = Some(amount.into());
self
}
}

View file

@ -0,0 +1,209 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://synor.io/schemas/bridge.json",
"title": "Synor Bridge SDK",
"description": "Cross-chain asset transfers and bridging operations",
"$defs": {
"BridgeConfig": {
"type": "object",
"allOf": [{ "$ref": "common.json#/$defs/SynorConfig" }]
},
"Asset": {
"type": "object",
"properties": {
"chain": { "$ref": "common.json#/$defs/ChainId" },
"address": { "type": "string" },
"symbol": { "type": "string" },
"decimals": { "type": "integer" },
"isNative": { "type": "boolean" },
"isWrapped": { "type": "boolean" }
},
"required": ["chain", "symbol", "decimals"]
},
"Chain": {
"type": "object",
"properties": {
"id": { "$ref": "common.json#/$defs/ChainId" },
"name": { "type": "string" },
"rpcUrl": { "type": "string", "format": "uri" },
"explorerUrl": { "type": "string", "format": "uri" },
"nativeCurrency": { "$ref": "#/$defs/Asset" },
"confirmationsRequired": { "type": "integer" }
},
"required": ["id", "name"]
},
"TransferStatus": {
"type": "string",
"enum": ["pending", "locked", "minting", "minted", "burning", "unlocking", "completed", "failed", "refunded"]
},
"Transfer": {
"type": "object",
"properties": {
"id": { "type": "string" },
"status": { "$ref": "#/$defs/TransferStatus" },
"sourceChain": { "$ref": "common.json#/$defs/ChainId" },
"targetChain": { "$ref": "common.json#/$defs/ChainId" },
"sourceAddress": { "type": "string" },
"targetAddress": { "type": "string" },
"asset": { "$ref": "#/$defs/Asset" },
"amount": { "$ref": "common.json#/$defs/Amount" },
"fee": { "$ref": "common.json#/$defs/Amount" },
"sourceTxHash": { "type": "string" },
"targetTxHash": { "type": "string" },
"createdAt": { "$ref": "common.json#/$defs/Timestamp" },
"completedAt": { "$ref": "common.json#/$defs/Timestamp" }
},
"required": ["id", "status", "sourceChain", "targetChain", "asset", "amount"]
},
"LockRequest": {
"type": "object",
"properties": {
"asset": { "$ref": "#/$defs/Asset" },
"amount": { "$ref": "common.json#/$defs/Amount" },
"targetChain": { "$ref": "common.json#/$defs/ChainId" },
"targetAddress": { "type": "string" }
},
"required": ["asset", "amount", "targetChain", "targetAddress"]
},
"LockReceipt": {
"type": "object",
"properties": {
"transferId": { "type": "string" },
"txHash": { "type": "string" },
"lockProof": { "$ref": "#/$defs/LockProof" },
"estimatedCompletion": { "$ref": "common.json#/$defs/Timestamp" }
},
"required": ["transferId", "txHash"]
},
"LockProof": {
"type": "object",
"properties": {
"blockHash": { "type": "string" },
"blockNumber": { "type": "integer" },
"txHash": { "type": "string" },
"logIndex": { "type": "integer" },
"proof": {
"type": "array",
"items": { "type": "string" }
},
"signatures": {
"type": "array",
"items": { "$ref": "common.json#/$defs/Signature" }
}
},
"required": ["blockHash", "blockNumber", "txHash", "proof"]
},
"MintRequest": {
"type": "object",
"properties": {
"proof": { "$ref": "#/$defs/LockProof" },
"targetAddress": { "type": "string" }
},
"required": ["proof", "targetAddress"]
},
"BurnRequest": {
"type": "object",
"properties": {
"wrappedAsset": { "$ref": "#/$defs/Asset" },
"amount": { "$ref": "common.json#/$defs/Amount" }
},
"required": ["wrappedAsset", "amount"]
},
"BurnReceipt": {
"type": "object",
"properties": {
"transferId": { "type": "string" },
"txHash": { "type": "string" },
"burnProof": { "$ref": "#/$defs/BurnProof" }
},
"required": ["transferId", "txHash"]
},
"BurnProof": {
"type": "object",
"properties": {
"blockHash": { "type": "string" },
"blockNumber": { "type": "integer" },
"txHash": { "type": "string" },
"proof": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["blockHash", "txHash", "proof"]
},
"UnlockRequest": {
"type": "object",
"properties": {
"proof": { "$ref": "#/$defs/BurnProof" }
},
"required": ["proof"]
},
"TransferFilter": {
"type": "object",
"properties": {
"status": { "$ref": "#/$defs/TransferStatus" },
"sourceChain": { "$ref": "common.json#/$defs/ChainId" },
"targetChain": { "$ref": "common.json#/$defs/ChainId" },
"address": { "type": "string" }
},
"allOf": [{ "$ref": "common.json#/$defs/PaginationParams" }]
},
"ExchangeRate": {
"type": "object",
"properties": {
"from": { "$ref": "#/$defs/Asset" },
"to": { "$ref": "#/$defs/Asset" },
"rate": { "$ref": "common.json#/$defs/Amount" },
"fee": { "$ref": "common.json#/$defs/Amount" },
"minAmount": { "$ref": "common.json#/$defs/Amount" },
"maxAmount": { "$ref": "common.json#/$defs/Amount" },
"estimatedTime": { "$ref": "common.json#/$defs/Duration" }
},
"required": ["from", "to", "rate", "fee"]
},
"GetSupportedChainsResponse": {
"type": "object",
"properties": {
"chains": {
"type": "array",
"items": { "$ref": "#/$defs/Chain" }
}
},
"required": ["chains"]
},
"GetSupportedAssetsRequest": {
"type": "object",
"properties": {
"chain": { "$ref": "common.json#/$defs/ChainId" }
},
"required": ["chain"]
},
"GetExchangeRateRequest": {
"type": "object",
"properties": {
"from": { "$ref": "#/$defs/Asset" },
"to": { "$ref": "#/$defs/Asset" },
"amount": { "$ref": "common.json#/$defs/Amount" }
},
"required": ["from", "to"]
}
}
}

View file

@ -0,0 +1,186 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://synor.io/schemas/common.json",
"title": "Synor Common Types",
"description": "Shared type definitions used across all Synor SDKs",
"$defs": {
"SynorConfig": {
"type": "object",
"description": "Base configuration for all Synor SDK clients",
"properties": {
"apiKey": {
"type": "string",
"description": "API key for authentication"
},
"endpoint": {
"type": "string",
"format": "uri",
"description": "API endpoint URL"
},
"timeout": {
"type": "integer",
"minimum": 1000,
"maximum": 300000,
"default": 30000,
"description": "Request timeout in milliseconds"
},
"retries": {
"type": "integer",
"minimum": 0,
"maximum": 10,
"default": 3,
"description": "Number of retry attempts"
},
"debug": {
"type": "boolean",
"default": false,
"description": "Enable debug logging"
}
},
"required": ["apiKey"]
},
"Address": {
"type": "string",
"pattern": "^synor1[a-z0-9]{38}$",
"description": "Synor blockchain address (bech32 encoded)"
},
"TxHash": {
"type": "string",
"pattern": "^[a-f0-9]{64}$",
"description": "Transaction hash (32 bytes hex)"
},
"BlockHash": {
"type": "string",
"pattern": "^[a-f0-9]{64}$",
"description": "Block hash (32 bytes hex)"
},
"ContentId": {
"type": "string",
"pattern": "^(Qm[1-9A-HJ-NP-Za-km-z]{44}|bafy[a-z0-9]{55})$",
"description": "Content identifier (IPFS CID v0 or v1)"
},
"Amount": {
"type": "string",
"pattern": "^[0-9]+(\\.[0-9]+)?$",
"description": "Decimal amount as string for precision"
},
"Timestamp": {
"type": "integer",
"minimum": 0,
"description": "Unix timestamp in milliseconds"
},
"Duration": {
"type": "integer",
"minimum": 0,
"description": "Duration in seconds"
},
"PublicKey": {
"type": "string",
"pattern": "^[a-f0-9]{66}$",
"description": "Compressed public key (33 bytes hex)"
},
"Signature": {
"type": "string",
"pattern": "^[a-f0-9]{128,144}$",
"description": "ECDSA or Schnorr signature"
},
"UTXO": {
"type": "object",
"properties": {
"txid": { "$ref": "#/$defs/TxHash" },
"vout": { "type": "integer", "minimum": 0 },
"amount": { "$ref": "#/$defs/Amount" },
"address": { "$ref": "#/$defs/Address" },
"confirmations": { "type": "integer", "minimum": 0 },
"scriptPubKey": { "type": "string" }
},
"required": ["txid", "vout", "amount", "address"]
},
"Balance": {
"type": "object",
"properties": {
"confirmed": { "$ref": "#/$defs/Amount" },
"unconfirmed": { "$ref": "#/$defs/Amount" },
"total": { "$ref": "#/$defs/Amount" }
},
"required": ["confirmed", "unconfirmed", "total"]
},
"ChainId": {
"type": "string",
"enum": ["synor-mainnet", "synor-testnet", "ethereum", "bitcoin", "solana", "polygon", "arbitrum", "optimism"],
"description": "Blockchain network identifier"
},
"Priority": {
"type": "string",
"enum": ["low", "medium", "high", "urgent"],
"description": "Transaction priority level"
},
"Status": {
"type": "string",
"enum": ["pending", "processing", "completed", "failed", "cancelled"],
"description": "Generic operation status"
},
"PaginationParams": {
"type": "object",
"properties": {
"limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 },
"offset": { "type": "integer", "minimum": 0, "default": 0 },
"cursor": { "type": "string" }
}
},
"PaginatedResponse": {
"type": "object",
"properties": {
"items": { "type": "array" },
"total": { "type": "integer" },
"hasMore": { "type": "boolean" },
"nextCursor": { "type": "string" }
},
"required": ["items", "hasMore"]
},
"ErrorCode": {
"type": "string",
"enum": [
"INVALID_REQUEST",
"AUTHENTICATION_FAILED",
"AUTHORIZATION_DENIED",
"RESOURCE_NOT_FOUND",
"RATE_LIMITED",
"INTERNAL_ERROR",
"SERVICE_UNAVAILABLE",
"TIMEOUT",
"VALIDATION_ERROR",
"INSUFFICIENT_FUNDS"
]
},
"ErrorResponse": {
"type": "object",
"properties": {
"code": { "$ref": "#/$defs/ErrorCode" },
"message": { "type": "string" },
"details": { "type": "object" },
"requestId": { "type": "string" }
},
"required": ["code", "message"]
}
}
}

View file

@ -0,0 +1,320 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://synor.io/schemas/contract.json",
"title": "Synor Contract SDK",
"description": "Smart contract deployment, interaction, and ABI management",
"$defs": {
"ContractConfig": {
"type": "object",
"allOf": [{ "$ref": "common.json#/$defs/SynorConfig" }]
},
"ContractAddress": {
"$ref": "common.json#/$defs/Address"
},
"AbiType": {
"type": "string",
"enum": [
"uint8", "uint16", "uint32", "uint64", "uint128", "uint256",
"int8", "int16", "int32", "int64", "int128", "int256",
"bool", "string", "bytes", "bytes32", "address",
"tuple", "array"
]
},
"AbiParameter": {
"type": "object",
"properties": {
"name": { "type": "string" },
"type": { "$ref": "#/$defs/AbiType" },
"indexed": { "type": "boolean" },
"components": {
"type": "array",
"items": { "$ref": "#/$defs/AbiParameter" }
}
},
"required": ["name", "type"]
},
"AbiFunction": {
"type": "object",
"properties": {
"name": { "type": "string" },
"type": {
"type": "string",
"enum": ["function", "constructor", "fallback", "receive"]
},
"stateMutability": {
"type": "string",
"enum": ["pure", "view", "nonpayable", "payable"]
},
"inputs": {
"type": "array",
"items": { "$ref": "#/$defs/AbiParameter" }
},
"outputs": {
"type": "array",
"items": { "$ref": "#/$defs/AbiParameter" }
}
},
"required": ["name", "type", "inputs"]
},
"AbiEvent": {
"type": "object",
"properties": {
"name": { "type": "string" },
"type": { "const": "event" },
"anonymous": { "type": "boolean" },
"inputs": {
"type": "array",
"items": { "$ref": "#/$defs/AbiParameter" }
}
},
"required": ["name", "type", "inputs"]
},
"AbiError": {
"type": "object",
"properties": {
"name": { "type": "string" },
"type": { "const": "error" },
"inputs": {
"type": "array",
"items": { "$ref": "#/$defs/AbiParameter" }
}
},
"required": ["name", "type"]
},
"ABI": {
"type": "array",
"items": {
"oneOf": [
{ "$ref": "#/$defs/AbiFunction" },
{ "$ref": "#/$defs/AbiEvent" },
{ "$ref": "#/$defs/AbiError" }
]
}
},
"ContractInterface": {
"type": "object",
"properties": {
"address": { "$ref": "#/$defs/ContractAddress" },
"abi": { "$ref": "#/$defs/ABI" },
"functions": {
"type": "object",
"additionalProperties": { "$ref": "#/$defs/AbiFunction" }
},
"events": {
"type": "object",
"additionalProperties": { "$ref": "#/$defs/AbiEvent" }
}
},
"required": ["abi"]
},
"DeployRequest": {
"type": "object",
"properties": {
"bytecode": { "type": "string", "description": "Contract bytecode (hex)" },
"abi": { "$ref": "#/$defs/ABI" },
"args": {
"type": "array",
"description": "Constructor arguments"
},
"value": { "$ref": "common.json#/$defs/Amount" },
"gasLimit": { "type": "integer" },
"gasPrice": { "$ref": "common.json#/$defs/Amount" }
},
"required": ["bytecode"]
},
"DeployResponse": {
"type": "object",
"properties": {
"address": { "$ref": "#/$defs/ContractAddress" },
"txHash": { "$ref": "common.json#/$defs/TxHash" },
"blockNumber": { "type": "integer" },
"gasUsed": { "type": "integer" }
},
"required": ["address", "txHash"]
},
"CallRequest": {
"type": "object",
"properties": {
"contract": { "$ref": "#/$defs/ContractAddress" },
"method": { "type": "string" },
"args": { "type": "array" },
"blockNumber": {
"oneOf": [
{ "type": "integer" },
{ "type": "string", "enum": ["latest", "pending", "earliest"] }
]
}
},
"required": ["contract", "method"]
},
"CallResponse": {
"type": "object",
"properties": {
"result": {},
"decoded": { "type": "object" }
},
"required": ["result"]
},
"SendRequest": {
"type": "object",
"properties": {
"contract": { "$ref": "#/$defs/ContractAddress" },
"method": { "type": "string" },
"args": { "type": "array" },
"value": { "$ref": "common.json#/$defs/Amount" },
"gasLimit": { "type": "integer" },
"gasPrice": { "$ref": "common.json#/$defs/Amount" },
"nonce": { "type": "integer" }
},
"required": ["contract", "method"]
},
"SendResponse": {
"type": "object",
"properties": {
"txHash": { "$ref": "common.json#/$defs/TxHash" },
"blockNumber": { "type": "integer" },
"gasUsed": { "type": "integer" },
"status": { "type": "boolean" },
"events": {
"type": "array",
"items": { "$ref": "#/$defs/Event" }
}
},
"required": ["txHash"]
},
"Event": {
"type": "object",
"properties": {
"address": { "$ref": "#/$defs/ContractAddress" },
"name": { "type": "string" },
"signature": { "type": "string" },
"topics": {
"type": "array",
"items": { "type": "string" }
},
"data": { "type": "string" },
"decoded": { "type": "object" },
"blockNumber": { "type": "integer" },
"txHash": { "$ref": "common.json#/$defs/TxHash" },
"logIndex": { "type": "integer" }
},
"required": ["address", "topics", "data"]
},
"EventFilter": {
"type": "object",
"properties": {
"address": { "$ref": "#/$defs/ContractAddress" },
"topics": {
"type": "array",
"items": {
"oneOf": [
{ "type": "string" },
{ "type": "array", "items": { "type": "string" } },
{ "type": "null" }
]
}
},
"fromBlock": {
"oneOf": [
{ "type": "integer" },
{ "type": "string", "enum": ["latest", "pending", "earliest"] }
]
},
"toBlock": {
"oneOf": [
{ "type": "integer" },
{ "type": "string", "enum": ["latest", "pending", "earliest"] }
]
}
}
},
"GetEventsRequest": {
"type": "object",
"properties": {
"contract": { "$ref": "#/$defs/ContractAddress" },
"filter": { "$ref": "#/$defs/EventFilter" },
"eventName": { "type": "string" }
},
"required": ["contract"]
},
"GetEventsResponse": {
"type": "object",
"properties": {
"events": {
"type": "array",
"items": { "$ref": "#/$defs/Event" }
}
},
"required": ["events"]
},
"EncodeCallRequest": {
"type": "object",
"properties": {
"method": { "type": "string" },
"args": { "type": "array" },
"abi": { "$ref": "#/$defs/ABI" }
},
"required": ["method", "abi"]
},
"EncodeCallResponse": {
"type": "object",
"properties": {
"data": { "type": "string", "description": "Encoded calldata (hex)" },
"selector": { "type": "string", "description": "Function selector (4 bytes hex)" }
},
"required": ["data", "selector"]
},
"DecodeResultRequest": {
"type": "object",
"properties": {
"data": { "type": "string" },
"method": { "type": "string" },
"abi": { "$ref": "#/$defs/ABI" }
},
"required": ["data", "method", "abi"]
},
"DecodeResultResponse": {
"type": "object",
"properties": {
"decoded": {},
"raw": { "type": "array" }
},
"required": ["decoded"]
},
"Subscription": {
"type": "object",
"properties": {
"id": { "type": "string" },
"contract": { "$ref": "#/$defs/ContractAddress" },
"eventName": { "type": "string" },
"filter": { "$ref": "#/$defs/EventFilter" },
"createdAt": { "$ref": "common.json#/$defs/Timestamp" }
},
"required": ["id", "contract"]
}
}
}

View file

@ -0,0 +1,275 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://synor.io/schemas/database.json",
"title": "Synor Database SDK",
"description": "Key-value, document, vector, and time-series database operations",
"$defs": {
"DatabaseConfig": {
"type": "object",
"allOf": [{ "$ref": "common.json#/$defs/SynorConfig" }],
"properties": {
"namespace": {
"type": "string",
"description": "Database namespace for isolation"
}
}
},
"Value": {
"oneOf": [
{ "type": "string" },
{ "type": "number" },
{ "type": "boolean" },
{ "type": "object" },
{ "type": "array" },
{ "type": "null" }
]
},
"KeyValue": {
"type": "object",
"properties": {
"key": { "type": "string" },
"value": { "$ref": "#/$defs/Value" },
"ttl": { "$ref": "common.json#/$defs/Duration" },
"createdAt": { "$ref": "common.json#/$defs/Timestamp" },
"updatedAt": { "$ref": "common.json#/$defs/Timestamp" }
},
"required": ["key", "value"]
},
"KvGetRequest": {
"type": "object",
"properties": {
"key": { "type": "string" }
},
"required": ["key"]
},
"KvSetRequest": {
"type": "object",
"properties": {
"key": { "type": "string" },
"value": { "$ref": "#/$defs/Value" },
"ttl": { "$ref": "common.json#/$defs/Duration" },
"nx": { "type": "boolean", "description": "Only set if key does not exist" },
"xx": { "type": "boolean", "description": "Only set if key exists" }
},
"required": ["key", "value"]
},
"KvListRequest": {
"type": "object",
"properties": {
"prefix": { "type": "string" },
"cursor": { "type": "string" },
"limit": { "type": "integer", "minimum": 1, "maximum": 1000, "default": 100 }
},
"required": ["prefix"]
},
"Document": {
"type": "object",
"properties": {
"_id": { "type": "string" },
"_rev": { "type": "string" },
"_createdAt": { "$ref": "common.json#/$defs/Timestamp" },
"_updatedAt": { "$ref": "common.json#/$defs/Timestamp" }
},
"additionalProperties": true,
"required": ["_id"]
},
"DocumentQuery": {
"type": "object",
"properties": {
"filter": { "type": "object" },
"sort": {
"type": "array",
"items": {
"type": "object",
"properties": {
"field": { "type": "string" },
"order": { "type": "string", "enum": ["asc", "desc"] }
}
}
},
"projection": {
"type": "array",
"items": { "type": "string" }
}
},
"allOf": [{ "$ref": "common.json#/$defs/PaginationParams" }]
},
"DocCreateRequest": {
"type": "object",
"properties": {
"collection": { "type": "string" },
"document": { "type": "object" }
},
"required": ["collection", "document"]
},
"DocGetRequest": {
"type": "object",
"properties": {
"collection": { "type": "string" },
"id": { "type": "string" }
},
"required": ["collection", "id"]
},
"DocUpdateRequest": {
"type": "object",
"properties": {
"collection": { "type": "string" },
"id": { "type": "string" },
"update": { "type": "object" },
"upsert": { "type": "boolean", "default": false }
},
"required": ["collection", "id", "update"]
},
"DocQueryRequest": {
"type": "object",
"properties": {
"collection": { "type": "string" },
"query": { "$ref": "#/$defs/DocumentQuery" }
},
"required": ["collection", "query"]
},
"VectorEntry": {
"type": "object",
"properties": {
"id": { "type": "string" },
"vector": {
"type": "array",
"items": { "type": "number" }
},
"metadata": { "type": "object" }
},
"required": ["id", "vector"]
},
"VectorUpsertRequest": {
"type": "object",
"properties": {
"collection": { "type": "string" },
"vectors": {
"type": "array",
"items": { "$ref": "#/$defs/VectorEntry" }
}
},
"required": ["collection", "vectors"]
},
"VectorSearchRequest": {
"type": "object",
"properties": {
"collection": { "type": "string" },
"vector": {
"type": "array",
"items": { "type": "number" }
},
"k": { "type": "integer", "minimum": 1, "maximum": 100, "default": 10 },
"filter": { "type": "object" },
"includeMetadata": { "type": "boolean", "default": true },
"includeVectors": { "type": "boolean", "default": false }
},
"required": ["collection", "vector"]
},
"VectorSearchResult": {
"type": "object",
"properties": {
"id": { "type": "string" },
"score": { "type": "number" },
"vector": {
"type": "array",
"items": { "type": "number" }
},
"metadata": { "type": "object" }
},
"required": ["id", "score"]
},
"DataPoint": {
"type": "object",
"properties": {
"timestamp": { "$ref": "common.json#/$defs/Timestamp" },
"value": { "type": "number" },
"tags": {
"type": "object",
"additionalProperties": { "type": "string" }
}
},
"required": ["timestamp", "value"]
},
"TimeRange": {
"type": "object",
"properties": {
"start": { "$ref": "common.json#/$defs/Timestamp" },
"end": { "$ref": "common.json#/$defs/Timestamp" }
},
"required": ["start", "end"]
},
"Aggregation": {
"type": "object",
"properties": {
"function": {
"type": "string",
"enum": ["avg", "sum", "min", "max", "count", "first", "last", "stddev"]
},
"interval": { "$ref": "common.json#/$defs/Duration" },
"groupBy": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["function"]
},
"TsWriteRequest": {
"type": "object",
"properties": {
"series": { "type": "string" },
"points": {
"type": "array",
"items": { "$ref": "#/$defs/DataPoint" }
}
},
"required": ["series", "points"]
},
"TsQueryRequest": {
"type": "object",
"properties": {
"series": { "type": "string" },
"range": { "$ref": "#/$defs/TimeRange" },
"aggregation": { "$ref": "#/$defs/Aggregation" },
"filter": {
"type": "object",
"additionalProperties": { "type": "string" }
}
},
"required": ["series", "range"]
},
"TsQueryResponse": {
"type": "object",
"properties": {
"series": { "type": "string" },
"points": {
"type": "array",
"items": { "$ref": "#/$defs/DataPoint" }
}
},
"required": ["series", "points"]
}
}
}

View file

@ -0,0 +1,245 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://synor.io/schemas/economics.json",
"title": "Synor Economics SDK",
"description": "Pricing, billing, staking, and reward management",
"$defs": {
"EconomicsConfig": {
"type": "object",
"allOf": [{ "$ref": "common.json#/$defs/SynorConfig" }]
},
"ServiceType": {
"type": "string",
"enum": ["compute", "storage", "hosting", "database", "rpc", "bridge", "mining"]
},
"UsageMetrics": {
"type": "object",
"properties": {
"service": { "$ref": "#/$defs/ServiceType" },
"computeSeconds": { "type": "number" },
"storageBytes": { "type": "integer" },
"bandwidthBytes": { "type": "integer" },
"requests": { "type": "integer" },
"gpuSeconds": { "type": "number" }
},
"required": ["service"]
},
"Price": {
"type": "object",
"properties": {
"amount": { "$ref": "common.json#/$defs/Amount" },
"currency": { "type": "string", "default": "SYNOR" },
"unit": { "type": "string" },
"breakdown": {
"type": "array",
"items": { "$ref": "#/$defs/PriceComponent" }
}
},
"required": ["amount", "currency"]
},
"PriceComponent": {
"type": "object",
"properties": {
"name": { "type": "string" },
"amount": { "$ref": "common.json#/$defs/Amount" },
"quantity": { "type": "number" },
"unitPrice": { "$ref": "common.json#/$defs/Amount" }
},
"required": ["name", "amount"]
},
"UsagePlan": {
"type": "object",
"properties": {
"services": {
"type": "array",
"items": { "$ref": "#/$defs/UsageMetrics" }
},
"period": { "$ref": "#/$defs/BillingPeriod" }
},
"required": ["services"]
},
"CostEstimate": {
"type": "object",
"properties": {
"total": { "$ref": "#/$defs/Price" },
"services": {
"type": "array",
"items": {
"type": "object",
"properties": {
"service": { "$ref": "#/$defs/ServiceType" },
"cost": { "$ref": "#/$defs/Price" }
}
}
},
"discounts": {
"type": "array",
"items": { "$ref": "#/$defs/AppliedDiscount" }
}
},
"required": ["total"]
},
"BillingPeriod": {
"type": "string",
"enum": ["hourly", "daily", "weekly", "monthly", "yearly"]
},
"Usage": {
"type": "object",
"properties": {
"period": { "$ref": "#/$defs/BillingPeriod" },
"startDate": { "$ref": "common.json#/$defs/Timestamp" },
"endDate": { "$ref": "common.json#/$defs/Timestamp" },
"services": {
"type": "array",
"items": {
"type": "object",
"properties": {
"service": { "$ref": "#/$defs/ServiceType" },
"metrics": { "$ref": "#/$defs/UsageMetrics" },
"cost": { "$ref": "#/$defs/Price" }
}
}
},
"total": { "$ref": "#/$defs/Price" }
},
"required": ["period", "startDate", "endDate", "total"]
},
"Invoice": {
"type": "object",
"properties": {
"id": { "type": "string" },
"period": { "$ref": "#/$defs/BillingPeriod" },
"startDate": { "$ref": "common.json#/$defs/Timestamp" },
"endDate": { "$ref": "common.json#/$defs/Timestamp" },
"amount": { "$ref": "#/$defs/Price" },
"status": {
"type": "string",
"enum": ["pending", "paid", "overdue", "cancelled"]
},
"paidAt": { "$ref": "common.json#/$defs/Timestamp" },
"txHash": { "$ref": "common.json#/$defs/TxHash" }
},
"required": ["id", "period", "amount", "status"]
},
"AccountBalance": {
"type": "object",
"properties": {
"available": { "$ref": "common.json#/$defs/Amount" },
"pending": { "$ref": "common.json#/$defs/Amount" },
"staked": { "$ref": "common.json#/$defs/Amount" },
"rewards": { "$ref": "common.json#/$defs/Amount" }
},
"required": ["available"]
},
"StakeRequest": {
"type": "object",
"properties": {
"amount": { "$ref": "common.json#/$defs/Amount" },
"duration": { "$ref": "common.json#/$defs/Duration" },
"validator": { "$ref": "common.json#/$defs/Address" }
},
"required": ["amount"]
},
"StakeReceipt": {
"type": "object",
"properties": {
"stakeId": { "type": "string" },
"txHash": { "$ref": "common.json#/$defs/TxHash" },
"amount": { "$ref": "common.json#/$defs/Amount" },
"startDate": { "$ref": "common.json#/$defs/Timestamp" },
"endDate": { "$ref": "common.json#/$defs/Timestamp" },
"estimatedApy": { "type": "number" }
},
"required": ["stakeId", "txHash", "amount"]
},
"UnstakeRequest": {
"type": "object",
"properties": {
"stakeId": { "type": "string" }
},
"required": ["stakeId"]
},
"UnstakeReceipt": {
"type": "object",
"properties": {
"txHash": { "$ref": "common.json#/$defs/TxHash" },
"amount": { "$ref": "common.json#/$defs/Amount" },
"rewards": { "$ref": "common.json#/$defs/Amount" },
"availableAt": { "$ref": "common.json#/$defs/Timestamp" }
},
"required": ["txHash", "amount"]
},
"Rewards": {
"type": "object",
"properties": {
"total": { "$ref": "common.json#/$defs/Amount" },
"claimed": { "$ref": "common.json#/$defs/Amount" },
"pending": { "$ref": "common.json#/$defs/Amount" },
"apy": { "type": "number" },
"breakdown": {
"type": "array",
"items": {
"type": "object",
"properties": {
"stakeId": { "type": "string" },
"amount": { "$ref": "common.json#/$defs/Amount" },
"period": { "type": "string" }
}
}
}
},
"required": ["total", "claimed", "pending"]
},
"Discount": {
"type": "object",
"properties": {
"code": { "type": "string" },
"name": { "type": "string" },
"type": {
"type": "string",
"enum": ["percentage", "fixed", "volume", "loyalty", "referral"]
},
"value": { "$ref": "common.json#/$defs/Amount" },
"minSpend": { "$ref": "common.json#/$defs/Amount" },
"maxDiscount": { "$ref": "common.json#/$defs/Amount" },
"validUntil": { "$ref": "common.json#/$defs/Timestamp" },
"usesRemaining": { "type": "integer" }
},
"required": ["code", "name", "type", "value"]
},
"AppliedDiscount": {
"type": "object",
"properties": {
"discount": { "$ref": "#/$defs/Discount" },
"savedAmount": { "$ref": "common.json#/$defs/Amount" }
},
"required": ["discount", "savedAmount"]
},
"ApplyDiscountRequest": {
"type": "object",
"properties": {
"code": { "type": "string" }
},
"required": ["code"]
}
}
}

View file

@ -0,0 +1,212 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://synor.io/schemas/governance.json",
"title": "Synor Governance SDK",
"description": "DAO, proposals, voting, and vesting management",
"$defs": {
"GovernanceConfig": {
"type": "object",
"allOf": [{ "$ref": "common.json#/$defs/SynorConfig" }]
},
"ProposalStatus": {
"type": "string",
"enum": ["draft", "pending", "active", "passed", "rejected", "executed", "cancelled", "expired"]
},
"ProposalType": {
"type": "string",
"enum": ["parameter_change", "treasury_spend", "contract_upgrade", "text", "emergency"]
},
"Proposal": {
"type": "object",
"properties": {
"id": { "type": "string" },
"title": { "type": "string" },
"description": { "type": "string" },
"type": { "$ref": "#/$defs/ProposalType" },
"status": { "$ref": "#/$defs/ProposalStatus" },
"proposer": { "$ref": "common.json#/$defs/Address" },
"createdAt": { "$ref": "common.json#/$defs/Timestamp" },
"votingStartsAt": { "$ref": "common.json#/$defs/Timestamp" },
"votingEndsAt": { "$ref": "common.json#/$defs/Timestamp" },
"executionDelay": { "$ref": "common.json#/$defs/Duration" },
"quorum": { "$ref": "common.json#/$defs/Amount" },
"currentQuorum": { "$ref": "common.json#/$defs/Amount" },
"votesFor": { "$ref": "common.json#/$defs/Amount" },
"votesAgainst": { "$ref": "common.json#/$defs/Amount" },
"votesAbstain": { "$ref": "common.json#/$defs/Amount" },
"actions": {
"type": "array",
"items": { "$ref": "#/$defs/ProposalAction" }
}
},
"required": ["id", "title", "type", "status", "proposer"]
},
"ProposalAction": {
"type": "object",
"properties": {
"target": { "$ref": "common.json#/$defs/Address" },
"method": { "type": "string" },
"args": { "type": "array" },
"value": { "$ref": "common.json#/$defs/Amount" }
},
"required": ["target", "method"]
},
"ProposalDraft": {
"type": "object",
"properties": {
"title": { "type": "string", "minLength": 10, "maxLength": 200 },
"description": { "type": "string", "minLength": 50 },
"type": { "$ref": "#/$defs/ProposalType" },
"actions": {
"type": "array",
"items": { "$ref": "#/$defs/ProposalAction" }
},
"votingPeriod": { "$ref": "common.json#/$defs/Duration" }
},
"required": ["title", "description", "type"]
},
"ProposalFilter": {
"type": "object",
"properties": {
"status": { "$ref": "#/$defs/ProposalStatus" },
"type": { "$ref": "#/$defs/ProposalType" },
"proposer": { "$ref": "common.json#/$defs/Address" }
},
"allOf": [{ "$ref": "common.json#/$defs/PaginationParams" }]
},
"VoteChoice": {
"type": "string",
"enum": ["for", "against", "abstain"]
},
"Vote": {
"type": "object",
"properties": {
"proposalId": { "type": "string" },
"choice": { "$ref": "#/$defs/VoteChoice" },
"weight": { "$ref": "common.json#/$defs/Amount" },
"reason": { "type": "string" }
},
"required": ["proposalId", "choice"]
},
"VoteReceipt": {
"type": "object",
"properties": {
"txHash": { "$ref": "common.json#/$defs/TxHash" },
"proposalId": { "type": "string" },
"voter": { "$ref": "common.json#/$defs/Address" },
"choice": { "$ref": "#/$defs/VoteChoice" },
"weight": { "$ref": "common.json#/$defs/Amount" },
"timestamp": { "$ref": "common.json#/$defs/Timestamp" }
},
"required": ["txHash", "proposalId", "voter", "choice", "weight"]
},
"DelegationReceipt": {
"type": "object",
"properties": {
"txHash": { "$ref": "common.json#/$defs/TxHash" },
"delegator": { "$ref": "common.json#/$defs/Address" },
"delegatee": { "$ref": "common.json#/$defs/Address" },
"amount": { "$ref": "common.json#/$defs/Amount" }
},
"required": ["txHash", "delegator", "delegatee", "amount"]
},
"VotingPower": {
"type": "object",
"properties": {
"address": { "$ref": "common.json#/$defs/Address" },
"ownPower": { "$ref": "common.json#/$defs/Amount" },
"delegatedPower": { "$ref": "common.json#/$defs/Amount" },
"totalPower": { "$ref": "common.json#/$defs/Amount" },
"delegatedTo": { "$ref": "common.json#/$defs/Address" },
"delegators": {
"type": "array",
"items": {
"type": "object",
"properties": {
"address": { "$ref": "common.json#/$defs/Address" },
"amount": { "$ref": "common.json#/$defs/Amount" }
}
}
}
},
"required": ["address", "totalPower"]
},
"DaoConfig": {
"type": "object",
"properties": {
"name": { "type": "string" },
"description": { "type": "string" },
"token": { "$ref": "common.json#/$defs/Address" },
"votingDelay": { "$ref": "common.json#/$defs/Duration" },
"votingPeriod": { "$ref": "common.json#/$defs/Duration" },
"proposalThreshold": { "$ref": "common.json#/$defs/Amount" },
"quorumThreshold": { "$ref": "common.json#/$defs/Amount" }
},
"required": ["name", "token"]
},
"Dao": {
"type": "object",
"properties": {
"id": { "type": "string" },
"address": { "$ref": "common.json#/$defs/Address" },
"name": { "type": "string" },
"description": { "type": "string" },
"token": { "$ref": "common.json#/$defs/Address" },
"treasury": { "$ref": "common.json#/$defs/Address" },
"proposalCount": { "type": "integer" },
"memberCount": { "type": "integer" },
"createdAt": { "$ref": "common.json#/$defs/Timestamp" }
},
"required": ["id", "address", "name", "token"]
},
"VestingSchedule": {
"type": "object",
"properties": {
"beneficiary": { "$ref": "common.json#/$defs/Address" },
"totalAmount": { "$ref": "common.json#/$defs/Amount" },
"startTime": { "$ref": "common.json#/$defs/Timestamp" },
"cliffDuration": { "$ref": "common.json#/$defs/Duration" },
"vestingDuration": { "$ref": "common.json#/$defs/Duration" },
"slicePeriod": { "$ref": "common.json#/$defs/Duration" },
"revocable": { "type": "boolean" }
},
"required": ["beneficiary", "totalAmount", "startTime", "vestingDuration"]
},
"VestingContract": {
"type": "object",
"properties": {
"id": { "type": "string" },
"address": { "$ref": "common.json#/$defs/Address" },
"schedule": { "$ref": "#/$defs/VestingSchedule" },
"released": { "$ref": "common.json#/$defs/Amount" },
"releasable": { "$ref": "common.json#/$defs/Amount" },
"revoked": { "type": "boolean" }
},
"required": ["id", "address", "schedule", "released"]
},
"ClaimVestedRequest": {
"type": "object",
"properties": {
"contractId": { "type": "string" }
},
"required": ["contractId"]
}
}
}

View file

@ -0,0 +1,190 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://synor.io/schemas/hosting.json",
"title": "Synor Hosting SDK",
"description": "Domain management, DNS, deployments, and SSL certificates",
"$defs": {
"HostingConfig": {
"type": "object",
"allOf": [{ "$ref": "common.json#/$defs/SynorConfig" }]
},
"Domain": {
"type": "object",
"properties": {
"name": { "type": "string" },
"owner": { "$ref": "common.json#/$defs/Address" },
"resolver": { "$ref": "common.json#/$defs/ContentId" },
"registeredAt": { "$ref": "common.json#/$defs/Timestamp" },
"expiresAt": { "$ref": "common.json#/$defs/Timestamp" },
"verified": { "type": "boolean" }
},
"required": ["name", "owner"]
},
"DomainRecord": {
"type": "object",
"properties": {
"domain": { "type": "string" },
"cid": { "$ref": "common.json#/$defs/ContentId" },
"dnslink": { "type": "string" },
"ipv4": {
"type": "array",
"items": { "type": "string", "format": "ipv4" }
},
"ipv6": {
"type": "array",
"items": { "type": "string", "format": "ipv6" }
},
"updatedAt": { "$ref": "common.json#/$defs/Timestamp" }
},
"required": ["domain"]
},
"RegisterDomainRequest": {
"type": "object",
"properties": {
"name": { "type": "string", "pattern": "^[a-z0-9-]+\\.synor$" },
"duration": { "$ref": "common.json#/$defs/Duration" }
},
"required": ["name"]
},
"ResolveDomainRequest": {
"type": "object",
"properties": {
"name": { "type": "string" }
},
"required": ["name"]
},
"UpdateDomainRequest": {
"type": "object",
"properties": {
"name": { "type": "string" },
"record": { "$ref": "#/$defs/DomainRecord" }
},
"required": ["name", "record"]
},
"DnsRecordType": {
"type": "string",
"enum": ["A", "AAAA", "CNAME", "TXT", "MX", "NS", "CAA", "DNSLINK"]
},
"DnsRecord": {
"type": "object",
"properties": {
"type": { "$ref": "#/$defs/DnsRecordType" },
"name": { "type": "string" },
"value": { "type": "string" },
"ttl": { "type": "integer", "minimum": 60, "default": 3600 },
"priority": { "type": "integer", "minimum": 0 }
},
"required": ["type", "name", "value"]
},
"SetDnsRecordsRequest": {
"type": "object",
"properties": {
"domain": { "type": "string" },
"records": {
"type": "array",
"items": { "$ref": "#/$defs/DnsRecord" }
}
},
"required": ["domain", "records"]
},
"GetDnsRecordsRequest": {
"type": "object",
"properties": {
"domain": { "type": "string" },
"type": { "$ref": "#/$defs/DnsRecordType" }
},
"required": ["domain"]
},
"DeploymentStatus": {
"type": "string",
"enum": ["queued", "building", "deploying", "ready", "failed", "cancelled"]
},
"Deployment": {
"type": "object",
"properties": {
"id": { "type": "string" },
"domain": { "type": "string" },
"cid": { "$ref": "common.json#/$defs/ContentId" },
"status": { "$ref": "#/$defs/DeploymentStatus" },
"url": { "type": "string", "format": "uri" },
"createdAt": { "$ref": "common.json#/$defs/Timestamp" },
"readyAt": { "$ref": "common.json#/$defs/Timestamp" },
"error": { "type": "string" }
},
"required": ["id", "domain", "cid", "status"]
},
"DeployRequest": {
"type": "object",
"properties": {
"cid": { "$ref": "common.json#/$defs/ContentId" },
"domain": { "type": "string" },
"buildCommand": { "type": "string" },
"outputDirectory": { "type": "string" },
"environment": {
"type": "object",
"additionalProperties": { "type": "string" }
}
},
"required": ["cid", "domain"]
},
"ListDeploymentsRequest": {
"type": "object",
"properties": {
"domain": { "type": "string" },
"status": { "$ref": "#/$defs/DeploymentStatus" }
},
"allOf": [{ "$ref": "common.json#/$defs/PaginationParams" }]
},
"Certificate": {
"type": "object",
"properties": {
"domain": { "type": "string" },
"issuer": { "type": "string" },
"validFrom": { "$ref": "common.json#/$defs/Timestamp" },
"validTo": { "$ref": "common.json#/$defs/Timestamp" },
"fingerprint": { "type": "string" },
"autoRenew": { "type": "boolean" }
},
"required": ["domain", "validFrom", "validTo"]
},
"ProvisionSslRequest": {
"type": "object",
"properties": {
"domain": { "type": "string" },
"wildcard": { "type": "boolean", "default": false }
},
"required": ["domain"]
},
"DomainVerification": {
"type": "object",
"properties": {
"domain": { "type": "string" },
"method": {
"type": "string",
"enum": ["dns", "http", "email"]
},
"token": { "type": "string" },
"verified": { "type": "boolean" },
"expiresAt": { "$ref": "common.json#/$defs/Timestamp" }
},
"required": ["domain", "method", "token"]
}
}
}

View file

@ -0,0 +1,195 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://synor.io/schemas/mining.json",
"title": "Synor Mining SDK",
"description": "Mining pool connection, block templates, and hashrate management",
"$defs": {
"MiningConfig": {
"type": "object",
"allOf": [{ "$ref": "common.json#/$defs/SynorConfig" }],
"properties": {
"stratumUrl": {
"type": "string",
"format": "uri",
"description": "Stratum protocol endpoint"
},
"workerName": { "type": "string" }
}
},
"PoolConfig": {
"type": "object",
"properties": {
"url": { "type": "string", "format": "uri" },
"username": { "type": "string" },
"password": { "type": "string" },
"workerName": { "type": "string" },
"algorithm": { "$ref": "#/$defs/MiningAlgorithm" }
},
"required": ["url", "username"]
},
"MiningAlgorithm": {
"type": "string",
"enum": ["sha256d", "scrypt", "ethash", "randomx", "kawpow", "synor-pow"]
},
"StratumConnection": {
"type": "object",
"properties": {
"id": { "type": "string" },
"poolUrl": { "type": "string" },
"connected": { "type": "boolean" },
"difficulty": { "type": "number" },
"extranonce1": { "type": "string" },
"extranonce2Size": { "type": "integer" },
"connectedAt": { "$ref": "common.json#/$defs/Timestamp" }
},
"required": ["id", "connected"]
},
"BlockTemplate": {
"type": "object",
"properties": {
"previousBlockHash": { "$ref": "common.json#/$defs/BlockHash" },
"height": { "type": "integer" },
"version": { "type": "integer" },
"bits": { "type": "string" },
"target": { "type": "string" },
"curTime": { "$ref": "common.json#/$defs/Timestamp" },
"coinbaseValue": { "$ref": "common.json#/$defs/Amount" },
"transactions": {
"type": "array",
"items": { "$ref": "#/$defs/TemplateTransaction" }
},
"merkleRoot": { "type": "string" },
"nonceRange": { "type": "string" }
},
"required": ["previousBlockHash", "height", "target", "coinbaseValue"]
},
"TemplateTransaction": {
"type": "object",
"properties": {
"data": { "type": "string" },
"txid": { "$ref": "common.json#/$defs/TxHash" },
"fee": { "$ref": "common.json#/$defs/Amount" },
"weight": { "type": "integer" }
},
"required": ["data", "txid", "fee"]
},
"MinedWork": {
"type": "object",
"properties": {
"jobId": { "type": "string" },
"extranonce2": { "type": "string" },
"nonce": { "type": "string" },
"nTime": { "type": "string" },
"hash": { "type": "string" }
},
"required": ["jobId", "extranonce2", "nonce", "nTime"]
},
"SubmitResult": {
"type": "object",
"properties": {
"accepted": { "type": "boolean" },
"blockHash": { "$ref": "common.json#/$defs/BlockHash" },
"difficulty": { "type": "number" },
"rejectReason": { "type": "string" }
},
"required": ["accepted"]
},
"Hashrate": {
"type": "object",
"properties": {
"current": { "type": "number", "description": "Hashes per second" },
"average1h": { "type": "number" },
"average24h": { "type": "number" },
"unit": {
"type": "string",
"enum": ["H/s", "KH/s", "MH/s", "GH/s", "TH/s", "PH/s"]
}
},
"required": ["current", "unit"]
},
"MiningStats": {
"type": "object",
"properties": {
"hashrate": { "$ref": "#/$defs/Hashrate" },
"sharesAccepted": { "type": "integer" },
"sharesRejected": { "type": "integer" },
"blocksFound": { "type": "integer" },
"uptime": { "$ref": "common.json#/$defs/Duration" },
"efficiency": { "type": "number", "minimum": 0, "maximum": 100 },
"lastShareAt": { "$ref": "common.json#/$defs/Timestamp" }
},
"required": ["hashrate", "sharesAccepted", "sharesRejected"]
},
"TimePeriod": {
"type": "string",
"enum": ["hour", "day", "week", "month", "all"]
},
"Earnings": {
"type": "object",
"properties": {
"period": { "$ref": "#/$defs/TimePeriod" },
"amount": { "$ref": "common.json#/$defs/Amount" },
"blocks": { "type": "integer" },
"shares": { "type": "integer" },
"estimatedDaily": { "$ref": "common.json#/$defs/Amount" }
},
"required": ["period", "amount"]
},
"MiningDevice": {
"type": "object",
"properties": {
"id": { "type": "string" },
"name": { "type": "string" },
"type": {
"type": "string",
"enum": ["cpu", "gpu", "asic", "fpga"]
},
"vendor": { "type": "string" },
"model": { "type": "string" },
"hashrate": { "$ref": "#/$defs/Hashrate" },
"temperature": { "type": "number" },
"fanSpeed": { "type": "integer", "minimum": 0, "maximum": 100 },
"power": { "type": "number", "description": "Watts" },
"enabled": { "type": "boolean" }
},
"required": ["id", "name", "type"]
},
"DeviceConfig": {
"type": "object",
"properties": {
"enabled": { "type": "boolean" },
"intensity": { "type": "integer", "minimum": 0, "maximum": 100 },
"powerLimit": { "type": "integer" },
"coreClockOffset": { "type": "integer" },
"memoryClockOffset": { "type": "integer" },
"fanSpeed": { "type": "integer", "minimum": 0, "maximum": 100 }
}
},
"ListDevicesResponse": {
"type": "object",
"properties": {
"devices": {
"type": "array",
"items": { "$ref": "#/$defs/MiningDevice" }
},
"totalHashrate": { "$ref": "#/$defs/Hashrate" }
},
"required": ["devices"]
}
}
}

View file

@ -0,0 +1,243 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://synor.io/schemas/privacy.json",
"title": "Synor Privacy SDK",
"description": "Confidential transactions, ring signatures, stealth addresses, and commitments",
"$defs": {
"PrivacyConfig": {
"type": "object",
"allOf": [{ "$ref": "common.json#/$defs/SynorConfig" }]
},
"Commitment": {
"type": "object",
"properties": {
"value": { "type": "string", "description": "Commitment value (hex)" },
"blindingFactor": { "type": "string", "description": "Blinding factor (hex)" }
},
"required": ["value"]
},
"RangeProof": {
"type": "object",
"properties": {
"proof": { "type": "string", "description": "Bulletproof range proof (hex)" },
"commitment": { "$ref": "#/$defs/Commitment" }
},
"required": ["proof", "commitment"]
},
"ConfidentialOutput": {
"type": "object",
"properties": {
"address": { "$ref": "common.json#/$defs/Address" },
"commitment": { "$ref": "#/$defs/Commitment" },
"rangeProof": { "$ref": "#/$defs/RangeProof" },
"encryptedAmount": { "type": "string" }
},
"required": ["address", "commitment", "rangeProof"]
},
"ConfidentialTransaction": {
"type": "object",
"properties": {
"txid": { "$ref": "common.json#/$defs/TxHash" },
"inputs": {
"type": "array",
"items": { "$ref": "common.json#/$defs/UTXO" }
},
"outputs": {
"type": "array",
"items": { "$ref": "#/$defs/ConfidentialOutput" }
},
"fee": { "$ref": "common.json#/$defs/Amount" },
"proof": { "type": "string" }
},
"required": ["inputs", "outputs"]
},
"CreateConfidentialTxRequest": {
"type": "object",
"properties": {
"inputs": {
"type": "array",
"items": { "$ref": "common.json#/$defs/UTXO" }
},
"outputs": {
"type": "array",
"items": {
"type": "object",
"properties": {
"address": { "$ref": "common.json#/$defs/Address" },
"amount": { "$ref": "common.json#/$defs/Amount" }
},
"required": ["address", "amount"]
}
}
},
"required": ["inputs", "outputs"]
},
"RingSignature": {
"type": "object",
"properties": {
"keyImage": { "type": "string" },
"c": {
"type": "array",
"items": { "type": "string" }
},
"r": {
"type": "array",
"items": { "type": "string" }
},
"ringSize": { "type": "integer" }
},
"required": ["keyImage", "c", "r", "ringSize"]
},
"CreateRingSignatureRequest": {
"type": "object",
"properties": {
"message": { "type": "string" },
"messageFormat": {
"type": "string",
"enum": ["text", "hex", "base64"],
"default": "hex"
},
"ring": {
"type": "array",
"items": { "$ref": "common.json#/$defs/PublicKey" },
"minItems": 2
},
"secretIndex": { "type": "integer" }
},
"required": ["message", "ring"]
},
"VerifyRingSignatureRequest": {
"type": "object",
"properties": {
"signature": { "$ref": "#/$defs/RingSignature" },
"message": { "type": "string" },
"ring": {
"type": "array",
"items": { "$ref": "common.json#/$defs/PublicKey" }
}
},
"required": ["signature", "message", "ring"]
},
"StealthAddress": {
"type": "object",
"properties": {
"address": { "$ref": "common.json#/$defs/Address" },
"viewKey": { "$ref": "common.json#/$defs/PublicKey" },
"spendKey": { "$ref": "common.json#/$defs/PublicKey" },
"ephemeralKey": { "$ref": "common.json#/$defs/PublicKey" }
},
"required": ["address", "viewKey", "spendKey"]
},
"GenerateStealthAddressResponse": {
"type": "object",
"properties": {
"stealthAddress": { "$ref": "#/$defs/StealthAddress" },
"sharedSecret": { "type": "string" }
},
"required": ["stealthAddress"]
},
"DeriveSharedSecretRequest": {
"type": "object",
"properties": {
"stealthAddress": { "$ref": "#/$defs/StealthAddress" },
"privateKey": { "type": "string" }
},
"required": ["stealthAddress", "privateKey"]
},
"SharedSecret": {
"type": "object",
"properties": {
"secret": { "type": "string" },
"derivedKey": { "type": "string" }
},
"required": ["secret"]
},
"CreateCommitmentRequest": {
"type": "object",
"properties": {
"value": { "$ref": "common.json#/$defs/Amount" },
"blinding": { "type": "string", "description": "Optional blinding factor (hex)" }
},
"required": ["value"]
},
"CreateCommitmentResponse": {
"type": "object",
"properties": {
"commitment": { "$ref": "#/$defs/Commitment" },
"blindingFactor": { "type": "string" }
},
"required": ["commitment", "blindingFactor"]
},
"OpenCommitmentRequest": {
"type": "object",
"properties": {
"commitment": { "$ref": "#/$defs/Commitment" },
"value": { "$ref": "common.json#/$defs/Amount" },
"blinding": { "type": "string" }
},
"required": ["commitment", "value", "blinding"]
},
"OpenCommitmentResponse": {
"type": "object",
"properties": {
"valid": { "type": "boolean" }
},
"required": ["valid"]
},
"ZeroKnowledgeProof": {
"type": "object",
"properties": {
"proofType": {
"type": "string",
"enum": ["groth16", "plonk", "bulletproofs", "stark"]
},
"proof": { "type": "string" },
"publicInputs": {
"type": "array",
"items": { "type": "string" }
},
"verificationKey": { "type": "string" }
},
"required": ["proofType", "proof"]
},
"VerifyZkProofRequest": {
"type": "object",
"properties": {
"proof": { "$ref": "#/$defs/ZeroKnowledgeProof" },
"publicInputs": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["proof"]
},
"VerifyZkProofResponse": {
"type": "object",
"properties": {
"valid": { "type": "boolean" },
"error": { "type": "string" }
},
"required": ["valid"]
}
}
}

205
sdk/shared/schemas/rpc.json Normal file
View file

@ -0,0 +1,205 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://synor.io/schemas/rpc.json",
"title": "Synor RPC SDK",
"description": "Blockchain RPC client for querying blocks, transactions, and chain state",
"$defs": {
"RpcConfig": {
"type": "object",
"allOf": [{ "$ref": "common.json#/$defs/SynorConfig" }],
"properties": {
"wsEndpoint": {
"type": "string",
"format": "uri",
"description": "WebSocket endpoint for subscriptions"
},
"network": {
"type": "string",
"enum": ["mainnet", "testnet"],
"default": "mainnet"
}
}
},
"BlockHeader": {
"type": "object",
"properties": {
"hash": { "$ref": "common.json#/$defs/BlockHash" },
"height": { "type": "integer", "minimum": 0 },
"version": { "type": "integer" },
"previousHash": { "$ref": "common.json#/$defs/BlockHash" },
"merkleRoot": { "type": "string" },
"timestamp": { "$ref": "common.json#/$defs/Timestamp" },
"difficulty": { "type": "string" },
"nonce": { "type": "integer" }
},
"required": ["hash", "height", "previousHash", "merkleRoot", "timestamp"]
},
"Block": {
"type": "object",
"allOf": [{ "$ref": "#/$defs/BlockHeader" }],
"properties": {
"transactions": {
"type": "array",
"items": { "$ref": "common.json#/$defs/TxHash" }
},
"size": { "type": "integer" },
"weight": { "type": "integer" },
"txCount": { "type": "integer" }
},
"required": ["transactions", "txCount"]
},
"TransactionStatus": {
"type": "string",
"enum": ["pending", "confirmed", "failed", "replaced"]
},
"Transaction": {
"type": "object",
"properties": {
"txid": { "$ref": "common.json#/$defs/TxHash" },
"blockHash": { "$ref": "common.json#/$defs/BlockHash" },
"blockHeight": { "type": "integer" },
"confirmations": { "type": "integer" },
"timestamp": { "$ref": "common.json#/$defs/Timestamp" },
"status": { "$ref": "#/$defs/TransactionStatus" },
"raw": { "type": "string" },
"size": { "type": "integer" },
"fee": { "$ref": "common.json#/$defs/Amount" },
"inputs": { "type": "array" },
"outputs": { "type": "array" }
},
"required": ["txid", "status"]
},
"GetBlockRequest": {
"type": "object",
"properties": {
"hash": { "$ref": "common.json#/$defs/BlockHash" },
"height": { "type": "integer" },
"includeTransactions": { "type": "boolean", "default": true }
},
"oneOf": [
{ "required": ["hash"] },
{ "required": ["height"] }
]
},
"GetTransactionRequest": {
"type": "object",
"properties": {
"txid": { "$ref": "common.json#/$defs/TxHash" }
},
"required": ["txid"]
},
"SendTransactionRequest": {
"type": "object",
"properties": {
"raw": { "type": "string", "description": "Hex-encoded signed transaction" }
},
"required": ["raw"]
},
"SendTransactionResponse": {
"type": "object",
"properties": {
"txid": { "$ref": "common.json#/$defs/TxHash" },
"accepted": { "type": "boolean" }
},
"required": ["txid", "accepted"]
},
"FeeEstimate": {
"type": "object",
"properties": {
"priority": { "$ref": "common.json#/$defs/Priority" },
"feeRate": { "$ref": "common.json#/$defs/Amount" },
"estimatedBlocks": { "type": "integer" }
},
"required": ["priority", "feeRate", "estimatedBlocks"]
},
"EstimateFeeRequest": {
"type": "object",
"properties": {
"priority": { "$ref": "common.json#/$defs/Priority" },
"targetBlocks": { "type": "integer", "minimum": 1, "maximum": 100 }
}
},
"ChainInfo": {
"type": "object",
"properties": {
"chain": { "type": "string" },
"network": { "type": "string" },
"height": { "type": "integer" },
"bestBlockHash": { "$ref": "common.json#/$defs/BlockHash" },
"difficulty": { "type": "string" },
"medianTime": { "$ref": "common.json#/$defs/Timestamp" },
"chainWork": { "type": "string" },
"syncing": { "type": "boolean" },
"syncProgress": { "type": "number", "minimum": 0, "maximum": 1 }
},
"required": ["chain", "network", "height", "bestBlockHash"]
},
"MempoolInfo": {
"type": "object",
"properties": {
"size": { "type": "integer" },
"bytes": { "type": "integer" },
"usage": { "type": "integer" },
"maxMempool": { "type": "integer" },
"minFee": { "$ref": "common.json#/$defs/Amount" }
},
"required": ["size", "bytes"]
},
"SubscriptionType": {
"type": "string",
"enum": ["blocks", "transactions", "address", "mempool"]
},
"SubscribeRequest": {
"type": "object",
"properties": {
"type": { "$ref": "#/$defs/SubscriptionType" },
"address": { "$ref": "common.json#/$defs/Address" }
},
"required": ["type"]
},
"Subscription": {
"type": "object",
"properties": {
"id": { "type": "string" },
"type": { "$ref": "#/$defs/SubscriptionType" },
"createdAt": { "$ref": "common.json#/$defs/Timestamp" }
},
"required": ["id", "type"]
},
"BlockNotification": {
"type": "object",
"properties": {
"type": { "const": "block" },
"block": { "$ref": "#/$defs/Block" }
},
"required": ["type", "block"]
},
"TransactionNotification": {
"type": "object",
"properties": {
"type": { "const": "transaction" },
"transaction": { "$ref": "#/$defs/Transaction" },
"address": { "$ref": "common.json#/$defs/Address" }
},
"required": ["type", "transaction"]
}
}
}

View file

@ -0,0 +1,259 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://synor.io/schemas/storage.json",
"title": "Synor Storage SDK",
"description": "Decentralized storage, pinning, and content retrieval",
"$defs": {
"StorageConfig": {
"type": "object",
"allOf": [{ "$ref": "common.json#/$defs/SynorConfig" }],
"properties": {
"gateway": {
"type": "string",
"format": "uri",
"description": "IPFS gateway URL"
},
"pinningService": {
"type": "string",
"format": "uri",
"description": "Remote pinning service URL"
},
"chunkSize": {
"type": "integer",
"minimum": 262144,
"maximum": 1048576,
"default": 262144,
"description": "Chunk size in bytes for large files"
}
}
},
"ContentId": {
"$ref": "common.json#/$defs/ContentId"
},
"UploadOptions": {
"type": "object",
"properties": {
"pin": { "type": "boolean", "default": true },
"wrapWithDirectory": { "type": "boolean", "default": false },
"cidVersion": { "type": "integer", "enum": [0, 1], "default": 1 },
"hashAlgorithm": {
"type": "string",
"enum": ["sha2-256", "blake3"],
"default": "sha2-256"
},
"onProgress": {
"type": "string",
"description": "Callback function name for progress updates"
}
}
},
"UploadResponse": {
"type": "object",
"properties": {
"cid": { "$ref": "#/$defs/ContentId" },
"size": { "type": "integer" },
"name": { "type": "string" },
"hash": { "type": "string" }
},
"required": ["cid", "size"]
},
"DownloadOptions": {
"type": "object",
"properties": {
"offset": { "type": "integer", "minimum": 0 },
"length": { "type": "integer", "minimum": 1 },
"onProgress": {
"type": "string",
"description": "Callback function name for progress updates"
}
}
},
"PinStatus": {
"type": "string",
"enum": ["queued", "pinning", "pinned", "failed", "unpinned"]
},
"Pin": {
"type": "object",
"properties": {
"cid": { "$ref": "#/$defs/ContentId" },
"status": { "$ref": "#/$defs/PinStatus" },
"name": { "type": "string" },
"size": { "type": "integer" },
"createdAt": { "$ref": "common.json#/$defs/Timestamp" },
"expiresAt": { "$ref": "common.json#/$defs/Timestamp" },
"delegates": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["cid", "status"]
},
"PinRequest": {
"type": "object",
"properties": {
"cid": { "$ref": "#/$defs/ContentId" },
"name": { "type": "string" },
"duration": { "$ref": "common.json#/$defs/Duration" },
"origins": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["cid"]
},
"ListPinsRequest": {
"type": "object",
"properties": {
"status": {
"type": "array",
"items": { "$ref": "#/$defs/PinStatus" }
},
"match": {
"type": "string",
"enum": ["exact", "iexact", "partial", "ipartial"]
},
"name": { "type": "string" }
},
"allOf": [{ "$ref": "common.json#/$defs/PaginationParams" }]
},
"GatewayUrl": {
"type": "object",
"properties": {
"url": { "type": "string", "format": "uri" },
"cid": { "$ref": "#/$defs/ContentId" },
"path": { "type": "string" }
},
"required": ["url", "cid"]
},
"CarFile": {
"type": "object",
"properties": {
"version": { "type": "integer", "enum": [1, 2] },
"roots": {
"type": "array",
"items": { "$ref": "#/$defs/ContentId" }
},
"blocks": {
"type": "array",
"items": { "$ref": "#/$defs/CarBlock" }
},
"size": { "type": "integer" }
},
"required": ["version", "roots"]
},
"CarBlock": {
"type": "object",
"properties": {
"cid": { "$ref": "#/$defs/ContentId" },
"data": { "type": "string", "description": "Base64-encoded block data" },
"size": { "type": "integer" }
},
"required": ["cid", "data"]
},
"FileEntry": {
"type": "object",
"properties": {
"name": { "type": "string" },
"content": { "type": "string", "description": "Base64-encoded file content" },
"cid": { "$ref": "#/$defs/ContentId" }
},
"required": ["name"]
},
"DirectoryEntry": {
"type": "object",
"properties": {
"name": { "type": "string" },
"cid": { "$ref": "#/$defs/ContentId" },
"size": { "type": "integer" },
"type": {
"type": "string",
"enum": ["file", "directory"]
}
},
"required": ["name", "cid", "type"]
},
"CreateDirectoryRequest": {
"type": "object",
"properties": {
"files": {
"type": "array",
"items": { "$ref": "#/$defs/FileEntry" }
}
},
"required": ["files"]
},
"ListDirectoryRequest": {
"type": "object",
"properties": {
"cid": { "$ref": "#/$defs/ContentId" },
"path": { "type": "string" }
},
"required": ["cid"]
},
"ListDirectoryResponse": {
"type": "object",
"properties": {
"entries": {
"type": "array",
"items": { "$ref": "#/$defs/DirectoryEntry" }
},
"cid": { "$ref": "#/$defs/ContentId" }
},
"required": ["entries", "cid"]
},
"ImportCarRequest": {
"type": "object",
"properties": {
"car": { "type": "string", "description": "Base64-encoded CAR file" },
"pin": { "type": "boolean", "default": true }
},
"required": ["car"]
},
"ImportCarResponse": {
"type": "object",
"properties": {
"roots": {
"type": "array",
"items": { "$ref": "#/$defs/ContentId" }
},
"blocksImported": { "type": "integer" }
},
"required": ["roots", "blocksImported"]
},
"StorageStats": {
"type": "object",
"properties": {
"totalSize": { "type": "integer" },
"pinCount": { "type": "integer" },
"bandwidth": {
"type": "object",
"properties": {
"upload": { "type": "integer" },
"download": { "type": "integer" }
}
}
},
"required": ["totalSize", "pinCount"]
}
}
}

View file

@ -0,0 +1,208 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://synor.io/schemas/wallet.json",
"title": "Synor Wallet SDK",
"description": "Wallet management, key operations, and transaction signing",
"$defs": {
"WalletConfig": {
"type": "object",
"allOf": [{ "$ref": "common.json#/$defs/SynorConfig" }],
"properties": {
"network": {
"type": "string",
"enum": ["mainnet", "testnet"],
"default": "mainnet"
},
"derivationPath": {
"type": "string",
"default": "m/44'/60'/0'/0"
}
}
},
"Wallet": {
"type": "object",
"properties": {
"id": { "type": "string", "format": "uuid" },
"address": { "$ref": "common.json#/$defs/Address" },
"publicKey": { "$ref": "common.json#/$defs/PublicKey" },
"createdAt": { "$ref": "common.json#/$defs/Timestamp" },
"type": {
"type": "string",
"enum": ["standard", "multisig", "stealth", "hardware"]
}
},
"required": ["id", "address", "publicKey", "type"]
},
"CreateWalletRequest": {
"type": "object",
"properties": {
"passphrase": { "type": "string", "minLength": 8 },
"type": {
"type": "string",
"enum": ["standard", "stealth"],
"default": "standard"
}
}
},
"CreateWalletResponse": {
"type": "object",
"properties": {
"wallet": { "$ref": "#/$defs/Wallet" },
"mnemonic": {
"type": "string",
"description": "BIP39 mnemonic phrase (24 words)"
}
},
"required": ["wallet", "mnemonic"]
},
"ImportWalletRequest": {
"type": "object",
"properties": {
"mnemonic": { "type": "string" },
"passphrase": { "type": "string" }
},
"required": ["mnemonic"]
},
"StealthAddress": {
"type": "object",
"properties": {
"address": { "$ref": "common.json#/$defs/Address" },
"viewKey": { "$ref": "common.json#/$defs/PublicKey" },
"spendKey": { "$ref": "common.json#/$defs/PublicKey" },
"ephemeralKey": { "$ref": "common.json#/$defs/PublicKey" }
},
"required": ["address", "viewKey", "spendKey"]
},
"TransactionInput": {
"type": "object",
"properties": {
"txid": { "$ref": "common.json#/$defs/TxHash" },
"vout": { "type": "integer", "minimum": 0 },
"amount": { "$ref": "common.json#/$defs/Amount" }
},
"required": ["txid", "vout", "amount"]
},
"TransactionOutput": {
"type": "object",
"properties": {
"address": { "$ref": "common.json#/$defs/Address" },
"amount": { "$ref": "common.json#/$defs/Amount" }
},
"required": ["address", "amount"]
},
"Transaction": {
"type": "object",
"properties": {
"version": { "type": "integer" },
"inputs": {
"type": "array",
"items": { "$ref": "#/$defs/TransactionInput" }
},
"outputs": {
"type": "array",
"items": { "$ref": "#/$defs/TransactionOutput" }
},
"lockTime": { "type": "integer" },
"fee": { "$ref": "common.json#/$defs/Amount" }
},
"required": ["version", "inputs", "outputs"]
},
"SignedTransaction": {
"type": "object",
"properties": {
"raw": { "type": "string", "description": "Hex-encoded signed transaction" },
"txid": { "$ref": "common.json#/$defs/TxHash" },
"size": { "type": "integer" },
"weight": { "type": "integer" }
},
"required": ["raw", "txid"]
},
"SignMessageRequest": {
"type": "object",
"properties": {
"message": { "type": "string" },
"format": {
"type": "string",
"enum": ["text", "hex", "base64"],
"default": "text"
}
},
"required": ["message"]
},
"SignMessageResponse": {
"type": "object",
"properties": {
"signature": { "$ref": "common.json#/$defs/Signature" },
"publicKey": { "$ref": "common.json#/$defs/PublicKey" },
"address": { "$ref": "common.json#/$defs/Address" }
},
"required": ["signature", "publicKey", "address"]
},
"GetBalanceRequest": {
"type": "object",
"properties": {
"address": { "$ref": "common.json#/$defs/Address" },
"includeTokens": { "type": "boolean", "default": false }
},
"required": ["address"]
},
"TokenBalance": {
"type": "object",
"properties": {
"token": { "$ref": "common.json#/$defs/Address" },
"symbol": { "type": "string" },
"decimals": { "type": "integer" },
"balance": { "$ref": "common.json#/$defs/Amount" }
},
"required": ["token", "balance"]
},
"GetBalanceResponse": {
"type": "object",
"properties": {
"native": { "$ref": "common.json#/$defs/Balance" },
"tokens": {
"type": "array",
"items": { "$ref": "#/$defs/TokenBalance" }
}
},
"required": ["native"]
},
"GetUtxosRequest": {
"type": "object",
"properties": {
"address": { "$ref": "common.json#/$defs/Address" },
"minConfirmations": { "type": "integer", "default": 1 },
"minAmount": { "$ref": "common.json#/$defs/Amount" }
},
"required": ["address"]
},
"GetUtxosResponse": {
"type": "object",
"properties": {
"utxos": {
"type": "array",
"items": { "$ref": "common.json#/$defs/UTXO" }
},
"total": { "$ref": "common.json#/$defs/Amount" }
},
"required": ["utxos", "total"]
}
}
}