synor/sdk/go/contract/contract.go
Gulshan Yadav e65ea40af2 feat: implement Privacy and Contract SDKs for all 12 languages (Phase 5)
Privacy SDK features:
- Confidential transactions with Pedersen commitments
- Bulletproof range proofs for value validation
- Ring signatures for anonymous signing with key images
- Stealth addresses for unlinkable payments
- Blinding factor generation and value operations

Contract SDK features:
- Smart contract deployment (standard and CREATE2)
- Call (view/pure) and Send (state-changing) operations
- Event log filtering, subscription, and decoding
- ABI encoding/decoding utilities
- Gas estimation and contract verification
- Multicall for batched operations
- Storage slot reading

Languages implemented:
- JavaScript/TypeScript
- Python (async with httpx)
- Go
- Rust (async with reqwest/tokio)
- Java (async with OkHttp)
- Kotlin (coroutines with Ktor)
- Swift (async/await with URLSession)
- Flutter/Dart
- C (header-only interface)
- C++ (header-only with std::future)
- C#/.NET (async with HttpClient)
- Ruby (Faraday HTTP client)

All SDKs follow consistent patterns:
- Configuration with API key, endpoint, timeout, retries
- Custom exception types with error codes
- Retry logic with exponential backoff
- Health check endpoints
- Closed state management
2026-01-28 09:03:34 +05:30

810 lines
21 KiB
Go

// Package contract provides smart contract deployment and interaction.
package contract
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"sync/atomic"
"time"
)
const DefaultEndpoint = "https://contract.synor.cc/api/v1"
// Config is the contract client configuration.
type Config struct {
APIKey string
Endpoint string
Timeout time.Duration
Retries int
Debug bool
DefaultGasLimit string
DefaultGasPrice string
}
// AbiEntryType is the type of ABI entry.
type AbiEntryType string
const (
AbiFunction AbiEntryType = "function"
AbiConstructor AbiEntryType = "constructor"
AbiEvent AbiEntryType = "event"
AbiError AbiEntryType = "error"
AbiFallback AbiEntryType = "fallback"
AbiReceive AbiEntryType = "receive"
)
// StateMutability represents function state mutability.
type StateMutability string
const (
StatePure StateMutability = "pure"
StateView StateMutability = "view"
StateNonpayable StateMutability = "nonpayable"
StatePayable StateMutability = "payable"
)
// TransactionStatus represents transaction status.
type TransactionStatus string
const (
StatusSuccess TransactionStatus = "success"
StatusReverted TransactionStatus = "reverted"
)
// VerificationStatus represents verification status.
type VerificationStatus string
const (
VerificationVerified VerificationStatus = "verified"
VerificationPending VerificationStatus = "pending"
VerificationFailed VerificationStatus = "failed"
)
// AbiParameter represents an ABI parameter.
type AbiParameter struct {
Name string `json:"name"`
Type string `json:"type"`
Indexed bool `json:"indexed,omitempty"`
Components []AbiParameter `json:"components,omitempty"`
InternalType string `json:"internalType,omitempty"`
}
// AbiEntry represents an ABI entry.
type AbiEntry struct {
Type AbiEntryType `json:"type"`
Name string `json:"name,omitempty"`
Inputs []AbiParameter `json:"inputs,omitempty"`
Outputs []AbiParameter `json:"outputs,omitempty"`
StateMutability StateMutability `json:"stateMutability,omitempty"`
Anonymous bool `json:"anonymous,omitempty"`
}
// Abi is a contract ABI.
type Abi []AbiEntry
// DeploymentResult is the result of a contract deployment.
type DeploymentResult struct {
Address string `json:"address"`
TransactionHash string `json:"transactionHash"`
BlockNumber int64 `json:"blockNumber"`
GasUsed string `json:"gasUsed"`
EffectiveGasPrice string `json:"effectiveGasPrice"`
}
// EventLog is a raw event log.
type EventLog struct {
LogIndex int `json:"logIndex"`
Address string `json:"address"`
Topics []string `json:"topics"`
Data string `json:"data"`
BlockNumber int64 `json:"blockNumber"`
TransactionHash string `json:"transactionHash"`
}
// DecodedEvent is a decoded event.
type DecodedEvent struct {
Name string `json:"name"`
Signature string `json:"signature"`
Args map[string]interface{} `json:"args"`
Log EventLog `json:"log"`
}
// TransactionResult is the result of a transaction.
type TransactionResult struct {
TransactionHash string `json:"transactionHash"`
BlockNumber int64 `json:"blockNumber"`
BlockHash string `json:"blockHash"`
GasUsed string `json:"gasUsed"`
EffectiveGasPrice string `json:"effectiveGasPrice"`
Status TransactionStatus `json:"status"`
Logs []EventLog `json:"logs"`
ReturnValue interface{} `json:"returnValue,omitempty"`
RevertReason string `json:"revertReason,omitempty"`
}
// ContractInterface is a parsed contract interface.
type ContractInterface struct {
Abi Abi
Functions map[string]AbiEntry
Events map[string]AbiEntry
Errors map[string]AbiEntry
}
// GasEstimation is a gas estimation result.
type GasEstimation struct {
GasLimit string `json:"gasLimit"`
GasPrice string `json:"gasPrice"`
EstimatedCost string `json:"estimatedCost"`
}
// BytecodeMetadata contains bytecode metadata.
type BytecodeMetadata struct {
Compiler string `json:"compiler"`
Language string `json:"language"`
Sources []string `json:"sources"`
}
// BytecodeInfo contains contract bytecode information.
type BytecodeInfo struct {
Bytecode string `json:"bytecode"`
DeployedBytecode string `json:"deployedBytecode,omitempty"`
Abi Abi `json:"abi,omitempty"`
Metadata *BytecodeMetadata `json:"metadata,omitempty"`
}
// VerificationResult is the result of contract verification.
type VerificationResult struct {
Status VerificationStatus `json:"status"`
Message string `json:"message"`
Abi Abi `json:"abi,omitempty"`
}
// MulticallRequest is a multicall request.
type MulticallRequest struct {
Address string `json:"address"`
CallData string `json:"callData"`
AllowFailure bool `json:"allowFailure,omitempty"`
}
// MulticallResult is a multicall result.
type MulticallResult struct {
Success bool `json:"success"`
ReturnData string `json:"returnData"`
Decoded interface{} `json:"decoded,omitempty"`
}
// Error is a contract error.
type Error struct {
Message string
Code string
StatusCode int
}
func (e *Error) Error() string {
return e.Message
}
// Client is a Synor Contract client.
type Client struct {
config Config
httpClient *http.Client
closed atomic.Bool
}
// NewClient creates a new contract client.
func NewClient(config Config) *Client {
if config.Endpoint == "" {
config.Endpoint = DefaultEndpoint
}
if config.Timeout == 0 {
config.Timeout = 30 * time.Second
}
if config.Retries == 0 {
config.Retries = 3
}
return &Client{
config: config,
httpClient: &http.Client{
Timeout: config.Timeout,
},
}
}
// ==================== Deployment ====================
// DeployOptions are options for deploying a contract.
type DeployOptions struct {
Bytecode string
Abi Abi
Args []interface{}
GasLimit string
GasPrice string
Value string
Salt string
}
// Deploy deploys a smart contract.
func (c *Client) Deploy(ctx context.Context, opts DeployOptions) (*DeploymentResult, error) {
body := map[string]interface{}{
"bytecode": opts.Bytecode,
}
if opts.Abi != nil {
body["abi"] = opts.Abi
}
if opts.Args != nil {
body["args"] = opts.Args
}
gasLimit := opts.GasLimit
if gasLimit == "" {
gasLimit = c.config.DefaultGasLimit
}
if gasLimit != "" {
body["gasLimit"] = gasLimit
}
gasPrice := opts.GasPrice
if gasPrice == "" {
gasPrice = c.config.DefaultGasPrice
}
if gasPrice != "" {
body["gasPrice"] = gasPrice
}
if opts.Value != "" {
body["value"] = opts.Value
}
if opts.Salt != "" {
body["salt"] = opts.Salt
}
var resp struct {
Deployment DeploymentResult `json:"deployment"`
}
if err := c.request(ctx, "POST", "/contracts/deploy", body, &resp); err != nil {
return nil, err
}
return &resp.Deployment, nil
}
// PredictAddress predicts the CREATE2 deployment address.
func (c *Client) PredictAddress(ctx context.Context, bytecode, salt string, constructorArgs []interface{}) (string, error) {
body := map[string]interface{}{
"bytecode": bytecode,
"salt": salt,
}
if constructorArgs != nil {
body["constructorArgs"] = constructorArgs
}
var resp struct {
Address string `json:"address"`
}
if err := c.request(ctx, "POST", "/contracts/predict-address", body, &resp); err != nil {
return "", err
}
return resp.Address, nil
}
// ==================== Contract Interaction ====================
// CallOptions are options for calling a contract.
type CallOptions struct {
Address string
Method string
Args []interface{}
Abi Abi
BlockNumber interface{}
}
// Call calls a view/pure function.
func (c *Client) Call(ctx context.Context, opts CallOptions) (interface{}, error) {
body := map[string]interface{}{
"address": opts.Address,
"method": opts.Method,
"abi": opts.Abi,
}
if opts.Args != nil {
body["args"] = opts.Args
}
if opts.BlockNumber != nil {
body["blockNumber"] = opts.BlockNumber
} else {
body["blockNumber"] = "latest"
}
var resp struct {
Result interface{} `json:"result"`
}
if err := c.request(ctx, "POST", "/contracts/call", body, &resp); err != nil {
return nil, err
}
return resp.Result, nil
}
// SendOptions are options for sending a transaction.
type SendOptions struct {
Address string
Method string
Args []interface{}
Abi Abi
GasLimit string
GasPrice string
Value string
}
// Send sends a state-changing transaction.
func (c *Client) Send(ctx context.Context, opts SendOptions) (*TransactionResult, error) {
body := map[string]interface{}{
"address": opts.Address,
"method": opts.Method,
"abi": opts.Abi,
}
if opts.Args != nil {
body["args"] = opts.Args
}
gasLimit := opts.GasLimit
if gasLimit == "" {
gasLimit = c.config.DefaultGasLimit
}
if gasLimit != "" {
body["gasLimit"] = gasLimit
}
gasPrice := opts.GasPrice
if gasPrice == "" {
gasPrice = c.config.DefaultGasPrice
}
if gasPrice != "" {
body["gasPrice"] = gasPrice
}
if opts.Value != "" {
body["value"] = opts.Value
}
var resp struct {
Transaction TransactionResult `json:"transaction"`
}
if err := c.request(ctx, "POST", "/contracts/send", body, &resp); err != nil {
return nil, err
}
return &resp.Transaction, nil
}
// Multicall executes multiple calls in a single request.
func (c *Client) Multicall(ctx context.Context, requests []MulticallRequest, abis map[string]Abi) ([]MulticallResult, error) {
body := map[string]interface{}{
"calls": requests,
}
if abis != nil {
body["abis"] = abis
}
var resp struct {
Results []MulticallResult `json:"results"`
}
if err := c.request(ctx, "POST", "/contracts/multicall", body, &resp); err != nil {
return nil, err
}
return resp.Results, nil
}
// ==================== Events ====================
// EventFilter is a filter for events.
type EventFilter struct {
Address string
EventName string
Abi Abi
FromBlock interface{}
ToBlock interface{}
Filter map[string]interface{}
}
// GetEvents gets historical events.
func (c *Client) GetEvents(ctx context.Context, filter EventFilter) ([]DecodedEvent, error) {
body := map[string]interface{}{
"address": filter.Address,
}
if filter.Abi != nil {
body["abi"] = filter.Abi
}
if filter.EventName != "" {
body["eventName"] = filter.EventName
}
if filter.FromBlock != nil {
body["fromBlock"] = filter.FromBlock
} else {
body["fromBlock"] = "earliest"
}
if filter.ToBlock != nil {
body["toBlock"] = filter.ToBlock
} else {
body["toBlock"] = "latest"
}
if filter.Filter != nil {
body["filter"] = filter.Filter
}
var resp struct {
Events []DecodedEvent `json:"events"`
}
if err := c.request(ctx, "POST", "/contracts/events", body, &resp); err != nil {
return nil, err
}
return resp.Events, nil
}
// GetLogs gets raw event logs.
func (c *Client) GetLogs(ctx context.Context, filter EventFilter) ([]EventLog, error) {
body := map[string]interface{}{
"address": filter.Address,
}
if filter.Abi != nil {
body["abi"] = filter.Abi
}
if filter.EventName != "" {
body["eventName"] = filter.EventName
}
if filter.FromBlock != nil {
body["fromBlock"] = filter.FromBlock
} else {
body["fromBlock"] = "earliest"
}
if filter.ToBlock != nil {
body["toBlock"] = filter.ToBlock
} else {
body["toBlock"] = "latest"
}
if filter.Filter != nil {
body["filter"] = filter.Filter
}
var resp struct {
Logs []EventLog `json:"logs"`
}
if err := c.request(ctx, "POST", "/contracts/logs", body, &resp); err != nil {
return nil, err
}
return resp.Logs, nil
}
// DecodeLog decodes a raw event log.
func (c *Client) DecodeLog(ctx context.Context, log EventLog, abi Abi) (*DecodedEvent, error) {
body := map[string]interface{}{
"log": log,
"abi": abi,
}
var resp struct {
Event DecodedEvent `json:"event"`
}
if err := c.request(ctx, "POST", "/contracts/decode-log", body, &resp); err != nil {
return nil, err
}
return &resp.Event, nil
}
// ==================== ABI Utilities ====================
// LoadAbi loads and parses a contract ABI.
func (c *Client) LoadAbi(abi Abi) *ContractInterface {
ci := &ContractInterface{
Abi: abi,
Functions: make(map[string]AbiEntry),
Events: make(map[string]AbiEntry),
Errors: make(map[string]AbiEntry),
}
for _, entry := range abi {
switch entry.Type {
case AbiFunction:
if entry.Name != "" {
ci.Functions[entry.Name] = entry
}
case AbiEvent:
if entry.Name != "" {
ci.Events[entry.Name] = entry
}
case AbiError:
if entry.Name != "" {
ci.Errors[entry.Name] = entry
}
}
}
return ci
}
// EncodeCall encodes a function call.
func (c *Client) EncodeCall(ctx context.Context, method string, args []interface{}, abi Abi) (string, error) {
body := map[string]interface{}{
"method": method,
"args": args,
"abi": abi,
}
var resp struct {
Data string `json:"data"`
}
if err := c.request(ctx, "POST", "/contracts/encode", body, &resp); err != nil {
return "", err
}
return resp.Data, nil
}
// DecodeResult decodes function return data.
func (c *Client) DecodeResult(ctx context.Context, method, data string, abi Abi) (interface{}, error) {
body := map[string]interface{}{
"method": method,
"data": data,
"abi": abi,
}
var resp struct {
Result interface{} `json:"result"`
}
if err := c.request(ctx, "POST", "/contracts/decode", body, &resp); err != nil {
return nil, err
}
return resp.Result, nil
}
// GetFunctionSelector gets the function selector.
func (c *Client) GetFunctionSelector(ctx context.Context, signature string) (string, error) {
body := map[string]interface{}{
"signature": signature,
}
var resp struct {
Selector string `json:"selector"`
}
if err := c.request(ctx, "POST", "/contracts/selector", body, &resp); err != nil {
return "", err
}
return resp.Selector, nil
}
// ==================== Gas Estimation ====================
// EstimateGasOptions are options for gas estimation.
type EstimateGasOptions struct {
Address string
Method string
Args []interface{}
Abi Abi
Bytecode string
Value string
From string
}
// EstimateGas estimates gas for a contract call or deployment.
func (c *Client) EstimateGas(ctx context.Context, opts EstimateGasOptions) (*GasEstimation, error) {
body := map[string]interface{}{}
if opts.Address != "" {
body["address"] = opts.Address
}
if opts.Method != "" {
body["method"] = opts.Method
}
if opts.Args != nil {
body["args"] = opts.Args
}
if opts.Abi != nil {
body["abi"] = opts.Abi
}
if opts.Bytecode != "" {
body["bytecode"] = opts.Bytecode
}
if opts.Value != "" {
body["value"] = opts.Value
}
if opts.From != "" {
body["from"] = opts.From
}
var resp struct {
Estimation GasEstimation `json:"estimation"`
}
if err := c.request(ctx, "POST", "/contracts/estimate-gas", body, &resp); err != nil {
return nil, err
}
return &resp.Estimation, nil
}
// ==================== Contract Info ====================
// GetBytecode gets contract bytecode.
func (c *Client) GetBytecode(ctx context.Context, address string) (*BytecodeInfo, error) {
var info BytecodeInfo
if err := c.request(ctx, "GET", fmt.Sprintf("/contracts/%s/bytecode", address), nil, &info); err != nil {
return nil, err
}
return &info, nil
}
// IsContract checks if an address is a contract.
func (c *Client) IsContract(ctx context.Context, address string) (bool, error) {
var resp struct {
IsContract bool `json:"isContract"`
}
if err := c.request(ctx, "GET", fmt.Sprintf("/contracts/%s/is-contract", address), nil, &resp); err != nil {
return false, err
}
return resp.IsContract, nil
}
// ReadStorage reads a contract storage slot.
func (c *Client) ReadStorage(ctx context.Context, address, slot string, blockNumber interface{}) (string, error) {
body := map[string]interface{}{
"address": address,
"slot": slot,
}
if blockNumber != nil {
body["blockNumber"] = blockNumber
} else {
body["blockNumber"] = "latest"
}
var resp struct {
Value string `json:"value"`
}
if err := c.request(ctx, "POST", "/contracts/storage", body, &resp); err != nil {
return "", err
}
return resp.Value, nil
}
// ==================== Verification ====================
// VerifyContractOptions are options for contract verification.
type VerifyContractOptions struct {
Address string
SourceCode string
CompilerVersion string
ConstructorArguments string
Optimization bool
OptimizationRuns int
ContractName string
}
// VerifyContract submits a contract for verification.
func (c *Client) VerifyContract(ctx context.Context, opts VerifyContractOptions) (*VerificationResult, error) {
body := map[string]interface{}{
"address": opts.Address,
"sourceCode": opts.SourceCode,
"compilerVersion": opts.CompilerVersion,
"optimization": opts.Optimization,
"optimizationRuns": opts.OptimizationRuns,
}
if opts.ConstructorArguments != "" {
body["constructorArguments"] = opts.ConstructorArguments
}
if opts.ContractName != "" {
body["contractName"] = opts.ContractName
}
var result VerificationResult
if err := c.request(ctx, "POST", "/contracts/verify", body, &result); err != nil {
return nil, err
}
return &result, nil
}
// GetVerificationStatus gets verification status.
func (c *Client) GetVerificationStatus(ctx context.Context, address string) (*VerificationResult, error) {
var result VerificationResult
if err := c.request(ctx, "GET", fmt.Sprintf("/contracts/%s/verification", address), nil, &result); err != nil {
return nil, err
}
return &result, nil
}
// GetVerifiedAbi gets the verified contract ABI.
func (c *Client) GetVerifiedAbi(ctx context.Context, address string) (Abi, error) {
var resp struct {
Abi Abi `json:"abi"`
}
if err := c.request(ctx, "GET", fmt.Sprintf("/contracts/%s/abi", address), nil, &resp); err != nil {
return nil, err
}
return resp.Abi, nil
}
// ==================== Lifecycle ====================
// HealthCheck checks if the service is healthy.
func (c *Client) HealthCheck(ctx context.Context) bool {
var resp struct {
Status string `json:"status"`
}
if err := c.request(ctx, "GET", "/health", nil, &resp); err != nil {
return false
}
return resp.Status == "healthy"
}
// Close closes the client.
func (c *Client) Close() {
c.closed.Store(true)
}
// IsClosed returns whether the client is closed.
func (c *Client) IsClosed() bool {
return c.closed.Load()
}
// ==================== Private ====================
func (c *Client) request(ctx context.Context, method, path string, body interface{}, result interface{}) error {
if c.closed.Load() {
return &Error{Message: "client has been closed"}
}
var lastErr error
for attempt := 0; attempt < c.config.Retries; attempt++ {
err := c.doRequest(ctx, method, path, body, result)
if err == nil {
return nil
}
lastErr = err
if attempt < c.config.Retries-1 {
time.Sleep(time.Duration(1<<attempt) * time.Second)
}
}
return lastErr
}
func (c *Client) doRequest(ctx context.Context, method, path string, body interface{}, result interface{}) error {
url := c.config.Endpoint + path
var bodyReader io.Reader
if body != nil {
jsonBody, err := json.Marshal(body)
if err != nil {
return &Error{Message: fmt.Sprintf("failed to marshal body: %v", err)}
}
bodyReader = bytes.NewReader(jsonBody)
}
req, err := http.NewRequestWithContext(ctx, method, url, bodyReader)
if err != nil {
return &Error{Message: fmt.Sprintf("failed to create request: %v", err)}
}
req.Header.Set("Authorization", "Bearer "+c.config.APIKey)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-SDK-Version", "go/0.1.0")
resp, err := c.httpClient.Do(req)
if err != nil {
return &Error{Message: fmt.Sprintf("request failed: %v", err)}
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return &Error{Message: fmt.Sprintf("failed to read response: %v", err)}
}
if resp.StatusCode >= 400 {
var errResp struct {
Message string `json:"message"`
Error string `json:"error"`
Code string `json:"code"`
}
json.Unmarshal(respBody, &errResp)
msg := errResp.Message
if msg == "" {
msg = errResp.Error
}
if msg == "" {
msg = fmt.Sprintf("HTTP %d", resp.StatusCode)
}
return &Error{
Message: msg,
Code: errResp.Code,
StatusCode: resp.StatusCode,
}
}
if result != nil {
if err := json.Unmarshal(respBody, result); err != nil {
return &Error{Message: fmt.Sprintf("failed to parse response: %v", err)}
}
}
return nil
}