Module: Solrengine::Sdp

Defined in:
lib/solrengine/sdp.rb,
lib/solrengine/sdp/ramps.rb,
lib/solrengine/sdp/engine.rb,
lib/solrengine/sdp/errors.rb,
lib/solrengine/sdp/faucet.rb,
lib/solrengine/sdp/version.rb,
lib/solrengine/sdp/broadcaster.rb,
lib/solrengine/sdp/wallet_owner.rb,
lib/solrengine/sdp/configuration.rb,
app/models/solrengine/sdp/transfer.rb,
app/jobs/solrengine/sdp/track_transfer_job.rb,
app/jobs/solrengine/sdp/provision_wallet_job.rb,
lib/generators/solrengine/sdp/install_generator.rb

Overview

Rails engine for custodial Solana wallets backed by the Solana Developer Platform (SDP). Composes the solana-sdp client with the solrengine family.

Defined Under Namespace

Modules: WalletOwner Classes: Broadcaster, Configuration, ConfigurationError, Engine, Error, Faucet, InstallGenerator, InsufficientBalance, ProvisionWalletJob, Ramps, TrackTransferJob, Transfer

Constant Summary collapse

EXEMPT_TASK_PREFIXES =

Rake task prefixes for which the boot-time api_key check is skipped: CI and Docker image builds run these without production secrets.

%w[assets: db: app: tmp: log:].freeze
REALTIME_SUBSCRIBER_NAME =

Name this engine registers under on the solrengine-realtime subscriber registry (start_realtime!/stop_realtime!). Apps can register their own subscribers alongside it under their own names.

:solrengine_sdp
VERSION =
"0.2.0"
COMPATIBLE_SDP_VERSION =

The SDP release this engine version is tested against. SDP breaks its API between minors; bump this (and re-verify) on every SDP upgrade.

"0.31"

Class Method Summary collapse

Class Method Details

.clientObject

Memoized SDP API client built from the configuration. Reset whenever ‘configure` runs, so reconfiguring always yields a fresh client.



43
44
45
46
47
48
49
# File 'lib/solrengine/sdp.rb', line 43

def client
  @client ||= ::Sdp::Client.new(
    api_key: configuration.validate!.api_key,
    base_url: configuration.base_url,
    custody_provider: configuration.custody_provider
  )
end

.configurationObject



30
31
32
# File 'lib/solrengine/sdp.rb', line 30

def configuration
  @configuration ||= Configuration.new
end

.configure {|configuration| ... } ⇒ Object

Yields:



34
35
36
37
38
39
# File 'lib/solrengine/sdp.rb', line 34

def configure
  yield(configuration)
  @client = nil
  @ramps = nil
  configuration
end

.exempt_context?(task_names) ⇒ Boolean

Pure function over rake task names so the exemption logic is unit-testable: exempt_context?() => true.

Returns:

  • (Boolean)


66
67
68
69
70
# File 'lib/solrengine/sdp.rb', line 66

def exempt_context?(task_names)
  Array(task_names).any? do |task|
    EXEMPT_TASK_PREFIXES.any? { |prefix| task.to_s.start_with?(prefix) }
  end
end

.exempt_rake_context?Boolean

True when the current process is an exempt rake task (boot check skip).

Returns:

  • (Boolean)


73
74
75
76
77
78
79
80
# File 'lib/solrengine/sdp.rb', line 73

def exempt_rake_context?
  return false unless defined?(Rake) && Rake.respond_to?(:application)

  application = Rake.application
  return false unless application.respond_to?(:top_level_tasks)

  exempt_context?(application.top_level_tasks)
end

.price_for(mint) ⇒ Object

USD price for a mint via the optional tokens gem. Returns nil when the gem is absent or the price lookup fails — price must never gate money flows (the U9 broadcaster builds on this).



99
100
101
102
103
104
105
# File 'lib/solrengine/sdp.rb', line 99

def price_for(mint)
  return nil unless price_source_available?

  Solrengine::Tokens::JupiterClient.fetch_prices([ mint ])[mint]
rescue StandardError
  nil
end

.price_source_available?Boolean

solrengine-tokens is an optional price source — never a hard dependency. True when its JupiterClient is loadable.

Returns:

  • (Boolean)


84
85
86
87
88
89
90
91
92
93
94
# File 'lib/solrengine/sdp.rb', line 84

def price_source_available?
  return true if defined?(Solrengine::Tokens::JupiterClient)

  begin
    require "solrengine/tokens"
  rescue LoadError
    return false
  end

  defined?(Solrengine::Tokens::JupiterClient) ? true : false
end

.rampsObject

Thin ramps helper bound to the configured client + default ramp provider. Reset alongside the client whenever configure runs. Sandbox-only in v0.2 (see Solrengine::Sdp::Ramps).



54
55
56
# File 'lib/solrengine/sdp.rb', line 54

def ramps
  @ramps ||= Ramps.new(client: client, provider: configuration.ramp_provider)
end

.reset_configuration!Object



58
59
60
61
62
# File 'lib/solrengine/sdp.rb', line 58

def reset_configuration!
  @configuration = nil
  @client = nil
  @ramps = nil
end

.start_realtime!Object

Registers the engine’s Broadcaster on the solrengine-realtime subscriber registry: every account-change dispatch re-fetches and broadcasts for that wallet. Called by the watcher process (bin/sdp_watcher); idempotent — re-subscribing replaces the block.



128
129
130
131
132
# File 'lib/solrengine/sdp.rb', line 128

def start_realtime!
  Solrengine::Realtime.subscribe(REALTIME_SUBSCRIBER_NAME) do |wallet_address|
    Broadcaster.call(wallet_address)
  end
end

.stop_realtime!Object



134
135
136
# File 'lib/solrengine/sdp.rb', line 134

def stop_realtime!
  Solrengine::Realtime.unsubscribe(REALTIME_SUBSCRIBER_NAME)
end

.usd_value_for(balance) ⇒ Object

USD value for an Sdp::Balance (AE3): SDP’s own usd_value when present (v0.29+ populates it), else derived from the optional tokens gem’s Jupiter price, else nil. Price data is decorative — every failure path degrades to nil so a price hiccup can NEVER fail a broadcaster fetch or gate a money-movement broadcast.



112
113
114
115
116
117
118
119
120
121
122
# File 'lib/solrengine/sdp.rb', line 112

def usd_value_for(balance)
  usd = balance.usd_value
  return usd unless usd.nil? || usd.to_s.empty?

  price = price_for(balance.mint)
  return nil unless price

  BigDecimal(balance.ui_amount.to_s) * BigDecimal(price.to_s)
rescue StandardError
  nil
end