synor/sdk/ruby/lib/synor_wallet/client.rb
Gulshan Yadav 74b82d2bb2 Add Synor Storage and Wallet SDKs for Swift
- Implement SynorStorage class for decentralized storage operations including upload, download, pinning, and CAR file management.
- Create supporting types and models for storage operations such as UploadOptions, Pin, and StorageConfig.
- Implement SynorWallet class for wallet operations including wallet creation, address generation, transaction signing, and balance queries.
- Create supporting types and models for wallet operations such as Wallet, Address, and Transaction.
- Introduce error handling for both storage and wallet operations.
2026-01-27 01:56:45 +05:30

244 lines
6 KiB
Ruby

# frozen_string_literal: true
require "faraday"
require "json"
module SynorWallet
# Synor Wallet SDK Client
#
# @example
# client = SynorWallet::Client.new(api_key: 'your-api-key')
#
# # Create wallet
# result = client.create_wallet
# puts result.mnemonic
#
# # Get balance
# balance = client.get_balance(address)
#
class Client
attr_reader :config
def initialize(api_key: nil, **options)
@config = Config.new(api_key: api_key, **options)
raise ArgumentError, "API key is required" unless @config.api_key
@conn = Faraday.new(url: @config.base_url) do |f|
f.request :json
f.response :json
f.options.timeout = @config.timeout
f.headers["Authorization"] = "Bearer #{@config.api_key}"
f.headers["X-SDK-Version"] = "ruby/#{VERSION}"
end
@closed = false
end
# ==================== Wallet Operations ====================
# Create a new wallet
#
# @param type [String] Wallet type (standard, multisig, hardware)
# @return [CreateWalletResult]
def create_wallet(type: WalletType::STANDARD)
check_closed!
body = {
type: type,
network: @config.network
}
response = with_retry { @conn.post("/wallets", body) }
CreateWalletResult.from_hash(response.body)
end
# Import wallet from mnemonic
#
# @param mnemonic [String] 12 or 24 word mnemonic phrase
# @param passphrase [String, nil] Optional passphrase
# @return [Wallet]
def import_wallet(mnemonic, passphrase: nil)
check_closed!
body = {
mnemonic: mnemonic,
passphrase: passphrase,
network: @config.network
}
response = with_retry { @conn.post("/wallets/import", body) }
Wallet.from_hash(response.body)
end
# Get wallet by ID
#
# @param wallet_id [String]
# @return [Wallet]
def get_wallet(wallet_id)
check_closed!
response = with_retry { @conn.get("/wallets/#{wallet_id}") }
Wallet.from_hash(response.body)
end
# Generate a new address
#
# @param wallet_id [String]
# @param is_change [Boolean]
# @return [Address]
def generate_address(wallet_id, is_change: false)
check_closed!
body = { is_change: is_change }
response = with_retry { @conn.post("/wallets/#{wallet_id}/addresses", body) }
Address.from_hash(response.body)
end
# Get stealth address
#
# @param wallet_id [String]
# @return [StealthAddress]
def get_stealth_address(wallet_id)
check_closed!
response = with_retry { @conn.get("/wallets/#{wallet_id}/stealth-address") }
StealthAddress.from_hash(response.body)
end
# ==================== Signing Operations ====================
# Sign a transaction
#
# @param wallet_id [String]
# @param transaction [Transaction]
# @return [SignedTransaction]
def sign_transaction(wallet_id, transaction)
check_closed!
body = {
wallet_id: wallet_id,
transaction: transaction.to_h
}
response = with_retry { @conn.post("/transactions/sign", body) }
SignedTransaction.from_hash(response.body)
end
# Sign a message
#
# @param wallet_id [String]
# @param message [String]
# @param address_index [Integer]
# @return [Signature]
def sign_message(wallet_id, message, address_index: 0)
check_closed!
body = {
wallet_id: wallet_id,
message: message,
address_index: address_index
}
response = with_retry { @conn.post("/messages/sign", body) }
Signature.from_hash(response.body)
end
# Verify a message signature
#
# @param message [String]
# @param signature [String]
# @param address [String]
# @return [Boolean]
def verify_message(message, signature, address)
check_closed!
body = {
message: message,
signature: signature,
address: address
}
response = with_retry { @conn.post("/messages/verify", body) }
response.body["valid"]
end
# ==================== Balance & UTXOs ====================
# Get balance for an address
#
# @param address [String]
# @return [Balance]
def get_balance(address)
check_closed!
response = with_retry { @conn.get("/addresses/#{address}/balance") }
Balance.from_hash(response.body)
end
# Get UTXOs for an address
#
# @param address [String]
# @param min_confirmations [Integer]
# @return [Array<UTXO>]
def get_utxos(address, min_confirmations: 1)
check_closed!
response = with_retry do
@conn.get("/addresses/#{address}/utxos", min_confirmations: min_confirmations)
end
response.body.map { |u| UTXO.from_hash(u) }
end
# ==================== Fee Estimation ====================
# Estimate fee
#
# @param priority [String]
# @return [Integer] Fee per byte in satoshis
def estimate_fee(priority: Priority::MEDIUM)
check_closed!
response = with_retry { @conn.get("/fees/estimate", priority: priority) }
response.body["fee_per_byte"]
end
# ==================== Lifecycle ====================
def close
@closed = true
@conn.close if @conn.respond_to?(:close)
end
def closed?
@closed
end
private
def check_closed!
raise ClientClosedError, "Client has been closed" if @closed
end
def with_retry
attempts = 0
begin
attempts += 1
response = yield
handle_response(response)
response
rescue Faraday::Error, ApiError => e
if attempts < @config.retries
sleep(attempts)
retry
end
raise
end
end
def handle_response(response)
return if response.success?
error_message = response.body["error"] || response.body["message"] || "Unknown error"
raise ApiError.new(error_message, status_code: response.status)
end
end
end