Class: XRPL::Client
- Inherits:
-
Object
- Object
- XRPL::Client
- Defined in:
- lib/xrpl/client.rb
Constant Summary collapse
- MAINNET_URL =
'wss://s1.ripple.com'- TESTNET_URL =
'wss://s.altnet.rippletest.net:51233'- DEVNET_URL =
'wss://s.devnet.rippletest.net:51233'- NETWORK_URLS =
{ 'mainnet' => MAINNET_URL, 'testnet' => TESTNET_URL, 'devnet' => DEVNET_URL }.freeze
- LEDGER_OFFSET =
Added to the current ledger index to set LastLedgerSequence during autofill.
20- LEDGER_CLOSE_TIME =
Approximate seconds between validated ledgers; used when polling for finality.
3- DEFAULT_FEE_DROPS =
Default fee (drops) if the server's fee cannot be determined.
10
Instance Attribute Summary collapse
-
#connection ⇒ Object
readonly
Returns the value of attribute connection.
-
#url ⇒ Object
readonly
Returns the value of attribute url.
Class Method Summary collapse
-
.to_transaction_hash(transaction) ⇒ Hash, Object
Accepts a transaction as a plain Hash or as an XRPL::Transaction and hands back the Hash the rest of the pipeline works with.
Instance Method Summary collapse
- #account_channels(**params) ⇒ Object
- #account_currencies(**params) ⇒ Object
- #account_info(**params) ⇒ Object
- #account_info_response(**params) ⇒ Object
- #account_lines(**params) ⇒ Object
- #account_nfts(**params) ⇒ Object
- #account_objects(**params) ⇒ Object
- #account_offers(**params) ⇒ Object
- #account_tx(**params) ⇒ Object
- #account_tx_all(**params) ⇒ Object
- #account_tx_response(**params) ⇒ Object
-
#autofill(transaction, signers_count: 0) ⇒ Hash
Fills in the fields a transaction needs before signing:
Sequence,FeeandLastLedgerSequence. -
#connect(wait: false, timeout: 10) ⇒ self
Opens the WebSocket connection.
-
#connect!(timeout: 10) ⇒ self
Opens the connection and blocks until it is ready to accept requests.
- #disconnect ⇒ Object
- #fee(**params) ⇒ Object
- #fee_response(**params) ⇒ Object
- #gateway_balances(**params) ⇒ Object
-
#initialize(url, logger: nil, max_fee_drops: Fee::MAX_FEE_DROPS) ⇒ Client
constructor
A new instance of Client.
- #ledger(**params) ⇒ Object
- #ledger_closed(**params) ⇒ Object
- #ledger_current(**params) ⇒ Object
- #ledger_data(**params) ⇒ Object
- #ledger_entry(**params) ⇒ Object
- #noripple_check(**params) ⇒ Object
-
#open? ⇒ Boolean
Whether the WebSocket connection is currently open.
- #request(command, params = {}) ⇒ Object
- #request_with_response(command, params = {}, timeout: 10) ⇒ Object
- #request_with_retry(command, params = {}, max_attempts: 3, timeout: 10, retry_exceptions: [RuntimeError, Timeout::Error], **keyword_params) ⇒ Object
-
#submit(transaction, wallet:, autofill: true, fail_hard: false) ⇒ Hash
Autofills (optional), signs with the given wallet and submits a transaction.
-
#submit_and_wait(transaction, wallet:, autofill: true, fail_hard: false, timeout: 20) ⇒ Hash
Like #submit, but then polls the ledger until the transaction is final (included in a validated ledger, or definitively failed/expired).
- #subscribe(**params) ⇒ Object
- #summarize_account_tx(response) ⇒ Object
- #tx(**params) ⇒ Object
- #tx_response(**params) ⇒ Object
- #unsubscribe(**params) ⇒ Object
-
#wait_until_open(timeout: 10) ⇒ true
Blocks the calling thread until the connection is open.
Constructor Details
#initialize(url, logger: nil, max_fee_drops: Fee::MAX_FEE_DROPS) ⇒ Client
Returns a new instance of Client.
59 60 61 62 63 64 65 66 67 |
# File 'lib/xrpl/client.rb', line 59 def initialize(url, logger: nil, max_fee_drops: Fee::MAX_FEE_DROPS) @url = resolve_url(url) @connection = nil @requests = {} @open = false @ready_queue = Queue.new @logger = logger @max_fee_drops = max_fee_drops end |
Instance Attribute Details
#connection ⇒ Object (readonly)
Returns the value of attribute connection.
36 37 38 |
# File 'lib/xrpl/client.rb', line 36 def connection @connection end |
#url ⇒ Object (readonly)
Returns the value of attribute url.
36 37 38 |
# File 'lib/xrpl/client.rb', line 36 def url @url end |
Class Method Details
.to_transaction_hash(transaction) ⇒ Hash, Object
Accepts a transaction as a plain Hash or as an XRPL::Transaction and hands back the Hash the rest of the pipeline works with. Anything else is passed through untouched, so a pre-signed blob still reaches submit.
44 45 46 47 48 49 |
# File 'lib/xrpl/client.rb', line 44 def self.to_transaction_hash(transaction) return transaction if transaction.is_a?(Hash) return transaction.to_h if transaction.is_a?(XRPL::Transaction) transaction end |
Instance Method Details
#account_channels(**params) ⇒ Object
203 204 205 |
# File 'lib/xrpl/client.rb', line 203 def account_channels(**params) request('account_channels', **params) end |
#account_currencies(**params) ⇒ Object
207 208 209 |
# File 'lib/xrpl/client.rb', line 207 def account_currencies(**params) request('account_currencies', **params) end |
#account_info(**params) ⇒ Object
211 212 213 |
# File 'lib/xrpl/client.rb', line 211 def account_info(**params) request('account_info', **params) end |
#account_info_response(**params) ⇒ Object
215 216 217 |
# File 'lib/xrpl/client.rb', line 215 def account_info_response(**params) request_with_retry('account_info', params) end |
#account_lines(**params) ⇒ Object
219 220 221 |
# File 'lib/xrpl/client.rb', line 219 def account_lines(**params) request('account_lines', **params) end |
#account_nfts(**params) ⇒ Object
223 224 225 |
# File 'lib/xrpl/client.rb', line 223 def account_nfts(**params) request('account_nfts', **params) end |
#account_objects(**params) ⇒ Object
227 228 229 |
# File 'lib/xrpl/client.rb', line 227 def account_objects(**params) request('account_objects', **params) end |
#account_offers(**params) ⇒ Object
231 232 233 |
# File 'lib/xrpl/client.rb', line 231 def account_offers(**params) request('account_offers', **params) end |
#account_tx(**params) ⇒ Object
235 236 237 |
# File 'lib/xrpl/client.rb', line 235 def account_tx(**params) request('account_tx', **params) end |
#account_tx_all(**params) ⇒ Object
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 |
# File 'lib/xrpl/client.rb', line 243 def account_tx_all(**params) page_limit = params.delete(:page_limit) max_attempts = params.delete(:max_attempts) || 3 timeout = params.delete(:timeout) || 10 current_params = params.dup responses = [] loop do response = request_with_retry('account_tx', current_params, max_attempts: max_attempts, timeout: timeout) responses << response marker = response.dig('result', 'marker') break unless marker break if page_limit && responses.size >= page_limit current_params = current_params.merge(marker: marker) end responses end |
#account_tx_response(**params) ⇒ Object
239 240 241 |
# File 'lib/xrpl/client.rb', line 239 def account_tx_response(**params) request_with_retry('account_tx', params) end |
#autofill(transaction, signers_count: 0) ⇒ Hash
Fills in the fields a transaction needs before signing: Sequence, Fee
and LastLedgerSequence. Existing values are never overwritten.
The fee follows the transaction type (see XRPL::Fee): an EscrowFinish pays for its Fulfillment, AccountDelete, AMMCreate and VaultCreate cost the owner reserve, a Batch pays for its inner transactions.
334 335 336 337 338 339 340 |
# File 'lib/xrpl/client.rb', line 334 def autofill(transaction, signers_count: 0) tx = self.class.to_transaction_hash(transaction).dup tx['Sequence'] ||= fetch_sequence(tx.fetch('Account')) tx['Fee'] ||= calculate_fee(tx, signers_count) tx['LastLedgerSequence'] ||= current_ledger_index + LEDGER_OFFSET tx end |
#connect(wait: false, timeout: 10) ⇒ self
Opens the WebSocket connection.
By default this is non-blocking (preserving the previous behaviour) and
returns self. Pass wait: true (or use #connect!) to block
until the socket is actually open, so a following request can't race with
connection setup and hit "Not connected".
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/xrpl/client.rb', line 79 def connect(wait: false, timeout: 10) @open = false @ready_queue = Queue.new Thread.new { EM.run } unless EM.reactor_running? EM.next_tick do @connection = Faye::WebSocket::Client.new(@url) @connection.on :open do |event| @open = true @ready_queue.push(:open) log("Connected to #{@url}") end @connection.on :message do |event| (JSON.parse(event.data)) end @connection.on :error do |event| @ready_queue.push([:error, event.]) end @connection.on :close do |event| @open = false @connection = nil log("Connection closed: #{event.code} #{event.reason}") end end wait_until_open(timeout: timeout) if wait self end |
#connect!(timeout: 10) ⇒ self
Opens the connection and blocks until it is ready to accept requests.
117 118 119 |
# File 'lib/xrpl/client.rb', line 117 def connect!(timeout: 10) connect(wait: true, timeout: timeout) end |
#disconnect ⇒ Object
145 146 147 |
# File 'lib/xrpl/client.rb', line 145 def disconnect @connection&.close end |
#fee(**params) ⇒ Object
306 307 308 |
# File 'lib/xrpl/client.rb', line 306 def fee(**params) request('fee', **params) end |
#fee_response(**params) ⇒ Object
310 311 312 |
# File 'lib/xrpl/client.rb', line 310 def fee_response(**params) request_with_retry('fee', params) end |
#gateway_balances(**params) ⇒ Object
278 279 280 |
# File 'lib/xrpl/client.rb', line 278 def gateway_balances(**params) request('gateway_balances', **params) end |
#ledger(**params) ⇒ Object
286 287 288 |
# File 'lib/xrpl/client.rb', line 286 def ledger(**params) request('ledger', **params) end |
#ledger_closed(**params) ⇒ Object
290 291 292 |
# File 'lib/xrpl/client.rb', line 290 def ledger_closed(**params) request('ledger_closed', **params) end |
#ledger_current(**params) ⇒ Object
294 295 296 |
# File 'lib/xrpl/client.rb', line 294 def ledger_current(**params) request('ledger_current', **params) end |
#ledger_data(**params) ⇒ Object
298 299 300 |
# File 'lib/xrpl/client.rb', line 298 def ledger_data(**params) request('ledger_data', **params) end |
#ledger_entry(**params) ⇒ Object
302 303 304 |
# File 'lib/xrpl/client.rb', line 302 def ledger_entry(**params) request('ledger_entry', **params) end |
#noripple_check(**params) ⇒ Object
282 283 284 |
# File 'lib/xrpl/client.rb', line 282 def noripple_check(**params) request('noripple_check', **params) end |
#open? ⇒ Boolean
Returns whether the WebSocket connection is currently open.
122 123 124 |
# File 'lib/xrpl/client.rb', line 122 def open? @open end |
#request(command, params = {}) ⇒ Object
149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/xrpl/client.rb', line 149 def request(command, params = {}) id = SecureRandom.uuid register_pending_request(id) payload = { id: id, command: command }.merge(params) (payload) # TODO: Implement promise/future or callback for response id end |
#request_with_response(command, params = {}, timeout: 10) ⇒ Object
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/xrpl/client.rb', line 162 def request_with_response(command, params = {}, timeout: 10) id = SecureRandom.uuid queue = Queue.new register_pending_request(id, queue: queue) payload = { id: id, command: command }.merge(params) (payload) Timeout.timeout(timeout) { queue.pop } rescue Timeout::Error @requests.delete(id) raise Timeout::Error, "Request timed out after #{timeout} seconds" end |
#request_with_retry(command, params = {}, max_attempts: 3, timeout: 10, retry_exceptions: [RuntimeError, Timeout::Error], **keyword_params) ⇒ Object
180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
# File 'lib/xrpl/client.rb', line 180 def request_with_retry(command, params = {}, max_attempts: 3, timeout: 10, retry_exceptions: [RuntimeError, Timeout::Error], **keyword_params) attempt = 0 request_params = keyword_params.empty? ? params : params.merge(keyword_params) begin attempt += 1 request_with_response(command, request_params, timeout: timeout) rescue *retry_exceptions => error raise error if attempt >= max_attempts retry end end |
#submit(transaction, wallet:, autofill: true, fail_hard: false) ⇒ Hash
Autofills (optional), signs with the given wallet and submits a transaction.
349 350 351 352 |
# File 'lib/xrpl/client.rb', line 349 def submit(transaction, wallet:, autofill: true, fail_hard: false) prepared = prepare_for_submit(transaction, wallet: wallet, autofill: autofill) submit_blob(prepared[:tx_blob], fail_hard: fail_hard) end |
#submit_and_wait(transaction, wallet:, autofill: true, fail_hard: false, timeout: 20) ⇒ Hash
Like #submit, but then polls the ledger until the transaction is final (included in a validated ledger, or definitively failed/expired).
364 365 366 367 368 369 370 371 372 373 374 375 |
# File 'lib/xrpl/client.rb', line 364 def submit_and_wait(transaction, wallet:, autofill: true, fail_hard: false, timeout: 20) prepared = prepare_for_submit(transaction, wallet: wallet, autofill: autofill) last_ledger = prepared[:tx]['LastLedgerSequence'] unless last_ledger raise ArgumentError, 'Transaction must contain a LastLedgerSequence for reliable submission' end response = submit_blob(prepared[:tx_blob], fail_hard: fail_hard) preliminary = response.dig('result', 'engine_result') wait_for_final_outcome(prepared[:hash], last_ledger, preliminary, timeout: timeout) end |
#subscribe(**params) ⇒ Object
195 196 197 |
# File 'lib/xrpl/client.rb', line 195 def subscribe(**params) request('subscribe', **params) end |
#summarize_account_tx(response) ⇒ Object
265 266 267 268 269 270 271 272 273 274 275 276 |
# File 'lib/xrpl/client.rb', line 265 def summarize_account_tx(response) result = response.fetch('result', {}) transactions = Array(result['transactions']) { 'ledger_index_min' => result['ledger_index_min'], 'ledger_index_max' => result['ledger_index_max'], 'transaction_count' => transactions.size, 'validated' => result['validated'] == true, 'marker_present' => !result['marker'].nil? } end |
#tx(**params) ⇒ Object
314 315 316 |
# File 'lib/xrpl/client.rb', line 314 def tx(**params) request('tx', **params) end |
#tx_response(**params) ⇒ Object
318 319 320 |
# File 'lib/xrpl/client.rb', line 318 def tx_response(**params) request_with_retry('tx', params) end |
#unsubscribe(**params) ⇒ Object
199 200 201 |
# File 'lib/xrpl/client.rb', line 199 def unsubscribe(**params) request('unsubscribe', **params) end |
#wait_until_open(timeout: 10) ⇒ true
Blocks the calling thread until the connection is open.
132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/xrpl/client.rb', line 132 def wait_until_open(timeout: 10) return true if @open signal = Timeout.timeout(timeout) { @ready_queue.pop } if signal.is_a?(Array) && signal.first == :error raise ConnectionError, "WebSocket connection failed: #{signal.last}" end true rescue Timeout::Error raise Timeout::Error, "Connection did not open within #{timeout} seconds" end |