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
This commit is contained in:
Gulshan Yadav 2026-01-28 09:03:34 +05:30
parent 6607223c9e
commit e65ea40af2
59 changed files with 15760 additions and 0 deletions

View file

@ -0,0 +1,383 @@
/**
* Synor Contract SDK for C
* Smart contract deployment, interaction, and event handling.
*/
#ifndef SYNOR_CONTRACT_H
#define SYNOR_CONTRACT_H
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
#define SYNOR_CONTRACT_VERSION "0.1.0"
/* ==================== Error Handling ==================== */
typedef enum {
SYNOR_CONTRACT_OK = 0,
SYNOR_CONTRACT_ERR_NULL_PARAM = -1,
SYNOR_CONTRACT_ERR_HTTP = -2,
SYNOR_CONTRACT_ERR_JSON = -3,
SYNOR_CONTRACT_ERR_API = -4,
SYNOR_CONTRACT_ERR_CLOSED = -5,
SYNOR_CONTRACT_ERR_MEMORY = -6,
SYNOR_CONTRACT_ERR_TIMEOUT = -7
} SynorContractError;
typedef struct {
int status_code;
char *code;
char *message;
} SynorContractErrorInfo;
/* ==================== Configuration ==================== */
typedef struct {
const char *api_key;
const char *endpoint;
int timeout_ms;
int retries;
} SynorContractConfig;
/* ==================== Client Handle ==================== */
typedef struct SynorContractClient SynorContractClient;
/* ==================== ABI Types ==================== */
typedef struct SynorAbiParameter {
char *name;
char *type;
bool indexed;
struct SynorAbiParameter *components;
size_t component_count;
} SynorAbiParameter;
typedef struct {
char *type;
char *name;
SynorAbiParameter *inputs;
size_t input_count;
SynorAbiParameter *outputs;
size_t output_count;
char *state_mutability;
bool anonymous;
} SynorAbiEntry;
/* ==================== Deployment ==================== */
typedef struct {
const char *bytecode;
const SynorAbiEntry *abi;
size_t abi_count;
const char **constructor_args;
size_t constructor_arg_count;
const char *value;
int64_t gas_limit;
const char *gas_price;
int64_t nonce;
} SynorDeployContractOptions;
typedef struct {
char *contract_address;
char *transaction_hash;
char *deployer;
int64_t gas_used;
int64_t block_number;
char *block_hash;
} SynorDeploymentResult;
/* ==================== Contract Interaction ==================== */
typedef struct {
const char *contract;
const char *method;
const char **args;
size_t arg_count;
const SynorAbiEntry *abi;
size_t abi_count;
} SynorCallContractOptions;
typedef struct {
const char *contract;
const char *method;
const char **args;
size_t arg_count;
const SynorAbiEntry *abi;
size_t abi_count;
const char *value;
int64_t gas_limit;
const char *gas_price;
int64_t nonce;
} SynorSendContractOptions;
typedef struct SynorEventLog {
char *address;
char **topics;
size_t topic_count;
char *data;
int64_t block_number;
char *transaction_hash;
int log_index;
char *block_hash;
bool removed;
} SynorEventLog;
typedef struct {
char *transaction_hash;
int64_t block_number;
char *block_hash;
int64_t gas_used;
char *effective_gas_price;
char *status;
SynorEventLog *logs;
size_t log_count;
} SynorTransactionResult;
/* ==================== Events ==================== */
typedef struct {
char *name;
char *signature;
char *args_json; /* JSON string for decoded args */
SynorEventLog log;
} SynorDecodedEvent;
typedef struct {
const char *contract;
const char *event;
int64_t from_block;
int64_t to_block;
const char **topics;
size_t topic_count;
const SynorAbiEntry *abi;
size_t abi_count;
} SynorEventFilter;
/* ==================== Gas Estimation ==================== */
typedef struct {
int64_t gas_limit;
char *gas_price;
char *max_fee_per_gas;
char *max_priority_fee_per_gas;
char *estimated_cost;
} SynorGasEstimation;
/* ==================== Contract Information ==================== */
typedef struct {
char *bytecode;
char *deployed_bytecode;
int size;
bool is_contract;
} SynorBytecodeInfo;
typedef struct {
const char *address;
const char *source_code;
const char *compiler_version;
const char *constructor_args;
bool optimization;
int optimization_runs;
const char *license;
} SynorVerifyContractOptions;
typedef struct {
bool verified;
char *address;
char *compiler_version;
bool optimization;
int optimization_runs;
char *license;
SynorAbiEntry *abi;
size_t abi_count;
char *source_code;
} SynorVerificationResult;
/* ==================== Multicall ==================== */
typedef struct {
const char *contract;
const char *method;
const char **args;
size_t arg_count;
const SynorAbiEntry *abi;
size_t abi_count;
} SynorMulticallRequest;
typedef struct {
bool success;
char *result_json;
char *error;
} SynorMulticallResult;
/* ==================== Client Lifecycle ==================== */
SynorContractError synor_contract_client_create(
const SynorContractConfig *config,
SynorContractClient **client
);
void synor_contract_client_close(SynorContractClient *client);
bool synor_contract_client_is_closed(const SynorContractClient *client);
bool synor_contract_health_check(SynorContractClient *client);
const SynorContractErrorInfo* synor_contract_get_last_error(const SynorContractClient *client);
/* ==================== Contract Deployment ==================== */
SynorContractError synor_contract_deploy(
SynorContractClient *client,
const SynorDeployContractOptions *options,
SynorDeploymentResult *result
);
SynorContractError synor_contract_deploy_create2(
SynorContractClient *client,
const SynorDeployContractOptions *options,
const char *salt,
SynorDeploymentResult *result
);
SynorContractError synor_contract_predict_address(
SynorContractClient *client,
const char *bytecode,
const char *salt,
const char *deployer,
char **address
);
/* ==================== Contract Interaction ==================== */
SynorContractError synor_contract_call(
SynorContractClient *client,
const SynorCallContractOptions *options,
char **result_json
);
SynorContractError synor_contract_send(
SynorContractClient *client,
const SynorSendContractOptions *options,
SynorTransactionResult *result
);
/* ==================== Events ==================== */
SynorContractError synor_contract_get_events(
SynorContractClient *client,
const SynorEventFilter *filter,
SynorDecodedEvent **events,
size_t *event_count
);
SynorContractError synor_contract_get_logs(
SynorContractClient *client,
const char *contract,
int64_t from_block,
int64_t to_block,
SynorEventLog **logs,
size_t *log_count
);
/* ==================== ABI Utilities ==================== */
SynorContractError synor_contract_encode_call(
SynorContractClient *client,
const char *method,
const char **args,
size_t arg_count,
const SynorAbiEntry *abi,
size_t abi_count,
char **data
);
SynorContractError synor_contract_decode_result(
SynorContractClient *client,
const char *data,
const char *method,
const SynorAbiEntry *abi,
size_t abi_count,
char **result_json
);
SynorContractError synor_contract_get_selector(
SynorContractClient *client,
const char *signature,
char **selector
);
/* ==================== Gas Estimation ==================== */
SynorContractError synor_contract_estimate_gas(
SynorContractClient *client,
const SynorCallContractOptions *options,
const char *value,
SynorGasEstimation *result
);
/* ==================== Contract Information ==================== */
SynorContractError synor_contract_get_bytecode(
SynorContractClient *client,
const char *address,
SynorBytecodeInfo *result
);
SynorContractError synor_contract_verify(
SynorContractClient *client,
const SynorVerifyContractOptions *options,
SynorVerificationResult *result
);
SynorContractError synor_contract_get_verification_status(
SynorContractClient *client,
const char *address,
SynorVerificationResult *result
);
/* ==================== Multicall ==================== */
SynorContractError synor_contract_multicall(
SynorContractClient *client,
const SynorMulticallRequest *requests,
size_t request_count,
SynorMulticallResult **results,
size_t *result_count
);
/* ==================== Storage ==================== */
SynorContractError synor_contract_read_storage(
SynorContractClient *client,
const char *contract,
const char *slot,
int64_t block_number,
char **value
);
/* ==================== Memory Management ==================== */
void synor_contract_free_string(char *str);
void synor_contract_free_deployment_result(SynorDeploymentResult *result);
void synor_contract_free_transaction_result(SynorTransactionResult *result);
void synor_contract_free_event_logs(SynorEventLog *logs, size_t count);
void synor_contract_free_decoded_events(SynorDecodedEvent *events, size_t count);
void synor_contract_free_gas_estimation(SynorGasEstimation *estimation);
void synor_contract_free_bytecode_info(SynorBytecodeInfo *info);
void synor_contract_free_verification_result(SynorVerificationResult *result);
void synor_contract_free_multicall_results(SynorMulticallResult *results, size_t count);
void synor_contract_free_abi(SynorAbiEntry *abi, size_t count);
#ifdef __cplusplus
}
#endif
#endif /* SYNOR_CONTRACT_H */

View file

@ -0,0 +1,310 @@
/**
* Synor Privacy SDK for C
* Confidential transactions, ring signatures, stealth addresses, and cryptographic commitments.
*/
#ifndef SYNOR_PRIVACY_H
#define SYNOR_PRIVACY_H
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
#define SYNOR_PRIVACY_VERSION "0.1.0"
/* ==================== Error Handling ==================== */
typedef enum {
SYNOR_PRIVACY_OK = 0,
SYNOR_PRIVACY_ERR_NULL_PARAM = -1,
SYNOR_PRIVACY_ERR_HTTP = -2,
SYNOR_PRIVACY_ERR_JSON = -3,
SYNOR_PRIVACY_ERR_API = -4,
SYNOR_PRIVACY_ERR_CLOSED = -5,
SYNOR_PRIVACY_ERR_MEMORY = -6,
SYNOR_PRIVACY_ERR_TIMEOUT = -7
} SynorPrivacyError;
typedef struct {
int status_code;
char *code;
char *message;
} SynorPrivacyErrorInfo;
/* ==================== Configuration ==================== */
typedef struct {
const char *api_key;
const char *endpoint;
int timeout_ms;
int retries;
} SynorPrivacyConfig;
/* ==================== Client Handle ==================== */
typedef struct SynorPrivacyClient SynorPrivacyClient;
/* ==================== Confidential Transactions ==================== */
typedef struct {
char *commitment;
char *blinding_factor;
char *value;
char *key_image;
} SynorConfidentialTxInput;
typedef struct {
char *commitment;
char *blinding_factor;
char *value;
char *recipient_public_key;
char *range_proof;
} SynorConfidentialTxOutput;
typedef struct {
char *id;
SynorConfidentialTxInput *inputs;
size_t input_count;
SynorConfidentialTxOutput *outputs;
size_t output_count;
char *fee;
char *excess;
char *excess_sig;
char *kernel_offset;
} SynorConfidentialTransaction;
/* ==================== Commitments ==================== */
typedef struct {
char *commitment;
char *blinding_factor;
} SynorCommitment;
typedef struct {
char *proof;
char *commitment;
int64_t min_value;
int64_t max_value;
} SynorRangeProof;
/* ==================== Ring Signatures ==================== */
typedef struct {
char *c0;
char **s;
size_t s_count;
char *key_image;
char **ring;
size_t ring_count;
} SynorRingSignature;
/* ==================== Stealth Addresses ==================== */
typedef struct {
char *spend_public_key;
char *spend_private_key;
char *view_public_key;
char *view_private_key;
} SynorStealthKeyPair;
typedef struct {
char *address;
char *ephemeral_public_key;
char *tx_public_key;
} SynorStealthAddress;
typedef struct {
char *tx_hash;
int output_index;
char *stealth_address;
char *amount;
int64_t block_height;
} SynorStealthOutput;
/* ==================== Client Lifecycle ==================== */
/**
* Create a new Privacy client.
* @param config Configuration options
* @param client Output pointer to client handle
* @return Error code
*/
SynorPrivacyError synor_privacy_client_create(
const SynorPrivacyConfig *config,
SynorPrivacyClient **client
);
/**
* Close and free the client.
*/
void synor_privacy_client_close(SynorPrivacyClient *client);
/**
* Check if the client is closed.
*/
bool synor_privacy_client_is_closed(const SynorPrivacyClient *client);
/**
* Check service health.
*/
bool synor_privacy_health_check(SynorPrivacyClient *client);
/**
* Get the last error info.
*/
const SynorPrivacyErrorInfo* synor_privacy_get_last_error(const SynorPrivacyClient *client);
/* ==================== Confidential Transactions ==================== */
SynorPrivacyError synor_privacy_create_confidential_tx(
SynorPrivacyClient *client,
const SynorConfidentialTxInput *inputs,
size_t input_count,
const SynorConfidentialTxOutput *outputs,
size_t output_count,
SynorConfidentialTransaction *result
);
SynorPrivacyError synor_privacy_verify_confidential_tx(
SynorPrivacyClient *client,
const SynorConfidentialTransaction *tx,
bool *valid
);
SynorPrivacyError synor_privacy_create_commitment(
SynorPrivacyClient *client,
const char *value,
const char *blinding_factor,
SynorCommitment *result
);
SynorPrivacyError synor_privacy_verify_commitment(
SynorPrivacyClient *client,
const char *commitment,
const char *value,
const char *blinding_factor,
bool *valid
);
SynorPrivacyError synor_privacy_create_range_proof(
SynorPrivacyClient *client,
const char *value,
const char *blinding_factor,
int64_t min_value,
int64_t max_value,
SynorRangeProof *result
);
SynorPrivacyError synor_privacy_verify_range_proof(
SynorPrivacyClient *client,
const SynorRangeProof *proof,
bool *valid
);
/* ==================== Ring Signatures ==================== */
SynorPrivacyError synor_privacy_create_ring_signature(
SynorPrivacyClient *client,
const char *message,
const char **ring,
size_t ring_count,
int signer_index,
const char *private_key,
SynorRingSignature *result
);
SynorPrivacyError synor_privacy_verify_ring_signature(
SynorPrivacyClient *client,
const SynorRingSignature *signature,
const char *message,
bool *valid
);
SynorPrivacyError synor_privacy_generate_decoys(
SynorPrivacyClient *client,
int count,
const char *exclude_key,
char ***decoys,
size_t *decoy_count
);
SynorPrivacyError synor_privacy_check_key_image(
SynorPrivacyClient *client,
const char *key_image,
bool *spent
);
/* ==================== Stealth Addresses ==================== */
SynorPrivacyError synor_privacy_generate_stealth_keypair(
SynorPrivacyClient *client,
SynorStealthKeyPair *result
);
SynorPrivacyError synor_privacy_derive_stealth_address(
SynorPrivacyClient *client,
const char *spend_public_key,
const char *view_public_key,
SynorStealthAddress *result
);
SynorPrivacyError synor_privacy_recover_stealth_private_key(
SynorPrivacyClient *client,
const char *stealth_address,
const char *view_private_key,
const char *spend_private_key,
char **private_key
);
SynorPrivacyError synor_privacy_scan_outputs(
SynorPrivacyClient *client,
const char *view_private_key,
const char *spend_public_key,
int64_t from_block,
int64_t to_block,
SynorStealthOutput **outputs,
size_t *output_count
);
/* ==================== Blinding ==================== */
SynorPrivacyError synor_privacy_generate_blinding_factor(
SynorPrivacyClient *client,
char **blinding_factor
);
SynorPrivacyError synor_privacy_blind_value(
SynorPrivacyClient *client,
const char *value,
const char *blinding_factor,
char **blinded_value
);
SynorPrivacyError synor_privacy_unblind_value(
SynorPrivacyClient *client,
const char *blinded_value,
const char *blinding_factor,
char **value
);
/* ==================== Memory Management ==================== */
void synor_privacy_free_string(char *str);
void synor_privacy_free_string_array(char **arr, size_t count);
void synor_privacy_free_confidential_tx(SynorConfidentialTransaction *tx);
void synor_privacy_free_ring_signature(SynorRingSignature *sig);
void synor_privacy_free_stealth_keypair(SynorStealthKeyPair *keypair);
void synor_privacy_free_stealth_address(SynorStealthAddress *addr);
void synor_privacy_free_stealth_outputs(SynorStealthOutput *outputs, size_t count);
void synor_privacy_free_commitment(SynorCommitment *commitment);
void synor_privacy_free_range_proof(SynorRangeProof *proof);
#ifdef __cplusplus
}
#endif
#endif /* SYNOR_PRIVACY_H */

View file

@ -0,0 +1,297 @@
/**
* Synor Contract SDK for C++
* Smart contract deployment, interaction, and event handling.
*/
#ifndef SYNOR_CONTRACT_HPP
#define SYNOR_CONTRACT_HPP
#include <string>
#include <vector>
#include <optional>
#include <memory>
#include <future>
#include <stdexcept>
#include <cstdint>
#include <any>
#include <map>
namespace synor::contract {
inline constexpr const char* VERSION = "0.1.0";
// ==================== Exceptions ====================
class ContractException : public std::runtime_error {
public:
ContractException(const std::string& message,
std::optional<int> status_code = std::nullopt,
std::optional<std::string> code = std::nullopt)
: std::runtime_error(message), status_code_(status_code), code_(code) {}
std::optional<int> status_code() const { return status_code_; }
std::optional<std::string> code() const { return code_; }
private:
std::optional<int> status_code_;
std::optional<std::string> code_;
};
// ==================== Configuration ====================
struct ContractConfig {
std::string api_key;
std::string endpoint = "https://contract.synor.io";
int timeout_ms = 30000;
int retries = 3;
};
// ==================== ABI Types ====================
struct AbiParameter {
std::optional<std::string> name;
std::string type;
std::optional<bool> indexed;
std::optional<std::vector<AbiParameter>> components;
};
struct AbiEntry {
std::string type;
std::optional<std::string> name;
std::optional<std::vector<AbiParameter>> inputs;
std::optional<std::vector<AbiParameter>> outputs;
std::optional<std::string> state_mutability;
std::optional<bool> anonymous;
};
using Abi = std::vector<AbiEntry>;
// ==================== Deployment ====================
struct DeployContractOptions {
std::string bytecode;
std::optional<Abi> abi;
std::optional<std::vector<std::any>> constructor_args;
std::optional<std::string> value;
std::optional<int64_t> gas_limit;
std::optional<std::string> gas_price;
std::optional<int64_t> nonce;
};
struct DeploymentResult {
std::string contract_address;
std::string transaction_hash;
std::optional<std::string> deployer;
std::optional<int64_t> gas_used;
std::optional<int64_t> block_number;
std::optional<std::string> block_hash;
};
// ==================== Contract Interaction ====================
struct CallContractOptions {
std::string contract;
std::string method;
std::vector<std::any> args;
Abi abi;
};
struct SendContractOptions {
std::string contract;
std::string method;
std::vector<std::any> args;
Abi abi;
std::optional<std::string> value;
std::optional<int64_t> gas_limit;
std::optional<std::string> gas_price;
std::optional<int64_t> nonce;
};
struct EventLog {
std::string address;
std::vector<std::string> topics;
std::string data;
std::optional<int64_t> block_number;
std::optional<std::string> transaction_hash;
std::optional<int> log_index;
std::optional<std::string> block_hash;
std::optional<bool> removed;
};
struct TransactionResult {
std::string transaction_hash;
std::optional<int64_t> block_number;
std::optional<std::string> block_hash;
std::optional<int64_t> gas_used;
std::optional<std::string> effective_gas_price;
std::optional<std::string> status;
std::optional<std::vector<EventLog>> logs;
};
// ==================== Events ====================
struct DecodedEvent {
std::string name;
std::optional<std::string> signature;
std::optional<std::map<std::string, std::any>> args;
std::optional<EventLog> log;
};
struct EventFilter {
std::string contract;
std::optional<std::string> event;
std::optional<int64_t> from_block;
std::optional<int64_t> to_block;
std::optional<std::vector<std::optional<std::string>>> topics;
std::optional<Abi> abi;
};
// ==================== ABI Utilities ====================
struct EncodeCallOptions {
std::string method;
std::vector<std::any> args;
Abi abi;
};
struct DecodeResultOptions {
std::string data;
std::string method;
Abi abi;
};
// ==================== Gas Estimation ====================
struct EstimateGasOptions {
std::string contract;
std::string method;
std::vector<std::any> args;
Abi abi;
std::optional<std::string> value;
};
struct GasEstimation {
int64_t gas_limit;
std::optional<std::string> gas_price;
std::optional<std::string> max_fee_per_gas;
std::optional<std::string> max_priority_fee_per_gas;
std::optional<std::string> estimated_cost;
};
// ==================== Contract Information ====================
struct BytecodeInfo {
std::string bytecode;
std::optional<std::string> deployed_bytecode;
std::optional<int> size;
std::optional<bool> is_contract;
};
struct VerifyContractOptions {
std::string address;
std::string source_code;
std::string compiler_version;
std::optional<std::string> constructor_args;
std::optional<bool> optimization;
std::optional<int> optimization_runs;
std::optional<std::string> license;
};
struct VerificationResult {
bool verified;
std::optional<std::string> address;
std::optional<std::string> compiler_version;
std::optional<bool> optimization;
std::optional<int> optimization_runs;
std::optional<std::string> license;
std::optional<Abi> abi;
std::optional<std::string> source_code;
};
// ==================== Multicall ====================
struct MulticallRequest {
std::string contract;
std::string method;
std::vector<std::any> args;
Abi abi;
};
struct MulticallResult {
bool success;
std::optional<std::any> result;
std::optional<std::string> error;
};
// ==================== Storage ====================
struct ReadStorageOptions {
std::string contract;
std::string slot;
std::optional<int64_t> block_number;
};
// ==================== Client ====================
class ContractClient {
public:
explicit ContractClient(const ContractConfig& config);
~ContractClient();
// Non-copyable
ContractClient(const ContractClient&) = delete;
ContractClient& operator=(const ContractClient&) = delete;
// Movable
ContractClient(ContractClient&&) noexcept;
ContractClient& operator=(ContractClient&&) noexcept;
// Deployment
std::future<DeploymentResult> deploy(const DeployContractOptions& options);
std::future<DeploymentResult> deploy_create2(const DeployContractOptions& options, const std::string& salt);
std::future<std::string> predict_address(const std::string& bytecode, const std::string& salt,
const std::optional<std::string>& deployer = std::nullopt);
// Contract Interaction
std::future<std::any> call(const CallContractOptions& options);
std::future<TransactionResult> send(const SendContractOptions& options);
// Events
std::future<std::vector<DecodedEvent>> get_events(const EventFilter& filter);
std::future<std::vector<EventLog>> get_logs(const std::string& contract,
std::optional<int64_t> from_block = std::nullopt,
std::optional<int64_t> to_block = std::nullopt);
std::future<std::vector<DecodedEvent>> decode_logs(const std::vector<EventLog>& logs, const Abi& abi);
// ABI Utilities
std::future<std::string> encode_call(const EncodeCallOptions& options);
std::future<std::any> decode_result(const DecodeResultOptions& options);
std::future<std::string> get_selector(const std::string& signature);
// Gas Estimation
std::future<GasEstimation> estimate_gas(const EstimateGasOptions& options);
// Contract Information
std::future<BytecodeInfo> get_bytecode(const std::string& address);
std::future<VerificationResult> verify(const VerifyContractOptions& options);
std::future<VerificationResult> get_verification_status(const std::string& address);
// Multicall
std::future<std::vector<MulticallResult>> multicall(const std::vector<MulticallRequest>& requests);
// Storage
std::future<std::string> read_storage(const ReadStorageOptions& options);
// Lifecycle
std::future<bool> health_check();
void close();
bool is_closed() const;
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace synor::contract
#endif // SYNOR_CONTRACT_HPP

View file

@ -0,0 +1,229 @@
/**
* Synor Privacy SDK for C++
* Confidential transactions, ring signatures, stealth addresses, and cryptographic commitments.
*/
#ifndef SYNOR_PRIVACY_HPP
#define SYNOR_PRIVACY_HPP
#include <string>
#include <vector>
#include <optional>
#include <memory>
#include <future>
#include <stdexcept>
#include <cstdint>
namespace synor::privacy {
inline constexpr const char* VERSION = "0.1.0";
// ==================== Exceptions ====================
class PrivacyException : public std::runtime_error {
public:
PrivacyException(const std::string& message,
std::optional<int> status_code = std::nullopt,
std::optional<std::string> code = std::nullopt)
: std::runtime_error(message), status_code_(status_code), code_(code) {}
std::optional<int> status_code() const { return status_code_; }
std::optional<std::string> code() const { return code_; }
private:
std::optional<int> status_code_;
std::optional<std::string> code_;
};
// ==================== Configuration ====================
struct PrivacyConfig {
std::string api_key;
std::string endpoint = "https://privacy.synor.io";
int timeout_ms = 30000;
int retries = 3;
};
// ==================== Confidential Transactions ====================
struct ConfidentialTxInput {
std::string commitment;
std::string blinding_factor;
std::string value;
std::optional<std::string> key_image;
};
struct ConfidentialTxOutput {
std::string commitment;
std::string blinding_factor;
std::string value;
std::string recipient_public_key;
std::optional<std::string> range_proof;
};
struct ConfidentialTransaction {
std::string id;
std::vector<ConfidentialTxInput> inputs;
std::vector<ConfidentialTxOutput> outputs;
std::string fee;
std::string excess;
std::string excess_sig;
std::optional<std::string> kernel_offset;
};
// ==================== Commitments ====================
struct Commitment {
std::string commitment;
std::string blinding_factor;
};
struct RangeProof {
std::string proof;
std::string commitment;
int64_t min_value;
int64_t max_value;
};
// ==================== Ring Signatures ====================
struct RingSignature {
std::string c0;
std::vector<std::string> s;
std::string key_image;
std::vector<std::string> ring;
};
// ==================== Stealth Addresses ====================
struct StealthKeyPair {
std::string spend_public_key;
std::string spend_private_key;
std::string view_public_key;
std::string view_private_key;
};
struct StealthAddress {
std::string address;
std::string ephemeral_public_key;
std::optional<std::string> tx_public_key;
};
struct StealthOutput {
std::string tx_hash;
int output_index;
std::string stealth_address;
std::string amount;
int64_t block_height;
};
// ==================== Client ====================
class PrivacyClient {
public:
explicit PrivacyClient(const PrivacyConfig& config);
~PrivacyClient();
// Non-copyable
PrivacyClient(const PrivacyClient&) = delete;
PrivacyClient& operator=(const PrivacyClient&) = delete;
// Movable
PrivacyClient(PrivacyClient&&) noexcept;
PrivacyClient& operator=(PrivacyClient&&) noexcept;
// Confidential Transactions
std::future<ConfidentialTransaction> create_confidential_tx(
const std::vector<ConfidentialTxInput>& inputs,
const std::vector<ConfidentialTxOutput>& outputs
);
std::future<bool> verify_confidential_tx(const ConfidentialTransaction& tx);
std::future<Commitment> create_commitment(
const std::string& value,
const std::string& blinding_factor
);
std::future<bool> verify_commitment(
const std::string& commitment,
const std::string& value,
const std::string& blinding_factor
);
std::future<RangeProof> create_range_proof(
const std::string& value,
const std::string& blinding_factor,
int64_t min_value,
int64_t max_value
);
std::future<bool> verify_range_proof(const RangeProof& proof);
// Ring Signatures
std::future<RingSignature> create_ring_signature(
const std::string& message,
const std::vector<std::string>& ring,
int signer_index,
const std::string& private_key
);
std::future<bool> verify_ring_signature(
const RingSignature& signature,
const std::string& message
);
std::future<std::vector<std::string>> generate_decoys(
int count,
const std::optional<std::string>& exclude_key = std::nullopt
);
std::future<bool> check_key_image(const std::string& key_image);
// Stealth Addresses
std::future<StealthKeyPair> generate_stealth_keypair();
std::future<StealthAddress> derive_stealth_address(
const std::string& spend_public_key,
const std::string& view_public_key
);
std::future<std::string> recover_stealth_private_key(
const std::string& stealth_address,
const std::string& view_private_key,
const std::string& spend_private_key
);
std::future<std::vector<StealthOutput>> scan_outputs(
const std::string& view_private_key,
const std::string& spend_public_key,
int64_t from_block,
std::optional<int64_t> to_block = std::nullopt
);
// Blinding
std::future<std::string> generate_blinding_factor();
std::future<std::string> blind_value(
const std::string& value,
const std::string& blinding_factor
);
std::future<std::string> unblind_value(
const std::string& blinded_value,
const std::string& blinding_factor
);
// Lifecycle
std::future<bool> health_check();
void close();
bool is_closed() const;
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace synor::privacy
#endif // SYNOR_PRIVACY_HPP

View file

@ -0,0 +1,307 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
namespace Synor.Contract
{
/// <summary>
/// Synor Contract SDK client for C#/.NET.
/// Smart contract deployment, interaction, and event handling.
/// </summary>
public sealed class ContractClient : IDisposable
{
public const string Version = "0.1.0";
private readonly ContractConfig _config;
private readonly HttpClient _httpClient;
private readonly JsonSerializerOptions _jsonOptions;
private bool _closed;
public ContractClient(ContractConfig config)
{
_config = config ?? throw new ArgumentNullException(nameof(config));
_httpClient = new HttpClient
{
BaseAddress = new Uri(config.Endpoint),
Timeout = TimeSpan.FromMilliseconds(config.TimeoutMs)
};
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {config.ApiKey}");
_httpClient.DefaultRequestHeaders.Add("X-SDK-Version", $"csharp/{Version}");
_jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
}
#region Contract Deployment
public async Task<DeploymentResult> DeployAsync(DeployContractOptions options, CancellationToken ct = default)
{
return await PostAsync<DeploymentResult>("/contract/deploy", options, ct);
}
public async Task<DeploymentResult> DeployCreate2Async(DeployContractOptions options, string salt, CancellationToken ct = default)
{
var body = new Dictionary<string, object?>
{
["bytecode"] = options.Bytecode,
["salt"] = salt,
["abi"] = options.Abi,
["constructor_args"] = options.ConstructorArgs,
["value"] = options.Value,
["gas_limit"] = options.GasLimit,
["gas_price"] = options.GasPrice
};
return await PostAsync<DeploymentResult>("/contract/deploy/create2", body, ct);
}
public async Task<string> PredictAddressAsync(string bytecode, string salt, string? deployer = null, CancellationToken ct = default)
{
var body = new { bytecode, salt, deployer };
var response = await PostAsync<Dictionary<string, object>>("/contract/predict-address", body, ct);
return response["address"]?.ToString() ?? throw new ContractException("Missing address");
}
#endregion
#region Contract Interaction
public async Task<JsonElement> CallAsync(CallContractOptions options, CancellationToken ct = default)
{
return await PostAsync<JsonElement>("/contract/call", options, ct);
}
public async Task<TransactionResult> SendAsync(SendContractOptions options, CancellationToken ct = default)
{
return await PostAsync<TransactionResult>("/contract/send", options, ct);
}
#endregion
#region Events
public async Task<DecodedEvent[]> GetEventsAsync(EventFilter filter, CancellationToken ct = default)
{
return await PostAsync<DecodedEvent[]>("/contract/events", filter, ct);
}
public async Task<EventLog[]> GetLogsAsync(string contract, long? fromBlock = null, long? toBlock = null, CancellationToken ct = default)
{
var path = $"/contract/logs?contract={HttpUtility.UrlEncode(contract)}";
if (fromBlock.HasValue) path += $"&from_block={fromBlock}";
if (toBlock.HasValue) path += $"&to_block={toBlock}";
return await GetAsync<EventLog[]>(path, ct);
}
public async Task<DecodedEvent[]> DecodeLogsAsync(IEnumerable<EventLog> logs, IEnumerable<AbiEntry> abi, CancellationToken ct = default)
{
var body = new { logs, abi };
return await PostAsync<DecodedEvent[]>("/contract/decode-logs", body, ct);
}
#endregion
#region ABI Utilities
public async Task<string> EncodeCallAsync(EncodeCallOptions options, CancellationToken ct = default)
{
var response = await PostAsync<Dictionary<string, object>>("/contract/encode", options, ct);
return response["data"]?.ToString() ?? throw new ContractException("Missing data");
}
public async Task<JsonElement> DecodeResultAsync(DecodeResultOptions options, CancellationToken ct = default)
{
var response = await PostAsync<Dictionary<string, JsonElement>>("/contract/decode", options, ct);
return response["result"];
}
public async Task<string> GetSelectorAsync(string signature, CancellationToken ct = default)
{
var response = await GetAsync<Dictionary<string, object>>($"/contract/selector?signature={HttpUtility.UrlEncode(signature)}", ct);
return response["selector"]?.ToString() ?? throw new ContractException("Missing selector");
}
#endregion
#region Gas Estimation
public async Task<GasEstimation> EstimateGasAsync(EstimateGasOptions options, CancellationToken ct = default)
{
return await PostAsync<GasEstimation>("/contract/estimate-gas", options, ct);
}
#endregion
#region Contract Information
public async Task<BytecodeInfo> GetBytecodeAsync(string address, CancellationToken ct = default)
{
return await GetAsync<BytecodeInfo>($"/contract/{HttpUtility.UrlEncode(address)}/bytecode", ct);
}
public async Task<VerificationResult> VerifyAsync(VerifyContractOptions options, CancellationToken ct = default)
{
return await PostAsync<VerificationResult>("/contract/verify", options, ct);
}
public async Task<VerificationResult> GetVerificationStatusAsync(string address, CancellationToken ct = default)
{
return await GetAsync<VerificationResult>($"/contract/{HttpUtility.UrlEncode(address)}/verification", ct);
}
#endregion
#region Multicall
public async Task<MulticallResult[]> MulticallAsync(IEnumerable<MulticallRequest> requests, CancellationToken ct = default)
{
var body = new { calls = requests };
return await PostAsync<MulticallResult[]>("/contract/multicall", body, ct);
}
#endregion
#region Storage
public async Task<string> ReadStorageAsync(ReadStorageOptions options, CancellationToken ct = default)
{
var path = $"/contract/storage?contract={HttpUtility.UrlEncode(options.Contract)}&slot={HttpUtility.UrlEncode(options.Slot)}";
if (options.BlockNumber.HasValue) path += $"&block={options.BlockNumber}";
var response = await GetAsync<Dictionary<string, object>>(path, ct);
return response["value"]?.ToString() ?? throw new ContractException("Missing value");
}
#endregion
#region Lifecycle
public async Task<bool> HealthCheckAsync(CancellationToken ct = default)
{
if (_closed) return false;
try
{
var response = await GetAsync<Dictionary<string, object>>("/health", ct);
return response.TryGetValue("status", out var status) &&
status is JsonElement elem && elem.GetString() == "healthy";
}
catch
{
return false;
}
}
public void Dispose()
{
_closed = true;
_httpClient.Dispose();
}
public bool IsClosed => _closed;
#endregion
#region HTTP Helpers
private async Task<T> GetAsync<T>(string path, CancellationToken ct)
{
ThrowIfClosed();
return await ExecuteWithRetryAsync(async () =>
{
var response = await _httpClient.GetAsync(path, ct);
return await HandleResponseAsync<T>(response, ct);
});
}
private async Task<T> PostAsync<T>(string path, object body, CancellationToken ct)
{
ThrowIfClosed();
return await ExecuteWithRetryAsync(async () =>
{
var response = await _httpClient.PostAsJsonAsync(path, body, _jsonOptions, ct);
return await HandleResponseAsync<T>(response, ct);
});
}
private async Task<T> HandleResponseAsync<T>(HttpResponseMessage response, CancellationToken ct)
{
var content = await response.Content.ReadAsStringAsync(ct);
if (response.IsSuccessStatusCode)
{
return JsonSerializer.Deserialize<T>(content, _jsonOptions)
?? throw new ContractException("Failed to deserialize response");
}
try
{
var error = JsonSerializer.Deserialize<Dictionary<string, object>>(content, _jsonOptions);
var message = error?.GetValueOrDefault("message")?.ToString()
?? error?.GetValueOrDefault("error")?.ToString()
?? "Unknown error";
var code = error?.GetValueOrDefault("code")?.ToString();
throw new ContractException(message, (int)response.StatusCode, code);
}
catch (JsonException)
{
throw new ContractException(content, (int)response.StatusCode);
}
}
private async Task<T> ExecuteWithRetryAsync<T>(Func<Task<T>> action)
{
Exception? lastException = null;
for (var attempt = 0; attempt < _config.Retries; attempt++)
{
try
{
return await action();
}
catch (Exception ex)
{
lastException = ex;
if (attempt < _config.Retries - 1)
{
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
}
}
}
throw lastException ?? new ContractException("Request failed");
}
private void ThrowIfClosed()
{
if (_closed) throw new ContractException("Client has been closed");
}
#endregion
}
public class ContractConfig
{
public required string ApiKey { get; init; }
public string Endpoint { get; init; } = "https://contract.synor.io";
public int TimeoutMs { get; init; } = 30000;
public int Retries { get; init; } = 3;
}
public class ContractException : Exception
{
public int? StatusCode { get; }
public string? Code { get; }
public ContractException(string message, int? statusCode = null, string? code = null)
: base(message)
{
StatusCode = statusCode;
Code = code;
}
}
}

View file

@ -0,0 +1,394 @@
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Synor.Contract
{
// ==================== ABI Types ====================
public record AbiParameter
{
[JsonPropertyName("name")]
public string? Name { get; init; }
[JsonPropertyName("type")]
public required string Type { get; init; }
[JsonPropertyName("indexed")]
public bool? Indexed { get; init; }
[JsonPropertyName("components")]
public AbiParameter[]? Components { get; init; }
}
public record AbiEntry
{
[JsonPropertyName("type")]
public required string Type { get; init; }
[JsonPropertyName("name")]
public string? Name { get; init; }
[JsonPropertyName("inputs")]
public AbiParameter[]? Inputs { get; init; }
[JsonPropertyName("outputs")]
public AbiParameter[]? Outputs { get; init; }
[JsonPropertyName("stateMutability")]
public string? StateMutability { get; init; }
[JsonPropertyName("anonymous")]
public bool? Anonymous { get; init; }
}
// ==================== Deployment ====================
public record DeployContractOptions
{
[JsonPropertyName("bytecode")]
public required string Bytecode { get; init; }
[JsonPropertyName("abi")]
public AbiEntry[]? Abi { get; init; }
[JsonPropertyName("constructor_args")]
public object[]? ConstructorArgs { get; init; }
[JsonPropertyName("value")]
public string? Value { get; init; }
[JsonPropertyName("gas_limit")]
public long? GasLimit { get; init; }
[JsonPropertyName("gas_price")]
public string? GasPrice { get; init; }
[JsonPropertyName("nonce")]
public long? Nonce { get; init; }
}
public record DeploymentResult
{
[JsonPropertyName("contract_address")]
public required string ContractAddress { get; init; }
[JsonPropertyName("transaction_hash")]
public required string TransactionHash { get; init; }
[JsonPropertyName("deployer")]
public string? Deployer { get; init; }
[JsonPropertyName("gas_used")]
public long? GasUsed { get; init; }
[JsonPropertyName("block_number")]
public long? BlockNumber { get; init; }
[JsonPropertyName("block_hash")]
public string? BlockHash { get; init; }
}
// ==================== Contract Interaction ====================
public record CallContractOptions
{
[JsonPropertyName("contract")]
public required string Contract { get; init; }
[JsonPropertyName("method")]
public required string Method { get; init; }
[JsonPropertyName("args")]
public object[] Args { get; init; } = [];
[JsonPropertyName("abi")]
public required AbiEntry[] Abi { get; init; }
}
public record SendContractOptions
{
[JsonPropertyName("contract")]
public required string Contract { get; init; }
[JsonPropertyName("method")]
public required string Method { get; init; }
[JsonPropertyName("args")]
public object[] Args { get; init; } = [];
[JsonPropertyName("abi")]
public required AbiEntry[] Abi { get; init; }
[JsonPropertyName("value")]
public string? Value { get; init; }
[JsonPropertyName("gas_limit")]
public long? GasLimit { get; init; }
[JsonPropertyName("gas_price")]
public string? GasPrice { get; init; }
[JsonPropertyName("nonce")]
public long? Nonce { get; init; }
}
public record EventLog
{
[JsonPropertyName("address")]
public required string Address { get; init; }
[JsonPropertyName("topics")]
public required string[] Topics { get; init; }
[JsonPropertyName("data")]
public required string Data { get; init; }
[JsonPropertyName("block_number")]
public long? BlockNumber { get; init; }
[JsonPropertyName("transaction_hash")]
public string? TransactionHash { get; init; }
[JsonPropertyName("log_index")]
public int? LogIndex { get; init; }
[JsonPropertyName("block_hash")]
public string? BlockHash { get; init; }
[JsonPropertyName("removed")]
public bool? Removed { get; init; }
}
public record TransactionResult
{
[JsonPropertyName("transaction_hash")]
public required string TransactionHash { get; init; }
[JsonPropertyName("block_number")]
public long? BlockNumber { get; init; }
[JsonPropertyName("block_hash")]
public string? BlockHash { get; init; }
[JsonPropertyName("gas_used")]
public long? GasUsed { get; init; }
[JsonPropertyName("effective_gas_price")]
public string? EffectiveGasPrice { get; init; }
[JsonPropertyName("status")]
public string? Status { get; init; }
[JsonPropertyName("logs")]
public EventLog[]? Logs { get; init; }
}
// ==================== Events ====================
public record DecodedEvent
{
[JsonPropertyName("name")]
public required string Name { get; init; }
[JsonPropertyName("signature")]
public string? Signature { get; init; }
[JsonPropertyName("args")]
public Dictionary<string, JsonElement>? Args { get; init; }
[JsonPropertyName("log")]
public EventLog? Log { get; init; }
}
public record EventFilter
{
[JsonPropertyName("contract")]
public required string Contract { get; init; }
[JsonPropertyName("event")]
public string? Event { get; init; }
[JsonPropertyName("from_block")]
public long? FromBlock { get; init; }
[JsonPropertyName("to_block")]
public long? ToBlock { get; init; }
[JsonPropertyName("topics")]
public string?[]? Topics { get; init; }
[JsonPropertyName("abi")]
public AbiEntry[]? Abi { get; init; }
}
// ==================== ABI Utilities ====================
public record EncodeCallOptions
{
[JsonPropertyName("method")]
public required string Method { get; init; }
[JsonPropertyName("args")]
public object[] Args { get; init; } = [];
[JsonPropertyName("abi")]
public required AbiEntry[] Abi { get; init; }
}
public record DecodeResultOptions
{
[JsonPropertyName("data")]
public required string Data { get; init; }
[JsonPropertyName("method")]
public required string Method { get; init; }
[JsonPropertyName("abi")]
public required AbiEntry[] Abi { get; init; }
}
// ==================== Gas Estimation ====================
public record EstimateGasOptions
{
[JsonPropertyName("contract")]
public required string Contract { get; init; }
[JsonPropertyName("method")]
public required string Method { get; init; }
[JsonPropertyName("args")]
public object[] Args { get; init; } = [];
[JsonPropertyName("abi")]
public required AbiEntry[] Abi { get; init; }
[JsonPropertyName("value")]
public string? Value { get; init; }
}
public record GasEstimation
{
[JsonPropertyName("gas_limit")]
public long GasLimit { get; init; }
[JsonPropertyName("gas_price")]
public string? GasPrice { get; init; }
[JsonPropertyName("max_fee_per_gas")]
public string? MaxFeePerGas { get; init; }
[JsonPropertyName("max_priority_fee_per_gas")]
public string? MaxPriorityFeePerGas { get; init; }
[JsonPropertyName("estimated_cost")]
public string? EstimatedCost { get; init; }
}
// ==================== Contract Information ====================
public record BytecodeInfo
{
[JsonPropertyName("bytecode")]
public required string Bytecode { get; init; }
[JsonPropertyName("deployed_bytecode")]
public string? DeployedBytecode { get; init; }
[JsonPropertyName("size")]
public int? Size { get; init; }
[JsonPropertyName("is_contract")]
public bool? IsContract { get; init; }
}
public record VerifyContractOptions
{
[JsonPropertyName("address")]
public required string Address { get; init; }
[JsonPropertyName("source_code")]
public required string SourceCode { get; init; }
[JsonPropertyName("compiler_version")]
public required string CompilerVersion { get; init; }
[JsonPropertyName("constructor_args")]
public string? ConstructorArgs { get; init; }
[JsonPropertyName("optimization")]
public bool? Optimization { get; init; }
[JsonPropertyName("optimization_runs")]
public int? OptimizationRuns { get; init; }
[JsonPropertyName("license")]
public string? License { get; init; }
}
public record VerificationResult
{
[JsonPropertyName("verified")]
public bool Verified { get; init; }
[JsonPropertyName("address")]
public string? Address { get; init; }
[JsonPropertyName("compiler_version")]
public string? CompilerVersion { get; init; }
[JsonPropertyName("optimization")]
public bool? Optimization { get; init; }
[JsonPropertyName("optimization_runs")]
public int? OptimizationRuns { get; init; }
[JsonPropertyName("license")]
public string? License { get; init; }
[JsonPropertyName("abi")]
public AbiEntry[]? Abi { get; init; }
[JsonPropertyName("source_code")]
public string? SourceCode { get; init; }
}
// ==================== Multicall ====================
public record MulticallRequest
{
[JsonPropertyName("contract")]
public required string Contract { get; init; }
[JsonPropertyName("method")]
public required string Method { get; init; }
[JsonPropertyName("args")]
public object[] Args { get; init; } = [];
[JsonPropertyName("abi")]
public required AbiEntry[] Abi { get; init; }
}
public record MulticallResult
{
[JsonPropertyName("success")]
public bool Success { get; init; }
[JsonPropertyName("result")]
public JsonElement? Result { get; init; }
[JsonPropertyName("error")]
public string? Error { get; init; }
}
// ==================== Storage ====================
public record ReadStorageOptions
{
public required string Contract { get; init; }
public required string Slot { get; init; }
public long? BlockNumber { get; init; }
}
}

View file

@ -0,0 +1,335 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
namespace Synor.Privacy
{
/// <summary>
/// Synor Privacy SDK client for C#/.NET.
/// Confidential transactions, ring signatures, stealth addresses, and cryptographic commitments.
/// </summary>
public sealed class PrivacyClient : IDisposable
{
public const string Version = "0.1.0";
private readonly PrivacyConfig _config;
private readonly HttpClient _httpClient;
private readonly JsonSerializerOptions _jsonOptions;
private bool _closed;
public PrivacyClient(PrivacyConfig config)
{
_config = config ?? throw new ArgumentNullException(nameof(config));
_httpClient = new HttpClient
{
BaseAddress = new Uri(config.Endpoint),
Timeout = TimeSpan.FromMilliseconds(config.TimeoutMs)
};
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {config.ApiKey}");
_httpClient.DefaultRequestHeaders.Add("X-SDK-Version", $"csharp/{Version}");
_jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
}
#region Confidential Transactions
public async Task<ConfidentialTransaction> CreateConfidentialTxAsync(
IEnumerable<ConfidentialTxInput> inputs,
IEnumerable<ConfidentialTxOutput> outputs,
CancellationToken ct = default)
{
var body = new { inputs, outputs };
return await PostAsync<ConfidentialTransaction>("/privacy/confidential/create", body, ct);
}
public async Task<bool> VerifyConfidentialTxAsync(ConfidentialTransaction tx, CancellationToken ct = default)
{
var body = new { transaction = tx };
var response = await PostAsync<Dictionary<string, object>>("/privacy/confidential/verify", body, ct);
return response.TryGetValue("valid", out var valid) && valid is JsonElement elem && elem.GetBoolean();
}
public async Task<Commitment> CreateCommitmentAsync(
string value,
string blindingFactor,
CancellationToken ct = default)
{
var body = new { value, blinding_factor = blindingFactor };
return await PostAsync<Commitment>("/privacy/commitment/create", body, ct);
}
public async Task<bool> VerifyCommitmentAsync(
string commitment,
string value,
string blindingFactor,
CancellationToken ct = default)
{
var body = new { commitment, value, blinding_factor = blindingFactor };
var response = await PostAsync<Dictionary<string, object>>("/privacy/commitment/verify", body, ct);
return response.TryGetValue("valid", out var valid) && valid is JsonElement elem && elem.GetBoolean();
}
public async Task<RangeProof> CreateRangeProofAsync(
string value,
string blindingFactor,
long minValue,
long maxValue,
CancellationToken ct = default)
{
var body = new { value, blinding_factor = blindingFactor, min_value = minValue, max_value = maxValue };
return await PostAsync<RangeProof>("/privacy/range-proof/create", body, ct);
}
public async Task<bool> VerifyRangeProofAsync(RangeProof proof, CancellationToken ct = default)
{
var body = new { proof };
var response = await PostAsync<Dictionary<string, object>>("/privacy/range-proof/verify", body, ct);
return response.TryGetValue("valid", out var valid) && valid is JsonElement elem && elem.GetBoolean();
}
#endregion
#region Ring Signatures
public async Task<RingSignature> CreateRingSignatureAsync(
string message,
IEnumerable<string> ring,
int signerIndex,
string privateKey,
CancellationToken ct = default)
{
var body = new { message, ring, signer_index = signerIndex, private_key = privateKey };
return await PostAsync<RingSignature>("/privacy/ring/sign", body, ct);
}
public async Task<bool> VerifyRingSignatureAsync(RingSignature signature, string message, CancellationToken ct = default)
{
var body = new { signature, message };
var response = await PostAsync<Dictionary<string, object>>("/privacy/ring/verify", body, ct);
return response.TryGetValue("valid", out var valid) && valid is JsonElement elem && elem.GetBoolean();
}
public async Task<string[]> GenerateDecoysAsync(int count, string? excludeKey = null, CancellationToken ct = default)
{
var path = $"/privacy/ring/decoys?count={count}";
if (!string.IsNullOrEmpty(excludeKey))
path += $"&exclude={Uri.EscapeDataString(excludeKey)}";
return await GetAsync<string[]>(path, ct);
}
public async Task<bool> CheckKeyImageAsync(string keyImage, CancellationToken ct = default)
{
var response = await GetAsync<Dictionary<string, object>>($"/privacy/ring/key-image/{keyImage}", ct);
return response.TryGetValue("spent", out var spent) && spent is JsonElement elem && elem.GetBoolean();
}
#endregion
#region Stealth Addresses
public async Task<StealthKeyPair> GenerateStealthKeyPairAsync(CancellationToken ct = default)
{
return await PostAsync<StealthKeyPair>("/privacy/stealth/generate", new { }, ct);
}
public async Task<StealthAddress> DeriveStealthAddressAsync(
string spendPublicKey,
string viewPublicKey,
CancellationToken ct = default)
{
var body = new { spend_public_key = spendPublicKey, view_public_key = viewPublicKey };
return await PostAsync<StealthAddress>("/privacy/stealth/derive", body, ct);
}
public async Task<string> RecoverStealthPrivateKeyAsync(
string stealthAddress,
string viewPrivateKey,
string spendPrivateKey,
CancellationToken ct = default)
{
var body = new
{
stealth_address = stealthAddress,
view_private_key = viewPrivateKey,
spend_private_key = spendPrivateKey
};
var response = await PostAsync<Dictionary<string, object>>("/privacy/stealth/recover", body, ct);
return response["private_key"]?.ToString() ?? throw new PrivacyException("Missing private_key");
}
public async Task<StealthOutput[]> ScanOutputsAsync(
string viewPrivateKey,
string spendPublicKey,
long fromBlock,
long? toBlock = null,
CancellationToken ct = default)
{
var body = new Dictionary<string, object>
{
["view_private_key"] = viewPrivateKey,
["spend_public_key"] = spendPublicKey,
["from_block"] = fromBlock
};
if (toBlock.HasValue) body["to_block"] = toBlock.Value;
return await PostAsync<StealthOutput[]>("/privacy/stealth/scan", body, ct);
}
#endregion
#region Blinding
public async Task<string> GenerateBlindingFactorAsync(CancellationToken ct = default)
{
var response = await PostAsync<Dictionary<string, object>>("/privacy/blinding/generate", new { }, ct);
return response["blinding_factor"]?.ToString() ?? throw new PrivacyException("Missing blinding_factor");
}
public async Task<string> BlindValueAsync(string value, string blindingFactor, CancellationToken ct = default)
{
var body = new { value, blinding_factor = blindingFactor };
var response = await PostAsync<Dictionary<string, object>>("/privacy/blinding/blind", body, ct);
return response["blinded_value"]?.ToString() ?? throw new PrivacyException("Missing blinded_value");
}
public async Task<string> UnblindValueAsync(string blindedValue, string blindingFactor, CancellationToken ct = default)
{
var body = new { blinded_value = blindedValue, blinding_factor = blindingFactor };
var response = await PostAsync<Dictionary<string, object>>("/privacy/blinding/unblind", body, ct);
return response["value"]?.ToString() ?? throw new PrivacyException("Missing value");
}
#endregion
#region Lifecycle
public async Task<bool> HealthCheckAsync(CancellationToken ct = default)
{
if (_closed) return false;
try
{
var response = await GetAsync<Dictionary<string, object>>("/health", ct);
return response.TryGetValue("status", out var status) &&
status is JsonElement elem && elem.GetString() == "healthy";
}
catch
{
return false;
}
}
public void Dispose()
{
_closed = true;
_httpClient.Dispose();
}
public bool IsClosed => _closed;
#endregion
#region HTTP Helpers
private async Task<T> GetAsync<T>(string path, CancellationToken ct)
{
ThrowIfClosed();
return await ExecuteWithRetryAsync(async () =>
{
var response = await _httpClient.GetAsync(path, ct);
return await HandleResponseAsync<T>(response, ct);
});
}
private async Task<T> PostAsync<T>(string path, object body, CancellationToken ct)
{
ThrowIfClosed();
return await ExecuteWithRetryAsync(async () =>
{
var response = await _httpClient.PostAsJsonAsync(path, body, _jsonOptions, ct);
return await HandleResponseAsync<T>(response, ct);
});
}
private async Task<T> HandleResponseAsync<T>(HttpResponseMessage response, CancellationToken ct)
{
var content = await response.Content.ReadAsStringAsync(ct);
if (response.IsSuccessStatusCode)
{
return JsonSerializer.Deserialize<T>(content, _jsonOptions)
?? throw new PrivacyException("Failed to deserialize response");
}
try
{
var error = JsonSerializer.Deserialize<Dictionary<string, object>>(content, _jsonOptions);
var message = error?.GetValueOrDefault("message")?.ToString()
?? error?.GetValueOrDefault("error")?.ToString()
?? "Unknown error";
var code = error?.GetValueOrDefault("code")?.ToString();
throw new PrivacyException(message, (int)response.StatusCode, code);
}
catch (JsonException)
{
throw new PrivacyException(content, (int)response.StatusCode);
}
}
private async Task<T> ExecuteWithRetryAsync<T>(Func<Task<T>> action)
{
Exception? lastException = null;
for (var attempt = 0; attempt < _config.Retries; attempt++)
{
try
{
return await action();
}
catch (Exception ex)
{
lastException = ex;
if (attempt < _config.Retries - 1)
{
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
}
}
}
throw lastException ?? new PrivacyException("Request failed");
}
private void ThrowIfClosed()
{
if (_closed) throw new PrivacyException("Client has been closed");
}
#endregion
}
public class PrivacyConfig
{
public required string ApiKey { get; init; }
public string Endpoint { get; init; } = "https://privacy.synor.io";
public int TimeoutMs { get; init; } = 30000;
public int Retries { get; init; } = 3;
}
public class PrivacyException : Exception
{
public int? StatusCode { get; }
public string? Code { get; }
public PrivacyException(string message, int? statusCode = null, string? code = null)
: base(message)
{
StatusCode = statusCode;
Code = code;
}
}
}

View file

@ -0,0 +1,153 @@
using System.Text.Json.Serialization;
namespace Synor.Privacy
{
// ==================== Confidential Transactions ====================
public record ConfidentialTxInput
{
[JsonPropertyName("commitment")]
public required string Commitment { get; init; }
[JsonPropertyName("blinding_factor")]
public required string BlindingFactor { get; init; }
[JsonPropertyName("value")]
public required string Value { get; init; }
[JsonPropertyName("key_image")]
public string? KeyImage { get; init; }
}
public record ConfidentialTxOutput
{
[JsonPropertyName("commitment")]
public required string Commitment { get; init; }
[JsonPropertyName("blinding_factor")]
public required string BlindingFactor { get; init; }
[JsonPropertyName("value")]
public required string Value { get; init; }
[JsonPropertyName("recipient_public_key")]
public required string RecipientPublicKey { get; init; }
[JsonPropertyName("range_proof")]
public string? RangeProof { get; init; }
}
public record ConfidentialTransaction
{
[JsonPropertyName("id")]
public required string Id { get; init; }
[JsonPropertyName("inputs")]
public required ConfidentialTxInput[] Inputs { get; init; }
[JsonPropertyName("outputs")]
public required ConfidentialTxOutput[] Outputs { get; init; }
[JsonPropertyName("fee")]
public required string Fee { get; init; }
[JsonPropertyName("excess")]
public required string Excess { get; init; }
[JsonPropertyName("excess_sig")]
public required string ExcessSig { get; init; }
[JsonPropertyName("kernel_offset")]
public string? KernelOffset { get; init; }
}
// ==================== Commitments ====================
public record Commitment
{
[JsonPropertyName("commitment")]
public required string CommitmentValue { get; init; }
[JsonPropertyName("blinding_factor")]
public required string BlindingFactor { get; init; }
}
public record RangeProof
{
[JsonPropertyName("proof")]
public required string Proof { get; init; }
[JsonPropertyName("commitment")]
public required string Commitment { get; init; }
[JsonPropertyName("min_value")]
public long MinValue { get; init; }
[JsonPropertyName("max_value")]
public long MaxValue { get; init; }
}
// ==================== Ring Signatures ====================
public record RingSignature
{
[JsonPropertyName("c0")]
public required string C0 { get; init; }
[JsonPropertyName("s")]
public required string[] S { get; init; }
[JsonPropertyName("key_image")]
public required string KeyImage { get; init; }
[JsonPropertyName("ring")]
public required string[] Ring { get; init; }
}
// ==================== Stealth Addresses ====================
public record StealthKeyPair
{
[JsonPropertyName("spend_public_key")]
public required string SpendPublicKey { get; init; }
[JsonPropertyName("spend_private_key")]
public required string SpendPrivateKey { get; init; }
[JsonPropertyName("view_public_key")]
public required string ViewPublicKey { get; init; }
[JsonPropertyName("view_private_key")]
public required string ViewPrivateKey { get; init; }
}
public record StealthAddress
{
[JsonPropertyName("address")]
public required string Address { get; init; }
[JsonPropertyName("ephemeral_public_key")]
public required string EphemeralPublicKey { get; init; }
[JsonPropertyName("tx_public_key")]
public string? TxPublicKey { get; init; }
}
public record StealthOutput
{
[JsonPropertyName("tx_hash")]
public required string TxHash { get; init; }
[JsonPropertyName("output_index")]
public int OutputIndex { get; init; }
[JsonPropertyName("stealth_address")]
public required string StealthAddress { get; init; }
[JsonPropertyName("amount")]
public required string Amount { get; init; }
[JsonPropertyName("block_height")]
public long BlockHeight { get; init; }
}
}

View file

@ -0,0 +1,320 @@
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'contract_types.dart';
/// Synor Contract SDK client for Flutter/Dart.
/// Smart contract deployment, interaction, and event handling.
class ContractClient {
static const String version = '0.1.0';
final ContractConfig config;
final http.Client _client;
bool _closed = false;
ContractClient(this.config) : _client = http.Client();
// ==================== Contract Deployment ====================
Future<DeploymentResult> deploy(DeployContractOptions options) async {
final body = {
'bytecode': options.bytecode,
if (options.abi != null) 'abi': options.abi!.map((e) => e.toJson()).toList(),
if (options.constructorArgs != null) 'constructor_args': options.constructorArgs,
if (options.value != null) 'value': options.value,
if (options.gasLimit != null) 'gas_limit': options.gasLimit,
if (options.gasPrice != null) 'gas_price': options.gasPrice,
if (options.nonce != null) 'nonce': options.nonce,
};
final response = await _post('/contract/deploy', body);
return DeploymentResult.fromJson(response);
}
Future<DeploymentResult> deployCreate2(DeployContractOptions options, String salt) async {
final body = {
'bytecode': options.bytecode,
'salt': salt,
if (options.abi != null) 'abi': options.abi!.map((e) => e.toJson()).toList(),
if (options.constructorArgs != null) 'constructor_args': options.constructorArgs,
if (options.value != null) 'value': options.value,
if (options.gasLimit != null) 'gas_limit': options.gasLimit,
if (options.gasPrice != null) 'gas_price': options.gasPrice,
};
final response = await _post('/contract/deploy/create2', body);
return DeploymentResult.fromJson(response);
}
Future<String> predictAddress(String bytecode, String salt, {String? deployer}) async {
final body = {
'bytecode': bytecode,
'salt': salt,
if (deployer != null) 'deployer': deployer,
};
final response = await _post('/contract/predict-address', body);
return response['address'] as String;
}
// ==================== Contract Interaction ====================
Future<dynamic> call(CallContractOptions options) async {
final body = {
'contract': options.contract,
'method': options.method,
'args': options.args,
'abi': options.abi.map((e) => e.toJson()).toList(),
};
return await _post('/contract/call', body);
}
Future<TransactionResult> send(SendContractOptions options) async {
final body = {
'contract': options.contract,
'method': options.method,
'args': options.args,
'abi': options.abi.map((e) => e.toJson()).toList(),
if (options.value != null) 'value': options.value,
if (options.gasLimit != null) 'gas_limit': options.gasLimit,
if (options.gasPrice != null) 'gas_price': options.gasPrice,
if (options.nonce != null) 'nonce': options.nonce,
};
final response = await _post('/contract/send', body);
return TransactionResult.fromJson(response);
}
// ==================== Events ====================
Future<List<DecodedEvent>> getEvents(EventFilter filter) async {
final body = {
'contract': filter.contract,
if (filter.event != null) 'event': filter.event,
if (filter.fromBlock != null) 'from_block': filter.fromBlock,
if (filter.toBlock != null) 'to_block': filter.toBlock,
if (filter.topics != null) 'topics': filter.topics,
if (filter.abi != null) 'abi': filter.abi!.map((e) => e.toJson()).toList(),
};
final response = await _post('/contract/events', body);
return (response as List).map((e) => DecodedEvent.fromJson(e)).toList();
}
Future<List<EventLog>> getLogs(String contract, {int? fromBlock, int? toBlock}) async {
var path = '/contract/logs?contract=${Uri.encodeComponent(contract)}';
if (fromBlock != null) path += '&from_block=$fromBlock';
if (toBlock != null) path += '&to_block=$toBlock';
final response = await _get(path);
return (response as List).map((e) => EventLog.fromJson(e)).toList();
}
Future<List<DecodedEvent>> decodeLogs(List<EventLog> logs, List<AbiEntry> abi) async {
final body = {
'logs': logs.map((e) => e.toJson()).toList(),
'abi': abi.map((e) => e.toJson()).toList(),
};
final response = await _post('/contract/decode-logs', body);
return (response as List).map((e) => DecodedEvent.fromJson(e)).toList();
}
// ==================== ABI Utilities ====================
Future<String> encodeCall(EncodeCallOptions options) async {
final body = {
'method': options.method,
'args': options.args,
'abi': options.abi.map((e) => e.toJson()).toList(),
};
final response = await _post('/contract/encode', body);
return response['data'] as String;
}
Future<dynamic> decodeResult(DecodeResultOptions options) async {
final body = {
'data': options.data,
'method': options.method,
'abi': options.abi.map((e) => e.toJson()).toList(),
};
final response = await _post('/contract/decode', body);
return response['result'];
}
Future<String> getSelector(String signature) async {
final response = await _get('/contract/selector?signature=${Uri.encodeComponent(signature)}');
return response['selector'] as String;
}
// ==================== Gas Estimation ====================
Future<GasEstimation> estimateGas(EstimateGasOptions options) async {
final body = {
'contract': options.contract,
'method': options.method,
'args': options.args,
'abi': options.abi.map((e) => e.toJson()).toList(),
if (options.value != null) 'value': options.value,
};
final response = await _post('/contract/estimate-gas', body);
return GasEstimation.fromJson(response);
}
// ==================== Contract Information ====================
Future<BytecodeInfo> getBytecode(String address) async {
final response = await _get('/contract/${Uri.encodeComponent(address)}/bytecode');
return BytecodeInfo.fromJson(response);
}
Future<VerificationResult> verify(VerifyContractOptions options) async {
final body = {
'address': options.address,
'source_code': options.sourceCode,
'compiler_version': options.compilerVersion,
if (options.constructorArgs != null) 'constructor_args': options.constructorArgs,
if (options.optimization != null) 'optimization': options.optimization,
if (options.optimizationRuns != null) 'optimization_runs': options.optimizationRuns,
if (options.license != null) 'license': options.license,
};
final response = await _post('/contract/verify', body);
return VerificationResult.fromJson(response);
}
Future<VerificationResult> getVerificationStatus(String address) async {
final response = await _get('/contract/${Uri.encodeComponent(address)}/verification');
return VerificationResult.fromJson(response);
}
// ==================== Multicall ====================
Future<List<MulticallResult>> multicall(List<MulticallRequest> requests) async {
final body = {
'calls': requests.map((e) => e.toJson()).toList(),
};
final response = await _post('/contract/multicall', body);
return (response as List).map((e) => MulticallResult.fromJson(e)).toList();
}
// ==================== Storage ====================
Future<String> readStorage(ReadStorageOptions options) async {
var path = '/contract/storage?contract=${Uri.encodeComponent(options.contract)}'
'&slot=${Uri.encodeComponent(options.slot)}';
if (options.blockNumber != null) path += '&block=${options.blockNumber}';
final response = await _get(path);
return response['value'] as String;
}
// ==================== Lifecycle ====================
Future<bool> healthCheck() async {
if (_closed) return false;
try {
final response = await _get('/health');
return response['status'] == 'healthy';
} catch (_) {
return false;
}
}
void close() {
_closed = true;
_client.close();
}
bool get isClosed => _closed;
// ==================== HTTP Helpers ====================
Future<dynamic> _get(String path) async {
_checkClosed();
return _executeWithRetry(() async {
final response = await _client
.get(
Uri.parse('${config.endpoint}$path'),
headers: _headers,
)
.timeout(Duration(milliseconds: config.timeoutMs));
return _handleResponse(response);
});
}
Future<Map<String, dynamic>> _post(String path, Map<String, dynamic> body) async {
_checkClosed();
return _executeWithRetry(() async {
final response = await _client
.post(
Uri.parse('${config.endpoint}$path'),
headers: _headers,
body: jsonEncode(body),
)
.timeout(Duration(milliseconds: config.timeoutMs));
return _handleResponse(response);
});
}
Map<String, String> get _headers => {
'Authorization': 'Bearer ${config.apiKey}',
'Content-Type': 'application/json',
'X-SDK-Version': 'dart/$version',
};
dynamic _handleResponse(http.Response response) {
final body = jsonDecode(response.body);
if (response.statusCode >= 200 && response.statusCode < 300) {
return body;
}
final message = body['message'] ?? body['error'] ?? 'Unknown error';
final code = body['code']?.toString();
throw ContractException(
message: message,
statusCode: response.statusCode,
code: code,
);
}
Future<T> _executeWithRetry<T>(Future<T> Function() action) async {
Object? lastError;
for (var attempt = 0; attempt < config.retries; attempt++) {
try {
return await action();
} catch (e) {
lastError = e;
if (attempt < config.retries - 1) {
await Future.delayed(Duration(seconds: 1 << attempt));
}
}
}
throw lastError ?? ContractException(message: 'Request failed');
}
void _checkClosed() {
if (_closed) {
throw ContractException(message: 'Client has been closed');
}
}
}
/// Contract SDK configuration.
class ContractConfig {
final String apiKey;
final String endpoint;
final int timeoutMs;
final int retries;
ContractConfig({
required this.apiKey,
this.endpoint = 'https://contract.synor.io',
this.timeoutMs = 30000,
this.retries = 3,
});
}
/// Contract SDK exception.
class ContractException implements Exception {
final String message;
final int? statusCode;
final String? code;
ContractException({required this.message, this.statusCode, this.code});
@override
String toString() => 'ContractException: $message (code: $code, status: $statusCode)';
}

View file

@ -0,0 +1,496 @@
// ==================== ABI Types ====================
class AbiParameter {
final String? name;
final String type;
final bool? indexed;
final List<AbiParameter>? components;
AbiParameter({
this.name,
required this.type,
this.indexed,
this.components,
});
factory AbiParameter.fromJson(Map<String, dynamic> json) {
return AbiParameter(
name: json['name'] as String?,
type: json['type'] as String,
indexed: json['indexed'] as bool?,
components: json['components'] != null
? (json['components'] as List).map((e) => AbiParameter.fromJson(e)).toList()
: null,
);
}
Map<String, dynamic> toJson() => {
if (name != null) 'name': name,
'type': type,
if (indexed != null) 'indexed': indexed,
if (components != null) 'components': components!.map((e) => e.toJson()).toList(),
};
}
class AbiEntry {
final String type;
final String? name;
final List<AbiParameter>? inputs;
final List<AbiParameter>? outputs;
final String? stateMutability;
final bool? anonymous;
AbiEntry({
required this.type,
this.name,
this.inputs,
this.outputs,
this.stateMutability,
this.anonymous,
});
factory AbiEntry.fromJson(Map<String, dynamic> json) {
return AbiEntry(
type: json['type'] as String,
name: json['name'] as String?,
inputs: json['inputs'] != null
? (json['inputs'] as List).map((e) => AbiParameter.fromJson(e)).toList()
: null,
outputs: json['outputs'] != null
? (json['outputs'] as List).map((e) => AbiParameter.fromJson(e)).toList()
: null,
stateMutability: json['stateMutability'] as String?,
anonymous: json['anonymous'] as bool?,
);
}
Map<String, dynamic> toJson() => {
'type': type,
if (name != null) 'name': name,
if (inputs != null) 'inputs': inputs!.map((e) => e.toJson()).toList(),
if (outputs != null) 'outputs': outputs!.map((e) => e.toJson()).toList(),
if (stateMutability != null) 'stateMutability': stateMutability,
if (anonymous != null) 'anonymous': anonymous,
};
}
// ==================== Deployment ====================
class DeployContractOptions {
final String bytecode;
final List<AbiEntry>? abi;
final List<dynamic>? constructorArgs;
final String? value;
final int? gasLimit;
final String? gasPrice;
final int? nonce;
DeployContractOptions({
required this.bytecode,
this.abi,
this.constructorArgs,
this.value,
this.gasLimit,
this.gasPrice,
this.nonce,
});
}
class DeploymentResult {
final String contractAddress;
final String transactionHash;
final String? deployer;
final int? gasUsed;
final int? blockNumber;
final String? blockHash;
DeploymentResult({
required this.contractAddress,
required this.transactionHash,
this.deployer,
this.gasUsed,
this.blockNumber,
this.blockHash,
});
factory DeploymentResult.fromJson(Map<String, dynamic> json) {
return DeploymentResult(
contractAddress: json['contract_address'] as String,
transactionHash: json['transaction_hash'] as String,
deployer: json['deployer'] as String?,
gasUsed: json['gas_used'] as int?,
blockNumber: json['block_number'] as int?,
blockHash: json['block_hash'] as String?,
);
}
}
// ==================== Contract Interaction ====================
class CallContractOptions {
final String contract;
final String method;
final List<dynamic> args;
final List<AbiEntry> abi;
CallContractOptions({
required this.contract,
required this.method,
this.args = const [],
required this.abi,
});
}
class SendContractOptions {
final String contract;
final String method;
final List<dynamic> args;
final List<AbiEntry> abi;
final String? value;
final int? gasLimit;
final String? gasPrice;
final int? nonce;
SendContractOptions({
required this.contract,
required this.method,
this.args = const [],
required this.abi,
this.value,
this.gasLimit,
this.gasPrice,
this.nonce,
});
}
class TransactionResult {
final String transactionHash;
final int? blockNumber;
final String? blockHash;
final int? gasUsed;
final String? effectiveGasPrice;
final String? status;
final List<EventLog>? logs;
TransactionResult({
required this.transactionHash,
this.blockNumber,
this.blockHash,
this.gasUsed,
this.effectiveGasPrice,
this.status,
this.logs,
});
factory TransactionResult.fromJson(Map<String, dynamic> json) {
return TransactionResult(
transactionHash: json['transaction_hash'] as String,
blockNumber: json['block_number'] as int?,
blockHash: json['block_hash'] as String?,
gasUsed: json['gas_used'] as int?,
effectiveGasPrice: json['effective_gas_price'] as String?,
status: json['status'] as String?,
logs: json['logs'] != null
? (json['logs'] as List).map((e) => EventLog.fromJson(e)).toList()
: null,
);
}
}
// ==================== Events ====================
class EventLog {
final String address;
final List<String> topics;
final String data;
final int? blockNumber;
final String? transactionHash;
final int? logIndex;
final String? blockHash;
final bool? removed;
EventLog({
required this.address,
required this.topics,
required this.data,
this.blockNumber,
this.transactionHash,
this.logIndex,
this.blockHash,
this.removed,
});
factory EventLog.fromJson(Map<String, dynamic> json) {
return EventLog(
address: json['address'] as String,
topics: (json['topics'] as List).map((e) => e as String).toList(),
data: json['data'] as String,
blockNumber: json['block_number'] as int?,
transactionHash: json['transaction_hash'] as String?,
logIndex: json['log_index'] as int?,
blockHash: json['block_hash'] as String?,
removed: json['removed'] as bool?,
);
}
Map<String, dynamic> toJson() => {
'address': address,
'topics': topics,
'data': data,
if (blockNumber != null) 'block_number': blockNumber,
if (transactionHash != null) 'transaction_hash': transactionHash,
if (logIndex != null) 'log_index': logIndex,
if (blockHash != null) 'block_hash': blockHash,
if (removed != null) 'removed': removed,
};
}
class DecodedEvent {
final String name;
final String? signature;
final Map<String, dynamic>? args;
final EventLog? log;
DecodedEvent({
required this.name,
this.signature,
this.args,
this.log,
});
factory DecodedEvent.fromJson(Map<String, dynamic> json) {
return DecodedEvent(
name: json['name'] as String,
signature: json['signature'] as String?,
args: json['args'] as Map<String, dynamic>?,
log: json['log'] != null ? EventLog.fromJson(json['log']) : null,
);
}
}
class EventFilter {
final String contract;
final String? event;
final int? fromBlock;
final int? toBlock;
final List<String?>? topics;
final List<AbiEntry>? abi;
EventFilter({
required this.contract,
this.event,
this.fromBlock,
this.toBlock,
this.topics,
this.abi,
});
}
// ==================== ABI Utilities ====================
class EncodeCallOptions {
final String method;
final List<dynamic> args;
final List<AbiEntry> abi;
EncodeCallOptions({
required this.method,
this.args = const [],
required this.abi,
});
}
class DecodeResultOptions {
final String data;
final String method;
final List<AbiEntry> abi;
DecodeResultOptions({
required this.data,
required this.method,
required this.abi,
});
}
// ==================== Gas Estimation ====================
class EstimateGasOptions {
final String contract;
final String method;
final List<dynamic> args;
final List<AbiEntry> abi;
final String? value;
EstimateGasOptions({
required this.contract,
required this.method,
this.args = const [],
required this.abi,
this.value,
});
}
class GasEstimation {
final int gasLimit;
final String? gasPrice;
final String? maxFeePerGas;
final String? maxPriorityFeePerGas;
final String? estimatedCost;
GasEstimation({
required this.gasLimit,
this.gasPrice,
this.maxFeePerGas,
this.maxPriorityFeePerGas,
this.estimatedCost,
});
factory GasEstimation.fromJson(Map<String, dynamic> json) {
return GasEstimation(
gasLimit: json['gas_limit'] as int,
gasPrice: json['gas_price'] as String?,
maxFeePerGas: json['max_fee_per_gas'] as String?,
maxPriorityFeePerGas: json['max_priority_fee_per_gas'] as String?,
estimatedCost: json['estimated_cost'] as String?,
);
}
}
// ==================== Contract Information ====================
class BytecodeInfo {
final String bytecode;
final String? deployedBytecode;
final int? size;
final bool? isContract;
BytecodeInfo({
required this.bytecode,
this.deployedBytecode,
this.size,
this.isContract,
});
factory BytecodeInfo.fromJson(Map<String, dynamic> json) {
return BytecodeInfo(
bytecode: json['bytecode'] as String,
deployedBytecode: json['deployed_bytecode'] as String?,
size: json['size'] as int?,
isContract: json['is_contract'] as bool?,
);
}
}
class VerifyContractOptions {
final String address;
final String sourceCode;
final String compilerVersion;
final String? constructorArgs;
final bool? optimization;
final int? optimizationRuns;
final String? license;
VerifyContractOptions({
required this.address,
required this.sourceCode,
required this.compilerVersion,
this.constructorArgs,
this.optimization,
this.optimizationRuns,
this.license,
});
}
class VerificationResult {
final bool verified;
final String? address;
final String? compilerVersion;
final bool? optimization;
final int? optimizationRuns;
final String? license;
final List<AbiEntry>? abi;
final String? sourceCode;
VerificationResult({
required this.verified,
this.address,
this.compilerVersion,
this.optimization,
this.optimizationRuns,
this.license,
this.abi,
this.sourceCode,
});
factory VerificationResult.fromJson(Map<String, dynamic> json) {
return VerificationResult(
verified: json['verified'] as bool,
address: json['address'] as String?,
compilerVersion: json['compiler_version'] as String?,
optimization: json['optimization'] as bool?,
optimizationRuns: json['optimization_runs'] as int?,
license: json['license'] as String?,
abi: json['abi'] != null
? (json['abi'] as List).map((e) => AbiEntry.fromJson(e)).toList()
: null,
sourceCode: json['source_code'] as String?,
);
}
}
// ==================== Multicall ====================
class MulticallRequest {
final String contract;
final String method;
final List<dynamic> args;
final List<AbiEntry> abi;
MulticallRequest({
required this.contract,
required this.method,
this.args = const [],
required this.abi,
});
Map<String, dynamic> toJson() => {
'contract': contract,
'method': method,
'args': args,
'abi': abi.map((e) => e.toJson()).toList(),
};
}
class MulticallResult {
final bool success;
final dynamic result;
final String? error;
MulticallResult({
required this.success,
this.result,
this.error,
});
factory MulticallResult.fromJson(Map<String, dynamic> json) {
return MulticallResult(
success: json['success'] as bool,
result: json['result'],
error: json['error'] as String?,
);
}
}
// ==================== Storage ====================
class ReadStorageOptions {
final String contract;
final String slot;
final int? blockNumber;
ReadStorageOptions({
required this.contract,
required this.slot,
this.blockNumber,
});
}

View file

@ -0,0 +1,317 @@
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'privacy_types.dart';
/// Synor Privacy SDK client for Flutter/Dart.
/// Confidential transactions, ring signatures, stealth addresses, and cryptographic commitments.
class PrivacyClient {
static const String version = '0.1.0';
final PrivacyConfig config;
final http.Client _client;
bool _closed = false;
PrivacyClient(this.config) : _client = http.Client();
// ==================== Confidential Transactions ====================
Future<ConfidentialTransaction> createConfidentialTx({
required List<ConfidentialTxInput> inputs,
required List<ConfidentialTxOutput> outputs,
}) async {
final body = {
'inputs': inputs.map((i) => i.toJson()).toList(),
'outputs': outputs.map((o) => o.toJson()).toList(),
};
final response = await _post('/privacy/confidential/create', body);
return ConfidentialTransaction.fromJson(response);
}
Future<bool> verifyConfidentialTx(ConfidentialTransaction tx) async {
final body = {'transaction': tx.toJson()};
final response = await _post('/privacy/confidential/verify', body);
return response['valid'] as bool? ?? false;
}
Future<Commitment> createCommitment({
required String value,
required String blindingFactor,
}) async {
final body = {
'value': value,
'blinding_factor': blindingFactor,
};
final response = await _post('/privacy/commitment/create', body);
return Commitment.fromJson(response);
}
Future<bool> verifyCommitment({
required String commitment,
required String value,
required String blindingFactor,
}) async {
final body = {
'commitment': commitment,
'value': value,
'blinding_factor': blindingFactor,
};
final response = await _post('/privacy/commitment/verify', body);
return response['valid'] as bool? ?? false;
}
Future<RangeProof> createRangeProof({
required String value,
required String blindingFactor,
required int minValue,
required int maxValue,
}) async {
final body = {
'value': value,
'blinding_factor': blindingFactor,
'min_value': minValue,
'max_value': maxValue,
};
final response = await _post('/privacy/range-proof/create', body);
return RangeProof.fromJson(response);
}
Future<bool> verifyRangeProof(RangeProof proof) async {
final body = {'proof': proof.toJson()};
final response = await _post('/privacy/range-proof/verify', body);
return response['valid'] as bool? ?? false;
}
// ==================== Ring Signatures ====================
Future<RingSignature> createRingSignature({
required String message,
required List<String> ring,
required int signerIndex,
required String privateKey,
}) async {
final body = {
'message': message,
'ring': ring,
'signer_index': signerIndex,
'private_key': privateKey,
};
final response = await _post('/privacy/ring/sign', body);
return RingSignature.fromJson(response);
}
Future<bool> verifyRingSignature(RingSignature signature, String message) async {
final body = {
'signature': signature.toJson(),
'message': message,
};
final response = await _post('/privacy/ring/verify', body);
return response['valid'] as bool? ?? false;
}
Future<List<String>> generateDecoys(int count, {String? excludeKey}) async {
var path = '/privacy/ring/decoys?count=$count';
if (excludeKey != null) {
path += '&exclude=${Uri.encodeComponent(excludeKey)}';
}
final response = await _get(path);
return (response as List).map((e) => e as String).toList();
}
Future<bool> checkKeyImage(String keyImage) async {
final response = await _get('/privacy/ring/key-image/$keyImage');
return response['spent'] as bool? ?? false;
}
// ==================== Stealth Addresses ====================
Future<StealthKeyPair> generateStealthKeyPair() async {
final response = await _post('/privacy/stealth/generate', {});
return StealthKeyPair.fromJson(response);
}
Future<StealthAddress> deriveStealthAddress({
required String spendPublicKey,
required String viewPublicKey,
}) async {
final body = {
'spend_public_key': spendPublicKey,
'view_public_key': viewPublicKey,
};
final response = await _post('/privacy/stealth/derive', body);
return StealthAddress.fromJson(response);
}
Future<String> recoverStealthPrivateKey({
required String stealthAddress,
required String viewPrivateKey,
required String spendPrivateKey,
}) async {
final body = {
'stealth_address': stealthAddress,
'view_private_key': viewPrivateKey,
'spend_private_key': spendPrivateKey,
};
final response = await _post('/privacy/stealth/recover', body);
return response['private_key'] as String;
}
Future<List<StealthOutput>> scanOutputs({
required String viewPrivateKey,
required String spendPublicKey,
required int fromBlock,
int? toBlock,
}) async {
final body = {
'view_private_key': viewPrivateKey,
'spend_public_key': spendPublicKey,
'from_block': fromBlock,
if (toBlock != null) 'to_block': toBlock,
};
final response = await _post('/privacy/stealth/scan', body);
return (response as List).map((e) => StealthOutput.fromJson(e)).toList();
}
// ==================== Blinding ====================
Future<String> generateBlindingFactor() async {
final response = await _post('/privacy/blinding/generate', {});
return response['blinding_factor'] as String;
}
Future<String> blindValue(String value, String blindingFactor) async {
final body = {
'value': value,
'blinding_factor': blindingFactor,
};
final response = await _post('/privacy/blinding/blind', body);
return response['blinded_value'] as String;
}
Future<String> unblindValue(String blindedValue, String blindingFactor) async {
final body = {
'blinded_value': blindedValue,
'blinding_factor': blindingFactor,
};
final response = await _post('/privacy/blinding/unblind', body);
return response['value'] as String;
}
// ==================== Lifecycle ====================
Future<bool> healthCheck() async {
if (_closed) return false;
try {
final response = await _get('/health');
return response['status'] == 'healthy';
} catch (_) {
return false;
}
}
void close() {
_closed = true;
_client.close();
}
bool get isClosed => _closed;
// ==================== HTTP Helpers ====================
Future<dynamic> _get(String path) async {
_checkClosed();
return _executeWithRetry(() async {
final response = await _client
.get(
Uri.parse('${config.endpoint}$path'),
headers: _headers,
)
.timeout(Duration(milliseconds: config.timeoutMs));
return _handleResponse(response);
});
}
Future<Map<String, dynamic>> _post(String path, Map<String, dynamic> body) async {
_checkClosed();
return _executeWithRetry(() async {
final response = await _client
.post(
Uri.parse('${config.endpoint}$path'),
headers: _headers,
body: jsonEncode(body),
)
.timeout(Duration(milliseconds: config.timeoutMs));
return _handleResponse(response);
});
}
Map<String, String> get _headers => {
'Authorization': 'Bearer ${config.apiKey}',
'Content-Type': 'application/json',
'X-SDK-Version': 'dart/$version',
};
dynamic _handleResponse(http.Response response) {
final body = jsonDecode(response.body);
if (response.statusCode >= 200 && response.statusCode < 300) {
return body;
}
final message = body['message'] ?? body['error'] ?? 'Unknown error';
final code = body['code']?.toString();
throw PrivacyException(
message: message,
statusCode: response.statusCode,
code: code,
);
}
Future<T> _executeWithRetry<T>(Future<T> Function() action) async {
Object? lastError;
for (var attempt = 0; attempt < config.retries; attempt++) {
try {
return await action();
} catch (e) {
lastError = e;
if (attempt < config.retries - 1) {
await Future.delayed(Duration(seconds: 1 << attempt));
}
}
}
throw lastError ?? PrivacyException(message: 'Request failed');
}
void _checkClosed() {
if (_closed) {
throw PrivacyException(message: 'Client has been closed');
}
}
}
/// Privacy SDK configuration.
class PrivacyConfig {
final String apiKey;
final String endpoint;
final int timeoutMs;
final int retries;
PrivacyConfig({
required this.apiKey,
this.endpoint = 'https://privacy.synor.io',
this.timeoutMs = 30000,
this.retries = 3,
});
}
/// Privacy SDK exception.
class PrivacyException implements Exception {
final String message;
final int? statusCode;
final String? code;
PrivacyException({required this.message, this.statusCode, this.code});
@override
String toString() => 'PrivacyException: $message (code: $code, status: $statusCode)';
}

View file

@ -0,0 +1,265 @@
// ==================== Confidential Transactions ====================
class ConfidentialTxInput {
final String commitment;
final String blindingFactor;
final String value;
final String? keyImage;
ConfidentialTxInput({
required this.commitment,
required this.blindingFactor,
required this.value,
this.keyImage,
});
factory ConfidentialTxInput.fromJson(Map<String, dynamic> json) {
return ConfidentialTxInput(
commitment: json['commitment'] as String,
blindingFactor: json['blinding_factor'] as String,
value: json['value'] as String,
keyImage: json['key_image'] as String?,
);
}
Map<String, dynamic> toJson() => {
'commitment': commitment,
'blinding_factor': blindingFactor,
'value': value,
if (keyImage != null) 'key_image': keyImage,
};
}
class ConfidentialTxOutput {
final String commitment;
final String blindingFactor;
final String value;
final String recipientPublicKey;
final String? rangeProof;
ConfidentialTxOutput({
required this.commitment,
required this.blindingFactor,
required this.value,
required this.recipientPublicKey,
this.rangeProof,
});
factory ConfidentialTxOutput.fromJson(Map<String, dynamic> json) {
return ConfidentialTxOutput(
commitment: json['commitment'] as String,
blindingFactor: json['blinding_factor'] as String,
value: json['value'] as String,
recipientPublicKey: json['recipient_public_key'] as String,
rangeProof: json['range_proof'] as String?,
);
}
Map<String, dynamic> toJson() => {
'commitment': commitment,
'blinding_factor': blindingFactor,
'value': value,
'recipient_public_key': recipientPublicKey,
if (rangeProof != null) 'range_proof': rangeProof,
};
}
class ConfidentialTransaction {
final String id;
final List<ConfidentialTxInput> inputs;
final List<ConfidentialTxOutput> outputs;
final String fee;
final String excess;
final String excessSig;
final String? kernelOffset;
ConfidentialTransaction({
required this.id,
required this.inputs,
required this.outputs,
required this.fee,
required this.excess,
required this.excessSig,
this.kernelOffset,
});
factory ConfidentialTransaction.fromJson(Map<String, dynamic> json) {
return ConfidentialTransaction(
id: json['id'] as String,
inputs: (json['inputs'] as List)
.map((e) => ConfidentialTxInput.fromJson(e))
.toList(),
outputs: (json['outputs'] as List)
.map((e) => ConfidentialTxOutput.fromJson(e))
.toList(),
fee: json['fee'] as String,
excess: json['excess'] as String,
excessSig: json['excess_sig'] as String,
kernelOffset: json['kernel_offset'] as String?,
);
}
Map<String, dynamic> toJson() => {
'id': id,
'inputs': inputs.map((i) => i.toJson()).toList(),
'outputs': outputs.map((o) => o.toJson()).toList(),
'fee': fee,
'excess': excess,
'excess_sig': excessSig,
if (kernelOffset != null) 'kernel_offset': kernelOffset,
};
}
// ==================== Commitments ====================
class Commitment {
final String commitment;
final String blindingFactor;
Commitment({required this.commitment, required this.blindingFactor});
factory Commitment.fromJson(Map<String, dynamic> json) {
return Commitment(
commitment: json['commitment'] as String,
blindingFactor: json['blinding_factor'] as String,
);
}
Map<String, dynamic> toJson() => {
'commitment': commitment,
'blinding_factor': blindingFactor,
};
}
class RangeProof {
final String proof;
final String commitment;
final int minValue;
final int maxValue;
RangeProof({
required this.proof,
required this.commitment,
required this.minValue,
required this.maxValue,
});
factory RangeProof.fromJson(Map<String, dynamic> json) {
return RangeProof(
proof: json['proof'] as String,
commitment: json['commitment'] as String,
minValue: json['min_value'] as int,
maxValue: json['max_value'] as int,
);
}
Map<String, dynamic> toJson() => {
'proof': proof,
'commitment': commitment,
'min_value': minValue,
'max_value': maxValue,
};
}
// ==================== Ring Signatures ====================
class RingSignature {
final String c0;
final List<String> s;
final String keyImage;
final List<String> ring;
RingSignature({
required this.c0,
required this.s,
required this.keyImage,
required this.ring,
});
factory RingSignature.fromJson(Map<String, dynamic> json) {
return RingSignature(
c0: json['c0'] as String,
s: (json['s'] as List).map((e) => e as String).toList(),
keyImage: json['key_image'] as String,
ring: (json['ring'] as List).map((e) => e as String).toList(),
);
}
Map<String, dynamic> toJson() => {
'c0': c0,
's': s,
'key_image': keyImage,
'ring': ring,
};
}
// ==================== Stealth Addresses ====================
class StealthKeyPair {
final String spendPublicKey;
final String spendPrivateKey;
final String viewPublicKey;
final String viewPrivateKey;
StealthKeyPair({
required this.spendPublicKey,
required this.spendPrivateKey,
required this.viewPublicKey,
required this.viewPrivateKey,
});
factory StealthKeyPair.fromJson(Map<String, dynamic> json) {
return StealthKeyPair(
spendPublicKey: json['spend_public_key'] as String,
spendPrivateKey: json['spend_private_key'] as String,
viewPublicKey: json['view_public_key'] as String,
viewPrivateKey: json['view_private_key'] as String,
);
}
}
class StealthAddress {
final String address;
final String ephemeralPublicKey;
final String? txPublicKey;
StealthAddress({
required this.address,
required this.ephemeralPublicKey,
this.txPublicKey,
});
factory StealthAddress.fromJson(Map<String, dynamic> json) {
return StealthAddress(
address: json['address'] as String,
ephemeralPublicKey: json['ephemeral_public_key'] as String,
txPublicKey: json['tx_public_key'] as String?,
);
}
}
class StealthOutput {
final String txHash;
final int outputIndex;
final String stealthAddress;
final String amount;
final int blockHeight;
StealthOutput({
required this.txHash,
required this.outputIndex,
required this.stealthAddress,
required this.amount,
required this.blockHeight,
});
factory StealthOutput.fromJson(Map<String, dynamic> json) {
return StealthOutput(
txHash: json['tx_hash'] as String,
outputIndex: json['output_index'] as int,
stealthAddress: json['stealth_address'] as String,
amount: json['amount'] as String,
blockHeight: json['block_height'] as int,
);
}
}

810
sdk/go/contract/contract.go Normal file
View file

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

682
sdk/go/privacy/privacy.go Normal file
View file

@ -0,0 +1,682 @@
// Package privacy provides privacy-enhancing cryptographic features.
package privacy
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"sync/atomic"
"time"
)
const DefaultEndpoint = "https://privacy.synor.cc/api/v1"
// Config is the privacy client configuration.
type Config struct {
APIKey string
Endpoint string
Timeout time.Duration
Retries int
Debug bool
}
// KeyType represents the type of cryptographic key.
type KeyType string
const (
KeyTypeEd25519 KeyType = "ed25519"
KeyTypeSecp256k1 KeyType = "secp256k1"
)
// GeneratorType represents the generator used for commitments.
type GeneratorType string
const (
GeneratorDefault GeneratorType = "default"
GeneratorAlternate GeneratorType = "alternate"
)
// PublicKey represents a public key.
type PublicKey struct {
Key string `json:"key"`
Type KeyType `json:"type"`
}
// PrivateKey represents a private key.
type PrivateKey struct {
Key string `json:"key"`
Type KeyType `json:"type"`
}
// ConfidentialUTXO is a UTXO for confidential transactions.
type ConfidentialUTXO struct {
TxID string `json:"txid"`
Vout int `json:"vout"`
Commitment string `json:"commitment"`
RangeProof string `json:"rangeProof"`
Blinding string `json:"blinding,omitempty"`
Amount string `json:"amount,omitempty"`
}
// ConfidentialOutput is an output for confidential transactions.
type ConfidentialOutput struct {
Recipient string `json:"recipient"`
Amount string `json:"amount"`
Blinding string `json:"blinding,omitempty"`
}
// OutputFeatures contains output feature flags.
type OutputFeatures struct {
Flags int `json:"flags"`
LockHeight int `json:"lockHeight,omitempty"`
}
// ConfidentialTxInput is a confidential transaction input.
type ConfidentialTxInput struct {
OutputRef string `json:"outputRef"`
Commitment string `json:"commitment"`
}
// ConfidentialTxOutput is a confidential transaction output.
type ConfidentialTxOutput struct {
Commitment string `json:"commitment"`
RangeProof string `json:"rangeProof"`
Features OutputFeatures `json:"features"`
}
// TransactionKernel is the kernel for confidential transactions.
type TransactionKernel struct {
Features int `json:"features"`
Fee string `json:"fee"`
LockHeight int `json:"lockHeight"`
Excess string `json:"excess"`
ExcessSignature string `json:"excessSignature"`
}
// ConfidentialTransaction is a confidential transaction.
type ConfidentialTransaction struct {
TxID string `json:"txid"`
Version int `json:"version"`
Inputs []ConfidentialTxInput `json:"inputs"`
Outputs []ConfidentialTxOutput `json:"outputs"`
Kernel TransactionKernel `json:"kernel"`
Offset string `json:"offset"`
Raw string `json:"raw"`
}
// VerifyConfidentialTxDetails contains verification details.
type VerifyConfidentialTxDetails struct {
CommitmentsBalance bool `json:"commitmentsBalance"`
RangeProofsValid bool `json:"rangeProofsValid"`
SignatureValid bool `json:"signatureValid"`
NoDuplicateInputs bool `json:"noDuplicateInputs"`
}
// VerifyConfidentialTxResult is the result of verifying a confidential transaction.
type VerifyConfidentialTxResult struct {
Valid bool `json:"valid"`
Details VerifyConfidentialTxDetails `json:"details"`
Error string `json:"error,omitempty"`
}
// RingSignatureComponents contains the signature components.
type RingSignatureComponents struct {
C []string `json:"c"`
R []string `json:"r"`
}
// RingSignature is a ring signature.
type RingSignature struct {
ID string `json:"id"`
MessageHash string `json:"messageHash"`
Ring []string `json:"ring"`
KeyImage string `json:"keyImage"`
Signature RingSignatureComponents `json:"signature"`
RingSize int `json:"ringSize"`
}
// VerifyRingSignatureResult is the result of verifying a ring signature.
type VerifyRingSignatureResult struct {
Valid bool `json:"valid"`
KeyImage string `json:"keyImage"`
}
// StealthAddress is a stealth address.
type StealthAddress struct {
Address string `json:"address"`
ScanPublicKey string `json:"scanPublicKey"`
SpendPublicKey string `json:"spendPublicKey"`
}
// StealthKeypair is a stealth address keypair.
type StealthKeypair struct {
Address StealthAddress `json:"address"`
ScanPrivateKey string `json:"scanPrivateKey"`
SpendPrivateKey string `json:"spendPrivateKey"`
}
// OneTimeAddress is a one-time address derived from a stealth address.
type OneTimeAddress struct {
Address string `json:"address"`
EphemeralPublicKey string `json:"ephemeralPublicKey"`
SharedSecret string `json:"sharedSecret,omitempty"`
}
// SharedSecret is the result of deriving a shared secret.
type SharedSecret struct {
Secret string `json:"secret"`
OneTimePrivateKey string `json:"oneTimePrivateKey"`
OneTimeAddress string `json:"oneTimeAddress"`
}
// Commitment is a Pedersen commitment.
type Commitment struct {
Commitment string `json:"commitment"`
Generator GeneratorType `json:"generator"`
}
// CommitmentWithBlinding is a commitment with its blinding factor.
type CommitmentWithBlinding struct {
Commitment Commitment `json:"commitment"`
Blinding string `json:"blinding"`
}
// RangeProof is a Bulletproof range proof.
type RangeProof struct {
Proof string `json:"proof"`
Commitment string `json:"commitment"`
BitLength int `json:"bitLength"`
}
// VerifyRangeProofResult is the result of verifying a range proof.
type VerifyRangeProofResult struct {
Valid bool `json:"valid"`
MinValue string `json:"minValue"`
MaxValue string `json:"maxValue"`
}
// Error is a privacy error.
type Error struct {
Message string
Code string
StatusCode int
}
func (e *Error) Error() string {
return e.Message
}
// Client is a Synor Privacy client.
type Client struct {
config Config
httpClient *http.Client
closed atomic.Bool
}
// NewClient creates a new privacy 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,
},
}
}
// ==================== Confidential Transactions ====================
// CreateConfidentialTxOptions are options for creating a confidential transaction.
type CreateConfidentialTxOptions struct {
Inputs []ConfidentialUTXO
Outputs []ConfidentialOutput
Fee string
LockHeight int
}
// CreateConfidentialTransaction creates a confidential transaction.
func (c *Client) CreateConfidentialTransaction(ctx context.Context, opts CreateConfidentialTxOptions) (*ConfidentialTransaction, error) {
body := map[string]interface{}{
"inputs": opts.Inputs,
"outputs": opts.Outputs,
}
if opts.Fee != "" {
body["fee"] = opts.Fee
}
if opts.LockHeight > 0 {
body["lockHeight"] = opts.LockHeight
}
var resp struct {
Transaction ConfidentialTransaction `json:"transaction"`
}
if err := c.request(ctx, "POST", "/transactions/confidential", body, &resp); err != nil {
return nil, err
}
return &resp.Transaction, nil
}
// VerifyConfidentialTransaction verifies a confidential transaction.
func (c *Client) VerifyConfidentialTransaction(ctx context.Context, tx *ConfidentialTransaction) (*VerifyConfidentialTxResult, error) {
body := map[string]interface{}{
"transaction": tx.Raw,
}
var result VerifyConfidentialTxResult
if err := c.request(ctx, "POST", "/transactions/confidential/verify", body, &result); err != nil {
return nil, err
}
return &result, nil
}
// DecodeConfidentialOutput decodes a confidential output.
func (c *Client) DecodeConfidentialOutput(ctx context.Context, commitment, blinding string) (string, error) {
body := map[string]interface{}{
"commitment": commitment,
"blinding": blinding,
}
var resp struct {
Amount string `json:"amount"`
}
if err := c.request(ctx, "POST", "/transactions/confidential/decode", body, &resp); err != nil {
return "", err
}
return resp.Amount, nil
}
// ==================== Ring Signatures ====================
// CreateRingSignatureOptions are options for creating a ring signature.
type CreateRingSignatureOptions struct {
Message string
Ring []string
PrivateKey string
SignerIndex *int
}
// CreateRingSignature creates a ring signature.
func (c *Client) CreateRingSignature(ctx context.Context, opts CreateRingSignatureOptions) (*RingSignature, error) {
body := map[string]interface{}{
"message": opts.Message,
"ring": opts.Ring,
"privateKey": opts.PrivateKey,
}
if opts.SignerIndex != nil {
body["signerIndex"] = *opts.SignerIndex
}
var resp struct {
Signature RingSignature `json:"signature"`
}
if err := c.request(ctx, "POST", "/ring-signatures/create", body, &resp); err != nil {
return nil, err
}
return &resp.Signature, nil
}
// VerifyRingSignature verifies a ring signature.
func (c *Client) VerifyRingSignature(ctx context.Context, signature *RingSignature, message string) (*VerifyRingSignatureResult, error) {
body := map[string]interface{}{
"signature": signature,
"message": message,
}
var result VerifyRingSignatureResult
if err := c.request(ctx, "POST", "/ring-signatures/verify", body, &result); err != nil {
return nil, err
}
return &result, nil
}
// IsKeyImageUsed checks if a key image has been used.
func (c *Client) IsKeyImageUsed(ctx context.Context, keyImage string) (bool, error) {
var resp struct {
Used bool `json:"used"`
}
if err := c.request(ctx, "GET", fmt.Sprintf("/ring-signatures/key-images/%s", keyImage), nil, &resp); err != nil {
return false, err
}
return resp.Used, nil
}
// GenerateRandomRing generates a random ring of public keys.
func (c *Client) GenerateRandomRing(ctx context.Context, size int, exclude []string) ([]string, error) {
body := map[string]interface{}{
"size": size,
}
if len(exclude) > 0 {
body["exclude"] = exclude
}
var resp struct {
Ring []string `json:"ring"`
}
if err := c.request(ctx, "POST", "/ring-signatures/random-ring", body, &resp); err != nil {
return nil, err
}
return resp.Ring, nil
}
// ==================== Stealth Addresses ====================
// GenerateStealthKeypair generates a new stealth address keypair.
func (c *Client) GenerateStealthKeypair(ctx context.Context) (*StealthKeypair, error) {
var resp struct {
Keypair StealthKeypair `json:"keypair"`
}
if err := c.request(ctx, "POST", "/stealth/generate", nil, &resp); err != nil {
return nil, err
}
return &resp.Keypair, nil
}
// CreateOneTimeAddress creates a one-time address for a stealth address.
func (c *Client) CreateOneTimeAddress(ctx context.Context, stealthAddress *StealthAddress) (*OneTimeAddress, error) {
body := map[string]interface{}{
"scanPublicKey": stealthAddress.ScanPublicKey,
"spendPublicKey": stealthAddress.SpendPublicKey,
}
var resp struct {
OneTimeAddress OneTimeAddress `json:"oneTimeAddress"`
}
if err := c.request(ctx, "POST", "/stealth/one-time-address", body, &resp); err != nil {
return nil, err
}
return &resp.OneTimeAddress, nil
}
// DeriveSharedSecret derives the shared secret for a stealth payment.
func (c *Client) DeriveSharedSecret(ctx context.Context, stealthAddress *StealthAddress, privateKey, ephemeralPublicKey string) (*SharedSecret, error) {
body := map[string]interface{}{
"scanPublicKey": stealthAddress.ScanPublicKey,
"spendPublicKey": stealthAddress.SpendPublicKey,
"privateKey": privateKey,
"ephemeralPublicKey": ephemeralPublicKey,
}
var secret SharedSecret
if err := c.request(ctx, "POST", "/stealth/derive-secret", body, &secret); err != nil {
return nil, err
}
return &secret, nil
}
// ScanForPayments scans transactions for stealth payments.
func (c *Client) ScanForPayments(ctx context.Context, scanPrivateKey, spendPublicKey string, transactions []string) ([]OneTimeAddress, error) {
body := map[string]interface{}{
"scanPrivateKey": scanPrivateKey,
"spendPublicKey": spendPublicKey,
"transactions": transactions,
}
var resp struct {
Payments []OneTimeAddress `json:"payments"`
}
if err := c.request(ctx, "POST", "/stealth/scan", body, &resp); err != nil {
return nil, err
}
return resp.Payments, nil
}
// ==================== Commitments ====================
// CreateCommitment creates a Pedersen commitment.
func (c *Client) CreateCommitment(ctx context.Context, value string, blinding string) (*CommitmentWithBlinding, error) {
body := map[string]interface{}{
"value": value,
}
if blinding != "" {
body["blinding"] = blinding
}
var result CommitmentWithBlinding
if err := c.request(ctx, "POST", "/commitments/create", body, &result); err != nil {
return nil, err
}
return &result, nil
}
// OpenCommitment verifies a Pedersen commitment.
func (c *Client) OpenCommitment(ctx context.Context, commitment, value, blinding string) (bool, error) {
body := map[string]interface{}{
"commitment": commitment,
"value": value,
"blinding": blinding,
}
var resp struct {
Valid bool `json:"valid"`
}
if err := c.request(ctx, "POST", "/commitments/open", body, &resp); err != nil {
return false, err
}
return resp.Valid, nil
}
// AddCommitments adds two commitments.
func (c *Client) AddCommitments(ctx context.Context, commitment1, commitment2 string) (string, error) {
body := map[string]interface{}{
"commitment1": commitment1,
"commitment2": commitment2,
}
var resp struct {
Commitment string `json:"commitment"`
}
if err := c.request(ctx, "POST", "/commitments/add", body, &resp); err != nil {
return "", err
}
return resp.Commitment, nil
}
// SubtractCommitments subtracts two commitments.
func (c *Client) SubtractCommitments(ctx context.Context, commitment1, commitment2 string) (string, error) {
body := map[string]interface{}{
"commitment1": commitment1,
"commitment2": commitment2,
}
var resp struct {
Commitment string `json:"commitment"`
}
if err := c.request(ctx, "POST", "/commitments/subtract", body, &resp); err != nil {
return "", err
}
return resp.Commitment, nil
}
// ComputeBlindingSum computes the sum of blinding factors.
func (c *Client) ComputeBlindingSum(ctx context.Context, positive, negative []string) (string, error) {
body := map[string]interface{}{
"positive": positive,
"negative": negative,
}
var resp struct {
Sum string `json:"sum"`
}
if err := c.request(ctx, "POST", "/commitments/blinding-sum", body, &resp); err != nil {
return "", err
}
return resp.Sum, nil
}
// GenerateBlinding generates a random blinding factor.
func (c *Client) GenerateBlinding(ctx context.Context) (string, error) {
var resp struct {
Blinding string `json:"blinding"`
}
if err := c.request(ctx, "POST", "/commitments/random-blinding", nil, &resp); err != nil {
return "", err
}
return resp.Blinding, nil
}
// ==================== Range Proofs ====================
// CreateRangeProofOptions are options for creating a range proof.
type CreateRangeProofOptions struct {
Value string
Blinding string
Message string
BitLength int
}
// CreateRangeProof creates a Bulletproof range proof.
func (c *Client) CreateRangeProof(ctx context.Context, opts CreateRangeProofOptions) (*RangeProof, error) {
body := map[string]interface{}{
"value": opts.Value,
"blinding": opts.Blinding,
}
if opts.Message != "" {
body["message"] = opts.Message
}
if opts.BitLength > 0 {
body["bitLength"] = opts.BitLength
} else {
body["bitLength"] = 64
}
var resp struct {
Proof RangeProof `json:"proof"`
}
if err := c.request(ctx, "POST", "/range-proofs/create", body, &resp); err != nil {
return nil, err
}
return &resp.Proof, nil
}
// VerifyRangeProof verifies a Bulletproof range proof.
func (c *Client) VerifyRangeProof(ctx context.Context, commitment, proof string) (*VerifyRangeProofResult, error) {
body := map[string]interface{}{
"commitment": commitment,
"proof": proof,
}
var result VerifyRangeProofResult
if err := c.request(ctx, "POST", "/range-proofs/verify", body, &result); err != nil {
return nil, err
}
return &result, nil
}
// CreateAggregatedRangeProof creates an aggregated range proof.
func (c *Client) CreateAggregatedRangeProof(ctx context.Context, outputs []map[string]string) (string, error) {
body := map[string]interface{}{
"outputs": outputs,
}
var resp struct {
Proof string `json:"proof"`
}
if err := c.request(ctx, "POST", "/range-proofs/aggregate", body, &resp); err != nil {
return "", err
}
return resp.Proof, 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
}

View file

@ -0,0 +1,324 @@
package io.synor.contract;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import okhttp3.*;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Synor Contract SDK client for Java.
* Smart contract deployment, interaction, and event handling.
*/
public class ContractClient implements AutoCloseable {
private static final String SDK_VERSION = "0.1.0";
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private final ContractConfig config;
private final OkHttpClient httpClient;
private final Gson gson;
private final AtomicBoolean closed = new AtomicBoolean(false);
public ContractClient(ContractConfig config) {
this.config = config;
this.gson = new GsonBuilder().create();
this.httpClient = new OkHttpClient.Builder()
.connectTimeout(config.getTimeoutMs(), TimeUnit.MILLISECONDS)
.readTimeout(config.getTimeoutMs(), TimeUnit.MILLISECONDS)
.writeTimeout(config.getTimeoutMs(), TimeUnit.MILLISECONDS)
.build();
}
// ==================== Contract Deployment ====================
public CompletableFuture<DeploymentResult> deploy(DeployContractOptions options) {
JsonObject body = new JsonObject();
body.addProperty("bytecode", options.bytecode);
if (options.abi != null) body.add("abi", gson.toJsonTree(options.abi));
if (options.constructorArgs != null) body.add("constructor_args", gson.toJsonTree(options.constructorArgs));
if (options.value != null) body.addProperty("value", options.value);
if (options.gasLimit != null) body.addProperty("gas_limit", options.gasLimit);
if (options.gasPrice != null) body.addProperty("gas_price", options.gasPrice);
if (options.nonce != null) body.addProperty("nonce", options.nonce);
return post("/contract/deploy", body, DeploymentResult.class);
}
public CompletableFuture<DeploymentResult> deployCreate2(DeployContractOptions options, String salt) {
JsonObject body = new JsonObject();
body.addProperty("bytecode", options.bytecode);
body.addProperty("salt", salt);
if (options.abi != null) body.add("abi", gson.toJsonTree(options.abi));
if (options.constructorArgs != null) body.add("constructor_args", gson.toJsonTree(options.constructorArgs));
if (options.value != null) body.addProperty("value", options.value);
if (options.gasLimit != null) body.addProperty("gas_limit", options.gasLimit);
if (options.gasPrice != null) body.addProperty("gas_price", options.gasPrice);
return post("/contract/deploy/create2", body, DeploymentResult.class);
}
public CompletableFuture<String> predictAddress(String bytecode, String salt, String deployer) {
JsonObject body = new JsonObject();
body.addProperty("bytecode", bytecode);
body.addProperty("salt", salt);
if (deployer != null) body.addProperty("deployer", deployer);
return post("/contract/predict-address", body, JsonObject.class)
.thenApply(response -> response.get("address").getAsString());
}
// ==================== Contract Interaction ====================
public CompletableFuture<Object> call(CallContractOptions options) {
JsonObject body = new JsonObject();
body.addProperty("contract", options.contract);
body.addProperty("method", options.method);
body.add("args", gson.toJsonTree(options.args));
body.add("abi", gson.toJsonTree(options.abi));
return post("/contract/call", body, Object.class);
}
public CompletableFuture<TransactionResult> send(SendContractOptions options) {
JsonObject body = new JsonObject();
body.addProperty("contract", options.contract);
body.addProperty("method", options.method);
body.add("args", gson.toJsonTree(options.args));
body.add("abi", gson.toJsonTree(options.abi));
if (options.value != null) body.addProperty("value", options.value);
if (options.gasLimit != null) body.addProperty("gas_limit", options.gasLimit);
if (options.gasPrice != null) body.addProperty("gas_price", options.gasPrice);
if (options.nonce != null) body.addProperty("nonce", options.nonce);
return post("/contract/send", body, TransactionResult.class);
}
// ==================== Events ====================
public CompletableFuture<List<DecodedEvent>> getEvents(EventFilter filter) {
JsonObject body = new JsonObject();
body.addProperty("contract", filter.contract);
if (filter.event != null) body.addProperty("event", filter.event);
if (filter.fromBlock != null) body.addProperty("from_block", filter.fromBlock);
if (filter.toBlock != null) body.addProperty("to_block", filter.toBlock);
if (filter.topics != null) body.add("topics", gson.toJsonTree(filter.topics));
if (filter.abi != null) body.add("abi", gson.toJsonTree(filter.abi));
Type listType = new TypeToken<List<DecodedEvent>>(){}.getType();
return post("/contract/events", body, listType);
}
public CompletableFuture<List<EventLog>> getLogs(String contract, Long fromBlock, Long toBlock) {
StringBuilder path = new StringBuilder("/contract/logs?contract=").append(encode(contract));
if (fromBlock != null) path.append("&from_block=").append(fromBlock);
if (toBlock != null) path.append("&to_block=").append(toBlock);
Type listType = new TypeToken<List<EventLog>>(){}.getType();
return get(path.toString(), listType);
}
public CompletableFuture<List<DecodedEvent>> decodeLogs(List<EventLog> logs, List<AbiEntry> abi) {
JsonObject body = new JsonObject();
body.add("logs", gson.toJsonTree(logs));
body.add("abi", gson.toJsonTree(abi));
Type listType = new TypeToken<List<DecodedEvent>>(){}.getType();
return post("/contract/decode-logs", body, listType);
}
// ==================== ABI Utilities ====================
public CompletableFuture<String> encodeCall(EncodeCallOptions options) {
JsonObject body = new JsonObject();
body.addProperty("method", options.method);
body.add("args", gson.toJsonTree(options.args));
body.add("abi", gson.toJsonTree(options.abi));
return post("/contract/encode", body, JsonObject.class)
.thenApply(response -> response.get("data").getAsString());
}
public CompletableFuture<Object> decodeResult(DecodeResultOptions options) {
JsonObject body = new JsonObject();
body.addProperty("data", options.data);
body.addProperty("method", options.method);
body.add("abi", gson.toJsonTree(options.abi));
return post("/contract/decode", body, JsonObject.class)
.thenApply(response -> response.get("result"));
}
public CompletableFuture<String> getSelector(String signature) {
return get("/contract/selector?signature=" + encode(signature), JsonObject.class)
.thenApply(response -> response.get("selector").getAsString());
}
// ==================== Gas Estimation ====================
public CompletableFuture<GasEstimation> estimateGas(EstimateGasOptions options) {
JsonObject body = new JsonObject();
body.addProperty("contract", options.contract);
body.addProperty("method", options.method);
body.add("args", gson.toJsonTree(options.args));
body.add("abi", gson.toJsonTree(options.abi));
if (options.value != null) body.addProperty("value", options.value);
return post("/contract/estimate-gas", body, GasEstimation.class);
}
// ==================== Contract Information ====================
public CompletableFuture<BytecodeInfo> getBytecode(String address) {
return get("/contract/" + encode(address) + "/bytecode", BytecodeInfo.class);
}
public CompletableFuture<VerificationResult> verify(VerifyContractOptions options) {
JsonObject body = new JsonObject();
body.addProperty("address", options.address);
body.addProperty("source_code", options.sourceCode);
body.addProperty("compiler_version", options.compilerVersion);
if (options.constructorArgs != null) body.addProperty("constructor_args", options.constructorArgs);
if (options.optimization != null) body.addProperty("optimization", options.optimization);
if (options.optimizationRuns != null) body.addProperty("optimization_runs", options.optimizationRuns);
if (options.license != null) body.addProperty("license", options.license);
return post("/contract/verify", body, VerificationResult.class);
}
public CompletableFuture<VerificationResult> getVerificationStatus(String address) {
return get("/contract/" + encode(address) + "/verification", VerificationResult.class);
}
// ==================== Multicall ====================
public CompletableFuture<List<MulticallResult>> multicall(List<MulticallRequest> requests) {
JsonObject body = new JsonObject();
body.add("calls", gson.toJsonTree(requests));
Type listType = new TypeToken<List<MulticallResult>>(){}.getType();
return post("/contract/multicall", body, listType);
}
// ==================== Storage ====================
public CompletableFuture<String> readStorage(ReadStorageOptions options) {
StringBuilder path = new StringBuilder("/contract/storage?contract=")
.append(encode(options.contract))
.append("&slot=").append(encode(options.slot));
if (options.blockNumber != null) path.append("&block=").append(options.blockNumber);
return get(path.toString(), JsonObject.class)
.thenApply(response -> response.get("value").getAsString());
}
// ==================== Lifecycle ====================
public CompletableFuture<Boolean> healthCheck() {
if (closed.get()) {
return CompletableFuture.completedFuture(false);
}
return get("/health", JsonObject.class)
.thenApply(response -> "healthy".equals(response.get("status").getAsString()))
.exceptionally(e -> false);
}
@Override
public void close() {
closed.set(true);
httpClient.dispatcher().executorService().shutdown();
httpClient.connectionPool().evictAll();
}
public boolean isClosed() {
return closed.get();
}
// ==================== HTTP Helpers ====================
private String encode(String value) {
return URLEncoder.encode(value, StandardCharsets.UTF_8);
}
private <T> CompletableFuture<T> get(String path, Type type) {
return executeRequest(path, null, type);
}
private <T> CompletableFuture<T> post(String path, JsonObject body, Type type) {
return executeRequest(path, body, type);
}
private <T> CompletableFuture<T> executeRequest(String path, JsonObject body, Type type) {
if (closed.get()) {
CompletableFuture<T> future = new CompletableFuture<>();
future.completeExceptionally(new ContractException("Client has been closed"));
return future;
}
CompletableFuture<T> future = new CompletableFuture<>();
Request.Builder requestBuilder = new Request.Builder()
.url(config.getEndpoint() + path)
.header("Authorization", "Bearer " + config.getApiKey())
.header("Content-Type", "application/json")
.header("X-SDK-Version", "java/" + SDK_VERSION);
if (body != null) {
requestBuilder.post(RequestBody.create(gson.toJson(body), JSON));
} else {
requestBuilder.get();
}
executeWithRetry(requestBuilder.build(), config.getRetries(), 0, future, type);
return future;
}
private <T> void executeWithRetry(Request request, int remainingRetries, int attempt, CompletableFuture<T> future, Type type) {
httpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
if (remainingRetries > 1) {
scheduleRetry(request, remainingRetries - 1, attempt + 1, future, type);
} else {
future.completeExceptionally(new ContractException("Request failed: " + e.getMessage(), e));
}
}
@Override
public void onResponse(Call call, Response response) throws IOException {
try (ResponseBody responseBody = response.body()) {
String bodyString = responseBody != null ? responseBody.string() : "";
if (response.isSuccessful()) {
T result = gson.fromJson(bodyString, type);
future.complete(result);
} else {
JsonObject errorObj = gson.fromJson(bodyString, JsonObject.class);
String message = errorObj.has("message") ? errorObj.get("message").getAsString() : "Unknown error";
String code = errorObj.has("code") ? errorObj.get("code").getAsString() : null;
if (remainingRetries > 1 && response.code() >= 500) {
scheduleRetry(request, remainingRetries - 1, attempt + 1, future, type);
} else {
future.completeExceptionally(new ContractException(message, response.code(), code));
}
}
}
}
});
}
private <T> void scheduleRetry(Request request, int remainingRetries, int attempt, CompletableFuture<T> future, Type type) {
long delay = (long) Math.pow(2, attempt) * 1000;
CompletableFuture.delayedExecutor(delay, TimeUnit.MILLISECONDS)
.execute(() -> executeWithRetry(request, remainingRetries, attempt, future, type));
}
}

View file

@ -0,0 +1,49 @@
package io.synor.contract;
/**
* Configuration for the Contract SDK client.
*/
public class ContractConfig {
private final String apiKey;
private String endpoint = "https://contract.synor.io";
private long timeoutMs = 30000;
private int retries = 3;
public ContractConfig(String apiKey) {
if (apiKey == null || apiKey.isEmpty()) {
throw new IllegalArgumentException("API key is required");
}
this.apiKey = apiKey;
}
public ContractConfig endpoint(String endpoint) {
this.endpoint = endpoint;
return this;
}
public ContractConfig timeoutMs(long timeoutMs) {
this.timeoutMs = timeoutMs;
return this;
}
public ContractConfig retries(int retries) {
this.retries = retries;
return this;
}
public String getApiKey() {
return apiKey;
}
public String getEndpoint() {
return endpoint;
}
public long getTimeoutMs() {
return timeoutMs;
}
public int getRetries() {
return retries;
}
}

View file

@ -0,0 +1,35 @@
package io.synor.contract;
/**
* Exception thrown by the Contract SDK.
*/
public class ContractException extends RuntimeException {
private final Integer statusCode;
private final String errorCode;
public ContractException(String message) {
super(message);
this.statusCode = null;
this.errorCode = null;
}
public ContractException(String message, Throwable cause) {
super(message, cause);
this.statusCode = null;
this.errorCode = null;
}
public ContractException(String message, int statusCode, String errorCode) {
super(message);
this.statusCode = statusCode;
this.errorCode = errorCode;
}
public Integer getStatusCode() {
return statusCode;
}
public String getErrorCode() {
return errorCode;
}
}

View file

@ -0,0 +1,294 @@
package io.synor.contract;
import com.google.gson.annotations.SerializedName;
import java.util.List;
import java.util.Map;
/**
* Contract SDK type definitions.
*/
public class ContractTypes {
// ==================== ABI Types ====================
public static class AbiParameter {
@SerializedName("name")
public String name;
@SerializedName("type")
public String type;
@SerializedName("indexed")
public Boolean indexed;
@SerializedName("components")
public List<AbiParameter> components;
}
public static class AbiEntry {
@SerializedName("type")
public String type;
@SerializedName("name")
public String name;
@SerializedName("inputs")
public List<AbiParameter> inputs;
@SerializedName("outputs")
public List<AbiParameter> outputs;
@SerializedName("stateMutability")
public String stateMutability;
@SerializedName("anonymous")
public Boolean anonymous;
}
// ==================== Deployment ====================
public static class DeployContractOptions {
public String bytecode;
public List<AbiEntry> abi;
public Object[] constructorArgs;
public String value;
public Long gasLimit;
public String gasPrice;
public Long nonce;
}
public static class DeploymentResult {
@SerializedName("contract_address")
public String contractAddress;
@SerializedName("transaction_hash")
public String transactionHash;
@SerializedName("deployer")
public String deployer;
@SerializedName("gas_used")
public Long gasUsed;
@SerializedName("block_number")
public Long blockNumber;
@SerializedName("block_hash")
public String blockHash;
}
// ==================== Contract Interaction ====================
public static class CallContractOptions {
public String contract;
public String method;
public Object[] args;
public List<AbiEntry> abi;
}
public static class SendContractOptions {
public String contract;
public String method;
public Object[] args;
public List<AbiEntry> abi;
public String value;
public Long gasLimit;
public String gasPrice;
public Long nonce;
}
public static class TransactionResult {
@SerializedName("transaction_hash")
public String transactionHash;
@SerializedName("block_number")
public Long blockNumber;
@SerializedName("block_hash")
public String blockHash;
@SerializedName("gas_used")
public Long gasUsed;
@SerializedName("effective_gas_price")
public String effectiveGasPrice;
@SerializedName("status")
public String status;
@SerializedName("logs")
public List<EventLog> logs;
}
// ==================== Events ====================
public static class EventLog {
@SerializedName("address")
public String address;
@SerializedName("topics")
public List<String> topics;
@SerializedName("data")
public String data;
@SerializedName("block_number")
public Long blockNumber;
@SerializedName("transaction_hash")
public String transactionHash;
@SerializedName("log_index")
public Integer logIndex;
@SerializedName("block_hash")
public String blockHash;
@SerializedName("removed")
public Boolean removed;
}
public static class DecodedEvent {
@SerializedName("name")
public String name;
@SerializedName("signature")
public String signature;
@SerializedName("args")
public Map<String, Object> args;
@SerializedName("log")
public EventLog log;
}
public static class EventFilter {
public String contract;
public String event;
public Long fromBlock;
public Long toBlock;
public List<String> topics;
public List<AbiEntry> abi;
}
// ==================== ABI Utilities ====================
public static class EncodeCallOptions {
public String method;
public Object[] args;
public List<AbiEntry> abi;
}
public static class DecodeResultOptions {
public String data;
public String method;
public List<AbiEntry> abi;
}
// ==================== Gas Estimation ====================
public static class EstimateGasOptions {
public String contract;
public String method;
public Object[] args;
public List<AbiEntry> abi;
public String value;
}
public static class GasEstimation {
@SerializedName("gas_limit")
public Long gasLimit;
@SerializedName("gas_price")
public String gasPrice;
@SerializedName("max_fee_per_gas")
public String maxFeePerGas;
@SerializedName("max_priority_fee_per_gas")
public String maxPriorityFeePerGas;
@SerializedName("estimated_cost")
public String estimatedCost;
}
// ==================== Contract Information ====================
public static class BytecodeInfo {
@SerializedName("bytecode")
public String bytecode;
@SerializedName("deployed_bytecode")
public String deployedBytecode;
@SerializedName("size")
public Integer size;
@SerializedName("is_contract")
public Boolean isContract;
}
public static class VerifyContractOptions {
public String address;
public String sourceCode;
public String compilerVersion;
public String constructorArgs;
public Boolean optimization;
public Integer optimizationRuns;
public String license;
}
public static class VerificationResult {
@SerializedName("verified")
public Boolean verified;
@SerializedName("address")
public String address;
@SerializedName("compiler_version")
public String compilerVersion;
@SerializedName("optimization")
public Boolean optimization;
@SerializedName("optimization_runs")
public Integer optimizationRuns;
@SerializedName("license")
public String license;
@SerializedName("abi")
public List<AbiEntry> abi;
@SerializedName("source_code")
public String sourceCode;
}
// ==================== Multicall ====================
public static class MulticallRequest {
public String contract;
public String method;
public Object[] args;
public List<AbiEntry> abi;
}
public static class MulticallResult {
@SerializedName("success")
public Boolean success;
@SerializedName("result")
public Object result;
@SerializedName("error")
public String error;
}
// ==================== Storage ====================
public static class ReadStorageOptions {
public String contract;
public String slot;
public Long blockNumber;
}
}

View file

@ -0,0 +1,273 @@
package io.synor.privacy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import okhttp3.*;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Synor Privacy SDK client for Java.
* Confidential transactions, ring signatures, stealth addresses, and cryptographic commitments.
*/
public class PrivacyClient implements AutoCloseable {
private static final String SDK_VERSION = "0.1.0";
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private final PrivacyConfig config;
private final OkHttpClient httpClient;
private final Gson gson;
private final AtomicBoolean closed = new AtomicBoolean(false);
public PrivacyClient(PrivacyConfig config) {
this.config = config;
this.gson = new GsonBuilder().create();
this.httpClient = new OkHttpClient.Builder()
.connectTimeout(config.getTimeoutMs(), TimeUnit.MILLISECONDS)
.readTimeout(config.getTimeoutMs(), TimeUnit.MILLISECONDS)
.writeTimeout(config.getTimeoutMs(), TimeUnit.MILLISECONDS)
.build();
}
// ==================== Confidential Transactions ====================
public CompletableFuture<ConfidentialTransaction> createConfidentialTx(ConfidentialTxInput[] inputs, ConfidentialTxOutput[] outputs) {
JsonObject body = new JsonObject();
body.add("inputs", gson.toJsonTree(inputs));
body.add("outputs", gson.toJsonTree(outputs));
return post("/privacy/confidential/create", body, ConfidentialTransaction.class);
}
public CompletableFuture<Boolean> verifyConfidentialTx(ConfidentialTransaction tx) {
JsonObject body = new JsonObject();
body.add("transaction", gson.toJsonTree(tx));
return post("/privacy/confidential/verify", body, JsonObject.class)
.thenApply(response -> response.get("valid").getAsBoolean());
}
public CompletableFuture<Commitment> createCommitment(String value, String blindingFactor) {
JsonObject body = new JsonObject();
body.addProperty("value", value);
body.addProperty("blinding_factor", blindingFactor);
return post("/privacy/commitment/create", body, Commitment.class);
}
public CompletableFuture<Boolean> verifyCommitment(String commitment, String value, String blindingFactor) {
JsonObject body = new JsonObject();
body.addProperty("commitment", commitment);
body.addProperty("value", value);
body.addProperty("blinding_factor", blindingFactor);
return post("/privacy/commitment/verify", body, JsonObject.class)
.thenApply(response -> response.get("valid").getAsBoolean());
}
public CompletableFuture<RangeProof> createRangeProof(String value, String blindingFactor, long minValue, long maxValue) {
JsonObject body = new JsonObject();
body.addProperty("value", value);
body.addProperty("blinding_factor", blindingFactor);
body.addProperty("min_value", minValue);
body.addProperty("max_value", maxValue);
return post("/privacy/range-proof/create", body, RangeProof.class);
}
public CompletableFuture<Boolean> verifyRangeProof(RangeProof proof) {
JsonObject body = new JsonObject();
body.add("proof", gson.toJsonTree(proof));
return post("/privacy/range-proof/verify", body, JsonObject.class)
.thenApply(response -> response.get("valid").getAsBoolean());
}
// ==================== Ring Signatures ====================
public CompletableFuture<RingSignature> createRingSignature(String message, String[] ring, int signerIndex, String privateKey) {
JsonObject body = new JsonObject();
body.addProperty("message", message);
body.add("ring", gson.toJsonTree(ring));
body.addProperty("signer_index", signerIndex);
body.addProperty("private_key", privateKey);
return post("/privacy/ring/sign", body, RingSignature.class);
}
public CompletableFuture<Boolean> verifyRingSignature(RingSignature signature, String message) {
JsonObject body = new JsonObject();
body.add("signature", gson.toJsonTree(signature));
body.addProperty("message", message);
return post("/privacy/ring/verify", body, JsonObject.class)
.thenApply(response -> response.get("valid").getAsBoolean());
}
public CompletableFuture<String[]> generateDecoys(int count, String excludeKey) {
String path = "/privacy/ring/decoys?count=" + count;
if (excludeKey != null) {
path += "&exclude=" + excludeKey;
}
return get(path, new TypeToken<String[]>(){}.getType());
}
public CompletableFuture<Boolean> checkKeyImage(String keyImage) {
return get("/privacy/ring/key-image/" + keyImage, JsonObject.class)
.thenApply(response -> response.get("spent").getAsBoolean());
}
// ==================== Stealth Addresses ====================
public CompletableFuture<StealthKeyPair> generateStealthKeyPair() {
return post("/privacy/stealth/generate", new JsonObject(), StealthKeyPair.class);
}
public CompletableFuture<StealthAddress> deriveStealthAddress(String spendPublicKey, String viewPublicKey) {
JsonObject body = new JsonObject();
body.addProperty("spend_public_key", spendPublicKey);
body.addProperty("view_public_key", viewPublicKey);
return post("/privacy/stealth/derive", body, StealthAddress.class);
}
public CompletableFuture<String> recoverStealthPrivateKey(String stealthAddress, String viewPrivateKey, String spendPrivateKey) {
JsonObject body = new JsonObject();
body.addProperty("stealth_address", stealthAddress);
body.addProperty("view_private_key", viewPrivateKey);
body.addProperty("spend_private_key", spendPrivateKey);
return post("/privacy/stealth/recover", body, JsonObject.class)
.thenApply(response -> response.get("private_key").getAsString());
}
public CompletableFuture<StealthOutput[]> scanOutputs(String viewPrivateKey, String spendPublicKey, long fromBlock, Long toBlock) {
JsonObject body = new JsonObject();
body.addProperty("view_private_key", viewPrivateKey);
body.addProperty("spend_public_key", spendPublicKey);
body.addProperty("from_block", fromBlock);
if (toBlock != null) {
body.addProperty("to_block", toBlock);
}
return post("/privacy/stealth/scan", body, StealthOutput[].class);
}
// ==================== Blinding ====================
public CompletableFuture<String> generateBlindingFactor() {
return post("/privacy/blinding/generate", new JsonObject(), JsonObject.class)
.thenApply(response -> response.get("blinding_factor").getAsString());
}
public CompletableFuture<String> blindValue(String value, String blindingFactor) {
JsonObject body = new JsonObject();
body.addProperty("value", value);
body.addProperty("blinding_factor", blindingFactor);
return post("/privacy/blinding/blind", body, JsonObject.class)
.thenApply(response -> response.get("blinded_value").getAsString());
}
public CompletableFuture<String> unblindValue(String blindedValue, String blindingFactor) {
JsonObject body = new JsonObject();
body.addProperty("blinded_value", blindedValue);
body.addProperty("blinding_factor", blindingFactor);
return post("/privacy/blinding/unblind", body, JsonObject.class)
.thenApply(response -> response.get("value").getAsString());
}
// ==================== Lifecycle ====================
public CompletableFuture<Boolean> healthCheck() {
if (closed.get()) {
return CompletableFuture.completedFuture(false);
}
return get("/health", JsonObject.class)
.thenApply(response -> "healthy".equals(response.get("status").getAsString()))
.exceptionally(e -> false);
}
@Override
public void close() {
closed.set(true);
httpClient.dispatcher().executorService().shutdown();
httpClient.connectionPool().evictAll();
}
public boolean isClosed() {
return closed.get();
}
// ==================== HTTP Helpers ====================
private <T> CompletableFuture<T> get(String path, Type type) {
return executeRequest(path, null, type);
}
private <T> CompletableFuture<T> post(String path, JsonObject body, Type type) {
return executeRequest(path, body, type);
}
private <T> CompletableFuture<T> executeRequest(String path, JsonObject body, Type type) {
if (closed.get()) {
CompletableFuture<T> future = new CompletableFuture<>();
future.completeExceptionally(new PrivacyException("Client has been closed"));
return future;
}
CompletableFuture<T> future = new CompletableFuture<>();
Request.Builder requestBuilder = new Request.Builder()
.url(config.getEndpoint() + path)
.header("Authorization", "Bearer " + config.getApiKey())
.header("Content-Type", "application/json")
.header("X-SDK-Version", "java/" + SDK_VERSION);
if (body != null) {
requestBuilder.post(RequestBody.create(gson.toJson(body), JSON));
} else {
requestBuilder.get();
}
executeWithRetry(requestBuilder.build(), config.getRetries(), 0, future, type);
return future;
}
private <T> void executeWithRetry(Request request, int remainingRetries, int attempt, CompletableFuture<T> future, Type type) {
httpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
if (remainingRetries > 1) {
scheduleRetry(request, remainingRetries - 1, attempt + 1, future, type);
} else {
future.completeExceptionally(new PrivacyException("Request failed: " + e.getMessage(), e));
}
}
@Override
public void onResponse(Call call, Response response) throws IOException {
try (ResponseBody responseBody = response.body()) {
String bodyString = responseBody != null ? responseBody.string() : "";
if (response.isSuccessful()) {
T result = gson.fromJson(bodyString, type);
future.complete(result);
} else {
JsonObject errorObj = gson.fromJson(bodyString, JsonObject.class);
String message = errorObj.has("message") ? errorObj.get("message").getAsString() : "Unknown error";
String code = errorObj.has("code") ? errorObj.get("code").getAsString() : null;
if (remainingRetries > 1 && response.code() >= 500) {
scheduleRetry(request, remainingRetries - 1, attempt + 1, future, type);
} else {
future.completeExceptionally(new PrivacyException(message, response.code(), code));
}
}
}
}
});
}
private <T> void scheduleRetry(Request request, int remainingRetries, int attempt, CompletableFuture<T> future, Type type) {
long delay = (long) Math.pow(2, attempt) * 1000;
CompletableFuture.delayedExecutor(delay, TimeUnit.MILLISECONDS)
.execute(() -> executeWithRetry(request, remainingRetries, attempt, future, type));
}
}

View file

@ -0,0 +1,49 @@
package io.synor.privacy;
/**
* Configuration for the Privacy SDK client.
*/
public class PrivacyConfig {
private final String apiKey;
private String endpoint = "https://privacy.synor.io";
private long timeoutMs = 30000;
private int retries = 3;
public PrivacyConfig(String apiKey) {
if (apiKey == null || apiKey.isEmpty()) {
throw new IllegalArgumentException("API key is required");
}
this.apiKey = apiKey;
}
public PrivacyConfig endpoint(String endpoint) {
this.endpoint = endpoint;
return this;
}
public PrivacyConfig timeoutMs(long timeoutMs) {
this.timeoutMs = timeoutMs;
return this;
}
public PrivacyConfig retries(int retries) {
this.retries = retries;
return this;
}
public String getApiKey() {
return apiKey;
}
public String getEndpoint() {
return endpoint;
}
public long getTimeoutMs() {
return timeoutMs;
}
public int getRetries() {
return retries;
}
}

View file

@ -0,0 +1,35 @@
package io.synor.privacy;
/**
* Exception thrown by the Privacy SDK.
*/
public class PrivacyException extends RuntimeException {
private final Integer statusCode;
private final String errorCode;
public PrivacyException(String message) {
super(message);
this.statusCode = null;
this.errorCode = null;
}
public PrivacyException(String message, Throwable cause) {
super(message, cause);
this.statusCode = null;
this.errorCode = null;
}
public PrivacyException(String message, int statusCode, String errorCode) {
super(message);
this.statusCode = statusCode;
this.errorCode = errorCode;
}
public Integer getStatusCode() {
return statusCode;
}
public String getErrorCode() {
return errorCode;
}
}

View file

@ -0,0 +1,149 @@
package io.synor.privacy;
import com.google.gson.annotations.SerializedName;
/**
* Privacy SDK type definitions.
*/
public class PrivacyTypes {
// ==================== Confidential Transactions ====================
public static class ConfidentialTxInput {
@SerializedName("commitment")
public String commitment;
@SerializedName("blinding_factor")
public String blindingFactor;
@SerializedName("value")
public String value;
@SerializedName("key_image")
public String keyImage;
}
public static class ConfidentialTxOutput {
@SerializedName("commitment")
public String commitment;
@SerializedName("blinding_factor")
public String blindingFactor;
@SerializedName("value")
public String value;
@SerializedName("recipient_public_key")
public String recipientPublicKey;
@SerializedName("range_proof")
public String rangeProof;
}
public static class ConfidentialTransaction {
@SerializedName("id")
public String id;
@SerializedName("inputs")
public ConfidentialTxInput[] inputs;
@SerializedName("outputs")
public ConfidentialTxOutput[] outputs;
@SerializedName("fee")
public String fee;
@SerializedName("excess")
public String excess;
@SerializedName("excess_sig")
public String excessSig;
@SerializedName("kernel_offset")
public String kernelOffset;
}
// ==================== Commitments ====================
public static class Commitment {
@SerializedName("commitment")
public String commitment;
@SerializedName("blinding_factor")
public String blindingFactor;
}
public static class RangeProof {
@SerializedName("proof")
public String proof;
@SerializedName("commitment")
public String commitment;
@SerializedName("min_value")
public long minValue;
@SerializedName("max_value")
public long maxValue;
}
// ==================== Ring Signatures ====================
public static class RingSignature {
@SerializedName("c0")
public String c0;
@SerializedName("s")
public String[] s;
@SerializedName("key_image")
public String keyImage;
@SerializedName("ring")
public String[] ring;
}
// ==================== Stealth Addresses ====================
public static class StealthKeyPair {
@SerializedName("spend_public_key")
public String spendPublicKey;
@SerializedName("spend_private_key")
public String spendPrivateKey;
@SerializedName("view_public_key")
public String viewPublicKey;
@SerializedName("view_private_key")
public String viewPrivateKey;
}
public static class StealthAddress {
@SerializedName("address")
public String address;
@SerializedName("ephemeral_public_key")
public String ephemeralPublicKey;
@SerializedName("tx_public_key")
public String txPublicKey;
}
public static class StealthOutput {
@SerializedName("tx_hash")
public String txHash;
@SerializedName("output_index")
public int outputIndex;
@SerializedName("stealth_address")
public String stealthAddress;
@SerializedName("amount")
public String amount;
@SerializedName("block_height")
public long blockHeight;
}
}

View file

@ -0,0 +1,583 @@
/**
* Synor Contract Client
*/
import type {
ContractConfig,
Abi,
AbiEntry,
DeployContractOptions,
DeploymentResult,
CallContractOptions,
SendContractOptions,
TransactionResult,
EventLog,
DecodedEvent,
EventFilter,
EventCallback,
EventSubscription,
ContractInterface,
EncodeCallOptions,
DecodeResultOptions,
EstimateGasOptions,
GasEstimation,
BytecodeInfo,
VerifyContractOptions,
VerificationResult,
MulticallRequest,
MulticallResult,
ReadStorageOptions,
} from './types';
const DEFAULT_ENDPOINT = 'https://contract.synor.cc/api/v1';
/**
* Synor Contract SDK error.
*/
export class ContractError extends Error {
constructor(
message: string,
public statusCode?: number,
public code?: string
) {
super(message);
this.name = 'ContractError';
}
}
/**
* Main Synor Contract client.
*
* Provides smart contract deployment, interaction, and event handling.
*
* @example
* ```ts
* const contract = new SynorContract({ apiKey: 'sk_...' });
*
* // Deploy a contract
* const result = await contract.deploy({
* bytecode: '0x608060...',
* abi: [...],
* args: [100, 'Token'],
* });
*
* // Call a view function
* const balance = await contract.call({
* address: result.address,
* method: 'balanceOf',
* args: ['0x...'],
* abi: [...],
* });
*
* // Send a transaction
* const tx = await contract.send({
* address: result.address,
* method: 'transfer',
* args: ['0x...', '1000000'],
* abi: [...],
* });
* ```
*/
export class SynorContract {
private config: Required<Omit<ContractConfig, 'defaultGasLimit' | 'defaultGasPrice'>> & {
defaultGasLimit?: string;
defaultGasPrice?: string;
};
private closed = false;
private subscriptions = new Map<string, WebSocket>();
constructor(config: ContractConfig) {
this.config = {
apiKey: config.apiKey,
endpoint: config.endpoint ?? DEFAULT_ENDPOINT,
timeout: config.timeout ?? 30000,
retries: config.retries ?? 3,
debug: config.debug ?? false,
defaultGasLimit: config.defaultGasLimit,
defaultGasPrice: config.defaultGasPrice,
};
}
// ==================== Deployment ====================
/**
* Deploy a smart contract.
*
* @param options - Deployment options
* @returns Deployment result with contract address
*/
async deploy(options: DeployContractOptions): Promise<DeploymentResult> {
const response = await this.request('POST', '/contracts/deploy', {
bytecode: options.bytecode,
abi: options.abi,
args: options.args,
gasLimit: options.gasLimit ?? this.config.defaultGasLimit,
gasPrice: options.gasPrice ?? this.config.defaultGasPrice,
value: options.value,
salt: options.salt,
});
return response.deployment;
}
/**
* Deploy using CREATE2 for deterministic addresses.
*
* @param options - Deployment options (salt required)
* @returns Deployment result
*/
async deployDeterministic(options: DeployContractOptions & { salt: string }): Promise<DeploymentResult> {
return this.deploy(options);
}
/**
* Predict CREATE2 deployment address.
*
* @param bytecode - Contract bytecode
* @param salt - Deployment salt
* @param constructorArgs - Constructor arguments
* @returns Predicted address
*/
async predictAddress(bytecode: string, salt: string, constructorArgs?: unknown[]): Promise<string> {
const response = await this.request('POST', '/contracts/predict-address', {
bytecode,
salt,
constructorArgs,
});
return response.address;
}
// ==================== Contract Interaction ====================
/**
* Call a view/pure function (no transaction).
*
* @param options - Call options
* @returns Function return value
*/
async call(options: CallContractOptions): Promise<unknown> {
const response = await this.request('POST', '/contracts/call', {
address: options.address,
method: options.method,
args: options.args,
abi: options.abi,
blockNumber: options.blockNumber ?? 'latest',
});
return response.result;
}
/**
* Send a state-changing transaction.
*
* @param options - Transaction options
* @returns Transaction result
*/
async send(options: SendContractOptions): Promise<TransactionResult> {
const response = await this.request('POST', '/contracts/send', {
address: options.address,
method: options.method,
args: options.args,
abi: options.abi,
gasLimit: options.gasLimit ?? this.config.defaultGasLimit,
gasPrice: options.gasPrice ?? this.config.defaultGasPrice,
value: options.value,
});
return response.transaction;
}
/**
* Execute multiple calls in a single request.
*
* @param requests - Array of multicall requests
* @param abis - Optional ABIs for decoding (keyed by address)
* @returns Array of results
*/
async multicall(
requests: MulticallRequest[],
abis?: Record<string, Abi>
): Promise<MulticallResult[]> {
const response = await this.request('POST', '/contracts/multicall', {
calls: requests,
abis,
});
return response.results;
}
// ==================== Events ====================
/**
* Get historical events.
*
* @param filter - Event filter
* @returns Array of decoded events
*/
async getEvents(filter: EventFilter): Promise<DecodedEvent[]> {
const response = await this.request('POST', '/contracts/events', {
address: filter.address,
eventName: filter.eventName,
abi: filter.abi,
fromBlock: filter.fromBlock ?? 'earliest',
toBlock: filter.toBlock ?? 'latest',
filter: filter.filter,
});
return response.events;
}
/**
* Get raw event logs.
*
* @param filter - Event filter
* @returns Array of raw logs
*/
async getLogs(filter: EventFilter): Promise<EventLog[]> {
const response = await this.request('POST', '/contracts/logs', {
address: filter.address,
eventName: filter.eventName,
abi: filter.abi,
fromBlock: filter.fromBlock ?? 'earliest',
toBlock: filter.toBlock ?? 'latest',
filter: filter.filter,
});
return response.logs;
}
/**
* Subscribe to contract events.
*
* @param filter - Event filter
* @param callback - Callback for new events
* @returns Subscription handle
*/
async subscribeEvents(filter: EventFilter, callback: EventCallback): Promise<EventSubscription> {
const wsEndpoint = this.config.endpoint.replace('https://', 'wss://').replace('http://', 'ws://');
const ws = new WebSocket(`${wsEndpoint}/contracts/events/subscribe`);
const id = crypto.randomUUID();
this.subscriptions.set(id, ws);
return new Promise((resolve, reject) => {
ws.onopen = () => {
ws.send(
JSON.stringify({
type: 'subscribe',
auth: this.config.apiKey,
filter: {
address: filter.address,
eventName: filter.eventName,
abi: filter.abi,
filter: filter.filter,
},
})
);
resolve({
id,
unsubscribe: async () => {
ws.close();
this.subscriptions.delete(id);
},
});
};
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (data.type === 'event') {
callback(data.event);
}
} catch (e) {
if (this.config.debug) {
console.error('[SynorContract] Failed to parse event:', e);
}
}
};
ws.onerror = (error) => {
reject(new ContractError('WebSocket error'));
};
});
}
/**
* Decode a raw event log.
*
* @param log - Raw log
* @param abi - Contract ABI
* @returns Decoded event
*/
async decodeLog(log: EventLog, abi: Abi): Promise<DecodedEvent> {
const response = await this.request('POST', '/contracts/decode-log', {
log,
abi,
});
return response.event;
}
// ==================== ABI Utilities ====================
/**
* Load and parse a contract ABI.
*
* @param abi - Contract ABI
* @returns Parsed contract interface
*/
loadAbi(abi: Abi): ContractInterface {
const functions: Record<string, AbiEntry> = {};
const events: Record<string, AbiEntry> = {};
const errors: Record<string, AbiEntry> = {};
for (const entry of abi) {
if (entry.type === 'function' && entry.name) {
functions[entry.name] = entry;
} else if (entry.type === 'event' && entry.name) {
events[entry.name] = entry;
} else if (entry.type === 'error' && entry.name) {
errors[entry.name] = entry;
}
}
return { abi, functions, events, errors };
}
/**
* Encode a function call.
*
* @param options - Encoding options
* @returns Encoded calldata (hex)
*/
async encodeCall(options: EncodeCallOptions): Promise<string> {
const response = await this.request('POST', '/contracts/encode', {
method: options.method,
args: options.args,
abi: options.abi,
});
return response.data;
}
/**
* Decode function return data.
*
* @param options - Decoding options
* @returns Decoded result
*/
async decodeResult(options: DecodeResultOptions): Promise<unknown> {
const response = await this.request('POST', '/contracts/decode', {
method: options.method,
data: options.data,
abi: options.abi,
});
return response.result;
}
/**
* Get function selector (first 4 bytes of keccak256 hash).
*
* @param signature - Function signature (e.g., "transfer(address,uint256)")
* @returns Function selector (hex)
*/
async getFunctionSelector(signature: string): Promise<string> {
const response = await this.request('POST', '/contracts/selector', {
signature,
});
return response.selector;
}
// ==================== Gas Estimation ====================
/**
* Estimate gas for a contract call or deployment.
*
* @param options - Estimation options
* @returns Gas estimation
*/
async estimateGas(options: EstimateGasOptions): Promise<GasEstimation> {
const response = await this.request('POST', '/contracts/estimate-gas', {
address: options.address,
method: options.method,
args: options.args,
abi: options.abi,
bytecode: options.bytecode,
value: options.value,
from: options.from,
});
return response.estimation;
}
// ==================== Contract Info ====================
/**
* Get contract bytecode.
*
* @param address - Contract address
* @returns Bytecode info
*/
async getBytecode(address: string): Promise<BytecodeInfo> {
const response = await this.request('GET', `/contracts/${address}/bytecode`);
return response;
}
/**
* Check if an address is a contract.
*
* @param address - Address to check
* @returns True if address has code
*/
async isContract(address: string): Promise<boolean> {
const response = await this.request('GET', `/contracts/${address}/is-contract`);
return response.isContract;
}
/**
* Read contract storage slot.
*
* @param options - Storage read options
* @returns Storage value (hex)
*/
async readStorage(options: ReadStorageOptions): Promise<string> {
const response = await this.request('POST', '/contracts/storage', {
address: options.address,
slot: options.slot,
blockNumber: options.blockNumber ?? 'latest',
});
return response.value;
}
// ==================== Verification ====================
/**
* Submit contract for verification.
*
* @param options - Verification options
* @returns Verification result
*/
async verifyContract(options: VerifyContractOptions): Promise<VerificationResult> {
const response = await this.request('POST', '/contracts/verify', {
address: options.address,
sourceCode: options.sourceCode,
compilerVersion: options.compilerVersion,
constructorArguments: options.constructorArguments,
optimization: options.optimization,
optimizationRuns: options.optimizationRuns,
contractName: options.contractName,
});
return response;
}
/**
* Check verification status.
*
* @param address - Contract address
* @returns Verification result
*/
async getVerificationStatus(address: string): Promise<VerificationResult> {
const response = await this.request('GET', `/contracts/${address}/verification`);
return response;
}
/**
* Get verified contract ABI.
*
* @param address - Contract address
* @returns Contract ABI (if verified)
*/
async getVerifiedAbi(address: string): Promise<Abi | null> {
try {
const response = await this.request('GET', `/contracts/${address}/abi`);
return response.abi;
} catch {
return null;
}
}
// ==================== Lifecycle ====================
/**
* Check if the contract service is healthy.
*
* @returns True if the service is operational
*/
async healthCheck(): Promise<boolean> {
try {
const response = await this.request('GET', '/health');
return response.status === 'healthy';
} catch {
return false;
}
}
/**
* Close the client and release resources.
*/
close(): void {
this.closed = true;
for (const ws of this.subscriptions.values()) {
ws.close();
}
this.subscriptions.clear();
}
/**
* Check if the client has been closed.
*/
isClosed(): boolean {
return this.closed;
}
// ==================== Private ====================
private async request(method: string, path: string, body?: unknown): Promise<any> {
if (this.closed) {
throw new ContractError('Client has been closed');
}
const url = `${this.config.endpoint}${path}`;
if (this.config.debug) {
console.log(`[SynorContract] ${method} ${url}`);
}
let lastError: Error | null = null;
for (let attempt = 0; attempt < this.config.retries; attempt++) {
try {
const response = await fetch(url, {
method,
headers: {
Authorization: `Bearer ${this.config.apiKey}`,
'Content-Type': 'application/json',
'X-SDK-Version': 'js/1.0.0',
},
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 ContractError(error.message || 'Request failed', response.status, error.code);
}
return response.json();
} catch (error) {
lastError = error as Error;
if (attempt < this.config.retries - 1) {
await new Promise((resolve) => setTimeout(resolve, Math.pow(2, attempt) * 1000));
}
}
}
throw lastError;
}
}

View file

@ -0,0 +1,42 @@
/**
* Synor Contract SDK
*
* Smart contract deployment, interaction, and event handling:
* - Deploy contracts (standard and CREATE2)
* - Call view/pure functions
* - Send state-changing transactions
* - Subscribe to events
* - ABI encoding/decoding utilities
*
* @packageDocumentation
*/
export { SynorContract, ContractError } from './client';
export type {
ContractConfig,
AbiEntryType,
AbiParameter,
AbiEntry,
Abi,
DeployContractOptions,
DeploymentResult,
CallContractOptions,
SendContractOptions,
TransactionResult,
EventLog,
DecodedEvent,
EventFilter,
EventCallback,
EventSubscription,
ContractInterface,
EncodeCallOptions,
DecodeResultOptions,
EstimateGasOptions,
GasEstimation,
BytecodeInfo,
VerifyContractOptions,
VerificationResult,
MulticallRequest,
MulticallResult,
ReadStorageOptions,
} from './types';

View file

@ -0,0 +1,332 @@
/**
* Synor Contract SDK Types
*/
/** Contract SDK configuration */
export interface ContractConfig {
/** API key for authentication */
apiKey: string;
/** API endpoint (defaults to production) */
endpoint?: string;
/** Request timeout in milliseconds */
timeout?: number;
/** Number of retries for failed requests */
retries?: number;
/** Enable debug logging */
debug?: boolean;
/** Default gas limit */
defaultGasLimit?: string;
/** Default gas price */
defaultGasPrice?: string;
}
/** ABI entry types */
export type AbiEntryType = 'function' | 'constructor' | 'event' | 'error' | 'fallback' | 'receive';
/** ABI input/output parameter */
export interface AbiParameter {
/** Parameter name */
name: string;
/** Parameter type */
type: string;
/** Indexed (for events) */
indexed?: boolean;
/** Component types (for tuples) */
components?: AbiParameter[];
/** Internal type */
internalType?: string;
}
/** ABI entry */
export interface AbiEntry {
/** Entry type */
type: AbiEntryType;
/** Function/event name */
name?: string;
/** Input parameters */
inputs?: AbiParameter[];
/** Output parameters */
outputs?: AbiParameter[];
/** State mutability */
stateMutability?: 'pure' | 'view' | 'nonpayable' | 'payable';
/** Anonymous (for events) */
anonymous?: boolean;
}
/** Contract ABI */
export type Abi = AbiEntry[];
/** Deploy contract options */
export interface DeployContractOptions {
/** Compiled bytecode (hex) */
bytecode: string;
/** Contract ABI */
abi?: Abi;
/** Constructor arguments */
args?: unknown[];
/** Gas limit */
gasLimit?: string;
/** Gas price */
gasPrice?: string;
/** Value to send with deployment */
value?: string;
/** Salt for deterministic deployment (CREATE2) */
salt?: string;
}
/** Deployment result */
export interface DeploymentResult {
/** Deployed contract address */
address: string;
/** Deployment transaction hash */
transactionHash: string;
/** Block number of deployment */
blockNumber: number;
/** Gas used */
gasUsed: string;
/** Effective gas price */
effectiveGasPrice: string;
}
/** Call contract options */
export interface CallContractOptions {
/** Contract address */
address: string;
/** Method name */
method: string;
/** Method arguments */
args?: unknown[];
/** Contract ABI (required for encoding) */
abi: Abi;
/** Block number or 'latest' */
blockNumber?: number | 'latest';
}
/** Send transaction options */
export interface SendContractOptions {
/** Contract address */
address: string;
/** Method name */
method: string;
/** Method arguments */
args?: unknown[];
/** Contract ABI (required for encoding) */
abi: Abi;
/** Gas limit */
gasLimit?: string;
/** Gas price */
gasPrice?: string;
/** Value to send */
value?: string;
}
/** Transaction result */
export interface TransactionResult {
/** Transaction hash */
transactionHash: string;
/** Block number */
blockNumber: number;
/** Block hash */
blockHash: string;
/** Gas used */
gasUsed: string;
/** Effective gas price */
effectiveGasPrice: string;
/** Transaction status */
status: 'success' | 'reverted';
/** Logs emitted */
logs: EventLog[];
/** Return value (if any) */
returnValue?: unknown;
/** Revert reason (if reverted) */
revertReason?: string;
}
/** Event log */
export interface EventLog {
/** Log index */
logIndex: number;
/** Contract address */
address: string;
/** Raw topics */
topics: string[];
/** Raw data */
data: string;
/** Block number */
blockNumber: number;
/** Transaction hash */
transactionHash: string;
}
/** Decoded event */
export interface DecodedEvent {
/** Event name */
name: string;
/** Event signature */
signature: string;
/** Decoded arguments */
args: Record<string, unknown>;
/** Raw log */
log: EventLog;
}
/** Event filter */
export interface EventFilter {
/** Contract address */
address: string;
/** Event name (optional) */
eventName?: string;
/** Contract ABI (required for decoding) */
abi?: Abi;
/** From block */
fromBlock?: number | 'earliest';
/** To block */
toBlock?: number | 'latest';
/** Indexed filter values */
filter?: Record<string, unknown>;
}
/** Event subscription callback */
export type EventCallback = (event: DecodedEvent) => void;
/** Event subscription */
export interface EventSubscription {
/** Subscription ID */
id: string;
/** Unsubscribe */
unsubscribe: () => Promise<void>;
}
/** Contract interface (from ABI) */
export interface ContractInterface {
/** Contract ABI */
abi: Abi;
/** Function signatures */
functions: Record<string, AbiEntry>;
/** Event signatures */
events: Record<string, AbiEntry>;
/** Error signatures */
errors: Record<string, AbiEntry>;
}
/** Encode call options */
export interface EncodeCallOptions {
/** Method name */
method: string;
/** Arguments */
args: unknown[];
/** Contract ABI */
abi: Abi;
}
/** Decode result options */
export interface DecodeResultOptions {
/** Method name */
method: string;
/** Raw data to decode */
data: string;
/** Contract ABI */
abi: Abi;
}
/** Gas estimation options */
export interface EstimateGasOptions {
/** Contract address (for calls) */
address?: string;
/** Method name */
method?: string;
/** Arguments */
args?: unknown[];
/** Contract ABI */
abi?: Abi;
/** Bytecode (for deployment) */
bytecode?: string;
/** Value to send */
value?: string;
/** Sender address */
from?: string;
}
/** Gas estimation result */
export interface GasEstimation {
/** Estimated gas units */
gasLimit: string;
/** Current gas price */
gasPrice: string;
/** Estimated cost */
estimatedCost: string;
}
/** Contract bytecode info */
export interface BytecodeInfo {
/** Raw bytecode */
bytecode: string;
/** Deployed bytecode (without constructor) */
deployedBytecode?: string;
/** ABI */
abi?: Abi;
/** Source metadata */
metadata?: {
compiler: string;
language: string;
sources: string[];
};
}
/** Contract verification options */
export interface VerifyContractOptions {
/** Contract address */
address: string;
/** Source code */
sourceCode: string;
/** Compiler version */
compilerVersion: string;
/** Constructor arguments (hex) */
constructorArguments?: string;
/** Optimization enabled */
optimization?: boolean;
/** Optimization runs */
optimizationRuns?: number;
/** Contract name */
contractName?: string;
}
/** Contract verification result */
export interface VerificationResult {
/** Verification status */
status: 'verified' | 'pending' | 'failed';
/** Verification message */
message: string;
/** ABI (if verified) */
abi?: Abi;
}
/** Multicall request */
export interface MulticallRequest {
/** Contract address */
address: string;
/** Encoded call data */
callData: string;
/** Allow failure */
allowFailure?: boolean;
}
/** Multicall result */
export interface MulticallResult {
/** Success status */
success: boolean;
/** Return data */
returnData: string;
/** Decoded result (if ABI provided) */
decoded?: unknown;
}
/** Storage read options */
export interface ReadStorageOptions {
/** Contract address */
address: string;
/** Storage slot */
slot: string;
/** Block number */
blockNumber?: number | 'latest';
}

View file

@ -0,0 +1,530 @@
/**
* Synor Privacy Client
*/
import type {
PrivacyConfig,
ConfidentialUTXO,
ConfidentialOutput,
ConfidentialTransaction,
CreateConfidentialTxOptions,
VerifyConfidentialTxResult,
RingSignature,
CreateRingSignatureOptions,
VerifyRingSignatureOptions,
VerifyRingSignatureResult,
StealthAddress,
StealthKeypair,
OneTimeAddress,
DeriveSharedSecretOptions,
SharedSecret,
Commitment,
CreateCommitmentOptions,
CommitmentWithBlinding,
OpenCommitmentOptions,
RangeProof,
CreateRangeProofOptions,
VerifyRangeProofResult,
BlindingSumOptions,
} from './types';
const DEFAULT_ENDPOINT = 'https://privacy.synor.cc/api/v1';
/**
* Synor Privacy SDK error.
*/
export class PrivacyError extends Error {
constructor(
message: string,
public statusCode?: number,
public code?: string
) {
super(message);
this.name = 'PrivacyError';
}
}
/**
* Main Synor Privacy client.
*
* Provides privacy-enhancing features including confidential transactions,
* ring signatures, stealth addresses, and Pedersen commitments.
*
* @example
* ```ts
* const privacy = new SynorPrivacy({ apiKey: 'sk_...' });
*
* // Generate a stealth address for receiving private payments
* const keypair = await privacy.generateStealthKeypair();
* console.log('Stealth address:', keypair.address.address);
*
* // Create a ring signature for anonymous signing
* const signature = await privacy.createRingSignature({
* message: 'Hello, World!',
* ring: [pubKey1, pubKey2, myPubKey, pubKey3],
* privateKey: myPrivateKey,
* });
* ```
*/
export class SynorPrivacy {
private config: Required<PrivacyConfig>;
private closed = false;
constructor(config: PrivacyConfig) {
this.config = {
apiKey: config.apiKey,
endpoint: config.endpoint ?? DEFAULT_ENDPOINT,
timeout: config.timeout ?? 30000,
retries: config.retries ?? 3,
debug: config.debug ?? false,
};
}
// ==================== Confidential Transactions ====================
/**
* Create a confidential transaction with hidden amounts.
*
* Uses Pedersen commitments and Bulletproof range proofs to hide
* transaction amounts while proving they are valid (non-negative).
*
* @param options - Transaction options
* @returns Confidential transaction ready for broadcast
*/
async createConfidentialTransaction(
options: CreateConfidentialTxOptions
): Promise<ConfidentialTransaction> {
const response = await this.request('POST', '/transactions/confidential', {
inputs: options.inputs.map((input) => ({
txid: input.txid,
vout: input.vout,
commitment: input.commitment,
rangeProof: input.rangeProof,
blinding: input.blinding,
amount: input.amount,
})),
outputs: options.outputs.map((output) => ({
recipient: output.recipient,
amount: output.amount,
blinding: output.blinding,
})),
fee: options.fee,
lockHeight: options.lockHeight,
});
return response.transaction;
}
/**
* Verify a confidential transaction.
*
* Checks that:
* - Commitments balance (inputs = outputs + fee)
* - All range proofs are valid (no negative amounts)
* - Kernel signature is valid
* - No duplicate inputs
*
* @param transaction - Transaction to verify
* @returns Verification result with details
*/
async verifyConfidentialTransaction(
transaction: ConfidentialTransaction
): Promise<VerifyConfidentialTxResult> {
const response = await this.request('POST', '/transactions/confidential/verify', {
transaction,
});
return response;
}
/**
* Decode a confidential transaction output.
*
* If you have the blinding factor, you can decode the committed amount.
*
* @param commitment - Output commitment
* @param blinding - Blinding factor
* @returns Decoded amount
*/
async decodeConfidentialOutput(commitment: string, blinding: string): Promise<string> {
const response = await this.request('POST', '/transactions/confidential/decode', {
commitment,
blinding,
});
return response.amount;
}
// ==================== Ring Signatures ====================
/**
* Create a ring signature.
*
* Ring signatures allow signing a message as "one of N" without
* revealing which member of the ring actually signed.
*
* @param options - Signing options
* @returns Ring signature
*/
async createRingSignature(options: CreateRingSignatureOptions): Promise<RingSignature> {
const response = await this.request('POST', '/ring-signatures/create', {
message: options.message,
ring: options.ring,
privateKey: options.privateKey,
signerIndex: options.signerIndex,
});
return response.signature;
}
/**
* Verify a ring signature.
*
* Verifies that the signature was created by one of the ring members
* without revealing which one.
*
* @param options - Verification options
* @returns Verification result
*/
async verifyRingSignature(options: VerifyRingSignatureOptions): Promise<VerifyRingSignatureResult> {
const response = await this.request('POST', '/ring-signatures/verify', {
signature: options.signature,
message: options.message,
});
return response;
}
/**
* Check if a key image has been used (double-spend detection).
*
* @param keyImage - Key image from ring signature
* @returns True if the key image has been seen before
*/
async isKeyImageUsed(keyImage: string): Promise<boolean> {
const response = await this.request('GET', `/ring-signatures/key-images/${keyImage}`);
return response.used;
}
/**
* Generate a random ring from available public keys.
*
* Useful for creating decoy sets for ring signatures.
*
* @param size - Desired ring size
* @param exclude - Public keys to exclude (e.g., your own)
* @returns Array of public keys for the ring
*/
async generateRandomRing(size: number, exclude?: string[]): Promise<string[]> {
const response = await this.request('POST', '/ring-signatures/random-ring', {
size,
exclude,
});
return response.ring;
}
// ==================== Stealth Addresses ====================
/**
* Generate a new stealth address keypair.
*
* Stealth addresses allow receiving payments at unique one-time
* addresses that cannot be linked to the recipient's public address.
*
* @returns Stealth keypair (keep private keys secure!)
*/
async generateStealthKeypair(): Promise<StealthKeypair> {
const response = await this.request('POST', '/stealth/generate');
return response.keypair;
}
/**
* Create a one-time address for sending to a stealth address.
*
* The sender creates this address using the recipient's stealth
* address. Only the recipient can detect and spend from it.
*
* @param stealthAddress - Recipient's stealth address
* @returns One-time address and ephemeral key
*/
async createOneTimeAddress(stealthAddress: StealthAddress): Promise<OneTimeAddress> {
const response = await this.request('POST', '/stealth/one-time-address', {
scanPublicKey: stealthAddress.scanPublicKey,
spendPublicKey: stealthAddress.spendPublicKey,
});
return response.oneTimeAddress;
}
/**
* Derive the shared secret for a stealth payment.
*
* The recipient uses this to derive the one-time private key
* needed to spend from the one-time address.
*
* @param options - Derivation options
* @returns Shared secret and derived keys
*/
async deriveSharedSecret(options: DeriveSharedSecretOptions): Promise<SharedSecret> {
const response = await this.request('POST', '/stealth/derive-secret', {
scanPublicKey: options.stealthAddress.scanPublicKey,
spendPublicKey: options.stealthAddress.spendPublicKey,
privateKey: options.privateKey,
ephemeralPublicKey: options.ephemeralPublicKey,
});
return response;
}
/**
* Scan transactions to find stealth payments to you.
*
* @param scanPrivateKey - Your scan private key
* @param spendPublicKey - Your spend public key
* @param transactions - Transaction IDs to scan
* @returns Found payments with one-time addresses
*/
async scanForPayments(
scanPrivateKey: string,
spendPublicKey: string,
transactions: string[]
): Promise<OneTimeAddress[]> {
const response = await this.request('POST', '/stealth/scan', {
scanPrivateKey,
spendPublicKey,
transactions,
});
return response.payments;
}
// ==================== Pedersen Commitments ====================
/**
* Create a Pedersen commitment.
*
* C = vG + bH where v is the value and b is the blinding factor.
*
* @param options - Commitment options
* @returns Commitment and blinding factor
*/
async createCommitment(options: CreateCommitmentOptions): Promise<CommitmentWithBlinding> {
const response = await this.request('POST', '/commitments/create', {
value: options.value,
blinding: options.blinding,
});
return response;
}
/**
* Open (verify) a Pedersen commitment.
*
* Verifies that the commitment commits to the claimed value.
*
* @param options - Opening options
* @returns True if the commitment opens correctly
*/
async openCommitment(options: OpenCommitmentOptions): Promise<boolean> {
const response = await this.request('POST', '/commitments/open', {
commitment: options.commitment,
value: options.value,
blinding: options.blinding,
});
return response.valid;
}
/**
* Add two commitments.
*
* C1 + C2 = C3 where the values and blindings also add.
*
* @param commitment1 - First commitment
* @param commitment2 - Second commitment
* @returns Sum commitment
*/
async addCommitments(commitment1: string, commitment2: string): Promise<string> {
const response = await this.request('POST', '/commitments/add', {
commitment1,
commitment2,
});
return response.commitment;
}
/**
* Subtract two commitments.
*
* @param commitment1 - First commitment
* @param commitment2 - Second commitment
* @returns Difference commitment
*/
async subtractCommitments(commitment1: string, commitment2: string): Promise<string> {
const response = await this.request('POST', '/commitments/subtract', {
commitment1,
commitment2,
});
return response.commitment;
}
/**
* Compute blinding factor sum for transaction balancing.
*
* For a balanced transaction: sum(input blindings) - sum(output blindings) = kernel blinding
*
* @param options - Blinding factors
* @returns Net blinding factor
*/
async computeBlindingSum(options: BlindingSumOptions): Promise<string> {
const response = await this.request('POST', '/commitments/blinding-sum', {
positive: options.positive,
negative: options.negative,
});
return response.sum;
}
/**
* Generate a random blinding factor.
*
* @returns Random 32-byte blinding factor (hex)
*/
async generateBlinding(): Promise<string> {
const response = await this.request('POST', '/commitments/random-blinding');
return response.blinding;
}
// ==================== Range Proofs ====================
/**
* Create a Bulletproof range proof.
*
* Proves that a committed value is in range [0, 2^n) without
* revealing the actual value.
*
* @param options - Range proof options
* @returns Range proof
*/
async createRangeProof(options: CreateRangeProofOptions): Promise<RangeProof> {
const response = await this.request('POST', '/range-proofs/create', {
value: options.value,
blinding: options.blinding,
message: options.message,
bitLength: options.bitLength ?? 64,
});
return response.proof;
}
/**
* Verify a Bulletproof range proof.
*
* @param commitment - Commitment the proof is for
* @param proof - Range proof data
* @returns Verification result
*/
async verifyRangeProof(commitment: string, proof: string): Promise<VerifyRangeProofResult> {
const response = await this.request('POST', '/range-proofs/verify', {
commitment,
proof,
});
return response;
}
/**
* Create an aggregated range proof for multiple outputs.
*
* More efficient than individual proofs for multiple outputs.
*
* @param outputs - Array of {value, blinding} pairs
* @returns Aggregated proof
*/
async createAggregatedRangeProof(
outputs: Array<{ value: string; blinding: string }>
): Promise<string> {
const response = await this.request('POST', '/range-proofs/aggregate', {
outputs,
});
return response.proof;
}
// ==================== Lifecycle ====================
/**
* Check if the privacy service is healthy.
*
* @returns True if the service is operational
*/
async healthCheck(): Promise<boolean> {
try {
const response = await this.request('GET', '/health');
return response.status === 'healthy';
} catch {
return false;
}
}
/**
* Close the client and release resources.
*/
close(): void {
this.closed = true;
}
/**
* Check if the client has been closed.
*/
isClosed(): boolean {
return this.closed;
}
// ==================== Private ====================
private async request(method: string, path: string, body?: unknown): Promise<any> {
if (this.closed) {
throw new PrivacyError('Client has been closed');
}
const url = `${this.config.endpoint}${path}`;
if (this.config.debug) {
console.log(`[SynorPrivacy] ${method} ${url}`);
}
let lastError: Error | null = null;
for (let attempt = 0; attempt < this.config.retries; attempt++) {
try {
const response = await fetch(url, {
method,
headers: {
Authorization: `Bearer ${this.config.apiKey}`,
'Content-Type': 'application/json',
'X-SDK-Version': 'js/1.0.0',
},
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 PrivacyError(error.message || 'Request failed', response.status, error.code);
}
return response.json();
} catch (error) {
lastError = error as Error;
if (attempt < this.config.retries - 1) {
await new Promise((resolve) => setTimeout(resolve, Math.pow(2, attempt) * 1000));
}
}
}
throw lastError;
}
}

View file

@ -0,0 +1,44 @@
/**
* Synor Privacy SDK
*
* Privacy-enhancing features for the Synor blockchain:
* - Confidential transactions with hidden amounts
* - Ring signatures for anonymous signing
* - Stealth addresses for unlinkable payments
* - Pedersen commitments and Bulletproof range proofs
*
* @packageDocumentation
*/
export { SynorPrivacy, PrivacyError } from './client';
export type {
PrivacyConfig,
PublicKey,
PrivateKey,
ConfidentialUTXO,
ConfidentialOutput,
ConfidentialTransaction,
ConfidentialTxInput,
ConfidentialTxOutput,
OutputFeatures,
TransactionKernel,
CreateConfidentialTxOptions,
VerifyConfidentialTxResult,
RingSignature,
CreateRingSignatureOptions,
VerifyRingSignatureOptions,
VerifyRingSignatureResult,
StealthAddress,
StealthKeypair,
OneTimeAddress,
DeriveSharedSecretOptions,
SharedSecret,
Commitment,
CreateCommitmentOptions,
CommitmentWithBlinding,
OpenCommitmentOptions,
RangeProof,
CreateRangeProofOptions,
VerifyRangeProofResult,
BlindingSumOptions,
} from './types';

321
sdk/js/src/privacy/types.ts Normal file
View file

@ -0,0 +1,321 @@
/**
* Synor Privacy SDK Types
*/
/** Privacy SDK configuration */
export interface PrivacyConfig {
/** API key for authentication */
apiKey: string;
/** API endpoint (defaults to production) */
endpoint?: string;
/** Request timeout in milliseconds */
timeout?: number;
/** Number of retries for failed requests */
retries?: number;
/** Enable debug logging */
debug?: boolean;
}
/** Public key representation */
export interface PublicKey {
/** Hex-encoded public key */
key: string;
/** Key type */
type: 'ed25519' | 'secp256k1';
}
/** Private key representation (for signing operations) */
export interface PrivateKey {
/** Hex-encoded private key */
key: string;
/** Key type */
type: 'ed25519' | 'secp256k1';
}
/** UTXO for confidential transaction input */
export interface ConfidentialUTXO {
/** Transaction hash */
txid: string;
/** Output index */
vout: number;
/** Commitment to the amount */
commitment: string;
/** Range proof */
rangeProof: string;
/** Blinding factor (private) */
blinding?: string;
/** Clear amount (private, known only to owner) */
amount?: string;
}
/** Output for confidential transaction */
export interface ConfidentialOutput {
/** Recipient stealth address or public key */
recipient: string;
/** Amount to send */
amount: string;
/** Optional blinding factor (generated if not provided) */
blinding?: string;
}
/** Confidential transaction */
export interface ConfidentialTransaction {
/** Transaction ID */
txid: string;
/** Version */
version: number;
/** Inputs with commitments */
inputs: ConfidentialTxInput[];
/** Outputs with commitments */
outputs: ConfidentialTxOutput[];
/** Kernel with signature */
kernel: TransactionKernel;
/** Transaction offset for signature aggregation */
offset: string;
/** Raw transaction hex */
raw: string;
}
/** Confidential transaction input */
export interface ConfidentialTxInput {
/** Reference to previous output */
outputRef: string;
/** Commitment */
commitment: string;
}
/** Confidential transaction output */
export interface ConfidentialTxOutput {
/** Pedersen commitment to the amount */
commitment: string;
/** Bulletproof range proof */
rangeProof: string;
/** Output features */
features: OutputFeatures;
}
/** Output features for confidential transactions */
export interface OutputFeatures {
/** Feature flags */
flags: number;
/** Lock height (if time-locked) */
lockHeight?: number;
}
/** Transaction kernel for confidential transactions */
export interface TransactionKernel {
/** Kernel features */
features: number;
/** Transaction fee */
fee: string;
/** Lock height */
lockHeight: number;
/** Excess commitment */
excess: string;
/** Excess signature */
excessSignature: string;
}
/** Create confidential transaction options */
export interface CreateConfidentialTxOptions {
/** Input UTXOs */
inputs: ConfidentialUTXO[];
/** Outputs to create */
outputs: ConfidentialOutput[];
/** Transaction fee */
fee?: string;
/** Lock height */
lockHeight?: number;
}
/** Confidential transaction verification result */
export interface VerifyConfidentialTxResult {
/** Whether the transaction is valid */
valid: boolean;
/** Verification details */
details: {
/** Commitments balance (inputs = outputs + fee) */
commitmentsBalance: boolean;
/** All range proofs are valid */
rangeProofsValid: boolean;
/** Kernel signature is valid */
signatureValid: boolean;
/** No duplicate inputs */
noDuplicateInputs: boolean;
};
/** Error message if invalid */
error?: string;
}
/** Ring signature */
export interface RingSignature {
/** Signature ID */
id: string;
/** Message hash that was signed */
messageHash: string;
/** Ring members (public keys) */
ring: string[];
/** Key image (for double-spend prevention) */
keyImage: string;
/** Signature components */
signature: {
/** Challenge values */
c: string[];
/** Response values */
r: string[];
};
/** Ring size */
ringSize: number;
}
/** Create ring signature options */
export interface CreateRingSignatureOptions {
/** Message to sign (hex or string) */
message: string;
/** Ring of public keys (must include signer's key) */
ring: string[];
/** Signer's private key */
privateKey: string;
/** Index of signer's key in ring (for optimization) */
signerIndex?: number;
}
/** Verify ring signature options */
export interface VerifyRingSignatureOptions {
/** Ring signature to verify */
signature: RingSignature;
/** Original message */
message: string;
}
/** Ring signature verification result */
export interface VerifyRingSignatureResult {
/** Whether the signature is valid */
valid: boolean;
/** Key image (can be used to detect double-signing) */
keyImage: string;
}
/** Stealth address */
export interface StealthAddress {
/** One-time public address */
address: string;
/** Scan public key (for detecting incoming payments) */
scanPublicKey: string;
/** Spend public key */
spendPublicKey: string;
}
/** Stealth address keypair */
export interface StealthKeypair {
/** Stealth address */
address: StealthAddress;
/** Scan private key */
scanPrivateKey: string;
/** Spend private key */
spendPrivateKey: string;
}
/** One-time address derived from stealth address */
export interface OneTimeAddress {
/** The one-time address */
address: string;
/** Ephemeral public key (included in transaction) */
ephemeralPublicKey: string;
/** Shared secret (for sender) */
sharedSecret?: string;
}
/** Derive shared secret options */
export interface DeriveSharedSecretOptions {
/** Stealth address */
stealthAddress: StealthAddress;
/** Private key for derivation */
privateKey: string;
/** Ephemeral public key from transaction */
ephemeralPublicKey: string;
}
/** Shared secret result */
export interface SharedSecret {
/** The derived shared secret (hex) */
secret: string;
/** One-time private key for spending */
oneTimePrivateKey: string;
/** One-time address */
oneTimeAddress: string;
}
/** Pedersen commitment */
export interface Commitment {
/** Commitment value (hex) */
commitment: string;
/** Generator point used */
generator: 'default' | 'alternate';
}
/** Create commitment options */
export interface CreateCommitmentOptions {
/** Value to commit to */
value: string;
/** Blinding factor (hex) - generated if not provided */
blinding?: string;
}
/** Commitment with blinding factor */
export interface CommitmentWithBlinding {
/** The commitment */
commitment: Commitment;
/** Blinding factor used (hex) */
blinding: string;
}
/** Open commitment options */
export interface OpenCommitmentOptions {
/** Commitment to open/verify */
commitment: string;
/** Claimed value */
value: string;
/** Blinding factor */
blinding: string;
}
/** Bulletproof range proof */
export interface RangeProof {
/** Proof data (hex) */
proof: string;
/** Commitment the proof is for */
commitment: string;
/** Bit length of the range */
bitLength: number;
}
/** Create range proof options */
export interface CreateRangeProofOptions {
/** Value to prove is in range */
value: string;
/** Blinding factor */
blinding: string;
/** Optional message to embed in proof */
message?: string;
/** Bit length (default 64) */
bitLength?: number;
}
/** Verify range proof result */
export interface VerifyRangeProofResult {
/** Whether the proof is valid */
valid: boolean;
/** Minimum value in range (usually 0) */
minValue: string;
/** Maximum value in range */
maxValue: string;
}
/** Blinding factor sum options */
export interface BlindingSumOptions {
/** Positive blinding factors (inputs) */
positive: string[];
/** Negative blinding factors (outputs) */
negative: string[];
}

View file

@ -0,0 +1,292 @@
package io.synor.contract
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.delay
import kotlinx.serialization.json.*
import java.io.Closeable
import java.net.URLEncoder
import java.util.concurrent.atomic.AtomicBoolean
/**
* Synor Contract SDK client for Kotlin.
* Smart contract deployment, interaction, and event handling.
*/
class ContractClient(private val config: ContractConfig) : Closeable {
private val closed = AtomicBoolean(false)
private val json = Json { ignoreUnknownKeys = true }
private val client = HttpClient(CIO) {
install(ContentNegotiation) {
json(json)
}
install(HttpTimeout) {
requestTimeoutMillis = config.timeoutMs
connectTimeoutMillis = config.timeoutMs
}
defaultRequest {
header("Authorization", "Bearer ${config.apiKey}")
header("X-SDK-Version", "kotlin/$VERSION")
contentType(ContentType.Application.Json)
}
}
// ==================== Contract Deployment ====================
suspend fun deploy(options: DeployContractOptions): DeploymentResult {
val body = buildJsonObject {
put("bytecode", options.bytecode)
options.abi?.let { put("abi", json.encodeToJsonElement(it)) }
options.constructorArgs?.let { put("constructor_args", json.encodeToJsonElement(it)) }
options.value?.let { put("value", it) }
options.gasLimit?.let { put("gas_limit", it) }
options.gasPrice?.let { put("gas_price", it) }
options.nonce?.let { put("nonce", it) }
}
return post("/contract/deploy", body)
}
suspend fun deployCreate2(options: DeployContractOptions, salt: String): DeploymentResult {
val body = buildJsonObject {
put("bytecode", options.bytecode)
put("salt", salt)
options.abi?.let { put("abi", json.encodeToJsonElement(it)) }
options.constructorArgs?.let { put("constructor_args", json.encodeToJsonElement(it)) }
options.value?.let { put("value", it) }
options.gasLimit?.let { put("gas_limit", it) }
options.gasPrice?.let { put("gas_price", it) }
}
return post("/contract/deploy/create2", body)
}
suspend fun predictAddress(bytecode: String, salt: String, deployer: String? = null): String {
val body = buildJsonObject {
put("bytecode", bytecode)
put("salt", salt)
deployer?.let { put("deployer", it) }
}
val response: JsonObject = post("/contract/predict-address", body)
return response["address"]?.jsonPrimitive?.content ?: throw ContractException("Missing address")
}
// ==================== Contract Interaction ====================
suspend fun call(options: CallContractOptions): JsonElement {
val body = buildJsonObject {
put("contract", options.contract)
put("method", options.method)
put("args", json.encodeToJsonElement(options.args))
put("abi", json.encodeToJsonElement(options.abi))
}
return post("/contract/call", body)
}
suspend fun send(options: SendContractOptions): TransactionResult {
val body = buildJsonObject {
put("contract", options.contract)
put("method", options.method)
put("args", json.encodeToJsonElement(options.args))
put("abi", json.encodeToJsonElement(options.abi))
options.value?.let { put("value", it) }
options.gasLimit?.let { put("gas_limit", it) }
options.gasPrice?.let { put("gas_price", it) }
options.nonce?.let { put("nonce", it) }
}
return post("/contract/send", body)
}
// ==================== Events ====================
suspend fun getEvents(filter: EventFilter): List<DecodedEvent> {
val body = buildJsonObject {
put("contract", filter.contract)
filter.event?.let { put("event", it) }
filter.fromBlock?.let { put("from_block", it) }
filter.toBlock?.let { put("to_block", it) }
filter.topics?.let { put("topics", json.encodeToJsonElement(it)) }
filter.abi?.let { put("abi", json.encodeToJsonElement(it)) }
}
return post("/contract/events", body)
}
suspend fun getLogs(contract: String, fromBlock: Long? = null, toBlock: Long? = null): List<EventLog> {
var path = "/contract/logs?contract=${encode(contract)}"
fromBlock?.let { path += "&from_block=$it" }
toBlock?.let { path += "&to_block=$it" }
return get(path)
}
suspend fun decodeLogs(logs: List<EventLog>, abi: List<AbiEntry>): List<DecodedEvent> {
val body = buildJsonObject {
put("logs", json.encodeToJsonElement(logs))
put("abi", json.encodeToJsonElement(abi))
}
return post("/contract/decode-logs", body)
}
// ==================== ABI Utilities ====================
suspend fun encodeCall(options: EncodeCallOptions): String {
val body = buildJsonObject {
put("method", options.method)
put("args", json.encodeToJsonElement(options.args))
put("abi", json.encodeToJsonElement(options.abi))
}
val response: JsonObject = post("/contract/encode", body)
return response["data"]?.jsonPrimitive?.content ?: throw ContractException("Missing data")
}
suspend fun decodeResult(options: DecodeResultOptions): JsonElement {
val body = buildJsonObject {
put("data", options.data)
put("method", options.method)
put("abi", json.encodeToJsonElement(options.abi))
}
val response: JsonObject = post("/contract/decode", body)
return response["result"] ?: throw ContractException("Missing result")
}
suspend fun getSelector(signature: String): String {
val response: JsonObject = get("/contract/selector?signature=${encode(signature)}")
return response["selector"]?.jsonPrimitive?.content ?: throw ContractException("Missing selector")
}
// ==================== Gas Estimation ====================
suspend fun estimateGas(options: EstimateGasOptions): GasEstimation {
val body = buildJsonObject {
put("contract", options.contract)
put("method", options.method)
put("args", json.encodeToJsonElement(options.args))
put("abi", json.encodeToJsonElement(options.abi))
options.value?.let { put("value", it) }
}
return post("/contract/estimate-gas", body)
}
// ==================== Contract Information ====================
suspend fun getBytecode(address: String): BytecodeInfo {
return get("/contract/${encode(address)}/bytecode")
}
suspend fun verify(options: VerifyContractOptions): VerificationResult {
val body = buildJsonObject {
put("address", options.address)
put("source_code", options.sourceCode)
put("compiler_version", options.compilerVersion)
options.constructorArgs?.let { put("constructor_args", it) }
options.optimization?.let { put("optimization", it) }
options.optimizationRuns?.let { put("optimization_runs", it) }
options.license?.let { put("license", it) }
}
return post("/contract/verify", body)
}
suspend fun getVerificationStatus(address: String): VerificationResult {
return get("/contract/${encode(address)}/verification")
}
// ==================== Multicall ====================
suspend fun multicall(requests: List<MulticallRequest>): List<MulticallResult> {
val body = buildJsonObject {
put("calls", json.encodeToJsonElement(requests))
}
return post("/contract/multicall", body)
}
// ==================== Storage ====================
suspend fun readStorage(options: ReadStorageOptions): String {
var path = "/contract/storage?contract=${encode(options.contract)}&slot=${encode(options.slot)}"
options.blockNumber?.let { path += "&block=$it" }
val response: JsonObject = get(path)
return response["value"]?.jsonPrimitive?.content ?: throw ContractException("Missing value")
}
// ==================== Lifecycle ====================
suspend fun healthCheck(): Boolean {
if (closed.get()) return false
return try {
val response: JsonObject = get("/health")
response["status"]?.jsonPrimitive?.content == "healthy"
} catch (e: Exception) {
false
}
}
override fun close() {
closed.set(true)
client.close()
}
fun isClosed(): Boolean = closed.get()
// ==================== HTTP Helpers ====================
private fun encode(value: String): String = URLEncoder.encode(value, "UTF-8")
private suspend inline fun <reified T> get(path: String): T {
checkClosed()
return executeWithRetry { client.get("${config.endpoint}$path").body() }
}
private suspend inline fun <reified T> post(path: String, body: JsonObject): T {
checkClosed()
return executeWithRetry {
client.post("${config.endpoint}$path") {
setBody(body)
}.body()
}
}
private suspend inline fun <T> executeWithRetry(crossinline block: suspend () -> T): T {
var lastException: Exception? = null
repeat(config.retries) { attempt ->
try {
return block()
} catch (e: Exception) {
lastException = e
if (attempt < config.retries - 1) {
delay((1L shl attempt) * 1000)
}
}
}
throw lastException ?: ContractException("Request failed")
}
private fun checkClosed() {
if (closed.get()) throw ContractException("Client has been closed")
}
companion object {
const val VERSION = "0.1.0"
}
}
/**
* Contract SDK configuration.
*/
data class ContractConfig(
val apiKey: String,
val endpoint: String = "https://contract.synor.io",
val timeoutMs: Long = 30000,
val retries: Int = 3
)
/**
* Contract SDK exception.
*/
class ContractException(
message: String,
val statusCode: Int? = null,
val errorCode: String? = null
) : RuntimeException(message)

View file

@ -0,0 +1,199 @@
package io.synor.contract
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
// ==================== ABI Types ====================
@Serializable
data class AbiParameter(
val name: String? = null,
val type: String,
val indexed: Boolean? = null,
val components: List<AbiParameter>? = null
)
@Serializable
data class AbiEntry(
val type: String,
val name: String? = null,
val inputs: List<AbiParameter>? = null,
val outputs: List<AbiParameter>? = null,
val stateMutability: String? = null,
val anonymous: Boolean? = null
)
// ==================== Deployment ====================
data class DeployContractOptions(
val bytecode: String,
val abi: List<AbiEntry>? = null,
val constructorArgs: List<Any?>? = null,
val value: String? = null,
val gasLimit: Long? = null,
val gasPrice: String? = null,
val nonce: Long? = null
)
@Serializable
data class DeploymentResult(
@SerialName("contract_address") val contractAddress: String,
@SerialName("transaction_hash") val transactionHash: String,
val deployer: String? = null,
@SerialName("gas_used") val gasUsed: Long? = null,
@SerialName("block_number") val blockNumber: Long? = null,
@SerialName("block_hash") val blockHash: String? = null
)
// ==================== Contract Interaction ====================
data class CallContractOptions(
val contract: String,
val method: String,
val args: List<Any?> = emptyList(),
val abi: List<AbiEntry>
)
data class SendContractOptions(
val contract: String,
val method: String,
val args: List<Any?> = emptyList(),
val abi: List<AbiEntry>,
val value: String? = null,
val gasLimit: Long? = null,
val gasPrice: String? = null,
val nonce: Long? = null
)
@Serializable
data class TransactionResult(
@SerialName("transaction_hash") val transactionHash: String,
@SerialName("block_number") val blockNumber: Long? = null,
@SerialName("block_hash") val blockHash: String? = null,
@SerialName("gas_used") val gasUsed: Long? = null,
@SerialName("effective_gas_price") val effectiveGasPrice: String? = null,
val status: String? = null,
val logs: List<EventLog>? = null
)
// ==================== Events ====================
@Serializable
data class EventLog(
val address: String,
val topics: List<String>,
val data: String,
@SerialName("block_number") val blockNumber: Long? = null,
@SerialName("transaction_hash") val transactionHash: String? = null,
@SerialName("log_index") val logIndex: Int? = null,
@SerialName("block_hash") val blockHash: String? = null,
val removed: Boolean? = null
)
@Serializable
data class DecodedEvent(
val name: String,
val signature: String? = null,
val args: Map<String, JsonElement>? = null,
val log: EventLog? = null
)
data class EventFilter(
val contract: String,
val event: String? = null,
val fromBlock: Long? = null,
val toBlock: Long? = null,
val topics: List<String?>? = null,
val abi: List<AbiEntry>? = null
)
// ==================== ABI Utilities ====================
data class EncodeCallOptions(
val method: String,
val args: List<Any?> = emptyList(),
val abi: List<AbiEntry>
)
data class DecodeResultOptions(
val data: String,
val method: String,
val abi: List<AbiEntry>
)
// ==================== Gas Estimation ====================
data class EstimateGasOptions(
val contract: String,
val method: String,
val args: List<Any?> = emptyList(),
val abi: List<AbiEntry>,
val value: String? = null
)
@Serializable
data class GasEstimation(
@SerialName("gas_limit") val gasLimit: Long,
@SerialName("gas_price") val gasPrice: String? = null,
@SerialName("max_fee_per_gas") val maxFeePerGas: String? = null,
@SerialName("max_priority_fee_per_gas") val maxPriorityFeePerGas: String? = null,
@SerialName("estimated_cost") val estimatedCost: String? = null
)
// ==================== Contract Information ====================
@Serializable
data class BytecodeInfo(
val bytecode: String,
@SerialName("deployed_bytecode") val deployedBytecode: String? = null,
val size: Int? = null,
@SerialName("is_contract") val isContract: Boolean? = null
)
data class VerifyContractOptions(
val address: String,
val sourceCode: String,
val compilerVersion: String,
val constructorArgs: String? = null,
val optimization: Boolean? = null,
val optimizationRuns: Int? = null,
val license: String? = null
)
@Serializable
data class VerificationResult(
val verified: Boolean,
val address: String? = null,
@SerialName("compiler_version") val compilerVersion: String? = null,
val optimization: Boolean? = null,
@SerialName("optimization_runs") val optimizationRuns: Int? = null,
val license: String? = null,
val abi: List<AbiEntry>? = null,
@SerialName("source_code") val sourceCode: String? = null
)
// ==================== Multicall ====================
@Serializable
data class MulticallRequest(
val contract: String,
val method: String,
val args: List<JsonElement> = emptyList(),
val abi: List<AbiEntry>
)
@Serializable
data class MulticallResult(
val success: Boolean,
val result: JsonElement? = null,
val error: String? = null
)
// ==================== Storage ====================
data class ReadStorageOptions(
val contract: String,
val slot: String,
val blockNumber: Long? = null
)

View file

@ -0,0 +1,286 @@
package io.synor.privacy
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.delay
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.*
import java.io.Closeable
import java.util.concurrent.atomic.AtomicBoolean
/**
* Synor Privacy SDK client for Kotlin.
* Confidential transactions, ring signatures, stealth addresses, and cryptographic commitments.
*/
class PrivacyClient(private val config: PrivacyConfig) : Closeable {
private val closed = AtomicBoolean(false)
private val json = Json { ignoreUnknownKeys = true }
private val client = HttpClient(CIO) {
install(ContentNegotiation) {
json(json)
}
install(HttpTimeout) {
requestTimeoutMillis = config.timeoutMs
connectTimeoutMillis = config.timeoutMs
}
defaultRequest {
header("Authorization", "Bearer ${config.apiKey}")
header("X-SDK-Version", "kotlin/$VERSION")
contentType(ContentType.Application.Json)
}
}
// ==================== Confidential Transactions ====================
suspend fun createConfidentialTx(
inputs: List<ConfidentialTxInput>,
outputs: List<ConfidentialTxOutput>
): ConfidentialTransaction {
val body = buildJsonObject {
put("inputs", json.encodeToJsonElement(inputs))
put("outputs", json.encodeToJsonElement(outputs))
}
return post("/privacy/confidential/create", body)
}
suspend fun verifyConfidentialTx(tx: ConfidentialTransaction): Boolean {
val body = buildJsonObject {
put("transaction", json.encodeToJsonElement(tx))
}
val response: JsonObject = post("/privacy/confidential/verify", body)
return response["valid"]?.jsonPrimitive?.boolean ?: false
}
suspend fun createCommitment(value: String, blindingFactor: String): Commitment {
val body = buildJsonObject {
put("value", value)
put("blinding_factor", blindingFactor)
}
return post("/privacy/commitment/create", body)
}
suspend fun verifyCommitment(commitment: String, value: String, blindingFactor: String): Boolean {
val body = buildJsonObject {
put("commitment", commitment)
put("value", value)
put("blinding_factor", blindingFactor)
}
val response: JsonObject = post("/privacy/commitment/verify", body)
return response["valid"]?.jsonPrimitive?.boolean ?: false
}
suspend fun createRangeProof(
value: String,
blindingFactor: String,
minValue: Long,
maxValue: Long
): RangeProof {
val body = buildJsonObject {
put("value", value)
put("blinding_factor", blindingFactor)
put("min_value", minValue)
put("max_value", maxValue)
}
return post("/privacy/range-proof/create", body)
}
suspend fun verifyRangeProof(proof: RangeProof): Boolean {
val body = buildJsonObject {
put("proof", json.encodeToJsonElement(proof))
}
val response: JsonObject = post("/privacy/range-proof/verify", body)
return response["valid"]?.jsonPrimitive?.boolean ?: false
}
// ==================== Ring Signatures ====================
suspend fun createRingSignature(
message: String,
ring: List<String>,
signerIndex: Int,
privateKey: String
): RingSignature {
val body = buildJsonObject {
put("message", message)
put("ring", json.encodeToJsonElement(ring))
put("signer_index", signerIndex)
put("private_key", privateKey)
}
return post("/privacy/ring/sign", body)
}
suspend fun verifyRingSignature(signature: RingSignature, message: String): Boolean {
val body = buildJsonObject {
put("signature", json.encodeToJsonElement(signature))
put("message", message)
}
val response: JsonObject = post("/privacy/ring/verify", body)
return response["valid"]?.jsonPrimitive?.boolean ?: false
}
suspend fun generateDecoys(count: Int, excludeKey: String? = null): List<String> {
var path = "/privacy/ring/decoys?count=$count"
if (excludeKey != null) path += "&exclude=$excludeKey"
return get(path)
}
suspend fun checkKeyImage(keyImage: String): Boolean {
val response: JsonObject = get("/privacy/ring/key-image/$keyImage")
return response["spent"]?.jsonPrimitive?.boolean ?: false
}
// ==================== Stealth Addresses ====================
suspend fun generateStealthKeyPair(): StealthKeyPair {
return post("/privacy/stealth/generate", buildJsonObject {})
}
suspend fun deriveStealthAddress(
spendPublicKey: String,
viewPublicKey: String
): StealthAddress {
val body = buildJsonObject {
put("spend_public_key", spendPublicKey)
put("view_public_key", viewPublicKey)
}
return post("/privacy/stealth/derive", body)
}
suspend fun recoverStealthPrivateKey(
stealthAddress: String,
viewPrivateKey: String,
spendPrivateKey: String
): String {
val body = buildJsonObject {
put("stealth_address", stealthAddress)
put("view_private_key", viewPrivateKey)
put("spend_private_key", spendPrivateKey)
}
val response: JsonObject = post("/privacy/stealth/recover", body)
return response["private_key"]?.jsonPrimitive?.content ?: throw PrivacyException("Missing private_key")
}
suspend fun scanOutputs(
viewPrivateKey: String,
spendPublicKey: String,
fromBlock: Long,
toBlock: Long? = null
): List<StealthOutput> {
val body = buildJsonObject {
put("view_private_key", viewPrivateKey)
put("spend_public_key", spendPublicKey)
put("from_block", fromBlock)
toBlock?.let { put("to_block", it) }
}
return post("/privacy/stealth/scan", body)
}
// ==================== Blinding ====================
suspend fun generateBlindingFactor(): String {
val response: JsonObject = post("/privacy/blinding/generate", buildJsonObject {})
return response["blinding_factor"]?.jsonPrimitive?.content ?: throw PrivacyException("Missing blinding_factor")
}
suspend fun blindValue(value: String, blindingFactor: String): String {
val body = buildJsonObject {
put("value", value)
put("blinding_factor", blindingFactor)
}
val response: JsonObject = post("/privacy/blinding/blind", body)
return response["blinded_value"]?.jsonPrimitive?.content ?: throw PrivacyException("Missing blinded_value")
}
suspend fun unblindValue(blindedValue: String, blindingFactor: String): String {
val body = buildJsonObject {
put("blinded_value", blindedValue)
put("blinding_factor", blindingFactor)
}
val response: JsonObject = post("/privacy/blinding/unblind", body)
return response["value"]?.jsonPrimitive?.content ?: throw PrivacyException("Missing value")
}
// ==================== Lifecycle ====================
suspend fun healthCheck(): Boolean {
if (closed.get()) return false
return try {
val response: JsonObject = get("/health")
response["status"]?.jsonPrimitive?.content == "healthy"
} catch (e: Exception) {
false
}
}
override fun close() {
closed.set(true)
client.close()
}
fun isClosed(): Boolean = closed.get()
// ==================== HTTP Helpers ====================
private suspend inline fun <reified T> get(path: String): T {
checkClosed()
return executeWithRetry { client.get("${config.endpoint}$path").body() }
}
private suspend inline fun <reified T> post(path: String, body: JsonObject): T {
checkClosed()
return executeWithRetry {
client.post("${config.endpoint}$path") {
setBody(body)
}.body()
}
}
private suspend inline fun <T> executeWithRetry(crossinline block: suspend () -> T): T {
var lastException: Exception? = null
repeat(config.retries) { attempt ->
try {
return block()
} catch (e: Exception) {
lastException = e
if (attempt < config.retries - 1) {
delay((1L shl attempt) * 1000)
}
}
}
throw lastException ?: PrivacyException("Request failed")
}
private fun checkClosed() {
if (closed.get()) throw PrivacyException("Client has been closed")
}
companion object {
const val VERSION = "0.1.0"
}
}
/**
* Privacy SDK configuration.
*/
data class PrivacyConfig(
val apiKey: String,
val endpoint: String = "https://privacy.synor.io",
val timeoutMs: Long = 30000,
val retries: Int = 3
)
/**
* Privacy SDK exception.
*/
class PrivacyException(
message: String,
val statusCode: Int? = null,
val errorCode: String? = null
) : RuntimeException(message)

View file

@ -0,0 +1,86 @@
package io.synor.privacy
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
// ==================== Confidential Transactions ====================
@Serializable
data class ConfidentialTxInput(
val commitment: String,
@SerialName("blinding_factor") val blindingFactor: String,
val value: String,
@SerialName("key_image") val keyImage: String? = null
)
@Serializable
data class ConfidentialTxOutput(
val commitment: String,
@SerialName("blinding_factor") val blindingFactor: String,
val value: String,
@SerialName("recipient_public_key") val recipientPublicKey: String,
@SerialName("range_proof") val rangeProof: String? = null
)
@Serializable
data class ConfidentialTransaction(
val id: String,
val inputs: List<ConfidentialTxInput>,
val outputs: List<ConfidentialTxOutput>,
val fee: String,
val excess: String,
@SerialName("excess_sig") val excessSig: String,
@SerialName("kernel_offset") val kernelOffset: String? = null
)
// ==================== Commitments ====================
@Serializable
data class Commitment(
val commitment: String,
@SerialName("blinding_factor") val blindingFactor: String
)
@Serializable
data class RangeProof(
val proof: String,
val commitment: String,
@SerialName("min_value") val minValue: Long,
@SerialName("max_value") val maxValue: Long
)
// ==================== Ring Signatures ====================
@Serializable
data class RingSignature(
val c0: String,
val s: List<String>,
@SerialName("key_image") val keyImage: String,
val ring: List<String>
)
// ==================== Stealth Addresses ====================
@Serializable
data class StealthKeyPair(
@SerialName("spend_public_key") val spendPublicKey: String,
@SerialName("spend_private_key") val spendPrivateKey: String,
@SerialName("view_public_key") val viewPublicKey: String,
@SerialName("view_private_key") val viewPrivateKey: String
)
@Serializable
data class StealthAddress(
val address: String,
@SerialName("ephemeral_public_key") val ephemeralPublicKey: String,
@SerialName("tx_public_key") val txPublicKey: String? = null
)
@Serializable
data class StealthOutput(
@SerialName("tx_hash") val txHash: String,
@SerialName("output_index") val outputIndex: Int,
@SerialName("stealth_address") val stealthAddress: String,
val amount: String,
@SerialName("block_height") val blockHeight: Long
)

View file

@ -0,0 +1,50 @@
"""
Synor Contract SDK
Smart contract deployment, interaction, and event handling:
- Deploy contracts (standard and CREATE2)
- Call view/pure functions
- Send state-changing transactions
- Subscribe to events
- ABI encoding/decoding utilities
"""
from .types import (
ContractConfig,
ContractError,
AbiEntryType,
AbiParameter,
AbiEntry,
DeploymentResult,
TransactionResult,
EventLog,
DecodedEvent,
ContractInterface,
GasEstimation,
BytecodeInfo,
VerificationResult,
MulticallRequest,
MulticallResult,
DEFAULT_CONTRACT_ENDPOINT,
)
from .client import SynorContract
__all__ = [
"SynorContract",
"ContractConfig",
"ContractError",
"AbiEntryType",
"AbiParameter",
"AbiEntry",
"DeploymentResult",
"TransactionResult",
"EventLog",
"DecodedEvent",
"ContractInterface",
"GasEstimation",
"BytecodeInfo",
"VerificationResult",
"MulticallRequest",
"MulticallResult",
]

View file

@ -0,0 +1,711 @@
"""
Synor Contract SDK Client
Smart contract deployment, interaction, and event handling.
"""
import asyncio
from typing import Optional, List, Dict, Any, Union
import httpx
from .types import (
ContractConfig,
ContractError,
AbiEntry,
DeploymentResult,
TransactionResult,
EventLog,
DecodedEvent,
ContractInterface,
GasEstimation,
BytecodeInfo,
VerificationResult,
MulticallRequest,
MulticallResult,
DEFAULT_CONTRACT_ENDPOINT,
)
# Type alias for ABI
Abi = List[AbiEntry]
class SynorContract:
"""
Synor Contract Client.
Provides smart contract deployment, interaction, and event handling.
Example:
async with SynorContract(api_key="sk_...") as contract:
# Deploy a contract
result = await contract.deploy(
bytecode="0x608060...",
abi=[...],
args=[100, "Token"],
)
# Call a view function
balance = await contract.call(
address=result.address,
method="balanceOf",
args=["0x..."],
abi=[...],
)
# Send a transaction
tx = await contract.send(
address=result.address,
method="transfer",
args=["0x...", "1000000"],
abi=[...],
)
"""
def __init__(
self,
api_key: str,
endpoint: str = DEFAULT_CONTRACT_ENDPOINT,
timeout: float = 30.0,
retries: int = 3,
debug: bool = False,
default_gas_limit: Optional[str] = None,
default_gas_price: Optional[str] = None,
):
self.config = ContractConfig(
api_key=api_key,
endpoint=endpoint,
timeout=timeout,
retries=retries,
debug=debug,
default_gas_limit=default_gas_limit,
default_gas_price=default_gas_price,
)
self._client = httpx.AsyncClient(
base_url=endpoint,
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"X-SDK-Version": "python/0.1.0",
},
timeout=timeout,
)
self._closed = False
# ==================== Deployment ====================
async def deploy(
self,
bytecode: str,
abi: Optional[Abi] = None,
args: Optional[List[Any]] = None,
gas_limit: Optional[str] = None,
gas_price: Optional[str] = None,
value: Optional[str] = None,
salt: Optional[str] = None,
) -> DeploymentResult:
"""
Deploy a smart contract.
Args:
bytecode: Compiled bytecode (hex)
abi: Contract ABI
args: Constructor arguments
gas_limit: Gas limit
gas_price: Gas price
value: Value to send with deployment
salt: Salt for deterministic deployment (CREATE2)
Returns:
Deployment result with contract address
"""
body: Dict[str, Any] = {"bytecode": bytecode}
if abi:
body["abi"] = [e.to_dict() for e in abi]
if args:
body["args"] = args
if gas_limit or self.config.default_gas_limit:
body["gasLimit"] = gas_limit or self.config.default_gas_limit
if gas_price or self.config.default_gas_price:
body["gasPrice"] = gas_price or self.config.default_gas_price
if value:
body["value"] = value
if salt:
body["salt"] = salt
response = await self._request("POST", "/contracts/deploy", body)
return DeploymentResult.from_dict(response["deployment"])
async def deploy_deterministic(
self,
bytecode: str,
salt: str,
abi: Optional[Abi] = None,
args: Optional[List[Any]] = None,
gas_limit: Optional[str] = None,
gas_price: Optional[str] = None,
value: Optional[str] = None,
) -> DeploymentResult:
"""Deploy using CREATE2 for deterministic addresses."""
return await self.deploy(
bytecode=bytecode,
abi=abi,
args=args,
gas_limit=gas_limit,
gas_price=gas_price,
value=value,
salt=salt,
)
async def predict_address(
self,
bytecode: str,
salt: str,
constructor_args: Optional[List[Any]] = None,
) -> str:
"""
Predict CREATE2 deployment address.
Args:
bytecode: Contract bytecode
salt: Deployment salt
constructor_args: Constructor arguments
Returns:
Predicted address
"""
body: Dict[str, Any] = {"bytecode": bytecode, "salt": salt}
if constructor_args:
body["constructorArgs"] = constructor_args
response = await self._request("POST", "/contracts/predict-address", body)
return response["address"]
# ==================== Contract Interaction ====================
async def call(
self,
address: str,
method: str,
abi: Abi,
args: Optional[List[Any]] = None,
block_number: Optional[Union[int, str]] = None,
) -> Any:
"""
Call a view/pure function (no transaction).
Args:
address: Contract address
method: Method name
abi: Contract ABI
args: Method arguments
block_number: Block number or 'latest'
Returns:
Function return value
"""
body: Dict[str, Any] = {
"address": address,
"method": method,
"abi": [e.to_dict() for e in abi],
"blockNumber": block_number or "latest",
}
if args:
body["args"] = args
response = await self._request("POST", "/contracts/call", body)
return response["result"]
async def send(
self,
address: str,
method: str,
abi: Abi,
args: Optional[List[Any]] = None,
gas_limit: Optional[str] = None,
gas_price: Optional[str] = None,
value: Optional[str] = None,
) -> TransactionResult:
"""
Send a state-changing transaction.
Args:
address: Contract address
method: Method name
abi: Contract ABI
args: Method arguments
gas_limit: Gas limit
gas_price: Gas price
value: Value to send
Returns:
Transaction result
"""
body: Dict[str, Any] = {
"address": address,
"method": method,
"abi": [e.to_dict() for e in abi],
}
if args:
body["args"] = args
if gas_limit or self.config.default_gas_limit:
body["gasLimit"] = gas_limit or self.config.default_gas_limit
if gas_price or self.config.default_gas_price:
body["gasPrice"] = gas_price or self.config.default_gas_price
if value:
body["value"] = value
response = await self._request("POST", "/contracts/send", body)
return TransactionResult.from_dict(response["transaction"])
async def multicall(
self,
requests: List[MulticallRequest],
abis: Optional[Dict[str, Abi]] = None,
) -> List[MulticallResult]:
"""
Execute multiple calls in a single request.
Args:
requests: Array of multicall requests
abis: Optional ABIs for decoding (keyed by address)
Returns:
Array of results
"""
body: Dict[str, Any] = {"calls": [r.to_dict() for r in requests]}
if abis:
body["abis"] = {
addr: [e.to_dict() for e in abi] for addr, abi in abis.items()
}
response = await self._request("POST", "/contracts/multicall", body)
return [MulticallResult.from_dict(r) for r in response["results"]]
# ==================== Events ====================
async def get_events(
self,
address: str,
abi: Optional[Abi] = None,
event_name: Optional[str] = None,
from_block: Optional[Union[int, str]] = None,
to_block: Optional[Union[int, str]] = None,
filter_values: Optional[Dict[str, Any]] = None,
) -> List[DecodedEvent]:
"""
Get historical events.
Args:
address: Contract address
abi: Contract ABI (required for decoding)
event_name: Event name (optional)
from_block: From block
to_block: To block
filter_values: Indexed filter values
Returns:
Array of decoded events
"""
body: Dict[str, Any] = {
"address": address,
"fromBlock": from_block or "earliest",
"toBlock": to_block or "latest",
}
if abi:
body["abi"] = [e.to_dict() for e in abi]
if event_name:
body["eventName"] = event_name
if filter_values:
body["filter"] = filter_values
response = await self._request("POST", "/contracts/events", body)
return [DecodedEvent.from_dict(e) for e in response["events"]]
async def get_logs(
self,
address: str,
abi: Optional[Abi] = None,
event_name: Optional[str] = None,
from_block: Optional[Union[int, str]] = None,
to_block: Optional[Union[int, str]] = None,
filter_values: Optional[Dict[str, Any]] = None,
) -> List[EventLog]:
"""
Get raw event logs.
Args:
address: Contract address
abi: Contract ABI
event_name: Event name (optional)
from_block: From block
to_block: To block
filter_values: Indexed filter values
Returns:
Array of raw logs
"""
body: Dict[str, Any] = {
"address": address,
"fromBlock": from_block or "earliest",
"toBlock": to_block or "latest",
}
if abi:
body["abi"] = [e.to_dict() for e in abi]
if event_name:
body["eventName"] = event_name
if filter_values:
body["filter"] = filter_values
response = await self._request("POST", "/contracts/logs", body)
return [EventLog.from_dict(l) for l in response["logs"]]
async def decode_log(self, log: EventLog, abi: Abi) -> DecodedEvent:
"""
Decode a raw event log.
Args:
log: Raw log
abi: Contract ABI
Returns:
Decoded event
"""
response = await self._request(
"POST",
"/contracts/decode-log",
{"log": log.to_dict(), "abi": [e.to_dict() for e in abi]},
)
return DecodedEvent.from_dict(response["event"])
# ==================== ABI Utilities ====================
def load_abi(self, abi: Abi) -> ContractInterface:
"""
Load and parse a contract ABI.
Args:
abi: Contract ABI
Returns:
Parsed contract interface
"""
functions: Dict[str, AbiEntry] = {}
events: Dict[str, AbiEntry] = {}
errors: Dict[str, AbiEntry] = {}
for entry in abi:
if entry.type == "function" and entry.name:
functions[entry.name] = entry
elif entry.type == "event" and entry.name:
events[entry.name] = entry
elif entry.type == "error" and entry.name:
errors[entry.name] = entry
return ContractInterface(
abi=abi,
functions=functions,
events=events,
errors=errors,
)
async def encode_call(
self, method: str, args: List[Any], abi: Abi
) -> str:
"""
Encode a function call.
Args:
method: Method name
args: Arguments
abi: Contract ABI
Returns:
Encoded calldata (hex)
"""
response = await self._request(
"POST",
"/contracts/encode",
{"method": method, "args": args, "abi": [e.to_dict() for e in abi]},
)
return response["data"]
async def decode_result(
self, method: str, data: str, abi: Abi
) -> Any:
"""
Decode function return data.
Args:
method: Method name
data: Raw data to decode
abi: Contract ABI
Returns:
Decoded result
"""
response = await self._request(
"POST",
"/contracts/decode",
{"method": method, "data": data, "abi": [e.to_dict() for e in abi]},
)
return response["result"]
async def get_function_selector(self, signature: str) -> str:
"""
Get function selector (first 4 bytes of keccak256 hash).
Args:
signature: Function signature (e.g., "transfer(address,uint256)")
Returns:
Function selector (hex)
"""
response = await self._request(
"POST", "/contracts/selector", {"signature": signature}
)
return response["selector"]
# ==================== Gas Estimation ====================
async def estimate_gas(
self,
address: Optional[str] = None,
method: Optional[str] = None,
args: Optional[List[Any]] = None,
abi: Optional[Abi] = None,
bytecode: Optional[str] = None,
value: Optional[str] = None,
from_address: Optional[str] = None,
) -> GasEstimation:
"""
Estimate gas for a contract call or deployment.
Args:
address: Contract address (for calls)
method: Method name
args: Arguments
abi: Contract ABI
bytecode: Bytecode (for deployment)
value: Value to send
from_address: Sender address
Returns:
Gas estimation
"""
body: Dict[str, Any] = {}
if address:
body["address"] = address
if method:
body["method"] = method
if args:
body["args"] = args
if abi:
body["abi"] = [e.to_dict() for e in abi]
if bytecode:
body["bytecode"] = bytecode
if value:
body["value"] = value
if from_address:
body["from"] = from_address
response = await self._request("POST", "/contracts/estimate-gas", body)
return GasEstimation.from_dict(response["estimation"])
# ==================== Contract Info ====================
async def get_bytecode(self, address: str) -> BytecodeInfo:
"""
Get contract bytecode.
Args:
address: Contract address
Returns:
Bytecode info
"""
response = await self._request("GET", f"/contracts/{address}/bytecode")
return BytecodeInfo.from_dict(response)
async def is_contract(self, address: str) -> bool:
"""
Check if an address is a contract.
Args:
address: Address to check
Returns:
True if address has code
"""
response = await self._request("GET", f"/contracts/{address}/is-contract")
return response["isContract"]
async def read_storage(
self,
address: str,
slot: str,
block_number: Optional[Union[int, str]] = None,
) -> str:
"""
Read contract storage slot.
Args:
address: Contract address
slot: Storage slot
block_number: Block number
Returns:
Storage value (hex)
"""
response = await self._request(
"POST",
"/contracts/storage",
{
"address": address,
"slot": slot,
"blockNumber": block_number or "latest",
},
)
return response["value"]
# ==================== Verification ====================
async def verify_contract(
self,
address: str,
source_code: str,
compiler_version: str,
constructor_arguments: Optional[str] = None,
optimization: bool = False,
optimization_runs: int = 200,
contract_name: Optional[str] = None,
) -> VerificationResult:
"""
Submit contract for verification.
Args:
address: Contract address
source_code: Source code
compiler_version: Compiler version
constructor_arguments: Constructor arguments (hex)
optimization: Optimization enabled
optimization_runs: Optimization runs
contract_name: Contract name
Returns:
Verification result
"""
body: Dict[str, Any] = {
"address": address,
"sourceCode": source_code,
"compilerVersion": compiler_version,
"optimization": optimization,
"optimizationRuns": optimization_runs,
}
if constructor_arguments:
body["constructorArguments"] = constructor_arguments
if contract_name:
body["contractName"] = contract_name
response = await self._request("POST", "/contracts/verify", body)
return VerificationResult.from_dict(response)
async def get_verification_status(self, address: str) -> VerificationResult:
"""Check verification status."""
response = await self._request("GET", f"/contracts/{address}/verification")
return VerificationResult.from_dict(response)
async def get_verified_abi(self, address: str) -> Optional[Abi]:
"""Get verified contract ABI."""
try:
response = await self._request("GET", f"/contracts/{address}/abi")
return [AbiEntry.from_dict(e) for e in response["abi"]]
except ContractError:
return None
# ==================== Lifecycle ====================
async def close(self) -> None:
"""Close the client."""
self._closed = True
await self._client.aclose()
def is_closed(self) -> bool:
"""Check if client is closed."""
return self._closed
async def health_check(self) -> bool:
"""Health check."""
try:
response = await self._request("GET", "/health")
return response.get("status") == "healthy"
except ContractError:
return False
async def __aenter__(self) -> "SynorContract":
return self
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
await self.close()
# ==================== Private Methods ====================
async def _request(
self,
method: str,
path: str,
body: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
"""Make HTTP request with retries."""
if self._closed:
raise ContractError("Client has been closed")
last_error: Optional[Exception] = None
for attempt in range(self.config.retries):
try:
return await self._do_request(method, path, body)
except ContractError as e:
if self.config.debug:
print(f"Attempt {attempt + 1} failed: {e}")
last_error = e
if attempt < self.config.retries - 1:
await asyncio.sleep(2**attempt)
if last_error:
raise last_error
raise ContractError("Unknown error after retries")
async def _do_request(
self,
method: str,
path: str,
body: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
"""Execute HTTP request."""
try:
response = await self._client.request(
method,
path,
json=body if body else None,
)
if response.status_code >= 400:
error_body = response.json() if response.content else {}
message = (
error_body.get("message")
or error_body.get("error")
or f"HTTP {response.status_code}"
)
raise ContractError(
message,
code=error_body.get("code"),
status_code=response.status_code,
details=error_body,
)
return response.json()
except httpx.TimeoutException as e:
raise ContractError(f"Request timeout: {e}")
except httpx.RequestError as e:
raise ContractError(f"Request failed: {e}")

View file

@ -0,0 +1,373 @@
"""
Synor Contract SDK Types
Smart contract types, ABI definitions, and transaction results.
"""
from dataclasses import dataclass, field
from typing import Optional, List, Dict, Any, Literal, Union, Callable
# ==================== Config Types ====================
DEFAULT_CONTRACT_ENDPOINT = "https://contract.synor.cc/api/v1"
@dataclass
class ContractConfig:
"""Contract SDK configuration."""
api_key: str
endpoint: str = DEFAULT_CONTRACT_ENDPOINT
timeout: float = 30.0
retries: int = 3
debug: bool = False
default_gas_limit: Optional[str] = None
default_gas_price: Optional[str] = None
class ContractError(Exception):
"""Contract-specific error."""
def __init__(
self,
message: str,
code: Optional[str] = None,
status_code: Optional[int] = None,
details: Optional[Dict[str, Any]] = None,
):
super().__init__(message)
self.message = message
self.code = code
self.status_code = status_code
self.details = details or {}
# ==================== ABI Types ====================
AbiEntryType = Literal["function", "constructor", "event", "error", "fallback", "receive"]
StateMutability = Literal["pure", "view", "nonpayable", "payable"]
@dataclass
class AbiParameter:
"""ABI input/output parameter."""
name: str
type: str
indexed: bool = False
components: Optional[List["AbiParameter"]] = None
internal_type: Optional[str] = None
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "AbiParameter":
components = None
if "components" in data:
components = [AbiParameter.from_dict(c) for c in data["components"]]
return cls(
name=data.get("name", ""),
type=data["type"],
indexed=data.get("indexed", False),
components=components,
internal_type=data.get("internalType", data.get("internal_type")),
)
def to_dict(self) -> Dict[str, Any]:
d: Dict[str, Any] = {"name": self.name, "type": self.type}
if self.indexed:
d["indexed"] = self.indexed
if self.components:
d["components"] = [c.to_dict() for c in self.components]
if self.internal_type:
d["internalType"] = self.internal_type
return d
@dataclass
class AbiEntry:
"""ABI entry."""
type: AbiEntryType
name: Optional[str] = None
inputs: Optional[List[AbiParameter]] = None
outputs: Optional[List[AbiParameter]] = None
state_mutability: Optional[StateMutability] = None
anonymous: bool = False
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "AbiEntry":
inputs = None
outputs = None
if "inputs" in data:
inputs = [AbiParameter.from_dict(i) for i in data["inputs"]]
if "outputs" in data:
outputs = [AbiParameter.from_dict(o) for o in data["outputs"]]
return cls(
type=data["type"],
name=data.get("name"),
inputs=inputs,
outputs=outputs,
state_mutability=data.get("stateMutability", data.get("state_mutability")),
anonymous=data.get("anonymous", False),
)
def to_dict(self) -> Dict[str, Any]:
d: Dict[str, Any] = {"type": self.type}
if self.name:
d["name"] = self.name
if self.inputs:
d["inputs"] = [i.to_dict() for i in self.inputs]
if self.outputs:
d["outputs"] = [o.to_dict() for o in self.outputs]
if self.state_mutability:
d["stateMutability"] = self.state_mutability
if self.anonymous:
d["anonymous"] = self.anonymous
return d
# Type alias for ABI
Abi = List[AbiEntry]
# ==================== Deployment Types ====================
@dataclass
class DeploymentResult:
"""Deployment result."""
address: str
transaction_hash: str
block_number: int
gas_used: str
effective_gas_price: str
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "DeploymentResult":
return cls(
address=data["address"],
transaction_hash=data.get("transactionHash", data.get("transaction_hash", "")),
block_number=data.get("blockNumber", data.get("block_number", 0)),
gas_used=data.get("gasUsed", data.get("gas_used", "")),
effective_gas_price=data.get("effectiveGasPrice", data.get("effective_gas_price", "")),
)
# ==================== Transaction Types ====================
TransactionStatus = Literal["success", "reverted"]
@dataclass
class EventLog:
"""Event log."""
log_index: int
address: str
topics: List[str]
data: str
block_number: int
transaction_hash: str
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "EventLog":
return cls(
log_index=data.get("logIndex", data.get("log_index", 0)),
address=data["address"],
topics=data["topics"],
data=data["data"],
block_number=data.get("blockNumber", data.get("block_number", 0)),
transaction_hash=data.get("transactionHash", data.get("transaction_hash", "")),
)
def to_dict(self) -> Dict[str, Any]:
return {
"logIndex": self.log_index,
"address": self.address,
"topics": self.topics,
"data": self.data,
"blockNumber": self.block_number,
"transactionHash": self.transaction_hash,
}
@dataclass
class DecodedEvent:
"""Decoded event."""
name: str
signature: str
args: Dict[str, Any]
log: EventLog
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "DecodedEvent":
return cls(
name=data["name"],
signature=data["signature"],
args=data["args"],
log=EventLog.from_dict(data["log"]),
)
@dataclass
class TransactionResult:
"""Transaction result."""
transaction_hash: str
block_number: int
block_hash: str
gas_used: str
effective_gas_price: str
status: TransactionStatus
logs: List[EventLog]
return_value: Optional[Any] = None
revert_reason: Optional[str] = None
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "TransactionResult":
return cls(
transaction_hash=data.get("transactionHash", data.get("transaction_hash", "")),
block_number=data.get("blockNumber", data.get("block_number", 0)),
block_hash=data.get("blockHash", data.get("block_hash", "")),
gas_used=data.get("gasUsed", data.get("gas_used", "")),
effective_gas_price=data.get("effectiveGasPrice", data.get("effective_gas_price", "")),
status=data["status"],
logs=[EventLog.from_dict(l) for l in data.get("logs", [])],
return_value=data.get("returnValue", data.get("return_value")),
revert_reason=data.get("revertReason", data.get("revert_reason")),
)
# ==================== Contract Interface Types ====================
@dataclass
class ContractInterface:
"""Contract interface (from ABI)."""
abi: List[AbiEntry]
functions: Dict[str, AbiEntry]
events: Dict[str, AbiEntry]
errors: Dict[str, AbiEntry]
# ==================== Gas Types ====================
@dataclass
class GasEstimation:
"""Gas estimation result."""
gas_limit: str
gas_price: str
estimated_cost: str
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "GasEstimation":
return cls(
gas_limit=data.get("gasLimit", data.get("gas_limit", "")),
gas_price=data.get("gasPrice", data.get("gas_price", "")),
estimated_cost=data.get("estimatedCost", data.get("estimated_cost", "")),
)
# ==================== Bytecode Types ====================
@dataclass
class BytecodeMetadata:
"""Bytecode metadata."""
compiler: str
language: str
sources: List[str]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "BytecodeMetadata":
return cls(
compiler=data.get("compiler", ""),
language=data.get("language", ""),
sources=data.get("sources", []),
)
@dataclass
class BytecodeInfo:
"""Contract bytecode info."""
bytecode: str
deployed_bytecode: Optional[str] = None
abi: Optional[List[AbiEntry]] = None
metadata: Optional[BytecodeMetadata] = None
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "BytecodeInfo":
abi = None
if "abi" in data:
abi = [AbiEntry.from_dict(e) for e in data["abi"]]
metadata = None
if "metadata" in data:
metadata = BytecodeMetadata.from_dict(data["metadata"])
return cls(
bytecode=data["bytecode"],
deployed_bytecode=data.get("deployedBytecode", data.get("deployed_bytecode")),
abi=abi,
metadata=metadata,
)
# ==================== Verification Types ====================
VerificationStatus = Literal["verified", "pending", "failed"]
@dataclass
class VerificationResult:
"""Contract verification result."""
status: VerificationStatus
message: str
abi: Optional[List[AbiEntry]] = None
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "VerificationResult":
abi = None
if "abi" in data:
abi = [AbiEntry.from_dict(e) for e in data["abi"]]
return cls(
status=data["status"],
message=data["message"],
abi=abi,
)
# ==================== Multicall Types ====================
@dataclass
class MulticallRequest:
"""Multicall request."""
address: str
call_data: str
allow_failure: bool = False
def to_dict(self) -> Dict[str, Any]:
return {
"address": self.address,
"callData": self.call_data,
"allowFailure": self.allow_failure,
}
@dataclass
class MulticallResult:
"""Multicall result."""
success: bool
return_data: str
decoded: Optional[Any] = None
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "MulticallResult":
return cls(
success=data["success"],
return_data=data.get("returnData", data.get("return_data", "")),
decoded=data.get("decoded"),
)
# ==================== Event Subscription Types ====================
# Callback type for event subscription
EventCallback = Callable[[DecodedEvent], None]
@dataclass
class EventSubscription:
"""Event subscription handle."""
id: str
unsubscribe: Callable[[], None]

View file

@ -0,0 +1,63 @@
"""
Synor Privacy SDK
Privacy-enhancing features for the Synor blockchain:
- Confidential transactions with hidden amounts
- Ring signatures for anonymous signing
- Stealth addresses for unlinkable payments
- Pedersen commitments and Bulletproof range proofs
"""
from .types import (
PrivacyConfig,
PrivacyError,
PublicKey,
PrivateKey,
ConfidentialUTXO,
ConfidentialOutput,
ConfidentialTransaction,
ConfidentialTxInput,
ConfidentialTxOutput,
OutputFeatures,
TransactionKernel,
VerifyConfidentialTxResult,
RingSignature,
VerifyRingSignatureResult,
StealthAddress,
StealthKeypair,
OneTimeAddress,
SharedSecret,
Commitment,
CommitmentWithBlinding,
RangeProof,
VerifyRangeProofResult,
DEFAULT_PRIVACY_ENDPOINT,
)
from .client import SynorPrivacy
__all__ = [
"SynorPrivacy",
"PrivacyConfig",
"PrivacyError",
"PublicKey",
"PrivateKey",
"ConfidentialUTXO",
"ConfidentialOutput",
"ConfidentialTransaction",
"ConfidentialTxInput",
"ConfidentialTxOutput",
"OutputFeatures",
"TransactionKernel",
"VerifyConfidentialTxResult",
"RingSignature",
"VerifyRingSignatureResult",
"StealthAddress",
"StealthKeypair",
"OneTimeAddress",
"SharedSecret",
"Commitment",
"CommitmentWithBlinding",
"RangeProof",
"VerifyRangeProofResult",
]

View file

@ -0,0 +1,609 @@
"""
Synor Privacy SDK Client
Confidential transactions, ring signatures, stealth addresses, and commitments.
"""
import asyncio
from typing import Optional, List, Dict, Any
import httpx
from .types import (
PrivacyConfig,
PrivacyError,
ConfidentialUTXO,
ConfidentialOutput,
ConfidentialTransaction,
VerifyConfidentialTxResult,
RingSignature,
VerifyRingSignatureResult,
StealthAddress,
StealthKeypair,
OneTimeAddress,
SharedSecret,
Commitment,
CommitmentWithBlinding,
RangeProof,
VerifyRangeProofResult,
DEFAULT_PRIVACY_ENDPOINT,
)
class SynorPrivacy:
"""
Synor Privacy Client.
Provides privacy-enhancing features including confidential transactions,
ring signatures, stealth addresses, and Pedersen commitments.
Example:
async with SynorPrivacy(api_key="sk_...") as privacy:
# Generate stealth keypair
keypair = await privacy.generate_stealth_keypair()
print(f"Stealth address: {keypair.address.address}")
# Create ring signature
signature = await privacy.create_ring_signature(
message="Hello, World!",
ring=[pub_key1, pub_key2, my_pub_key, pub_key3],
private_key=my_private_key,
)
"""
def __init__(
self,
api_key: str,
endpoint: str = DEFAULT_PRIVACY_ENDPOINT,
timeout: float = 30.0,
retries: int = 3,
debug: bool = False,
):
self.config = PrivacyConfig(
api_key=api_key,
endpoint=endpoint,
timeout=timeout,
retries=retries,
debug=debug,
)
self._client = httpx.AsyncClient(
base_url=endpoint,
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"X-SDK-Version": "python/0.1.0",
},
timeout=timeout,
)
self._closed = False
# ==================== Confidential Transactions ====================
async def create_confidential_transaction(
self,
inputs: List[ConfidentialUTXO],
outputs: List[ConfidentialOutput],
fee: Optional[str] = None,
lock_height: Optional[int] = None,
) -> ConfidentialTransaction:
"""
Create a confidential transaction with hidden amounts.
Uses Pedersen commitments and Bulletproof range proofs to hide
transaction amounts while proving they are valid (non-negative).
Args:
inputs: Input UTXOs with commitments
outputs: Outputs to create
fee: Transaction fee
lock_height: Lock height for the transaction
Returns:
Confidential transaction ready for broadcast
"""
body: Dict[str, Any] = {
"inputs": [i.to_dict() for i in inputs],
"outputs": [o.to_dict() for o in outputs],
}
if fee:
body["fee"] = fee
if lock_height is not None:
body["lockHeight"] = lock_height
response = await self._request("POST", "/transactions/confidential", body)
return ConfidentialTransaction.from_dict(response["transaction"])
async def verify_confidential_transaction(
self, transaction: ConfidentialTransaction
) -> VerifyConfidentialTxResult:
"""
Verify a confidential transaction.
Checks that:
- Commitments balance (inputs = outputs + fee)
- All range proofs are valid (no negative amounts)
- Kernel signature is valid
- No duplicate inputs
Args:
transaction: Transaction to verify
Returns:
Verification result with details
"""
response = await self._request(
"POST",
"/transactions/confidential/verify",
{"transaction": transaction.raw},
)
return VerifyConfidentialTxResult.from_dict(response)
async def decode_confidential_output(
self, commitment: str, blinding: str
) -> str:
"""
Decode a confidential transaction output.
Args:
commitment: Output commitment
blinding: Blinding factor
Returns:
Decoded amount
"""
response = await self._request(
"POST",
"/transactions/confidential/decode",
{"commitment": commitment, "blinding": blinding},
)
return response["amount"]
# ==================== Ring Signatures ====================
async def create_ring_signature(
self,
message: str,
ring: List[str],
private_key: str,
signer_index: Optional[int] = None,
) -> RingSignature:
"""
Create a ring signature.
Ring signatures allow signing a message as "one of N" without
revealing which member of the ring actually signed.
Args:
message: Message to sign (hex or string)
ring: Ring of public keys (must include signer's key)
private_key: Signer's private key
signer_index: Optional index of signer's key in ring
Returns:
Ring signature
"""
body: Dict[str, Any] = {
"message": message,
"ring": ring,
"privateKey": private_key,
}
if signer_index is not None:
body["signerIndex"] = signer_index
response = await self._request("POST", "/ring-signatures/create", body)
return RingSignature.from_dict(response["signature"])
async def verify_ring_signature(
self, signature: RingSignature, message: str
) -> VerifyRingSignatureResult:
"""
Verify a ring signature.
Args:
signature: Ring signature to verify
message: Original message
Returns:
Verification result
"""
response = await self._request(
"POST",
"/ring-signatures/verify",
{"signature": signature.to_dict(), "message": message},
)
return VerifyRingSignatureResult.from_dict(response)
async def is_key_image_used(self, key_image: str) -> bool:
"""
Check if a key image has been used (double-spend detection).
Args:
key_image: Key image from ring signature
Returns:
True if the key image has been seen before
"""
response = await self._request(
"GET", f"/ring-signatures/key-images/{key_image}"
)
return response["used"]
async def generate_random_ring(
self, size: int, exclude: Optional[List[str]] = None
) -> List[str]:
"""
Generate a random ring from available public keys.
Args:
size: Desired ring size
exclude: Public keys to exclude
Returns:
Array of public keys for the ring
"""
body: Dict[str, Any] = {"size": size}
if exclude:
body["exclude"] = exclude
response = await self._request("POST", "/ring-signatures/random-ring", body)
return response["ring"]
# ==================== Stealth Addresses ====================
async def generate_stealth_keypair(self) -> StealthKeypair:
"""
Generate a new stealth address keypair.
Stealth addresses allow receiving payments at unique one-time
addresses that cannot be linked to the recipient's public address.
Returns:
Stealth keypair (keep private keys secure!)
"""
response = await self._request("POST", "/stealth/generate")
return StealthKeypair.from_dict(response["keypair"])
async def create_one_time_address(
self, stealth_address: StealthAddress
) -> OneTimeAddress:
"""
Create a one-time address for sending to a stealth address.
Args:
stealth_address: Recipient's stealth address
Returns:
One-time address and ephemeral key
"""
response = await self._request(
"POST",
"/stealth/one-time-address",
{
"scanPublicKey": stealth_address.scan_public_key,
"spendPublicKey": stealth_address.spend_public_key,
},
)
return OneTimeAddress.from_dict(response["oneTimeAddress"])
async def derive_shared_secret(
self,
stealth_address: StealthAddress,
private_key: str,
ephemeral_public_key: str,
) -> SharedSecret:
"""
Derive the shared secret for a stealth payment.
Args:
stealth_address: Stealth address
private_key: Private key for derivation
ephemeral_public_key: Ephemeral public key from transaction
Returns:
Shared secret and derived keys
"""
response = await self._request(
"POST",
"/stealth/derive-secret",
{
"scanPublicKey": stealth_address.scan_public_key,
"spendPublicKey": stealth_address.spend_public_key,
"privateKey": private_key,
"ephemeralPublicKey": ephemeral_public_key,
},
)
return SharedSecret.from_dict(response)
async def scan_for_payments(
self,
scan_private_key: str,
spend_public_key: str,
transactions: List[str],
) -> List[OneTimeAddress]:
"""
Scan transactions to find stealth payments.
Args:
scan_private_key: Your scan private key
spend_public_key: Your spend public key
transactions: Transaction IDs to scan
Returns:
Found payments with one-time addresses
"""
response = await self._request(
"POST",
"/stealth/scan",
{
"scanPrivateKey": scan_private_key,
"spendPublicKey": spend_public_key,
"transactions": transactions,
},
)
return [OneTimeAddress.from_dict(p) for p in response["payments"]]
# ==================== Pedersen Commitments ====================
async def create_commitment(
self, value: str, blinding: Optional[str] = None
) -> CommitmentWithBlinding:
"""
Create a Pedersen commitment.
C = vG + bH where v is the value and b is the blinding factor.
Args:
value: Value to commit to
blinding: Blinding factor (generated if not provided)
Returns:
Commitment and blinding factor
"""
body: Dict[str, Any] = {"value": value}
if blinding:
body["blinding"] = blinding
response = await self._request("POST", "/commitments/create", body)
return CommitmentWithBlinding.from_dict(response)
async def open_commitment(
self, commitment: str, value: str, blinding: str
) -> bool:
"""
Open (verify) a Pedersen commitment.
Args:
commitment: Commitment to open
value: Claimed value
blinding: Blinding factor
Returns:
True if the commitment opens correctly
"""
response = await self._request(
"POST",
"/commitments/open",
{"commitment": commitment, "value": value, "blinding": blinding},
)
return response["valid"]
async def add_commitments(self, commitment1: str, commitment2: str) -> str:
"""
Add two commitments.
Args:
commitment1: First commitment
commitment2: Second commitment
Returns:
Sum commitment
"""
response = await self._request(
"POST",
"/commitments/add",
{"commitment1": commitment1, "commitment2": commitment2},
)
return response["commitment"]
async def subtract_commitments(self, commitment1: str, commitment2: str) -> str:
"""
Subtract two commitments.
Args:
commitment1: First commitment
commitment2: Second commitment
Returns:
Difference commitment
"""
response = await self._request(
"POST",
"/commitments/subtract",
{"commitment1": commitment1, "commitment2": commitment2},
)
return response["commitment"]
async def compute_blinding_sum(
self, positive: List[str], negative: List[str]
) -> str:
"""
Compute blinding factor sum for transaction balancing.
Args:
positive: Positive blinding factors (inputs)
negative: Negative blinding factors (outputs)
Returns:
Net blinding factor
"""
response = await self._request(
"POST",
"/commitments/blinding-sum",
{"positive": positive, "negative": negative},
)
return response["sum"]
async def generate_blinding(self) -> str:
"""
Generate a random blinding factor.
Returns:
Random 32-byte blinding factor (hex)
"""
response = await self._request("POST", "/commitments/random-blinding")
return response["blinding"]
# ==================== Range Proofs ====================
async def create_range_proof(
self,
value: str,
blinding: str,
message: Optional[str] = None,
bit_length: int = 64,
) -> RangeProof:
"""
Create a Bulletproof range proof.
Args:
value: Value to prove is in range
blinding: Blinding factor
message: Optional message to embed in proof
bit_length: Bit length (default 64)
Returns:
Range proof
"""
body: Dict[str, Any] = {
"value": value,
"blinding": blinding,
"bitLength": bit_length,
}
if message:
body["message"] = message
response = await self._request("POST", "/range-proofs/create", body)
return RangeProof.from_dict(response["proof"])
async def verify_range_proof(
self, commitment: str, proof: str
) -> VerifyRangeProofResult:
"""
Verify a Bulletproof range proof.
Args:
commitment: Commitment the proof is for
proof: Range proof data
Returns:
Verification result
"""
response = await self._request(
"POST",
"/range-proofs/verify",
{"commitment": commitment, "proof": proof},
)
return VerifyRangeProofResult.from_dict(response)
async def create_aggregated_range_proof(
self, outputs: List[Dict[str, str]]
) -> str:
"""
Create an aggregated range proof for multiple outputs.
Args:
outputs: Array of {value, blinding} pairs
Returns:
Aggregated proof
"""
response = await self._request(
"POST", "/range-proofs/aggregate", {"outputs": outputs}
)
return response["proof"]
# ==================== Lifecycle ====================
async def close(self) -> None:
"""Close the client."""
self._closed = True
await self._client.aclose()
def is_closed(self) -> bool:
"""Check if client is closed."""
return self._closed
async def health_check(self) -> bool:
"""Health check."""
try:
response = await self._request("GET", "/health")
return response.get("status") == "healthy"
except PrivacyError:
return False
async def __aenter__(self) -> "SynorPrivacy":
return self
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
await self.close()
# ==================== Private Methods ====================
async def _request(
self,
method: str,
path: str,
body: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
"""Make HTTP request with retries."""
if self._closed:
raise PrivacyError("Client has been closed")
last_error: Optional[Exception] = None
for attempt in range(self.config.retries):
try:
return await self._do_request(method, path, body)
except PrivacyError as e:
if self.config.debug:
print(f"Attempt {attempt + 1} failed: {e}")
last_error = e
if attempt < self.config.retries - 1:
await asyncio.sleep(2**attempt)
if last_error:
raise last_error
raise PrivacyError("Unknown error after retries")
async def _do_request(
self,
method: str,
path: str,
body: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
"""Execute HTTP request."""
try:
response = await self._client.request(
method,
path,
json=body if body else None,
)
if response.status_code >= 400:
error_body = response.json() if response.content else {}
message = (
error_body.get("message")
or error_body.get("error")
or f"HTTP {response.status_code}"
)
raise PrivacyError(
message,
code=error_body.get("code"),
status_code=response.status_code,
details=error_body,
)
return response.json()
except httpx.TimeoutException as e:
raise PrivacyError(f"Request timeout: {e}")
except httpx.RequestError as e:
raise PrivacyError(f"Request failed: {e}")

View file

@ -0,0 +1,436 @@
"""
Synor Privacy SDK Types
Confidential transactions, ring signatures, stealth addresses, and commitments.
"""
from dataclasses import dataclass, field
from typing import Optional, List, Dict, Any, Literal
# ==================== Config Types ====================
DEFAULT_PRIVACY_ENDPOINT = "https://privacy.synor.cc/api/v1"
@dataclass
class PrivacyConfig:
"""Privacy SDK configuration."""
api_key: str
endpoint: str = DEFAULT_PRIVACY_ENDPOINT
timeout: float = 30.0
retries: int = 3
debug: bool = False
class PrivacyError(Exception):
"""Privacy-specific error."""
def __init__(
self,
message: str,
code: Optional[str] = None,
status_code: Optional[int] = None,
details: Optional[Dict[str, Any]] = None,
):
super().__init__(message)
self.message = message
self.code = code
self.status_code = status_code
self.details = details or {}
# ==================== Key Types ====================
KeyType = Literal["ed25519", "secp256k1"]
@dataclass
class PublicKey:
"""Public key representation."""
key: str
type: KeyType
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "PublicKey":
return cls(
key=data["key"],
type=data["type"],
)
@dataclass
class PrivateKey:
"""Private key representation (for signing operations)."""
key: str
type: KeyType
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "PrivateKey":
return cls(
key=data["key"],
type=data["type"],
)
# ==================== Confidential Transaction Types ====================
@dataclass
class ConfidentialUTXO:
"""UTXO for confidential transaction input."""
txid: str
vout: int
commitment: str
range_proof: str
blinding: Optional[str] = None
amount: Optional[str] = None
def to_dict(self) -> Dict[str, Any]:
d = {
"txid": self.txid,
"vout": self.vout,
"commitment": self.commitment,
"rangeProof": self.range_proof,
}
if self.blinding:
d["blinding"] = self.blinding
if self.amount:
d["amount"] = self.amount
return d
@dataclass
class ConfidentialOutput:
"""Output for confidential transaction."""
recipient: str
amount: str
blinding: Optional[str] = None
def to_dict(self) -> Dict[str, Any]:
d = {
"recipient": self.recipient,
"amount": self.amount,
}
if self.blinding:
d["blinding"] = self.blinding
return d
@dataclass
class OutputFeatures:
"""Output features for confidential transactions."""
flags: int
lock_height: Optional[int] = None
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "OutputFeatures":
return cls(
flags=data["flags"],
lock_height=data.get("lockHeight", data.get("lock_height")),
)
@dataclass
class ConfidentialTxInput:
"""Confidential transaction input."""
output_ref: str
commitment: str
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "ConfidentialTxInput":
return cls(
output_ref=data.get("outputRef", data.get("output_ref", "")),
commitment=data["commitment"],
)
@dataclass
class ConfidentialTxOutput:
"""Confidential transaction output."""
commitment: str
range_proof: str
features: OutputFeatures
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "ConfidentialTxOutput":
return cls(
commitment=data["commitment"],
range_proof=data.get("rangeProof", data.get("range_proof", "")),
features=OutputFeatures.from_dict(data["features"]),
)
@dataclass
class TransactionKernel:
"""Transaction kernel for confidential transactions."""
features: int
fee: str
lock_height: int
excess: str
excess_signature: str
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "TransactionKernel":
return cls(
features=data["features"],
fee=data["fee"],
lock_height=data.get("lockHeight", data.get("lock_height", 0)),
excess=data["excess"],
excess_signature=data.get("excessSignature", data.get("excess_signature", "")),
)
@dataclass
class ConfidentialTransaction:
"""Confidential transaction."""
txid: str
version: int
inputs: List[ConfidentialTxInput]
outputs: List[ConfidentialTxOutput]
kernel: TransactionKernel
offset: str
raw: str
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "ConfidentialTransaction":
return cls(
txid=data["txid"],
version=data["version"],
inputs=[ConfidentialTxInput.from_dict(i) for i in data.get("inputs", [])],
outputs=[ConfidentialTxOutput.from_dict(o) for o in data.get("outputs", [])],
kernel=TransactionKernel.from_dict(data["kernel"]),
offset=data["offset"],
raw=data["raw"],
)
@dataclass
class VerifyConfidentialTxDetails:
"""Verification details for confidential transaction."""
commitments_balance: bool
range_proofs_valid: bool
signature_valid: bool
no_duplicate_inputs: bool
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "VerifyConfidentialTxDetails":
return cls(
commitments_balance=data.get("commitmentsBalance", data.get("commitments_balance", False)),
range_proofs_valid=data.get("rangeProofsValid", data.get("range_proofs_valid", False)),
signature_valid=data.get("signatureValid", data.get("signature_valid", False)),
no_duplicate_inputs=data.get("noDuplicateInputs", data.get("no_duplicate_inputs", False)),
)
@dataclass
class VerifyConfidentialTxResult:
"""Confidential transaction verification result."""
valid: bool
details: VerifyConfidentialTxDetails
error: Optional[str] = None
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "VerifyConfidentialTxResult":
return cls(
valid=data["valid"],
details=VerifyConfidentialTxDetails.from_dict(data["details"]),
error=data.get("error"),
)
# ==================== Ring Signature Types ====================
@dataclass
class RingSignatureComponents:
"""Ring signature components."""
c: List[str]
r: List[str]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "RingSignatureComponents":
return cls(
c=data["c"],
r=data["r"],
)
@dataclass
class RingSignature:
"""Ring signature."""
id: str
message_hash: str
ring: List[str]
key_image: str
signature: RingSignatureComponents
ring_size: int
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "RingSignature":
return cls(
id=data["id"],
message_hash=data.get("messageHash", data.get("message_hash", "")),
ring=data["ring"],
key_image=data.get("keyImage", data.get("key_image", "")),
signature=RingSignatureComponents.from_dict(data["signature"]),
ring_size=data.get("ringSize", data.get("ring_size", 0)),
)
def to_dict(self) -> Dict[str, Any]:
return {
"id": self.id,
"messageHash": self.message_hash,
"ring": self.ring,
"keyImage": self.key_image,
"signature": {
"c": self.signature.c,
"r": self.signature.r,
},
"ringSize": self.ring_size,
}
@dataclass
class VerifyRingSignatureResult:
"""Ring signature verification result."""
valid: bool
key_image: str
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "VerifyRingSignatureResult":
return cls(
valid=data["valid"],
key_image=data.get("keyImage", data.get("key_image", "")),
)
# ==================== Stealth Address Types ====================
@dataclass
class StealthAddress:
"""Stealth address."""
address: str
scan_public_key: str
spend_public_key: str
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "StealthAddress":
return cls(
address=data["address"],
scan_public_key=data.get("scanPublicKey", data.get("scan_public_key", "")),
spend_public_key=data.get("spendPublicKey", data.get("spend_public_key", "")),
)
@dataclass
class StealthKeypair:
"""Stealth address keypair."""
address: StealthAddress
scan_private_key: str
spend_private_key: str
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "StealthKeypair":
return cls(
address=StealthAddress.from_dict(data["address"]),
scan_private_key=data.get("scanPrivateKey", data.get("scan_private_key", "")),
spend_private_key=data.get("spendPrivateKey", data.get("spend_private_key", "")),
)
@dataclass
class OneTimeAddress:
"""One-time address derived from stealth address."""
address: str
ephemeral_public_key: str
shared_secret: Optional[str] = None
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "OneTimeAddress":
return cls(
address=data["address"],
ephemeral_public_key=data.get("ephemeralPublicKey", data.get("ephemeral_public_key", "")),
shared_secret=data.get("sharedSecret", data.get("shared_secret")),
)
@dataclass
class SharedSecret:
"""Shared secret result."""
secret: str
one_time_private_key: str
one_time_address: str
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "SharedSecret":
return cls(
secret=data["secret"],
one_time_private_key=data.get("oneTimePrivateKey", data.get("one_time_private_key", "")),
one_time_address=data.get("oneTimeAddress", data.get("one_time_address", "")),
)
# ==================== Commitment Types ====================
GeneratorType = Literal["default", "alternate"]
@dataclass
class Commitment:
"""Pedersen commitment."""
commitment: str
generator: GeneratorType
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "Commitment":
return cls(
commitment=data["commitment"],
generator=data.get("generator", "default"),
)
@dataclass
class CommitmentWithBlinding:
"""Commitment with blinding factor."""
commitment: Commitment
blinding: str
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "CommitmentWithBlinding":
return cls(
commitment=Commitment.from_dict(data["commitment"]),
blinding=data["blinding"],
)
# ==================== Range Proof Types ====================
@dataclass
class RangeProof:
"""Bulletproof range proof."""
proof: str
commitment: str
bit_length: int
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "RangeProof":
return cls(
proof=data["proof"],
commitment=data["commitment"],
bit_length=data.get("bitLength", data.get("bit_length", 64)),
)
@dataclass
class VerifyRangeProofResult:
"""Range proof verification result."""
valid: bool
min_value: str
max_value: str
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "VerifyRangeProofResult":
return cls(
valid=data["valid"],
min_value=data.get("minValue", data.get("min_value", "0")),
max_value=data.get("maxValue", data.get("max_value", "")),
)

View file

@ -0,0 +1,9 @@
# frozen_string_literal: true
require_relative "synor_contract/version"
require_relative "synor_contract/types"
require_relative "synor_contract/client"
module SynorContract
VERSION = "0.1.0"
end

View file

@ -0,0 +1,370 @@
# frozen_string_literal: true
require "faraday"
require "json"
require "uri"
module SynorContract
# Synor Contract SDK client for Ruby.
# Smart contract deployment, interaction, and event handling.
class Client
attr_reader :closed
def initialize(config)
@config = config
@closed = false
@conn = Faraday.new(url: config.endpoint) do |f|
f.request :json
f.response :json
f.options.timeout = config.timeout
f.headers["Authorization"] = "Bearer #{config.api_key}"
f.headers["Content-Type"] = "application/json"
f.headers["X-SDK-Version"] = "ruby/#{VERSION}"
end
end
# ==================== Contract Deployment ====================
def deploy(options)
body = {
bytecode: options.bytecode
}
body[:abi] = options.abi.map(&:to_h) if options.abi
body[:constructor_args] = options.constructor_args if options.constructor_args
body[:value] = options.value if options.value
body[:gas_limit] = options.gas_limit if options.gas_limit
body[:gas_price] = options.gas_price if options.gas_price
body[:nonce] = options.nonce if options.nonce
response = post("/contract/deploy", body)
parse_deployment_result(response)
end
def deploy_create2(options, salt:)
body = {
bytecode: options.bytecode,
salt: salt
}
body[:abi] = options.abi.map(&:to_h) if options.abi
body[:constructor_args] = options.constructor_args if options.constructor_args
body[:value] = options.value if options.value
body[:gas_limit] = options.gas_limit if options.gas_limit
body[:gas_price] = options.gas_price if options.gas_price
response = post("/contract/deploy/create2", body)
parse_deployment_result(response)
end
def predict_address(bytecode:, salt:, deployer: nil)
body = { bytecode: bytecode, salt: salt }
body[:deployer] = deployer if deployer
response = post("/contract/predict-address", body)
response["address"]
end
# ==================== Contract Interaction ====================
def call(options)
body = {
contract: options.contract,
method: options.method,
args: options.args,
abi: options.abi.map(&:to_h)
}
post("/contract/call", body)
end
def send(options)
body = {
contract: options.contract,
method: options.method,
args: options.args,
abi: options.abi.map(&:to_h)
}
body[:value] = options.value if options.value
body[:gas_limit] = options.gas_limit if options.gas_limit
body[:gas_price] = options.gas_price if options.gas_price
body[:nonce] = options.nonce if options.nonce
response = post("/contract/send", body)
parse_transaction_result(response)
end
# ==================== Events ====================
def get_events(filter)
body = { contract: filter.contract }
body[:event] = filter.event if filter.event
body[:from_block] = filter.from_block if filter.from_block
body[:to_block] = filter.to_block if filter.to_block
body[:topics] = filter.topics if filter.topics
body[:abi] = filter.abi.map(&:to_h) if filter.abi
response = post("/contract/events", body)
response.map { |e| parse_decoded_event(e) }
end
def get_logs(contract, from_block: nil, to_block: nil)
path = "/contract/logs?contract=#{encode(contract)}"
path += "&from_block=#{from_block}" if from_block
path += "&to_block=#{to_block}" if to_block
response = get(path)
response.map { |l| parse_event_log(l) }
end
def decode_logs(logs:, abi:)
body = {
logs: logs.map(&:to_h),
abi: abi.map(&:to_h)
}
response = post("/contract/decode-logs", body)
response.map { |e| parse_decoded_event(e) }
end
# ==================== ABI Utilities ====================
def encode_call(options)
body = {
method: options.method,
args: options.args,
abi: options.abi.map(&:to_h)
}
response = post("/contract/encode", body)
response["data"]
end
def decode_result(options)
body = {
data: options.data,
method: options.method,
abi: options.abi.map(&:to_h)
}
response = post("/contract/decode", body)
response["result"]
end
def get_selector(signature)
response = get("/contract/selector?signature=#{encode(signature)}")
response["selector"]
end
# ==================== Gas Estimation ====================
def estimate_gas(options)
body = {
contract: options.contract,
method: options.method,
args: options.args,
abi: options.abi.map(&:to_h)
}
body[:value] = options.value if options.value
response = post("/contract/estimate-gas", body)
parse_gas_estimation(response)
end
# ==================== Contract Information ====================
def get_bytecode(address)
response = get("/contract/#{encode(address)}/bytecode")
parse_bytecode_info(response)
end
def verify(options)
body = {
address: options.address,
source_code: options.source_code,
compiler_version: options.compiler_version
}
body[:constructor_args] = options.constructor_args if options.constructor_args
body[:optimization] = options.optimization if options.optimization
body[:optimization_runs] = options.optimization_runs if options.optimization_runs
body[:license] = options.license if options.license
response = post("/contract/verify", body)
parse_verification_result(response)
end
def get_verification_status(address)
response = get("/contract/#{encode(address)}/verification")
parse_verification_result(response)
end
# ==================== Multicall ====================
def multicall(requests)
body = { calls: requests.map(&:to_h) }
response = post("/contract/multicall", body)
response.map { |r| parse_multicall_result(r) }
end
# ==================== Storage ====================
def read_storage(options)
path = "/contract/storage?contract=#{encode(options.contract)}&slot=#{encode(options.slot)}"
path += "&block=#{options.block_number}" if options.block_number
response = get(path)
response["value"]
end
# ==================== Lifecycle ====================
def health_check
response = get("/health")
response["status"] == "healthy"
rescue StandardError
false
end
def close
@closed = true
@conn.close if @conn.respond_to?(:close)
end
private
def get(path, params = {})
execute { @conn.get(path, params).body }
end
def post(path, body)
execute { @conn.post(path, body).body }
end
def execute
raise ClientClosedError, "Client has been closed" if @closed
last_error = nil
@config.retries.times do |attempt|
begin
response = yield
check_error(response) if response.is_a?(Hash)
return response
rescue StandardError => e
last_error = e
sleep(2**attempt) if attempt < @config.retries - 1
end
end
raise last_error
end
def check_error(response)
return unless response["error"] || (response["code"] && response["message"])
message = response["message"] || response["error"] || "Unknown error"
code = response["code"]
status = response["status_code"] || 0
raise HttpError.new(message, status_code: status, code: code)
end
def encode(str)
URI.encode_www_form_component(str.to_s)
end
def parse_deployment_result(data)
DeploymentResult.new(
contract_address: data["contract_address"],
transaction_hash: data["transaction_hash"],
deployer: data["deployer"],
gas_used: data["gas_used"],
block_number: data["block_number"],
block_hash: data["block_hash"]
)
end
def parse_transaction_result(data)
TransactionResult.new(
transaction_hash: data["transaction_hash"],
block_number: data["block_number"],
block_hash: data["block_hash"],
gas_used: data["gas_used"],
effective_gas_price: data["effective_gas_price"],
status: data["status"],
logs: (data["logs"] || []).map { |l| parse_event_log(l) }
)
end
def parse_event_log(data)
EventLog.new(
address: data["address"],
topics: data["topics"],
data: data["data"],
block_number: data["block_number"],
transaction_hash: data["transaction_hash"],
log_index: data["log_index"],
block_hash: data["block_hash"],
removed: data["removed"]
)
end
def parse_decoded_event(data)
DecodedEvent.new(
name: data["name"],
signature: data["signature"],
args: data["args"],
log: data["log"] ? parse_event_log(data["log"]) : nil
)
end
def parse_gas_estimation(data)
GasEstimation.new(
gas_limit: data["gas_limit"],
gas_price: data["gas_price"],
max_fee_per_gas: data["max_fee_per_gas"],
max_priority_fee_per_gas: data["max_priority_fee_per_gas"],
estimated_cost: data["estimated_cost"]
)
end
def parse_bytecode_info(data)
BytecodeInfo.new(
bytecode: data["bytecode"],
deployed_bytecode: data["deployed_bytecode"],
size: data["size"],
is_contract: data["is_contract"]
)
end
def parse_verification_result(data)
VerificationResult.new(
verified: data["verified"],
address: data["address"],
compiler_version: data["compiler_version"],
optimization: data["optimization"],
optimization_runs: data["optimization_runs"],
license: data["license"],
abi: data["abi"]&.map { |e| parse_abi_entry(e) },
source_code: data["source_code"]
)
end
def parse_abi_entry(data)
AbiEntry.new(
type: data["type"],
name: data["name"],
inputs: data["inputs"]&.map { |p| parse_abi_parameter(p) },
outputs: data["outputs"]&.map { |p| parse_abi_parameter(p) },
state_mutability: data["stateMutability"],
anonymous: data["anonymous"]
)
end
def parse_abi_parameter(data)
AbiParameter.new(
name: data["name"],
type: data["type"],
indexed: data["indexed"],
components: data["components"]&.map { |c| parse_abi_parameter(c) }
)
end
def parse_multicall_result(data)
MulticallResult.new(
success: data["success"],
result: data["result"],
error: data["error"]
)
end
end
end

View file

@ -0,0 +1,326 @@
# frozen_string_literal: true
module SynorContract
# Configuration for the Contract SDK.
class Config
attr_accessor :api_key, :endpoint, :timeout, :retries
def initialize(api_key:, endpoint: "https://contract.synor.io", timeout: 30, retries: 3)
@api_key = api_key
@endpoint = endpoint
@timeout = timeout
@retries = retries
end
end
# ABI parameter.
class AbiParameter
attr_accessor :name, :type, :indexed, :components
def initialize(name: nil, type:, indexed: nil, components: nil)
@name = name
@type = type
@indexed = indexed
@components = components
end
def to_h
h = { type: @type }
h[:name] = @name if @name
h[:indexed] = @indexed unless @indexed.nil?
h[:components] = @components.map(&:to_h) if @components
h
end
end
# ABI entry.
class AbiEntry
attr_accessor :type, :name, :inputs, :outputs, :state_mutability, :anonymous
def initialize(type:, name: nil, inputs: nil, outputs: nil, state_mutability: nil, anonymous: nil)
@type = type
@name = name
@inputs = inputs
@outputs = outputs
@state_mutability = state_mutability
@anonymous = anonymous
end
def to_h
h = { type: @type }
h[:name] = @name if @name
h[:inputs] = @inputs.map(&:to_h) if @inputs
h[:outputs] = @outputs.map(&:to_h) if @outputs
h[:stateMutability] = @state_mutability if @state_mutability
h[:anonymous] = @anonymous unless @anonymous.nil?
h
end
end
# Deploy contract options.
class DeployContractOptions
attr_accessor :bytecode, :abi, :constructor_args, :value, :gas_limit, :gas_price, :nonce
def initialize(bytecode:, abi: nil, constructor_args: nil, value: nil, gas_limit: nil, gas_price: nil, nonce: nil)
@bytecode = bytecode
@abi = abi
@constructor_args = constructor_args
@value = value
@gas_limit = gas_limit
@gas_price = gas_price
@nonce = nonce
end
end
# Deployment result.
class DeploymentResult
attr_accessor :contract_address, :transaction_hash, :deployer, :gas_used, :block_number, :block_hash
def initialize(contract_address:, transaction_hash:, deployer: nil, gas_used: nil, block_number: nil, block_hash: nil)
@contract_address = contract_address
@transaction_hash = transaction_hash
@deployer = deployer
@gas_used = gas_used
@block_number = block_number
@block_hash = block_hash
end
end
# Call contract options.
class CallContractOptions
attr_accessor :contract, :method, :args, :abi
def initialize(contract:, method:, args: [], abi:)
@contract = contract
@method = method
@args = args
@abi = abi
end
end
# Send contract options.
class SendContractOptions
attr_accessor :contract, :method, :args, :abi, :value, :gas_limit, :gas_price, :nonce
def initialize(contract:, method:, args: [], abi:, value: nil, gas_limit: nil, gas_price: nil, nonce: nil)
@contract = contract
@method = method
@args = args
@abi = abi
@value = value
@gas_limit = gas_limit
@gas_price = gas_price
@nonce = nonce
end
end
# Event log.
class EventLog
attr_accessor :address, :topics, :data, :block_number, :transaction_hash, :log_index, :block_hash, :removed
def initialize(address:, topics:, data:, block_number: nil, transaction_hash: nil, log_index: nil, block_hash: nil, removed: nil)
@address = address
@topics = topics
@data = data
@block_number = block_number
@transaction_hash = transaction_hash
@log_index = log_index
@block_hash = block_hash
@removed = removed
end
def to_h
h = { address: @address, topics: @topics, data: @data }
h[:block_number] = @block_number if @block_number
h[:transaction_hash] = @transaction_hash if @transaction_hash
h[:log_index] = @log_index if @log_index
h[:block_hash] = @block_hash if @block_hash
h[:removed] = @removed unless @removed.nil?
h
end
end
# Transaction result.
class TransactionResult
attr_accessor :transaction_hash, :block_number, :block_hash, :gas_used, :effective_gas_price, :status, :logs
def initialize(transaction_hash:, block_number: nil, block_hash: nil, gas_used: nil, effective_gas_price: nil, status: nil, logs: nil)
@transaction_hash = transaction_hash
@block_number = block_number
@block_hash = block_hash
@gas_used = gas_used
@effective_gas_price = effective_gas_price
@status = status
@logs = logs
end
end
# Decoded event.
class DecodedEvent
attr_accessor :name, :signature, :args, :log
def initialize(name:, signature: nil, args: nil, log: nil)
@name = name
@signature = signature
@args = args
@log = log
end
end
# Event filter.
class EventFilter
attr_accessor :contract, :event, :from_block, :to_block, :topics, :abi
def initialize(contract:, event: nil, from_block: nil, to_block: nil, topics: nil, abi: nil)
@contract = contract
@event = event
@from_block = from_block
@to_block = to_block
@topics = topics
@abi = abi
end
end
# Encode call options.
class EncodeCallOptions
attr_accessor :method, :args, :abi
def initialize(method:, args: [], abi:)
@method = method
@args = args
@abi = abi
end
end
# Decode result options.
class DecodeResultOptions
attr_accessor :data, :method, :abi
def initialize(data:, method:, abi:)
@data = data
@method = method
@abi = abi
end
end
# Estimate gas options.
class EstimateGasOptions
attr_accessor :contract, :method, :args, :abi, :value
def initialize(contract:, method:, args: [], abi:, value: nil)
@contract = contract
@method = method
@args = args
@abi = abi
@value = value
end
end
# Gas estimation result.
class GasEstimation
attr_accessor :gas_limit, :gas_price, :max_fee_per_gas, :max_priority_fee_per_gas, :estimated_cost
def initialize(gas_limit:, gas_price: nil, max_fee_per_gas: nil, max_priority_fee_per_gas: nil, estimated_cost: nil)
@gas_limit = gas_limit
@gas_price = gas_price
@max_fee_per_gas = max_fee_per_gas
@max_priority_fee_per_gas = max_priority_fee_per_gas
@estimated_cost = estimated_cost
end
end
# Bytecode info.
class BytecodeInfo
attr_accessor :bytecode, :deployed_bytecode, :size, :is_contract
def initialize(bytecode:, deployed_bytecode: nil, size: nil, is_contract: nil)
@bytecode = bytecode
@deployed_bytecode = deployed_bytecode
@size = size
@is_contract = is_contract
end
end
# Verify contract options.
class VerifyContractOptions
attr_accessor :address, :source_code, :compiler_version, :constructor_args, :optimization, :optimization_runs, :license
def initialize(address:, source_code:, compiler_version:, constructor_args: nil, optimization: nil, optimization_runs: nil, license: nil)
@address = address
@source_code = source_code
@compiler_version = compiler_version
@constructor_args = constructor_args
@optimization = optimization
@optimization_runs = optimization_runs
@license = license
end
end
# Verification result.
class VerificationResult
attr_accessor :verified, :address, :compiler_version, :optimization, :optimization_runs, :license, :abi, :source_code
def initialize(verified:, address: nil, compiler_version: nil, optimization: nil, optimization_runs: nil, license: nil, abi: nil, source_code: nil)
@verified = verified
@address = address
@compiler_version = compiler_version
@optimization = optimization
@optimization_runs = optimization_runs
@license = license
@abi = abi
@source_code = source_code
end
end
# Multicall request.
class MulticallRequest
attr_accessor :contract, :method, :args, :abi
def initialize(contract:, method:, args: [], abi:)
@contract = contract
@method = method
@args = args
@abi = abi
end
def to_h
{ contract: @contract, method: @method, args: @args, abi: @abi.map(&:to_h) }
end
end
# Multicall result.
class MulticallResult
attr_accessor :success, :result, :error
def initialize(success:, result: nil, error: nil)
@success = success
@result = result
@error = error
end
end
# Read storage options.
class ReadStorageOptions
attr_accessor :contract, :slot, :block_number
def initialize(contract:, slot:, block_number: nil)
@contract = contract
@slot = slot
@block_number = block_number
end
end
# Client closed error.
class ClientClosedError < StandardError; end
# HTTP error.
class HttpError < StandardError
attr_reader :status_code, :code
def initialize(message, status_code: nil, code: nil)
super(message)
@status_code = status_code
@code = code
end
end
end

View file

@ -0,0 +1,5 @@
# frozen_string_literal: true
module SynorContract
VERSION = "0.1.0"
end

View file

@ -0,0 +1,9 @@
# frozen_string_literal: true
require_relative "synor_privacy/version"
require_relative "synor_privacy/types"
require_relative "synor_privacy/client"
module SynorPrivacy
VERSION = "0.1.0"
end

View file

@ -0,0 +1,269 @@
# frozen_string_literal: true
require "faraday"
require "json"
require "uri"
module SynorPrivacy
# Synor Privacy SDK client for Ruby.
# Confidential transactions, ring signatures, stealth addresses, and cryptographic commitments.
class Client
attr_reader :closed
def initialize(config)
@config = config
@closed = false
@conn = Faraday.new(url: config.endpoint) do |f|
f.request :json
f.response :json
f.options.timeout = config.timeout
f.headers["Authorization"] = "Bearer #{config.api_key}"
f.headers["Content-Type"] = "application/json"
f.headers["X-SDK-Version"] = "ruby/#{VERSION}"
end
end
# ==================== Confidential Transactions ====================
def create_confidential_tx(inputs:, outputs:)
body = { inputs: inputs.map(&:to_h), outputs: outputs.map(&:to_h) }
response = post("/privacy/confidential/create", body)
parse_confidential_transaction(response)
end
def verify_confidential_tx(tx:)
body = { transaction: tx.to_h }
response = post("/privacy/confidential/verify", body)
response["valid"] == true
end
def create_commitment(value:, blinding_factor:)
body = { value: value, blinding_factor: blinding_factor }
response = post("/privacy/commitment/create", body)
Commitment.new(
commitment: response["commitment"],
blinding_factor: response["blinding_factor"]
)
end
def verify_commitment(commitment:, value:, blinding_factor:)
body = { commitment: commitment, value: value, blinding_factor: blinding_factor }
response = post("/privacy/commitment/verify", body)
response["valid"] == true
end
def create_range_proof(value:, blinding_factor:, min_value:, max_value:)
body = {
value: value,
blinding_factor: blinding_factor,
min_value: min_value,
max_value: max_value
}
response = post("/privacy/range-proof/create", body)
RangeProof.new(
proof: response["proof"],
commitment: response["commitment"],
min_value: response["min_value"],
max_value: response["max_value"]
)
end
def verify_range_proof(proof:)
body = { proof: proof.to_h }
response = post("/privacy/range-proof/verify", body)
response["valid"] == true
end
# ==================== Ring Signatures ====================
def create_ring_signature(message:, ring:, signer_index:, private_key:)
body = {
message: message,
ring: ring,
signer_index: signer_index,
private_key: private_key
}
response = post("/privacy/ring/sign", body)
RingSignature.new(
c0: response["c0"],
s: response["s"],
key_image: response["key_image"],
ring: response["ring"]
)
end
def verify_ring_signature(signature:, message:)
body = { signature: signature.to_h, message: message }
response = post("/privacy/ring/verify", body)
response["valid"] == true
end
def generate_decoys(count, exclude_key: nil)
path = "/privacy/ring/decoys?count=#{count}"
path += "&exclude=#{encode(exclude_key)}" if exclude_key
get(path)
end
def check_key_image(key_image)
response = get("/privacy/ring/key-image/#{key_image}")
response["spent"] == true
end
# ==================== Stealth Addresses ====================
def generate_stealth_keypair
response = post("/privacy/stealth/generate", {})
StealthKeyPair.new(
spend_public_key: response["spend_public_key"],
spend_private_key: response["spend_private_key"],
view_public_key: response["view_public_key"],
view_private_key: response["view_private_key"]
)
end
def derive_stealth_address(spend_public_key:, view_public_key:)
body = { spend_public_key: spend_public_key, view_public_key: view_public_key }
response = post("/privacy/stealth/derive", body)
StealthAddress.new(
address: response["address"],
ephemeral_public_key: response["ephemeral_public_key"],
tx_public_key: response["tx_public_key"]
)
end
def recover_stealth_private_key(stealth_address:, view_private_key:, spend_private_key:)
body = {
stealth_address: stealth_address,
view_private_key: view_private_key,
spend_private_key: spend_private_key
}
response = post("/privacy/stealth/recover", body)
response["private_key"]
end
def scan_outputs(view_private_key:, spend_public_key:, from_block:, to_block: nil)
body = {
view_private_key: view_private_key,
spend_public_key: spend_public_key,
from_block: from_block
}
body[:to_block] = to_block if to_block
response = post("/privacy/stealth/scan", body)
response.map { |o| parse_stealth_output(o) }
end
# ==================== Blinding ====================
def generate_blinding_factor
response = post("/privacy/blinding/generate", {})
response["blinding_factor"]
end
def blind_value(value:, blinding_factor:)
body = { value: value, blinding_factor: blinding_factor }
response = post("/privacy/blinding/blind", body)
response["blinded_value"]
end
def unblind_value(blinded_value:, blinding_factor:)
body = { blinded_value: blinded_value, blinding_factor: blinding_factor }
response = post("/privacy/blinding/unblind", body)
response["value"]
end
# ==================== Lifecycle ====================
def health_check
response = get("/health")
response["status"] == "healthy"
rescue StandardError
false
end
def close
@closed = true
@conn.close if @conn.respond_to?(:close)
end
private
def get(path, params = {})
execute { @conn.get(path, params).body }
end
def post(path, body)
execute { @conn.post(path, body).body }
end
def execute
raise ClientClosedError, "Client has been closed" if @closed
last_error = nil
@config.retries.times do |attempt|
begin
response = yield
check_error(response) if response.is_a?(Hash)
return response
rescue StandardError => e
last_error = e
sleep(2**attempt) if attempt < @config.retries - 1
end
end
raise last_error
end
def check_error(response)
return unless response["error"] || (response["code"] && response["message"])
message = response["message"] || response["error"] || "Unknown error"
code = response["code"]
status = response["status_code"] || 0
raise HttpError.new(message, status_code: status, code: code)
end
def encode(str)
URI.encode_www_form_component(str)
end
def parse_confidential_transaction(data)
ConfidentialTransaction.new(
id: data["id"],
inputs: (data["inputs"] || []).map { |i| parse_confidential_input(i) },
outputs: (data["outputs"] || []).map { |o| parse_confidential_output(o) },
fee: data["fee"],
excess: data["excess"],
excess_sig: data["excess_sig"],
kernel_offset: data["kernel_offset"]
)
end
def parse_confidential_input(data)
ConfidentialTxInput.new(
commitment: data["commitment"],
blinding_factor: data["blinding_factor"],
value: data["value"],
key_image: data["key_image"]
)
end
def parse_confidential_output(data)
ConfidentialTxOutput.new(
commitment: data["commitment"],
blinding_factor: data["blinding_factor"],
value: data["value"],
recipient_public_key: data["recipient_public_key"],
range_proof: data["range_proof"]
)
end
def parse_stealth_output(data)
StealthOutput.new(
tx_hash: data["tx_hash"],
output_index: data["output_index"],
stealth_address: data["stealth_address"],
amount: data["amount"],
block_height: data["block_height"]
)
end
end
end

View file

@ -0,0 +1,190 @@
# frozen_string_literal: true
module SynorPrivacy
# Configuration for the Privacy SDK.
class Config
attr_accessor :api_key, :endpoint, :timeout, :retries
def initialize(api_key:, endpoint: "https://privacy.synor.io", timeout: 30, retries: 3)
@api_key = api_key
@endpoint = endpoint
@timeout = timeout
@retries = retries
end
end
# Confidential transaction input.
class ConfidentialTxInput
attr_accessor :commitment, :blinding_factor, :value, :key_image
def initialize(commitment:, blinding_factor:, value:, key_image: nil)
@commitment = commitment
@blinding_factor = blinding_factor
@value = value
@key_image = key_image
end
def to_h
h = {
commitment: @commitment,
blinding_factor: @blinding_factor,
value: @value
}
h[:key_image] = @key_image if @key_image
h
end
end
# Confidential transaction output.
class ConfidentialTxOutput
attr_accessor :commitment, :blinding_factor, :value, :recipient_public_key, :range_proof
def initialize(commitment:, blinding_factor:, value:, recipient_public_key:, range_proof: nil)
@commitment = commitment
@blinding_factor = blinding_factor
@value = value
@recipient_public_key = recipient_public_key
@range_proof = range_proof
end
def to_h
h = {
commitment: @commitment,
blinding_factor: @blinding_factor,
value: @value,
recipient_public_key: @recipient_public_key
}
h[:range_proof] = @range_proof if @range_proof
h
end
end
# Confidential transaction.
class ConfidentialTransaction
attr_accessor :id, :inputs, :outputs, :fee, :excess, :excess_sig, :kernel_offset
def initialize(id:, inputs:, outputs:, fee:, excess:, excess_sig:, kernel_offset: nil)
@id = id
@inputs = inputs
@outputs = outputs
@fee = fee
@excess = excess
@excess_sig = excess_sig
@kernel_offset = kernel_offset
end
def to_h
h = {
id: @id,
inputs: @inputs.map(&:to_h),
outputs: @outputs.map(&:to_h),
fee: @fee,
excess: @excess,
excess_sig: @excess_sig
}
h[:kernel_offset] = @kernel_offset if @kernel_offset
h
end
end
# Pedersen commitment.
class Commitment
attr_accessor :commitment, :blinding_factor
def initialize(commitment:, blinding_factor:)
@commitment = commitment
@blinding_factor = blinding_factor
end
def to_h
{ commitment: @commitment, blinding_factor: @blinding_factor }
end
end
# Bulletproof range proof.
class RangeProof
attr_accessor :proof, :commitment, :min_value, :max_value
def initialize(proof:, commitment:, min_value:, max_value:)
@proof = proof
@commitment = commitment
@min_value = min_value
@max_value = max_value
end
def to_h
{
proof: @proof,
commitment: @commitment,
min_value: @min_value,
max_value: @max_value
}
end
end
# Ring signature for anonymous signing.
class RingSignature
attr_accessor :c0, :s, :key_image, :ring
def initialize(c0:, s:, key_image:, ring:)
@c0 = c0
@s = s
@key_image = key_image
@ring = ring
end
def to_h
{ c0: @c0, s: @s, key_image: @key_image, ring: @ring }
end
end
# Stealth address key pair.
class StealthKeyPair
attr_accessor :spend_public_key, :spend_private_key, :view_public_key, :view_private_key
def initialize(spend_public_key:, spend_private_key:, view_public_key:, view_private_key:)
@spend_public_key = spend_public_key
@spend_private_key = spend_private_key
@view_public_key = view_public_key
@view_private_key = view_private_key
end
end
# Derived stealth address.
class StealthAddress
attr_accessor :address, :ephemeral_public_key, :tx_public_key
def initialize(address:, ephemeral_public_key:, tx_public_key: nil)
@address = address
@ephemeral_public_key = ephemeral_public_key
@tx_public_key = tx_public_key
end
end
# Scanned stealth output.
class StealthOutput
attr_accessor :tx_hash, :output_index, :stealth_address, :amount, :block_height
def initialize(tx_hash:, output_index:, stealth_address:, amount:, block_height:)
@tx_hash = tx_hash
@output_index = output_index
@stealth_address = stealth_address
@amount = amount
@block_height = block_height
end
end
# Client closed error.
class ClientClosedError < StandardError; end
# HTTP error.
class HttpError < StandardError
attr_reader :status_code, :code
def initialize(message, status_code: nil, code: nil)
super(message)
@status_code = status_code
@code = code
end
end
end

View file

@ -0,0 +1,5 @@
# frozen_string_literal: true
module SynorPrivacy
VERSION = "0.1.0"
end

View file

@ -0,0 +1,471 @@
//! Synor Contract SDK client
use reqwest::Client as HttpClient;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use super::error::{ContractError, Result};
use super::types::*;
/// Contract SDK configuration
#[derive(Debug, Clone)]
pub struct ContractConfig {
/// API key for authentication
pub api_key: String,
/// API endpoint URL
pub endpoint: String,
/// Request timeout in milliseconds
pub timeout_ms: u64,
/// Number of retry attempts
pub retries: u32,
}
impl ContractConfig {
/// 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://contract.synor.io".to_string(),
timeout_ms: 30000,
retries: 3,
}
}
/// Set the endpoint URL
pub fn endpoint(mut self, endpoint: impl Into<String>) -> Self {
self.endpoint = endpoint.into();
self
}
/// Set the timeout in milliseconds
pub fn timeout_ms(mut self, timeout_ms: u64) -> Self {
self.timeout_ms = timeout_ms;
self
}
/// Set the number of retries
pub fn retries(mut self, retries: u32) -> Self {
self.retries = retries;
self
}
}
/// Synor Contract SDK client
pub struct SynorContract {
config: ContractConfig,
client: HttpClient,
closed: Arc<AtomicBool>,
}
impl SynorContract {
/// Create a new Contract client
pub fn new(config: ContractConfig) -> Result<Self> {
let client = HttpClient::builder()
.timeout(std::time::Duration::from_millis(config.timeout_ms))
.build()
.map_err(|e| ContractError::Request(e.to_string()))?;
Ok(Self {
config,
client,
closed: Arc::new(AtomicBool::new(false)),
})
}
fn check_closed(&self) -> Result<()> {
if self.closed.load(Ordering::SeqCst) {
return Err(ContractError::Closed);
}
Ok(())
}
async fn request<T: serde::de::DeserializeOwned>(
&self,
method: reqwest::Method,
path: &str,
body: Option<serde_json::Value>,
) -> Result<T> {
self.check_closed()?;
let url = format!("{}{}", self.config.endpoint, path);
let mut last_error = None;
for attempt in 0..self.config.retries {
let mut req = self
.client
.request(method.clone(), &url)
.header("Authorization", format!("Bearer {}", self.config.api_key))
.header("Content-Type", "application/json")
.header("X-SDK-Version", format!("rust/{}", env!("CARGO_PKG_VERSION")));
if let Some(ref b) = body {
req = req.json(b);
}
match req.send().await {
Ok(response) => {
let status = response.status();
let text = response.text().await.map_err(|e| ContractError::Response(e.to_string()))?;
if status.is_success() {
return serde_json::from_str(&text).map_err(ContractError::from);
}
// Try to parse error response
if let Ok(error_response) = serde_json::from_str::<serde_json::Value>(&text) {
let message = error_response
.get("message")
.or_else(|| error_response.get("error"))
.and_then(|v| v.as_str())
.unwrap_or("Unknown error")
.to_string();
let code = error_response
.get("code")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
last_error = Some(ContractError::Api {
message,
code,
status_code: Some(status.as_u16()),
});
} else {
last_error = Some(ContractError::Response(text));
}
}
Err(e) => {
last_error = Some(ContractError::Request(e.to_string()));
}
}
if attempt < self.config.retries - 1 {
tokio::time::sleep(std::time::Duration::from_millis(
2u64.pow(attempt) * 1000,
))
.await;
}
}
Err(last_error.unwrap_or_else(|| ContractError::Request("Unknown error".to_string())))
}
async fn get<T: serde::de::DeserializeOwned>(&self, path: &str) -> Result<T> {
self.request(reqwest::Method::GET, path, None).await
}
async fn post<T: serde::de::DeserializeOwned>(
&self,
path: &str,
body: serde_json::Value,
) -> Result<T> {
self.request(reqwest::Method::POST, path, Some(body)).await
}
// ==================== Contract Deployment ====================
/// Deploy a new smart contract
pub async fn deploy(&self, options: DeployContractOptions) -> Result<DeploymentResult> {
let mut body = serde_json::json!({
"bytecode": options.bytecode,
});
if let Some(abi) = &options.abi {
body["abi"] = serde_json::to_value(abi)?;
}
if let Some(args) = &options.constructor_args {
body["constructor_args"] = serde_json::to_value(args)?;
}
if let Some(value) = &options.value {
body["value"] = serde_json::json!(value);
}
if let Some(gas_limit) = options.gas_limit {
body["gas_limit"] = serde_json::json!(gas_limit);
}
if let Some(gas_price) = &options.gas_price {
body["gas_price"] = serde_json::json!(gas_price);
}
if let Some(nonce) = options.nonce {
body["nonce"] = serde_json::json!(nonce);
}
self.post("/contract/deploy", body).await
}
/// Deploy a contract using CREATE2 for deterministic addresses
pub async fn deploy_create2(&self, options: DeployContractOptions, salt: &str) -> Result<DeploymentResult> {
let mut body = serde_json::json!({
"bytecode": options.bytecode,
"salt": salt,
});
if let Some(abi) = &options.abi {
body["abi"] = serde_json::to_value(abi)?;
}
if let Some(args) = &options.constructor_args {
body["constructor_args"] = serde_json::to_value(args)?;
}
if let Some(value) = &options.value {
body["value"] = serde_json::json!(value);
}
if let Some(gas_limit) = options.gas_limit {
body["gas_limit"] = serde_json::json!(gas_limit);
}
if let Some(gas_price) = &options.gas_price {
body["gas_price"] = serde_json::json!(gas_price);
}
self.post("/contract/deploy/create2", body).await
}
/// Predict the address from a CREATE2 deployment
pub async fn predict_address(&self, bytecode: &str, salt: &str, deployer: Option<&str>) -> Result<String> {
let mut body = serde_json::json!({
"bytecode": bytecode,
"salt": salt,
});
if let Some(d) = deployer {
body["deployer"] = serde_json::json!(d);
}
let response: serde_json::Value = self.post("/contract/predict-address", body).await?;
response["address"]
.as_str()
.map(|s| s.to_string())
.ok_or_else(|| ContractError::Response("Missing address in response".to_string()))
}
// ==================== Contract Interaction ====================
/// Call a view/pure function (read-only, no gas)
pub async fn call(&self, options: CallContractOptions) -> Result<serde_json::Value> {
let body = serde_json::json!({
"contract": options.contract,
"method": options.method,
"args": options.args,
"abi": options.abi,
});
self.post("/contract/call", body).await
}
/// Send a state-changing transaction
pub async fn send(&self, options: SendContractOptions) -> Result<TransactionResult> {
let mut body = serde_json::json!({
"contract": options.contract,
"method": options.method,
"args": options.args,
"abi": options.abi,
});
if let Some(value) = &options.value {
body["value"] = serde_json::json!(value);
}
if let Some(gas_limit) = options.gas_limit {
body["gas_limit"] = serde_json::json!(gas_limit);
}
if let Some(gas_price) = &options.gas_price {
body["gas_price"] = serde_json::json!(gas_price);
}
if let Some(nonce) = options.nonce {
body["nonce"] = serde_json::json!(nonce);
}
self.post("/contract/send", body).await
}
// ==================== Events ====================
/// Get events from a contract
pub async fn get_events(&self, filter: EventFilter) -> Result<Vec<DecodedEvent>> {
let mut body = serde_json::json!({
"contract": filter.contract,
});
if let Some(event) = &filter.event {
body["event"] = serde_json::json!(event);
}
if let Some(from_block) = filter.from_block {
body["from_block"] = serde_json::json!(from_block);
}
if let Some(to_block) = filter.to_block {
body["to_block"] = serde_json::json!(to_block);
}
if let Some(topics) = &filter.topics {
body["topics"] = serde_json::to_value(topics)?;
}
if let Some(abi) = &filter.abi {
body["abi"] = serde_json::to_value(abi)?;
}
self.post("/contract/events", body).await
}
/// Get the logs for a contract
pub async fn get_logs(&self, contract: &str, from_block: Option<u64>, to_block: Option<u64>) -> Result<Vec<EventLog>> {
let mut params = format!("contract={}", contract);
if let Some(from) = from_block {
params.push_str(&format!("&from_block={}", from));
}
if let Some(to) = to_block {
params.push_str(&format!("&to_block={}", to));
}
self.get(&format!("/contract/logs?{}", params)).await
}
/// Decode event logs using an ABI
pub async fn decode_logs(&self, logs: &[EventLog], abi: &Abi) -> Result<Vec<DecodedEvent>> {
let body = serde_json::json!({
"logs": logs,
"abi": abi,
});
self.post("/contract/decode-logs", body).await
}
// ==================== ABI Utilities ====================
/// Encode a function call
pub async fn encode_call(&self, options: EncodeCallOptions) -> Result<String> {
let body = serde_json::json!({
"method": options.method,
"args": options.args,
"abi": options.abi,
});
let response: serde_json::Value = self.post("/contract/encode", body).await?;
response["data"]
.as_str()
.map(|s| s.to_string())
.ok_or_else(|| ContractError::Response("Missing data in response".to_string()))
}
/// Decode a function result
pub async fn decode_result(&self, options: DecodeResultOptions) -> Result<serde_json::Value> {
let body = serde_json::json!({
"data": options.data,
"method": options.method,
"abi": options.abi,
});
let response: serde_json::Value = self.post("/contract/decode", body).await?;
Ok(response["result"].clone())
}
/// Get the function selector for a method
pub async fn get_selector(&self, signature: &str) -> Result<String> {
let response: serde_json::Value = self
.get(&format!("/contract/selector?signature={}", urlencoding::encode(signature)))
.await?;
response["selector"]
.as_str()
.map(|s| s.to_string())
.ok_or_else(|| ContractError::Response("Missing selector in response".to_string()))
}
// ==================== Gas Estimation ====================
/// Estimate gas for a contract interaction
pub async fn estimate_gas(&self, options: EstimateGasOptions) -> Result<GasEstimation> {
let mut body = serde_json::json!({
"contract": options.contract,
"method": options.method,
"args": options.args,
"abi": options.abi,
});
if let Some(value) = &options.value {
body["value"] = serde_json::json!(value);
}
self.post("/contract/estimate-gas", body).await
}
// ==================== Contract Information ====================
/// Get bytecode deployed at an address
pub async fn get_bytecode(&self, address: &str) -> Result<BytecodeInfo> {
self.get(&format!("/contract/{}/bytecode", address)).await
}
/// Verify contract source code
pub async fn verify(&self, options: VerifyContractOptions) -> Result<VerificationResult> {
let mut body = serde_json::json!({
"address": options.address,
"source_code": options.source_code,
"compiler_version": options.compiler_version,
});
if let Some(ctor_args) = &options.constructor_args {
body["constructor_args"] = serde_json::json!(ctor_args);
}
if let Some(optimization) = options.optimization {
body["optimization"] = serde_json::json!(optimization);
}
if let Some(runs) = options.optimization_runs {
body["optimization_runs"] = serde_json::json!(runs);
}
if let Some(license) = &options.license {
body["license"] = serde_json::json!(license);
}
self.post("/contract/verify", body).await
}
/// Get verification status for a contract
pub async fn get_verification_status(&self, address: &str) -> Result<VerificationResult> {
self.get(&format!("/contract/{}/verification", address)).await
}
// ==================== Multicall ====================
/// Execute multiple calls in a single request
pub async fn multicall(&self, requests: &[MulticallRequest]) -> Result<Vec<MulticallResult>> {
let body = serde_json::json!({
"calls": requests,
});
self.post("/contract/multicall", body).await
}
// ==================== Storage ====================
/// Read a storage slot from a contract
pub async fn read_storage(&self, options: ReadStorageOptions) -> Result<String> {
let mut params = format!("contract={}&slot={}", options.contract, options.slot);
if let Some(block) = options.block_number {
params.push_str(&format!("&block={}", block));
}
let response: serde_json::Value = self.get(&format!("/contract/storage?{}", params)).await?;
response["value"]
.as_str()
.map(|s| s.to_string())
.ok_or_else(|| ContractError::Response("Missing value in response".to_string()))
}
// ==================== Lifecycle ====================
/// Check if the service is healthy
pub async fn health_check(&self) -> bool {
if self.closed.load(Ordering::SeqCst) {
return false;
}
match self.get::<serde_json::Value>("/health").await {
Ok(response) => response.get("status").and_then(|s| s.as_str()) == Some("healthy"),
Err(_) => false,
}
}
/// 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)
}
}

View file

@ -0,0 +1,60 @@
//! Contract SDK errors
use std::fmt;
/// Contract SDK result type
pub type Result<T> = std::result::Result<T, ContractError>;
/// Contract SDK error
#[derive(Debug)]
pub enum ContractError {
/// HTTP request error
Request(String),
/// Invalid response
Response(String),
/// Serialization error
Serialization(String),
/// API error
Api {
message: String,
code: Option<String>,
status_code: Option<u16>,
},
/// Client is closed
Closed,
}
impl fmt::Display for ContractError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ContractError::Request(msg) => write!(f, "Request error: {}", msg),
ContractError::Response(msg) => write!(f, "Response error: {}", msg),
ContractError::Serialization(msg) => write!(f, "Serialization error: {}", msg),
ContractError::Api { message, code, status_code } => {
write!(f, "API error: {}", message)?;
if let Some(c) = code {
write!(f, " (code: {})", c)?;
}
if let Some(s) = status_code {
write!(f, " (status: {})", s)?;
}
Ok(())
}
ContractError::Closed => write!(f, "Client has been closed"),
}
}
}
impl std::error::Error for ContractError {}
impl From<reqwest::Error> for ContractError {
fn from(err: reqwest::Error) -> Self {
ContractError::Request(err.to_string())
}
}
impl From<serde_json::Error> for ContractError {
fn from(err: serde_json::Error) -> Self {
ContractError::Serialization(err.to_string())
}
}

View file

@ -0,0 +1,16 @@
//! Synor Contract SDK for Rust
//!
//! Smart contract deployment, interaction, and event handling:
//! - Deploy contracts (standard and CREATE2)
//! - Call view/pure functions
//! - Send state-changing transactions
//! - Get events and logs
//! - ABI encoding/decoding utilities
mod types;
mod error;
mod client;
pub use types::*;
pub use error::*;
pub use client::*;

View file

@ -0,0 +1,316 @@
//! Contract SDK types
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// Default contract endpoint
pub const DEFAULT_CONTRACT_ENDPOINT: &str = "https://contract.synor.cc/api/v1";
/// Contract client configuration
#[derive(Clone, Debug)]
pub struct ContractConfig {
pub api_key: String,
pub endpoint: String,
pub timeout_secs: u64,
pub retries: u32,
pub debug: bool,
pub default_gas_limit: Option<String>,
pub default_gas_price: Option<String>,
}
impl Default for ContractConfig {
fn default() -> Self {
Self {
api_key: String::new(),
endpoint: DEFAULT_CONTRACT_ENDPOINT.to_string(),
timeout_secs: 30,
retries: 3,
debug: false,
default_gas_limit: None,
default_gas_price: None,
}
}
}
/// ABI entry type
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum AbiEntryType {
Function,
Constructor,
Event,
Error,
Fallback,
Receive,
}
/// State mutability
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum StateMutability {
Pure,
View,
Nonpayable,
Payable,
}
/// Transaction status
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum TransactionStatus {
Success,
Reverted,
}
/// Verification status
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum VerificationStatus {
Verified,
Pending,
Failed,
}
/// ABI parameter
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AbiParameter {
pub name: String,
#[serde(rename = "type")]
pub param_type: String,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub indexed: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub components: Option<Vec<AbiParameter>>,
#[serde(rename = "internalType", skip_serializing_if = "Option::is_none")]
pub internal_type: Option<String>,
}
/// ABI entry
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AbiEntry {
#[serde(rename = "type")]
pub entry_type: AbiEntryType,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub inputs: Option<Vec<AbiParameter>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub outputs: Option<Vec<AbiParameter>>,
#[serde(rename = "stateMutability", skip_serializing_if = "Option::is_none")]
pub state_mutability: Option<StateMutability>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub anonymous: bool,
}
/// Contract ABI
pub type Abi = Vec<AbiEntry>;
/// Deployment result
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DeploymentResult {
pub address: String,
#[serde(rename = "transactionHash")]
pub transaction_hash: String,
#[serde(rename = "blockNumber")]
pub block_number: u64,
#[serde(rename = "gasUsed")]
pub gas_used: String,
#[serde(rename = "effectiveGasPrice")]
pub effective_gas_price: String,
}
/// Event log
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EventLog {
#[serde(rename = "logIndex")]
pub log_index: u32,
pub address: String,
pub topics: Vec<String>,
pub data: String,
#[serde(rename = "blockNumber")]
pub block_number: u64,
#[serde(rename = "transactionHash")]
pub transaction_hash: String,
}
/// Decoded event
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DecodedEvent {
pub name: String,
pub signature: String,
pub args: HashMap<String, serde_json::Value>,
pub log: EventLog,
}
/// Transaction result
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TransactionResult {
#[serde(rename = "transactionHash")]
pub transaction_hash: String,
#[serde(rename = "blockNumber")]
pub block_number: u64,
#[serde(rename = "blockHash")]
pub block_hash: String,
#[serde(rename = "gasUsed")]
pub gas_used: String,
#[serde(rename = "effectiveGasPrice")]
pub effective_gas_price: String,
pub status: TransactionStatus,
pub logs: Vec<EventLog>,
#[serde(rename = "returnValue", skip_serializing_if = "Option::is_none")]
pub return_value: Option<serde_json::Value>,
#[serde(rename = "revertReason", skip_serializing_if = "Option::is_none")]
pub revert_reason: Option<String>,
}
/// Contract interface
#[derive(Clone, Debug)]
pub struct ContractInterface {
pub abi: Abi,
pub functions: HashMap<String, AbiEntry>,
pub events: HashMap<String, AbiEntry>,
pub errors: HashMap<String, AbiEntry>,
}
/// Gas estimation
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GasEstimation {
#[serde(rename = "gasLimit")]
pub gas_limit: String,
#[serde(rename = "gasPrice")]
pub gas_price: String,
#[serde(rename = "estimatedCost")]
pub estimated_cost: String,
}
/// Bytecode metadata
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BytecodeMetadata {
pub compiler: String,
pub language: String,
pub sources: Vec<String>,
}
/// Bytecode info
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BytecodeInfo {
pub bytecode: String,
#[serde(rename = "deployedBytecode", skip_serializing_if = "Option::is_none")]
pub deployed_bytecode: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub abi: Option<Abi>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<BytecodeMetadata>,
}
/// Verification result
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct VerificationResult {
pub status: VerificationStatus,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub abi: Option<Abi>,
}
/// Multicall request
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MulticallRequest {
pub address: String,
#[serde(rename = "callData")]
pub call_data: String,
#[serde(rename = "allowFailure", default)]
pub allow_failure: bool,
}
/// Multicall result
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MulticallResult {
pub success: bool,
#[serde(rename = "returnData")]
pub return_data: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub decoded: Option<serde_json::Value>,
}
/// Deploy options
#[derive(Clone, Debug, Default)]
pub struct DeployOptions {
pub bytecode: String,
pub abi: Option<Abi>,
pub args: Option<Vec<serde_json::Value>>,
pub gas_limit: Option<String>,
pub gas_price: Option<String>,
pub value: Option<String>,
pub salt: Option<String>,
}
/// Call options
#[derive(Clone, Debug)]
pub struct CallOptions {
pub address: String,
pub method: String,
pub abi: Abi,
pub args: Option<Vec<serde_json::Value>>,
pub block_number: Option<BlockNumber>,
}
/// Send options
#[derive(Clone, Debug)]
pub struct SendOptions {
pub address: String,
pub method: String,
pub abi: Abi,
pub args: Option<Vec<serde_json::Value>>,
pub gas_limit: Option<String>,
pub gas_price: Option<String>,
pub value: Option<String>,
}
/// Block number specifier
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum BlockNumber {
Number(u64),
Tag(String),
}
impl Default for BlockNumber {
fn default() -> Self {
BlockNumber::Tag("latest".to_string())
}
}
/// Event filter
#[derive(Clone, Debug, Default)]
pub struct EventFilter {
pub address: String,
pub event_name: Option<String>,
pub abi: Option<Abi>,
pub from_block: Option<BlockNumber>,
pub to_block: Option<BlockNumber>,
pub filter: Option<HashMap<String, serde_json::Value>>,
}
/// Estimate gas options
#[derive(Clone, Debug, Default)]
pub struct EstimateGasOptions {
pub address: Option<String>,
pub method: Option<String>,
pub args: Option<Vec<serde_json::Value>>,
pub abi: Option<Abi>,
pub bytecode: Option<String>,
pub value: Option<String>,
pub from: Option<String>,
}
/// Verify contract options
#[derive(Clone, Debug)]
pub struct VerifyContractOptions {
pub address: String,
pub source_code: String,
pub compiler_version: String,
pub constructor_arguments: Option<String>,
pub optimization: bool,
pub optimization_runs: u32,
pub contract_name: Option<String>,
}

View file

@ -54,6 +54,11 @@ pub mod storage;
pub mod database;
pub mod hosting;
pub mod bridge;
pub mod economics;
pub mod governance;
pub mod mining;
pub mod privacy;
pub mod contract;
#[cfg(test)]
mod tests;

View file

@ -0,0 +1,626 @@
//! Privacy client implementation
use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_TYPE};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use super::error::{PrivacyError, Result};
use super::types::*;
/// Synor Privacy Client
pub struct PrivacyClient {
config: PrivacyConfig,
http_client: reqwest::Client,
closed: Arc<AtomicBool>,
}
impl PrivacyClient {
/// Create a new privacy client
pub fn new(config: PrivacyConfig) -> Self {
let mut headers = HeaderMap::new();
headers.insert(
AUTHORIZATION,
HeaderValue::from_str(&format!("Bearer {}", config.api_key)).unwrap(),
);
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
headers.insert("X-SDK-Version", HeaderValue::from_static("rust/0.1.0"));
let http_client = reqwest::Client::builder()
.default_headers(headers)
.timeout(Duration::from_secs(config.timeout_secs))
.build()
.expect("Failed to create HTTP client");
Self {
config,
http_client,
closed: Arc::new(AtomicBool::new(false)),
}
}
// ==================== Confidential Transactions ====================
/// Create a confidential transaction
pub async fn create_confidential_transaction(
&self,
opts: CreateConfidentialTxOptions,
) -> Result<ConfidentialTransaction> {
#[derive(Serialize)]
struct Request {
inputs: Vec<ConfidentialUtxo>,
outputs: Vec<ConfidentialOutput>,
#[serde(skip_serializing_if = "Option::is_none")]
fee: Option<String>,
#[serde(rename = "lockHeight", skip_serializing_if = "Option::is_none")]
lock_height: Option<u64>,
}
#[derive(Deserialize)]
struct Response {
transaction: ConfidentialTransaction,
}
let resp: Response = self.request(
"POST",
"/transactions/confidential",
Some(Request {
inputs: opts.inputs,
outputs: opts.outputs,
fee: opts.fee,
lock_height: opts.lock_height,
}),
).await?;
Ok(resp.transaction)
}
/// Verify a confidential transaction
pub async fn verify_confidential_transaction(
&self,
tx: &ConfidentialTransaction,
) -> Result<VerifyConfidentialTxResult> {
#[derive(Serialize)]
struct Request {
transaction: String,
}
self.request(
"POST",
"/transactions/confidential/verify",
Some(Request { transaction: tx.raw.clone() }),
).await
}
/// Decode a confidential output
pub async fn decode_confidential_output(
&self,
commitment: &str,
blinding: &str,
) -> Result<String> {
#[derive(Serialize)]
struct Request {
commitment: String,
blinding: String,
}
#[derive(Deserialize)]
struct Response {
amount: String,
}
let resp: Response = self.request(
"POST",
"/transactions/confidential/decode",
Some(Request {
commitment: commitment.to_string(),
blinding: blinding.to_string(),
}),
).await?;
Ok(resp.amount)
}
// ==================== Ring Signatures ====================
/// Create a ring signature
pub async fn create_ring_signature(
&self,
opts: CreateRingSignatureOptions,
) -> Result<RingSignature> {
#[derive(Serialize)]
struct Request {
message: String,
ring: Vec<String>,
#[serde(rename = "privateKey")]
private_key: String,
#[serde(rename = "signerIndex", skip_serializing_if = "Option::is_none")]
signer_index: Option<u32>,
}
#[derive(Deserialize)]
struct Response {
signature: RingSignature,
}
let resp: Response = self.request(
"POST",
"/ring-signatures/create",
Some(Request {
message: opts.message,
ring: opts.ring,
private_key: opts.private_key,
signer_index: opts.signer_index,
}),
).await?;
Ok(resp.signature)
}
/// Verify a ring signature
pub async fn verify_ring_signature(
&self,
signature: &RingSignature,
message: &str,
) -> Result<VerifyRingSignatureResult> {
#[derive(Serialize)]
struct Request<'a> {
signature: &'a RingSignature,
message: String,
}
self.request(
"POST",
"/ring-signatures/verify",
Some(Request {
signature,
message: message.to_string(),
}),
).await
}
/// Check if a key image has been used
pub async fn is_key_image_used(&self, key_image: &str) -> Result<bool> {
#[derive(Deserialize)]
struct Response {
used: bool,
}
let resp: Response = self.request(
"GET",
&format!("/ring-signatures/key-images/{}", urlencoding::encode(key_image)),
Option::<()>::None,
).await?;
Ok(resp.used)
}
/// Generate a random ring
pub async fn generate_random_ring(
&self,
size: u32,
exclude: Option<Vec<String>>,
) -> Result<Vec<String>> {
#[derive(Serialize)]
struct Request {
size: u32,
#[serde(skip_serializing_if = "Option::is_none")]
exclude: Option<Vec<String>>,
}
#[derive(Deserialize)]
struct Response {
ring: Vec<String>,
}
let resp: Response = self.request(
"POST",
"/ring-signatures/random-ring",
Some(Request { size, exclude }),
).await?;
Ok(resp.ring)
}
// ==================== Stealth Addresses ====================
/// Generate a stealth keypair
pub async fn generate_stealth_keypair(&self) -> Result<StealthKeypair> {
#[derive(Deserialize)]
struct Response {
keypair: StealthKeypair,
}
let resp: Response = self.request("POST", "/stealth/generate", Option::<()>::None).await?;
Ok(resp.keypair)
}
/// Create a one-time address
pub async fn create_one_time_address(
&self,
stealth_address: &StealthAddress,
) -> Result<OneTimeAddress> {
#[derive(Serialize)]
struct Request {
#[serde(rename = "scanPublicKey")]
scan_public_key: String,
#[serde(rename = "spendPublicKey")]
spend_public_key: String,
}
#[derive(Deserialize)]
struct Response {
#[serde(rename = "oneTimeAddress")]
one_time_address: OneTimeAddress,
}
let resp: Response = self.request(
"POST",
"/stealth/one-time-address",
Some(Request {
scan_public_key: stealth_address.scan_public_key.clone(),
spend_public_key: stealth_address.spend_public_key.clone(),
}),
).await?;
Ok(resp.one_time_address)
}
/// Derive shared secret
pub async fn derive_shared_secret(
&self,
stealth_address: &StealthAddress,
private_key: &str,
ephemeral_public_key: &str,
) -> Result<SharedSecret> {
#[derive(Serialize)]
struct Request {
#[serde(rename = "scanPublicKey")]
scan_public_key: String,
#[serde(rename = "spendPublicKey")]
spend_public_key: String,
#[serde(rename = "privateKey")]
private_key: String,
#[serde(rename = "ephemeralPublicKey")]
ephemeral_public_key: String,
}
self.request(
"POST",
"/stealth/derive-secret",
Some(Request {
scan_public_key: stealth_address.scan_public_key.clone(),
spend_public_key: stealth_address.spend_public_key.clone(),
private_key: private_key.to_string(),
ephemeral_public_key: ephemeral_public_key.to_string(),
}),
).await
}
/// Scan for payments
pub async fn scan_for_payments(
&self,
scan_private_key: &str,
spend_public_key: &str,
transactions: Vec<String>,
) -> Result<Vec<OneTimeAddress>> {
#[derive(Serialize)]
struct Request {
#[serde(rename = "scanPrivateKey")]
scan_private_key: String,
#[serde(rename = "spendPublicKey")]
spend_public_key: String,
transactions: Vec<String>,
}
#[derive(Deserialize)]
struct Response {
payments: Vec<OneTimeAddress>,
}
let resp: Response = self.request(
"POST",
"/stealth/scan",
Some(Request {
scan_private_key: scan_private_key.to_string(),
spend_public_key: spend_public_key.to_string(),
transactions,
}),
).await?;
Ok(resp.payments)
}
// ==================== Commitments ====================
/// Create a commitment
pub async fn create_commitment(
&self,
value: &str,
blinding: Option<&str>,
) -> Result<CommitmentWithBlinding> {
#[derive(Serialize)]
struct Request {
value: String,
#[serde(skip_serializing_if = "Option::is_none")]
blinding: Option<String>,
}
self.request(
"POST",
"/commitments/create",
Some(Request {
value: value.to_string(),
blinding: blinding.map(|s| s.to_string()),
}),
).await
}
/// Open (verify) a commitment
pub async fn open_commitment(
&self,
commitment: &str,
value: &str,
blinding: &str,
) -> Result<bool> {
#[derive(Serialize)]
struct Request {
commitment: String,
value: String,
blinding: String,
}
#[derive(Deserialize)]
struct Response {
valid: bool,
}
let resp: Response = self.request(
"POST",
"/commitments/open",
Some(Request {
commitment: commitment.to_string(),
value: value.to_string(),
blinding: blinding.to_string(),
}),
).await?;
Ok(resp.valid)
}
/// Add two commitments
pub async fn add_commitments(
&self,
commitment1: &str,
commitment2: &str,
) -> Result<String> {
#[derive(Serialize)]
struct Request {
commitment1: String,
commitment2: String,
}
#[derive(Deserialize)]
struct Response {
commitment: String,
}
let resp: Response = self.request(
"POST",
"/commitments/add",
Some(Request {
commitment1: commitment1.to_string(),
commitment2: commitment2.to_string(),
}),
).await?;
Ok(resp.commitment)
}
/// Subtract two commitments
pub async fn subtract_commitments(
&self,
commitment1: &str,
commitment2: &str,
) -> Result<String> {
#[derive(Serialize)]
struct Request {
commitment1: String,
commitment2: String,
}
#[derive(Deserialize)]
struct Response {
commitment: String,
}
let resp: Response = self.request(
"POST",
"/commitments/subtract",
Some(Request {
commitment1: commitment1.to_string(),
commitment2: commitment2.to_string(),
}),
).await?;
Ok(resp.commitment)
}
/// Compute blinding sum
pub async fn compute_blinding_sum(
&self,
positive: Vec<String>,
negative: Vec<String>,
) -> Result<String> {
#[derive(Serialize)]
struct Request {
positive: Vec<String>,
negative: Vec<String>,
}
#[derive(Deserialize)]
struct Response {
sum: String,
}
let resp: Response = self.request(
"POST",
"/commitments/blinding-sum",
Some(Request { positive, negative }),
).await?;
Ok(resp.sum)
}
/// Generate a random blinding factor
pub async fn generate_blinding(&self) -> Result<String> {
#[derive(Deserialize)]
struct Response {
blinding: String,
}
let resp: Response = self.request("POST", "/commitments/random-blinding", Option::<()>::None).await?;
Ok(resp.blinding)
}
// ==================== Range Proofs ====================
/// Create a range proof
pub async fn create_range_proof(&self, opts: CreateRangeProofOptions) -> Result<RangeProof> {
#[derive(Serialize)]
struct Request {
value: String,
blinding: String,
#[serde(skip_serializing_if = "Option::is_none")]
message: Option<String>,
#[serde(rename = "bitLength")]
bit_length: u32,
}
#[derive(Deserialize)]
struct Response {
proof: RangeProof,
}
let resp: Response = self.request(
"POST",
"/range-proofs/create",
Some(Request {
value: opts.value,
blinding: opts.blinding,
message: opts.message,
bit_length: opts.bit_length.unwrap_or(64),
}),
).await?;
Ok(resp.proof)
}
/// Verify a range proof
pub async fn verify_range_proof(
&self,
commitment: &str,
proof: &str,
) -> Result<VerifyRangeProofResult> {
#[derive(Serialize)]
struct Request {
commitment: String,
proof: String,
}
self.request(
"POST",
"/range-proofs/verify",
Some(Request {
commitment: commitment.to_string(),
proof: proof.to_string(),
}),
).await
}
/// Create aggregated range proof
pub async fn create_aggregated_range_proof(
&self,
outputs: Vec<(String, String)>,
) -> Result<String> {
#[derive(Serialize)]
struct Output {
value: String,
blinding: String,
}
#[derive(Serialize)]
struct Request {
outputs: Vec<Output>,
}
#[derive(Deserialize)]
struct Response {
proof: String,
}
let resp: Response = self.request(
"POST",
"/range-proofs/aggregate",
Some(Request {
outputs: outputs.into_iter().map(|(v, b)| Output { value: v, blinding: b }).collect(),
}),
).await?;
Ok(resp.proof)
}
// ==================== Lifecycle ====================
/// Health check
pub async fn health_check(&self) -> bool {
#[derive(Deserialize)]
struct Response {
status: String,
}
match self.request::<_, Response>("GET", "/health", Option::<()>::None).await {
Ok(resp) => resp.status == "healthy",
Err(_) => false,
}
}
/// Close the client
pub fn close(&self) {
self.closed.store(true, Ordering::SeqCst);
}
/// Check if client is closed
pub fn is_closed(&self) -> bool {
self.closed.load(Ordering::SeqCst)
}
// ==================== Private ====================
async fn request<B, R>(&self, method: &str, path: &str, body: Option<B>) -> Result<R>
where
B: Serialize,
R: DeserializeOwned,
{
if self.closed.load(Ordering::SeqCst) {
return Err(PrivacyError::Closed);
}
let mut last_error = None;
for attempt in 0..self.config.retries {
match self.do_request(method, path, &body).await {
Ok(result) => return Ok(result),
Err(e) => {
last_error = Some(e);
if attempt < self.config.retries - 1 {
tokio::time::sleep(Duration::from_secs(1 << attempt)).await;
}
}
}
}
Err(last_error.unwrap_or(PrivacyError::Request("Unknown error".to_string())))
}
async fn do_request<B, R>(&self, method: &str, path: &str, body: &Option<B>) -> Result<R>
where
B: Serialize,
R: DeserializeOwned,
{
let url = format!("{}{}", self.config.endpoint, path);
let mut req = match method {
"GET" => self.http_client.get(&url),
"POST" => self.http_client.post(&url),
"PUT" => self.http_client.put(&url),
"DELETE" => self.http_client.delete(&url),
_ => return Err(PrivacyError::Request(format!("Unknown method: {}", method))),
};
if let Some(b) = body {
req = req.json(b);
}
let response = req.send().await?;
let status = response.status();
if status.is_client_error() || status.is_server_error() {
#[derive(Deserialize)]
struct ErrorResponse {
message: Option<String>,
error: Option<String>,
code: Option<String>,
}
let error_body: ErrorResponse = response.json().await.unwrap_or(ErrorResponse {
message: None,
error: None,
code: None,
});
let message = error_body.message
.or(error_body.error)
.unwrap_or_else(|| format!("HTTP {}", status));
return Err(PrivacyError::Api {
message,
code: error_body.code,
status_code: Some(status.as_u16()),
});
}
response.json().await.map_err(|e| PrivacyError::Response(e.to_string()))
}
}

View file

@ -0,0 +1,60 @@
//! Privacy SDK errors
use std::fmt;
/// Privacy SDK result type
pub type Result<T> = std::result::Result<T, PrivacyError>;
/// Privacy SDK error
#[derive(Debug)]
pub enum PrivacyError {
/// HTTP request error
Request(String),
/// Invalid response
Response(String),
/// Serialization error
Serialization(String),
/// API error
Api {
message: String,
code: Option<String>,
status_code: Option<u16>,
},
/// Client is closed
Closed,
}
impl fmt::Display for PrivacyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PrivacyError::Request(msg) => write!(f, "Request error: {}", msg),
PrivacyError::Response(msg) => write!(f, "Response error: {}", msg),
PrivacyError::Serialization(msg) => write!(f, "Serialization error: {}", msg),
PrivacyError::Api { message, code, status_code } => {
write!(f, "API error: {}", message)?;
if let Some(c) = code {
write!(f, " (code: {})", c)?;
}
if let Some(s) = status_code {
write!(f, " (status: {})", s)?;
}
Ok(())
}
PrivacyError::Closed => write!(f, "Client has been closed"),
}
}
}
impl std::error::Error for PrivacyError {}
impl From<reqwest::Error> for PrivacyError {
fn from(err: reqwest::Error) -> Self {
PrivacyError::Request(err.to_string())
}
}
impl From<serde_json::Error> for PrivacyError {
fn from(err: serde_json::Error) -> Self {
PrivacyError::Serialization(err.to_string())
}
}

View file

@ -0,0 +1,15 @@
//! Synor Privacy SDK for Rust
//!
//! Privacy-enhancing features for the Synor blockchain:
//! - Confidential transactions with hidden amounts
//! - Ring signatures for anonymous signing
//! - Stealth addresses for unlinkable payments
//! - Pedersen commitments and Bulletproof range proofs
mod types;
mod error;
mod client;
pub use types::*;
pub use error::*;
pub use client::*;

View file

@ -0,0 +1,290 @@
//! Privacy SDK types
use serde::{Deserialize, Serialize};
/// Default privacy endpoint
pub const DEFAULT_PRIVACY_ENDPOINT: &str = "https://privacy.synor.cc/api/v1";
/// Privacy client configuration
#[derive(Clone, Debug)]
pub struct PrivacyConfig {
pub api_key: String,
pub endpoint: String,
pub timeout_secs: u64,
pub retries: u32,
pub debug: bool,
}
impl Default for PrivacyConfig {
fn default() -> Self {
Self {
api_key: String::new(),
endpoint: DEFAULT_PRIVACY_ENDPOINT.to_string(),
timeout_secs: 30,
retries: 3,
debug: false,
}
}
}
/// Key type
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum KeyType {
Ed25519,
Secp256k1,
}
/// Generator type for commitments
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum GeneratorType {
Default,
Alternate,
}
impl Default for GeneratorType {
fn default() -> Self {
Self::Default
}
}
/// Public key
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PublicKey {
pub key: String,
#[serde(rename = "type")]
pub key_type: KeyType,
}
/// Private key
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PrivateKey {
pub key: String,
#[serde(rename = "type")]
pub key_type: KeyType,
}
/// UTXO for confidential transactions
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ConfidentialUtxo {
pub txid: String,
pub vout: u32,
pub commitment: String,
#[serde(rename = "rangeProof")]
pub range_proof: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub blinding: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub amount: Option<String>,
}
/// Output for confidential transactions
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ConfidentialOutput {
pub recipient: String,
pub amount: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub blinding: Option<String>,
}
/// Output features
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OutputFeatures {
pub flags: u32,
#[serde(rename = "lockHeight", skip_serializing_if = "Option::is_none")]
pub lock_height: Option<u64>,
}
/// Confidential transaction input
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ConfidentialTxInput {
#[serde(rename = "outputRef")]
pub output_ref: String,
pub commitment: String,
}
/// Confidential transaction output
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ConfidentialTxOutput {
pub commitment: String,
#[serde(rename = "rangeProof")]
pub range_proof: String,
pub features: OutputFeatures,
}
/// Transaction kernel
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TransactionKernel {
pub features: u32,
pub fee: String,
#[serde(rename = "lockHeight")]
pub lock_height: u64,
pub excess: String,
#[serde(rename = "excessSignature")]
pub excess_signature: String,
}
/// Confidential transaction
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ConfidentialTransaction {
pub txid: String,
pub version: u32,
pub inputs: Vec<ConfidentialTxInput>,
pub outputs: Vec<ConfidentialTxOutput>,
pub kernel: TransactionKernel,
pub offset: String,
pub raw: String,
}
/// Verification details for confidential transaction
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct VerifyConfidentialTxDetails {
#[serde(rename = "commitmentsBalance")]
pub commitments_balance: bool,
#[serde(rename = "rangeProofsValid")]
pub range_proofs_valid: bool,
#[serde(rename = "signatureValid")]
pub signature_valid: bool,
#[serde(rename = "noDuplicateInputs")]
pub no_duplicate_inputs: bool,
}
/// Verification result for confidential transaction
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct VerifyConfidentialTxResult {
pub valid: bool,
pub details: VerifyConfidentialTxDetails,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
/// Ring signature components
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RingSignatureComponents {
pub c: Vec<String>,
pub r: Vec<String>,
}
/// Ring signature
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RingSignature {
pub id: String,
#[serde(rename = "messageHash")]
pub message_hash: String,
pub ring: Vec<String>,
#[serde(rename = "keyImage")]
pub key_image: String,
pub signature: RingSignatureComponents,
#[serde(rename = "ringSize")]
pub ring_size: u32,
}
/// Ring signature verification result
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct VerifyRingSignatureResult {
pub valid: bool,
#[serde(rename = "keyImage")]
pub key_image: String,
}
/// Stealth address
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct StealthAddress {
pub address: String,
#[serde(rename = "scanPublicKey")]
pub scan_public_key: String,
#[serde(rename = "spendPublicKey")]
pub spend_public_key: String,
}
/// Stealth keypair
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct StealthKeypair {
pub address: StealthAddress,
#[serde(rename = "scanPrivateKey")]
pub scan_private_key: String,
#[serde(rename = "spendPrivateKey")]
pub spend_private_key: String,
}
/// One-time address
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OneTimeAddress {
pub address: String,
#[serde(rename = "ephemeralPublicKey")]
pub ephemeral_public_key: String,
#[serde(rename = "sharedSecret", skip_serializing_if = "Option::is_none")]
pub shared_secret: Option<String>,
}
/// Shared secret
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SharedSecret {
pub secret: String,
#[serde(rename = "oneTimePrivateKey")]
pub one_time_private_key: String,
#[serde(rename = "oneTimeAddress")]
pub one_time_address: String,
}
/// Pedersen commitment
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Commitment {
pub commitment: String,
#[serde(default)]
pub generator: GeneratorType,
}
/// Commitment with blinding factor
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CommitmentWithBlinding {
pub commitment: Commitment,
pub blinding: String,
}
/// Range proof
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RangeProof {
pub proof: String,
pub commitment: String,
#[serde(rename = "bitLength")]
pub bit_length: u32,
}
/// Range proof verification result
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct VerifyRangeProofResult {
pub valid: bool,
#[serde(rename = "minValue")]
pub min_value: String,
#[serde(rename = "maxValue")]
pub max_value: String,
}
/// Options for creating a confidential transaction
#[derive(Clone, Debug, Default)]
pub struct CreateConfidentialTxOptions {
pub inputs: Vec<ConfidentialUtxo>,
pub outputs: Vec<ConfidentialOutput>,
pub fee: Option<String>,
pub lock_height: Option<u64>,
}
/// Options for creating a ring signature
#[derive(Clone, Debug)]
pub struct CreateRingSignatureOptions {
pub message: String,
pub ring: Vec<String>,
pub private_key: String,
pub signer_index: Option<u32>,
}
/// Options for creating a range proof
#[derive(Clone, Debug)]
pub struct CreateRangeProofOptions {
pub value: String,
pub blinding: String,
pub message: Option<String>,
pub bit_length: Option<u32>,
}

View file

@ -0,0 +1,314 @@
import Foundation
/// Synor Contract SDK client for Swift.
/// Smart contract deployment, interaction, and event handling.
public actor ContractClient {
public static let version = "0.1.0"
private let config: ContractConfig
private let session: URLSession
private var isClosed = false
public init(config: ContractConfig) {
self.config = config
let sessionConfig = URLSessionConfiguration.default
sessionConfig.timeoutIntervalForRequest = TimeInterval(config.timeoutMs) / 1000.0
self.session = URLSession(configuration: sessionConfig)
}
// MARK: - Contract Deployment
public func deploy(_ options: DeployContractOptions) async throws -> DeploymentResult {
var body: [String: Any] = ["bytecode": options.bytecode]
if let abi = options.abi { body["abi"] = abi.map { $0.toDictionary() } }
if let args = options.constructorArgs { body["constructor_args"] = args }
if let value = options.value { body["value"] = value }
if let gasLimit = options.gasLimit { body["gas_limit"] = gasLimit }
if let gasPrice = options.gasPrice { body["gas_price"] = gasPrice }
if let nonce = options.nonce { body["nonce"] = nonce }
return try await post("/contract/deploy", body: body)
}
public func deployCreate2(_ options: DeployContractOptions, salt: String) async throws -> DeploymentResult {
var body: [String: Any] = ["bytecode": options.bytecode, "salt": salt]
if let abi = options.abi { body["abi"] = abi.map { $0.toDictionary() } }
if let args = options.constructorArgs { body["constructor_args"] = args }
if let value = options.value { body["value"] = value }
if let gasLimit = options.gasLimit { body["gas_limit"] = gasLimit }
if let gasPrice = options.gasPrice { body["gas_price"] = gasPrice }
return try await post("/contract/deploy/create2", body: body)
}
public func predictAddress(bytecode: String, salt: String, deployer: String? = nil) async throws -> String {
var body: [String: Any] = ["bytecode": bytecode, "salt": salt]
if let deployer = deployer { body["deployer"] = deployer }
let response: [String: Any] = try await post("/contract/predict-address", body: body)
guard let address = response["address"] as? String else {
throw ContractError.response("Missing address")
}
return address
}
// MARK: - Contract Interaction
public func call(_ options: CallContractOptions) async throws -> Any {
let body: [String: Any] = [
"contract": options.contract,
"method": options.method,
"args": options.args,
"abi": options.abi.map { $0.toDictionary() }
]
return try await post("/contract/call", body: body) as [String: Any]
}
public func send(_ options: SendContractOptions) async throws -> TransactionResult {
var body: [String: Any] = [
"contract": options.contract,
"method": options.method,
"args": options.args,
"abi": options.abi.map { $0.toDictionary() }
]
if let value = options.value { body["value"] = value }
if let gasLimit = options.gasLimit { body["gas_limit"] = gasLimit }
if let gasPrice = options.gasPrice { body["gas_price"] = gasPrice }
if let nonce = options.nonce { body["nonce"] = nonce }
return try await post("/contract/send", body: body)
}
// MARK: - Events
public func getEvents(_ filter: EventFilter) async throws -> [DecodedEvent] {
var body: [String: Any] = ["contract": filter.contract]
if let event = filter.event { body["event"] = event }
if let fromBlock = filter.fromBlock { body["from_block"] = fromBlock }
if let toBlock = filter.toBlock { body["to_block"] = toBlock }
if let topics = filter.topics { body["topics"] = topics }
if let abi = filter.abi { body["abi"] = abi.map { $0.toDictionary() } }
return try await post("/contract/events", body: body)
}
public func getLogs(contract: String, fromBlock: Int64? = nil, toBlock: Int64? = nil) async throws -> [EventLog] {
var path = "/contract/logs?contract=\(contract.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? contract)"
if let fromBlock = fromBlock { path += "&from_block=\(fromBlock)" }
if let toBlock = toBlock { path += "&to_block=\(toBlock)" }
return try await get(path)
}
public func decodeLogs(_ logs: [EventLog], abi: [AbiEntry]) async throws -> [DecodedEvent] {
let body: [String: Any] = [
"logs": logs.map { $0.toDictionary() },
"abi": abi.map { $0.toDictionary() }
]
return try await post("/contract/decode-logs", body: body)
}
// MARK: - ABI Utilities
public func encodeCall(_ options: EncodeCallOptions) async throws -> String {
let body: [String: Any] = [
"method": options.method,
"args": options.args,
"abi": options.abi.map { $0.toDictionary() }
]
let response: [String: Any] = try await post("/contract/encode", body: body)
guard let data = response["data"] as? String else {
throw ContractError.response("Missing data")
}
return data
}
public func decodeResult(_ options: DecodeResultOptions) async throws -> Any {
let body: [String: Any] = [
"data": options.data,
"method": options.method,
"abi": options.abi.map { $0.toDictionary() }
]
let response: [String: Any] = try await post("/contract/decode", body: body)
return response["result"] as Any
}
public func getSelector(_ signature: String) async throws -> String {
let encoded = signature.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? signature
let response: [String: Any] = try await get("/contract/selector?signature=\(encoded)")
guard let selector = response["selector"] as? String else {
throw ContractError.response("Missing selector")
}
return selector
}
// MARK: - Gas Estimation
public func estimateGas(_ options: EstimateGasOptions) async throws -> GasEstimation {
var body: [String: Any] = [
"contract": options.contract,
"method": options.method,
"args": options.args,
"abi": options.abi.map { $0.toDictionary() }
]
if let value = options.value { body["value"] = value }
return try await post("/contract/estimate-gas", body: body)
}
// MARK: - Contract Information
public func getBytecode(_ address: String) async throws -> BytecodeInfo {
let encoded = address.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? address
return try await get("/contract/\(encoded)/bytecode")
}
public func verify(_ options: VerifyContractOptions) async throws -> VerificationResult {
var body: [String: Any] = [
"address": options.address,
"source_code": options.sourceCode,
"compiler_version": options.compilerVersion
]
if let args = options.constructorArgs { body["constructor_args"] = args }
if let optimization = options.optimization { body["optimization"] = optimization }
if let runs = options.optimizationRuns { body["optimization_runs"] = runs }
if let license = options.license { body["license"] = license }
return try await post("/contract/verify", body: body)
}
public func getVerificationStatus(_ address: String) async throws -> VerificationResult {
let encoded = address.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? address
return try await get("/contract/\(encoded)/verification")
}
// MARK: - Multicall
public func multicall(_ requests: [MulticallRequest]) async throws -> [MulticallResult] {
let body: [String: Any] = ["calls": requests.map { $0.toDictionary() }]
return try await post("/contract/multicall", body: body)
}
// MARK: - Storage
public func readStorage(_ options: ReadStorageOptions) async throws -> String {
let contract = options.contract.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? options.contract
let slot = options.slot.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? options.slot
var path = "/contract/storage?contract=\(contract)&slot=\(slot)"
if let block = options.blockNumber { path += "&block=\(block)" }
let response: [String: Any] = try await get(path)
guard let value = response["value"] as? String else {
throw ContractError.response("Missing value")
}
return value
}
// MARK: - Lifecycle
public func healthCheck() async -> Bool {
if isClosed { return false }
do {
let response: [String: Any] = try await get("/health")
return response["status"] as? String == "healthy"
} catch {
return false
}
}
public func close() {
isClosed = true
session.invalidateAndCancel()
}
// MARK: - HTTP Helpers
private func get<T: Decodable>(_ path: String) async throws -> T {
try checkClosed()
return try await executeWithRetry {
let url = URL(string: "\(self.config.endpoint)\(path)")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
self.addHeaders(to: &request)
return try await self.execute(request)
}
}
private func post<T: Decodable>(_ path: String, body: [String: Any]) async throws -> T {
try checkClosed()
return try await executeWithRetry {
let url = URL(string: "\(self.config.endpoint)\(path)")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = try JSONSerialization.data(withJSONObject: body)
self.addHeaders(to: &request)
return try await self.execute(request)
}
}
private func addHeaders(to request: inout URLRequest) {
request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("swift/\(Self.version)", forHTTPHeaderField: "X-SDK-Version")
}
private func execute<T: Decodable>(_ request: URLRequest) async throws -> T {
let (data, response) = try await session.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw ContractError.request("Invalid response")
}
if httpResponse.statusCode >= 200 && httpResponse.statusCode < 300 {
return try JSONDecoder().decode(T.self, from: data)
}
if let errorJson = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
let message = errorJson["message"] as? String ?? errorJson["error"] as? String ?? "Unknown error"
let code = errorJson["code"] as? String
throw ContractError.api(message: message, code: code, statusCode: httpResponse.statusCode)
}
throw ContractError.response("HTTP \(httpResponse.statusCode)")
}
private func executeWithRetry<T>(_ block: () async throws -> T) async throws -> T {
var lastError: Error?
for attempt in 0..<config.retries {
do {
return try await block()
} catch {
lastError = error
if attempt < config.retries - 1 {
try await Task.sleep(nanoseconds: UInt64(pow(2.0, Double(attempt))) * 1_000_000_000)
}
}
}
throw lastError ?? ContractError.request("Request failed")
}
private func checkClosed() throws {
if isClosed {
throw ContractError.closed
}
}
}
/// Contract SDK configuration.
public struct ContractConfig {
public let apiKey: String
public var endpoint: String
public var timeoutMs: Int
public var retries: Int
public init(
apiKey: String,
endpoint: String = "https://contract.synor.io",
timeoutMs: Int = 30000,
retries: Int = 3
) {
self.apiKey = apiKey
self.endpoint = endpoint
self.timeoutMs = timeoutMs
self.retries = retries
}
}
/// Contract SDK errors.
public enum ContractError: Error {
case request(String)
case response(String)
case api(message: String, code: String?, statusCode: Int)
case closed
}

View file

@ -0,0 +1,422 @@
import Foundation
// MARK: - ABI Types
public struct AbiParameter: Codable {
public let name: String?
public let type: String
public let indexed: Bool?
public let components: [AbiParameter]?
public init(name: String? = nil, type: String, indexed: Bool? = nil, components: [AbiParameter]? = nil) {
self.name = name
self.type = type
self.indexed = indexed
self.components = components
}
func toDictionary() -> [String: Any] {
var dict: [String: Any] = ["type": type]
if let name = name { dict["name"] = name }
if let indexed = indexed { dict["indexed"] = indexed }
if let components = components { dict["components"] = components.map { $0.toDictionary() } }
return dict
}
}
public struct AbiEntry: Codable {
public let type: String
public let name: String?
public let inputs: [AbiParameter]?
public let outputs: [AbiParameter]?
public let stateMutability: String?
public let anonymous: Bool?
public init(type: String, name: String? = nil, inputs: [AbiParameter]? = nil, outputs: [AbiParameter]? = nil, stateMutability: String? = nil, anonymous: Bool? = nil) {
self.type = type
self.name = name
self.inputs = inputs
self.outputs = outputs
self.stateMutability = stateMutability
self.anonymous = anonymous
}
func toDictionary() -> [String: Any] {
var dict: [String: Any] = ["type": type]
if let name = name { dict["name"] = name }
if let inputs = inputs { dict["inputs"] = inputs.map { $0.toDictionary() } }
if let outputs = outputs { dict["outputs"] = outputs.map { $0.toDictionary() } }
if let stateMutability = stateMutability { dict["stateMutability"] = stateMutability }
if let anonymous = anonymous { dict["anonymous"] = anonymous }
return dict
}
}
// MARK: - Deployment
public struct DeployContractOptions {
public let bytecode: String
public let abi: [AbiEntry]?
public let constructorArgs: [Any]?
public let value: String?
public let gasLimit: Int64?
public let gasPrice: String?
public let nonce: Int64?
public init(bytecode: String, abi: [AbiEntry]? = nil, constructorArgs: [Any]? = nil, value: String? = nil, gasLimit: Int64? = nil, gasPrice: String? = nil, nonce: Int64? = nil) {
self.bytecode = bytecode
self.abi = abi
self.constructorArgs = constructorArgs
self.value = value
self.gasLimit = gasLimit
self.gasPrice = gasPrice
self.nonce = nonce
}
}
public struct DeploymentResult: Codable {
public let contractAddress: String
public let transactionHash: String
public let deployer: String?
public let gasUsed: Int64?
public let blockNumber: Int64?
public let blockHash: String?
enum CodingKeys: String, CodingKey {
case contractAddress = "contract_address"
case transactionHash = "transaction_hash"
case deployer
case gasUsed = "gas_used"
case blockNumber = "block_number"
case blockHash = "block_hash"
}
}
// MARK: - Contract Interaction
public struct CallContractOptions {
public let contract: String
public let method: String
public let args: [Any]
public let abi: [AbiEntry]
public init(contract: String, method: String, args: [Any] = [], abi: [AbiEntry]) {
self.contract = contract
self.method = method
self.args = args
self.abi = abi
}
}
public struct SendContractOptions {
public let contract: String
public let method: String
public let args: [Any]
public let abi: [AbiEntry]
public let value: String?
public let gasLimit: Int64?
public let gasPrice: String?
public let nonce: Int64?
public init(contract: String, method: String, args: [Any] = [], abi: [AbiEntry], value: String? = nil, gasLimit: Int64? = nil, gasPrice: String? = nil, nonce: Int64? = nil) {
self.contract = contract
self.method = method
self.args = args
self.abi = abi
self.value = value
self.gasLimit = gasLimit
self.gasPrice = gasPrice
self.nonce = nonce
}
}
public struct TransactionResult: Codable {
public let transactionHash: String
public let blockNumber: Int64?
public let blockHash: String?
public let gasUsed: Int64?
public let effectiveGasPrice: String?
public let status: String?
public let logs: [EventLog]?
enum CodingKeys: String, CodingKey {
case transactionHash = "transaction_hash"
case blockNumber = "block_number"
case blockHash = "block_hash"
case gasUsed = "gas_used"
case effectiveGasPrice = "effective_gas_price"
case status
case logs
}
}
// MARK: - Events
public struct EventLog: Codable {
public let address: String
public let topics: [String]
public let data: String
public let blockNumber: Int64?
public let transactionHash: String?
public let logIndex: Int?
public let blockHash: String?
public let removed: Bool?
enum CodingKeys: String, CodingKey {
case address, topics, data
case blockNumber = "block_number"
case transactionHash = "transaction_hash"
case logIndex = "log_index"
case blockHash = "block_hash"
case removed
}
func toDictionary() -> [String: Any] {
var dict: [String: Any] = [
"address": address,
"topics": topics,
"data": data
]
if let blockNumber = blockNumber { dict["block_number"] = blockNumber }
if let transactionHash = transactionHash { dict["transaction_hash"] = transactionHash }
if let logIndex = logIndex { dict["log_index"] = logIndex }
if let blockHash = blockHash { dict["block_hash"] = blockHash }
if let removed = removed { dict["removed"] = removed }
return dict
}
}
public struct DecodedEvent: Codable {
public let name: String
public let signature: String?
public let args: [String: AnyCodable]?
public let log: EventLog?
}
public struct EventFilter {
public let contract: String
public let event: String?
public let fromBlock: Int64?
public let toBlock: Int64?
public let topics: [String?]?
public let abi: [AbiEntry]?
public init(contract: String, event: String? = nil, fromBlock: Int64? = nil, toBlock: Int64? = nil, topics: [String?]? = nil, abi: [AbiEntry]? = nil) {
self.contract = contract
self.event = event
self.fromBlock = fromBlock
self.toBlock = toBlock
self.topics = topics
self.abi = abi
}
}
// MARK: - ABI Utilities
public struct EncodeCallOptions {
public let method: String
public let args: [Any]
public let abi: [AbiEntry]
public init(method: String, args: [Any] = [], abi: [AbiEntry]) {
self.method = method
self.args = args
self.abi = abi
}
}
public struct DecodeResultOptions {
public let data: String
public let method: String
public let abi: [AbiEntry]
public init(data: String, method: String, abi: [AbiEntry]) {
self.data = data
self.method = method
self.abi = abi
}
}
// MARK: - Gas Estimation
public struct EstimateGasOptions {
public let contract: String
public let method: String
public let args: [Any]
public let abi: [AbiEntry]
public let value: String?
public init(contract: String, method: String, args: [Any] = [], abi: [AbiEntry], value: String? = nil) {
self.contract = contract
self.method = method
self.args = args
self.abi = abi
self.value = value
}
}
public struct GasEstimation: Codable {
public let gasLimit: Int64
public let gasPrice: String?
public let maxFeePerGas: String?
public let maxPriorityFeePerGas: String?
public let estimatedCost: String?
enum CodingKeys: String, CodingKey {
case gasLimit = "gas_limit"
case gasPrice = "gas_price"
case maxFeePerGas = "max_fee_per_gas"
case maxPriorityFeePerGas = "max_priority_fee_per_gas"
case estimatedCost = "estimated_cost"
}
}
// MARK: - Contract Information
public struct BytecodeInfo: Codable {
public let bytecode: String
public let deployedBytecode: String?
public let size: Int?
public let isContract: Bool?
enum CodingKeys: String, CodingKey {
case bytecode
case deployedBytecode = "deployed_bytecode"
case size
case isContract = "is_contract"
}
}
public struct VerifyContractOptions {
public let address: String
public let sourceCode: String
public let compilerVersion: String
public let constructorArgs: String?
public let optimization: Bool?
public let optimizationRuns: Int?
public let license: String?
public init(address: String, sourceCode: String, compilerVersion: String, constructorArgs: String? = nil, optimization: Bool? = nil, optimizationRuns: Int? = nil, license: String? = nil) {
self.address = address
self.sourceCode = sourceCode
self.compilerVersion = compilerVersion
self.constructorArgs = constructorArgs
self.optimization = optimization
self.optimizationRuns = optimizationRuns
self.license = license
}
}
public struct VerificationResult: Codable {
public let verified: Bool
public let address: String?
public let compilerVersion: String?
public let optimization: Bool?
public let optimizationRuns: Int?
public let license: String?
public let abi: [AbiEntry]?
public let sourceCode: String?
enum CodingKeys: String, CodingKey {
case verified, address
case compilerVersion = "compiler_version"
case optimization
case optimizationRuns = "optimization_runs"
case license, abi
case sourceCode = "source_code"
}
}
// MARK: - Multicall
public struct MulticallRequest {
public let contract: String
public let method: String
public let args: [Any]
public let abi: [AbiEntry]
public init(contract: String, method: String, args: [Any] = [], abi: [AbiEntry]) {
self.contract = contract
self.method = method
self.args = args
self.abi = abi
}
func toDictionary() -> [String: Any] {
return [
"contract": contract,
"method": method,
"args": args,
"abi": abi.map { $0.toDictionary() }
]
}
}
public struct MulticallResult: Codable {
public let success: Bool
public let result: AnyCodable?
public let error: String?
}
// MARK: - Storage
public struct ReadStorageOptions {
public let contract: String
public let slot: String
public let blockNumber: Int64?
public init(contract: String, slot: String, blockNumber: Int64? = nil) {
self.contract = contract
self.slot = slot
self.blockNumber = blockNumber
}
}
// MARK: - AnyCodable Helper
public struct AnyCodable: Codable {
public let value: Any
public init(_ value: Any) {
self.value = value
}
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let intVal = try? container.decode(Int.self) {
value = intVal
} else if let doubleVal = try? container.decode(Double.self) {
value = doubleVal
} else if let boolVal = try? container.decode(Bool.self) {
value = boolVal
} else if let stringVal = try? container.decode(String.self) {
value = stringVal
} else if let arrayVal = try? container.decode([AnyCodable].self) {
value = arrayVal.map { $0.value }
} else if let dictVal = try? container.decode([String: AnyCodable].self) {
value = dictVal.mapValues { $0.value }
} else {
value = NSNull()
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch value {
case let intVal as Int:
try container.encode(intVal)
case let doubleVal as Double:
try container.encode(doubleVal)
case let boolVal as Bool:
try container.encode(boolVal)
case let stringVal as String:
try container.encode(stringVal)
case let arrayVal as [Any]:
try container.encode(arrayVal.map { AnyCodable($0) })
case let dictVal as [String: Any]:
try container.encode(dictVal.mapValues { AnyCodable($0) })
default:
try container.encodeNil()
}
}
}

View file

@ -0,0 +1,314 @@
import Foundation
/// Synor Privacy SDK client for Swift.
/// Confidential transactions, ring signatures, stealth addresses, and cryptographic commitments.
public actor PrivacyClient {
public static let version = "0.1.0"
private let config: PrivacyConfig
private let session: URLSession
private var isClosed = false
public init(config: PrivacyConfig) {
self.config = config
let sessionConfig = URLSessionConfiguration.default
sessionConfig.timeoutIntervalForRequest = TimeInterval(config.timeoutMs) / 1000.0
self.session = URLSession(configuration: sessionConfig)
}
// MARK: - Confidential Transactions
public func createConfidentialTx(
inputs: [ConfidentialTxInput],
outputs: [ConfidentialTxOutput]
) async throws -> ConfidentialTransaction {
let body: [String: Any] = [
"inputs": inputs.map { $0.toDictionary() },
"outputs": outputs.map { $0.toDictionary() }
]
return try await post("/privacy/confidential/create", body: body)
}
public func verifyConfidentialTx(_ tx: ConfidentialTransaction) async throws -> Bool {
let body: [String: Any] = ["transaction": tx.toDictionary()]
let response: [String: Any] = try await post("/privacy/confidential/verify", body: body)
return response["valid"] as? Bool ?? false
}
public func createCommitment(value: String, blindingFactor: String) async throws -> Commitment {
let body: [String: Any] = [
"value": value,
"blinding_factor": blindingFactor
]
return try await post("/privacy/commitment/create", body: body)
}
public func verifyCommitment(commitment: String, value: String, blindingFactor: String) async throws -> Bool {
let body: [String: Any] = [
"commitment": commitment,
"value": value,
"blinding_factor": blindingFactor
]
let response: [String: Any] = try await post("/privacy/commitment/verify", body: body)
return response["valid"] as? Bool ?? false
}
public func createRangeProof(
value: String,
blindingFactor: String,
minValue: Int64,
maxValue: Int64
) async throws -> RangeProof {
let body: [String: Any] = [
"value": value,
"blinding_factor": blindingFactor,
"min_value": minValue,
"max_value": maxValue
]
return try await post("/privacy/range-proof/create", body: body)
}
public func verifyRangeProof(_ proof: RangeProof) async throws -> Bool {
let body: [String: Any] = ["proof": proof.toDictionary()]
let response: [String: Any] = try await post("/privacy/range-proof/verify", body: body)
return response["valid"] as? Bool ?? false
}
// MARK: - Ring Signatures
public func createRingSignature(
message: String,
ring: [String],
signerIndex: Int,
privateKey: String
) async throws -> RingSignature {
let body: [String: Any] = [
"message": message,
"ring": ring,
"signer_index": signerIndex,
"private_key": privateKey
]
return try await post("/privacy/ring/sign", body: body)
}
public func verifyRingSignature(_ signature: RingSignature, message: String) async throws -> Bool {
let body: [String: Any] = [
"signature": signature.toDictionary(),
"message": message
]
let response: [String: Any] = try await post("/privacy/ring/verify", body: body)
return response["valid"] as? Bool ?? false
}
public func generateDecoys(count: Int, excludeKey: String? = nil) async throws -> [String] {
var path = "/privacy/ring/decoys?count=\(count)"
if let excludeKey = excludeKey {
path += "&exclude=\(excludeKey.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? excludeKey)"
}
return try await get(path)
}
public func checkKeyImage(_ keyImage: String) async throws -> Bool {
let response: [String: Any] = try await get("/privacy/ring/key-image/\(keyImage)")
return response["spent"] as? Bool ?? false
}
// MARK: - Stealth Addresses
public func generateStealthKeyPair() async throws -> StealthKeyPair {
return try await post("/privacy/stealth/generate", body: [:])
}
public func deriveStealthAddress(spendPublicKey: String, viewPublicKey: String) async throws -> StealthAddress {
let body: [String: Any] = [
"spend_public_key": spendPublicKey,
"view_public_key": viewPublicKey
]
return try await post("/privacy/stealth/derive", body: body)
}
public func recoverStealthPrivateKey(
stealthAddress: String,
viewPrivateKey: String,
spendPrivateKey: String
) async throws -> String {
let body: [String: Any] = [
"stealth_address": stealthAddress,
"view_private_key": viewPrivateKey,
"spend_private_key": spendPrivateKey
]
let response: [String: Any] = try await post("/privacy/stealth/recover", body: body)
guard let privateKey = response["private_key"] as? String else {
throw PrivacyError.response("Missing private_key")
}
return privateKey
}
public func scanOutputs(
viewPrivateKey: String,
spendPublicKey: String,
fromBlock: Int64,
toBlock: Int64? = nil
) async throws -> [StealthOutput] {
var body: [String: Any] = [
"view_private_key": viewPrivateKey,
"spend_public_key": spendPublicKey,
"from_block": fromBlock
]
if let toBlock = toBlock {
body["to_block"] = toBlock
}
return try await post("/privacy/stealth/scan", body: body)
}
// MARK: - Blinding
public func generateBlindingFactor() async throws -> String {
let response: [String: Any] = try await post("/privacy/blinding/generate", body: [:])
guard let factor = response["blinding_factor"] as? String else {
throw PrivacyError.response("Missing blinding_factor")
}
return factor
}
public func blindValue(value: String, blindingFactor: String) async throws -> String {
let body: [String: Any] = [
"value": value,
"blinding_factor": blindingFactor
]
let response: [String: Any] = try await post("/privacy/blinding/blind", body: body)
guard let blindedValue = response["blinded_value"] as? String else {
throw PrivacyError.response("Missing blinded_value")
}
return blindedValue
}
public func unblindValue(blindedValue: String, blindingFactor: String) async throws -> String {
let body: [String: Any] = [
"blinded_value": blindedValue,
"blinding_factor": blindingFactor
]
let response: [String: Any] = try await post("/privacy/blinding/unblind", body: body)
guard let value = response["value"] as? String else {
throw PrivacyError.response("Missing value")
}
return value
}
// MARK: - Lifecycle
public func healthCheck() async -> Bool {
if isClosed { return false }
do {
let response: [String: Any] = try await get("/health")
return response["status"] as? String == "healthy"
} catch {
return false
}
}
public func close() {
isClosed = true
session.invalidateAndCancel()
}
// MARK: - HTTP Helpers
private func get<T: Decodable>(_ path: String) async throws -> T {
try checkClosed()
return try await executeWithRetry {
let url = URL(string: "\(self.config.endpoint)\(path)")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
self.addHeaders(to: &request)
return try await self.execute(request)
}
}
private func post<T: Decodable>(_ path: String, body: [String: Any]) async throws -> T {
try checkClosed()
return try await executeWithRetry {
let url = URL(string: "\(self.config.endpoint)\(path)")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = try JSONSerialization.data(withJSONObject: body)
self.addHeaders(to: &request)
return try await self.execute(request)
}
}
private func addHeaders(to request: inout URLRequest) {
request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("swift/\(Self.version)", forHTTPHeaderField: "X-SDK-Version")
}
private func execute<T: Decodable>(_ request: URLRequest) async throws -> T {
let (data, response) = try await session.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw PrivacyError.request("Invalid response")
}
if httpResponse.statusCode >= 200 && httpResponse.statusCode < 300 {
return try JSONDecoder().decode(T.self, from: data)
}
if let errorJson = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
let message = errorJson["message"] as? String ?? errorJson["error"] as? String ?? "Unknown error"
let code = errorJson["code"] as? String
throw PrivacyError.api(message: message, code: code, statusCode: httpResponse.statusCode)
}
throw PrivacyError.response("HTTP \(httpResponse.statusCode)")
}
private func executeWithRetry<T>(_ block: () async throws -> T) async throws -> T {
var lastError: Error?
for attempt in 0..<config.retries {
do {
return try await block()
} catch {
lastError = error
if attempt < config.retries - 1 {
try await Task.sleep(nanoseconds: UInt64(pow(2.0, Double(attempt))) * 1_000_000_000)
}
}
}
throw lastError ?? PrivacyError.request("Request failed")
}
private func checkClosed() throws {
if isClosed {
throw PrivacyError.closed
}
}
}
/// Privacy SDK configuration.
public struct PrivacyConfig {
public let apiKey: String
public var endpoint: String
public var timeoutMs: Int
public var retries: Int
public init(
apiKey: String,
endpoint: String = "https://privacy.synor.io",
timeoutMs: Int = 30000,
retries: Int = 3
) {
self.apiKey = apiKey
self.endpoint = endpoint
self.timeoutMs = timeoutMs
self.retries = retries
}
}
/// Privacy SDK errors.
public enum PrivacyError: Error {
case request(String)
case response(String)
case api(message: String, code: String?, statusCode: Int)
case closed
}

View file

@ -0,0 +1,205 @@
import Foundation
// MARK: - Confidential Transactions
public struct ConfidentialTxInput: Codable {
public let commitment: String
public let blindingFactor: String
public let value: String
public let keyImage: String?
public init(commitment: String, blindingFactor: String, value: String, keyImage: String? = nil) {
self.commitment = commitment
self.blindingFactor = blindingFactor
self.value = value
self.keyImage = keyImage
}
enum CodingKeys: String, CodingKey {
case commitment
case blindingFactor = "blinding_factor"
case value
case keyImage = "key_image"
}
func toDictionary() -> [String: Any] {
var dict: [String: Any] = [
"commitment": commitment,
"blinding_factor": blindingFactor,
"value": value
]
if let keyImage = keyImage {
dict["key_image"] = keyImage
}
return dict
}
}
public struct ConfidentialTxOutput: Codable {
public let commitment: String
public let blindingFactor: String
public let value: String
public let recipientPublicKey: String
public let rangeProof: String?
public init(commitment: String, blindingFactor: String, value: String, recipientPublicKey: String, rangeProof: String? = nil) {
self.commitment = commitment
self.blindingFactor = blindingFactor
self.value = value
self.recipientPublicKey = recipientPublicKey
self.rangeProof = rangeProof
}
enum CodingKeys: String, CodingKey {
case commitment
case blindingFactor = "blinding_factor"
case value
case recipientPublicKey = "recipient_public_key"
case rangeProof = "range_proof"
}
func toDictionary() -> [String: Any] {
var dict: [String: Any] = [
"commitment": commitment,
"blinding_factor": blindingFactor,
"value": value,
"recipient_public_key": recipientPublicKey
]
if let rangeProof = rangeProof {
dict["range_proof"] = rangeProof
}
return dict
}
}
public struct ConfidentialTransaction: Codable {
public let id: String
public let inputs: [ConfidentialTxInput]
public let outputs: [ConfidentialTxOutput]
public let fee: String
public let excess: String
public let excessSig: String
public let kernelOffset: String?
enum CodingKeys: String, CodingKey {
case id, inputs, outputs, fee, excess
case excessSig = "excess_sig"
case kernelOffset = "kernel_offset"
}
func toDictionary() -> [String: Any] {
var dict: [String: Any] = [
"id": id,
"inputs": inputs.map { $0.toDictionary() },
"outputs": outputs.map { $0.toDictionary() },
"fee": fee,
"excess": excess,
"excess_sig": excessSig
]
if let kernelOffset = kernelOffset {
dict["kernel_offset"] = kernelOffset
}
return dict
}
}
// MARK: - Commitments
public struct Commitment: Codable {
public let commitment: String
public let blindingFactor: String
enum CodingKeys: String, CodingKey {
case commitment
case blindingFactor = "blinding_factor"
}
}
public struct RangeProof: Codable {
public let proof: String
public let commitment: String
public let minValue: Int64
public let maxValue: Int64
enum CodingKeys: String, CodingKey {
case proof, commitment
case minValue = "min_value"
case maxValue = "max_value"
}
func toDictionary() -> [String: Any] {
return [
"proof": proof,
"commitment": commitment,
"min_value": minValue,
"max_value": maxValue
]
}
}
// MARK: - Ring Signatures
public struct RingSignature: Codable {
public let c0: String
public let s: [String]
public let keyImage: String
public let ring: [String]
enum CodingKeys: String, CodingKey {
case c0, s, ring
case keyImage = "key_image"
}
func toDictionary() -> [String: Any] {
return [
"c0": c0,
"s": s,
"key_image": keyImage,
"ring": ring
]
}
}
// MARK: - Stealth Addresses
public struct StealthKeyPair: Codable {
public let spendPublicKey: String
public let spendPrivateKey: String
public let viewPublicKey: String
public let viewPrivateKey: String
enum CodingKeys: String, CodingKey {
case spendPublicKey = "spend_public_key"
case spendPrivateKey = "spend_private_key"
case viewPublicKey = "view_public_key"
case viewPrivateKey = "view_private_key"
}
}
public struct StealthAddress: Codable {
public let address: String
public let ephemeralPublicKey: String
public let txPublicKey: String?
enum CodingKeys: String, CodingKey {
case address
case ephemeralPublicKey = "ephemeral_public_key"
case txPublicKey = "tx_public_key"
}
}
public struct StealthOutput: Codable {
public let txHash: String
public let outputIndex: Int
public let stealthAddress: String
public let amount: String
public let blockHeight: Int64
enum CodingKeys: String, CodingKey {
case txHash = "tx_hash"
case outputIndex = "output_index"
case stealthAddress = "stealth_address"
case amount
case blockHeight = "block_height"
}
}