solana_ruby_wallet_adapter 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7a0e9caf19e2a18ea8c6c910d69b21a97391c7f52ad77997a069ef8dd8665c4c
4
+ data.tar.gz: cf3ec248114950685f094a702eb0a9b4a89eea60086f8d5547a78d71c4a24514
5
+ SHA512:
6
+ metadata.gz: 76a89389d97d4708aa3257bce24933e8d439651efc6aa7be58a02f39276e10baadcec6e1c5a69faecae3fea1428aae106bf48b6231fa4fa4dc5311cf2a77e520
7
+ data.tar.gz: 2e963b0f4e6c5aa70033d1563cccecfefca59bd6dbffc576d07bb16be1bf16cbfa0a8183fde81ee66be3bdce5d10223163d8b02b2f7bad2874ccc8346c394705
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Paul Zupan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,608 @@
1
+ # solana-ruby-wallet-adapter
2
+
3
+ A Ruby on Rails gem port of [@solana/wallet-adapter](https://github.com/pzupan/wallet-adapter), with [Sorbet](https://sorbet.org) static types throughout.
4
+
5
+ Provides:
6
+ - Wallet metadata (name, icon, URL) for seeding a frontend wallet picker
7
+ - Server-side Ed25519 signature verification
8
+ - [Sign-In-With-Solana (SIWS)](https://login.xyz/solana) challenge/verify helpers
9
+ - A typed adapter hierarchy that mirrors the original TypeScript classes
10
+
11
+ > **How it differs from the TypeScript original.** Browser-only features — DOM wallet detection, `window.phantom`, popup flows — have no server-side equivalent. The Ruby adapters serve as **metadata providers** for the frontend and **verifiers** for signatures returned by the browser wallet.
12
+
13
+ ---
14
+
15
+ ## Table of contents
16
+
17
+ 1. [Installation](#installation)
18
+ 2. [Setup](#setup)
19
+ 3. [Usage](#usage)
20
+ - [Seed wallets to the frontend](#1-seed-wallets-to-the-frontend)
21
+ - [Simple message signing](#2-simple-message-signing-authentication)
22
+ - [Sign-In-With-Solana (SIWS)](#3-sign-in-with-solana-siws)
23
+ - [Send a pre-signed transaction](#4-send-a-pre-signed-transaction)
24
+ - [Working with public keys](#5-working-with-public-keys)
25
+ - [Networks](#6-networks)
26
+ 4. [Writing a custom adapter](#writing-a-custom-adapter)
27
+ 5. [Error reference](#error-reference)
28
+ 6. [API reference](#api-reference)
29
+ 7. [Development](#development)
30
+ 8. [License](#license)
31
+
32
+ ---
33
+
34
+ ## Installation
35
+
36
+ Add to your `Gemfile`:
37
+
38
+ ```ruby
39
+ gem "solana_ruby_wallet_adapter"
40
+ ```
41
+
42
+ Then run:
43
+
44
+ ```bash
45
+ bundle install
46
+ ```
47
+
48
+ ### Requirements
49
+
50
+ | Requirement | Version |
51
+ |---|---|
52
+ | Ruby | >= 3.2 |
53
+ | Rails | >= 7.0 (optional – auto-detected) |
54
+ | `sorbet-runtime` | >= 0.5 |
55
+ | `ed25519` | >= 1.2 |
56
+ | `base58` | >= 0.2 |
57
+
58
+ ---
59
+
60
+ ## Setup
61
+
62
+ Generate the initializer:
63
+
64
+ ```bash
65
+ # create config/initializers/solana_wallet_adapter.rb manually, or copy the example below
66
+ ```
67
+
68
+ ```ruby
69
+ # config/initializers/solana_wallet_adapter.rb
70
+ SolanaWalletAdapter::WalletRegistry.register(
71
+ SolanaWalletAdapter::Wallets::PhantomWalletAdapter,
72
+ SolanaWalletAdapter::Wallets::SolflareWalletAdapter,
73
+ SolanaWalletAdapter::Wallets::LedgerWalletAdapter,
74
+ SolanaWalletAdapter::Wallets::CoinbaseWalletAdapter,
75
+ SolanaWalletAdapter::Wallets::WalletConnectWalletAdapter,
76
+ )
77
+ ```
78
+
79
+ That's it. The gem auto-loads its Railtie when Rails is present, so
80
+ `ControllerHelpers` and `ViewHelpers` are mixed in automatically.
81
+
82
+ ---
83
+
84
+ ## Usage
85
+
86
+ ### 1. Seed wallets to the frontend
87
+
88
+ Render wallet metadata into your layout so the JavaScript wallet-picker knows
89
+ which wallets to offer and what icons to show:
90
+
91
+ ```erb
92
+ <%# app/views/layouts/application.html.erb %>
93
+ <head>
94
+ <%# ... %>
95
+ <script>
96
+ window.__SOLANA_WALLETS__ = <%= solana_wallets_json %>;
97
+ </script>
98
+ </head>
99
+ ```
100
+
101
+ `solana_wallets_json` returns a JSON array like:
102
+
103
+ ```json
104
+ [
105
+ {
106
+ "name": "Phantom",
107
+ "url": "https://phantom.app",
108
+ "icon": "data:image/svg+xml;base64,...",
109
+ "readyState": "Unsupported",
110
+ "connected": false,
111
+ "publicKey": null
112
+ },
113
+ { "name": "Solflare", ... },
114
+ ...
115
+ ]
116
+ ```
117
+
118
+ Or fetch it from a dedicated endpoint:
119
+
120
+ ```ruby
121
+ # config/routes.rb
122
+ get "/wallets", to: "wallets#index"
123
+ ```
124
+
125
+ ```ruby
126
+ # app/controllers/wallets_controller.rb
127
+ class WalletsController < ApplicationController
128
+ def index
129
+ render json: SolanaWalletAdapter::WalletRegistry.to_json_array
130
+ end
131
+ end
132
+ ```
133
+
134
+ ---
135
+
136
+ ### 2. Simple message signing (authentication)
137
+
138
+ **Flow:**
139
+ 1. Server generates a nonce and stores it in the session.
140
+ 2. Browser wallet signs `"Sign in to MyApp\nNonce: <nonce>"`.
141
+ 3. Browser POSTs `{ public_key, message, signature }` to the server.
142
+ 4. Server verifies the signature, then creates the session.
143
+
144
+ ```ruby
145
+ # app/controllers/wallet_sessions_controller.rb
146
+ class WalletSessionsController < ApplicationController
147
+ # GET /wallet_sessions/new
148
+ # Returns a nonce for the client to include in the message it signs.
149
+ def new
150
+ session[:wallet_nonce] = SecureRandom.hex(16)
151
+ render json: { nonce: session[:wallet_nonce] }
152
+ end
153
+
154
+ # POST /wallet_sessions
155
+ # Body: { public_key: "...", message: "...", signature: "..." }
156
+ def create
157
+ expected_message = "Sign in to MyApp\nNonce: #{session[:wallet_nonce]}"
158
+
159
+ unless params[:message] == expected_message
160
+ return render json: { error: "Message mismatch" }, status: :unprocessable_entity
161
+ end
162
+
163
+ verify_wallet_signature!(
164
+ public_key_b58: params.require(:public_key),
165
+ message: params.require(:message),
166
+ signature_b64: params.require(:signature), # Base64-encoded 64-byte signature
167
+ )
168
+
169
+ session[:wallet_public_key] = params[:public_key]
170
+ session.delete(:wallet_nonce)
171
+
172
+ render json: { ok: true, public_key: params[:public_key] }
173
+
174
+ rescue SolanaWalletAdapter::WalletSignMessageError => e
175
+ render json: { error: "Invalid signature: #{e.message}" }, status: :unauthorized
176
+ end
177
+
178
+ # DELETE /wallet_sessions
179
+ def destroy
180
+ session.delete(:wallet_public_key)
181
+ head :no_content
182
+ end
183
+ end
184
+ ```
185
+
186
+ Matching JavaScript (browser side, using any wallet adapter):
187
+
188
+ ```js
189
+ // Sign the expected message with the connected wallet
190
+ const message = new TextEncoder().encode(`Sign in to MyApp\nNonce: ${nonce}`);
191
+ const { signature } = await wallet.signMessage(message);
192
+
193
+ await fetch("/wallet_sessions", {
194
+ method: "POST",
195
+ headers: { "Content-Type": "application/json" },
196
+ body: JSON.stringify({
197
+ public_key: wallet.publicKey.toBase58(),
198
+ message: `Sign in to MyApp\nNonce: ${nonce}`,
199
+ signature: btoa(String.fromCharCode(...signature)), // Base64
200
+ }),
201
+ });
202
+ ```
203
+
204
+ ---
205
+
206
+ ### 3. Sign-In-With-Solana (SIWS)
207
+
208
+ SIWS is the Solana equivalent of [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361).
209
+ It produces a structured, human-readable message that the wallet displays before
210
+ signing.
211
+
212
+ #### 3a. Issue a challenge
213
+
214
+ ```ruby
215
+ # GET /siws/challenge
216
+ # Returns the SIWS message string and the input fields needed to verify later.
217
+ def challenge
218
+ input = SolanaWalletAdapter::SignInInput.new(
219
+ domain: request.host,
220
+ address: params.require(:address), # wallet public key (Base58)
221
+ statement: "Sign in to #{Rails.application.class.module_parent_name}",
222
+ uri: root_url,
223
+ version: "1",
224
+ nonce: SecureRandom.hex(16),
225
+ issued_at: Time.now.utc.iso8601,
226
+ expiration_time: 10.minutes.from_now.utc.iso8601,
227
+ )
228
+
229
+ # Store the input in the session so the verify step can reconstruct it.
230
+ session[:siws_input] = {
231
+ domain: input.domain,
232
+ address: input.address,
233
+ statement: input.statement,
234
+ uri: input.uri,
235
+ version: input.version,
236
+ nonce: input.nonce,
237
+ issued_at: input.issued_at,
238
+ expiration_time: input.expiration_time,
239
+ }
240
+
241
+ render json: { message: input.to_message, input: session[:siws_input] }
242
+ end
243
+ ```
244
+
245
+ `input.to_message` produces:
246
+
247
+ ```
248
+ example.com wants you to sign in with your Solana account:
249
+ 9noXzpXnLTia8oBHFMza9tqPuFvPpRCtAfJmZeoBc7x2
250
+
251
+ Sign in to MyApp
252
+
253
+ URI: https://example.com/
254
+ Version: 1
255
+ Nonce: a3f1b2c8d4e5f6a7
256
+ Issued At: 2024-06-01T12:00:00Z
257
+ Expiration Time: 2024-06-01T12:10:00Z
258
+ ```
259
+
260
+ #### 3b. Verify the signed response
261
+
262
+ ```ruby
263
+ # POST /siws/verify
264
+ # Body: { output: { address:, signed_message:, signature: } }
265
+ def verify
266
+ stored_input = session[:siws_input]
267
+ return render json: { error: "No active SIWS challenge" }, status: :bad_request if stored_input.nil?
268
+
269
+ verify_sign_in!(
270
+ input_params: stored_input,
271
+ output_params: params.require(:output).to_unsafe_h,
272
+ # output must contain:
273
+ # address: the signer's Base58 public key
274
+ # signed_message: the exact SIWS message that was signed (UTF-8 string)
275
+ # signature: Base64-encoded 64-byte Ed25519 signature
276
+ )
277
+
278
+ session[:wallet_public_key] = params.dig(:output, :address)
279
+ session.delete(:siws_input)
280
+
281
+ render json: { ok: true }
282
+
283
+ rescue SolanaWalletAdapter::WalletSignInError => e
284
+ render json: { error: e.message }, status: :unauthorized
285
+ end
286
+ ```
287
+
288
+ Matching JavaScript:
289
+
290
+ ```js
291
+ // 1. Fetch the challenge
292
+ const { message, input } = await fetch(`/siws/challenge?address=${publicKey}`).then(r => r.json());
293
+
294
+ // 2. Sign with wallet
295
+ const encoded = new TextEncoder().encode(message);
296
+ const { signature } = await wallet.signMessage(encoded);
297
+
298
+ // 3. POST the output
299
+ await fetch("/siws/verify", {
300
+ method: "POST",
301
+ headers: { "Content-Type": "application/json" },
302
+ body: JSON.stringify({
303
+ output: {
304
+ address: publicKey.toBase58(),
305
+ signed_message: message,
306
+ signature: btoa(String.fromCharCode(...signature)),
307
+ },
308
+ }),
309
+ });
310
+ ```
311
+
312
+ ---
313
+
314
+ ### 4. Send a pre-signed transaction
315
+
316
+ For use cases where the browser signs a transaction and the server forwards it
317
+ to the RPC (e.g., sponsored transactions, relayers):
318
+
319
+ ```ruby
320
+ # POST /transactions
321
+ # Body: { signed_tx: "<Base64-encoded serialised transaction>" }
322
+ def submit
323
+ raw_bytes = Base64.strict_decode64(params.require(:signed_tx))
324
+ tx = SolanaWalletAdapter::Transaction.new(raw_bytes)
325
+
326
+ rpc_url = SolanaWalletAdapter::Network::MainnetBeta.rpc_url
327
+ options = SolanaWalletAdapter::SendTransactionOptions.new(
328
+ skip_preflight: false,
329
+ preflight_commitment: "confirmed",
330
+ )
331
+
332
+ # BaseSignerWalletAdapter#send_raw_transaction posts the bytes to the RPC.
333
+ # Since the tx is already signed, instantiate a pass-through adapter helper:
334
+ signature = SolanaWalletAdapter::RpcClient.new(rpc_url).send_raw_transaction(raw_bytes, options)
335
+
336
+ render json: { signature: signature }
337
+ rescue SolanaWalletAdapter::WalletSendTransactionError => e
338
+ render json: { error: e.message }, status: :unprocessable_entity
339
+ end
340
+ ```
341
+
342
+ Or call the RPC directly from any adapter instance using the protected helper:
343
+
344
+ ```ruby
345
+ adapter = SolanaWalletAdapter::Wallets::PhantomWalletAdapter.new
346
+ # (internal method, exposed here for illustration)
347
+ signature = adapter.send(:send_raw_transaction, raw_bytes, rpc_url, options)
348
+ ```
349
+
350
+ ---
351
+
352
+ ### 5. Working with public keys
353
+
354
+ ```ruby
355
+ # From a Base58 string (most common – what wallets return)
356
+ pk = SolanaWalletAdapter::PublicKey.new("9noXzpXnLTia8oBHFMza9tqPuFvPpRCtAfJmZeoBc7x2")
357
+ pk.to_base58 # => "9noXzpXnLTia8oBHFMza9tqPuFvPpRCtAfJmZeoBc7x2"
358
+ pk.to_bytes # => [138, 12, ...] Array of 32 integers
359
+ pk.bytes # => "\x8a\x0c..." raw 32-byte binary String
360
+
361
+ # From a raw binary string (e.g. decoded from a transaction)
362
+ pk = SolanaWalletAdapter::PublicKey.new("\x00" * 32)
363
+
364
+ # From a Uint8Array-style Integer array
365
+ pk = SolanaWalletAdapter::PublicKey.new([0] * 32)
366
+
367
+ # Equality
368
+ pk1 = SolanaWalletAdapter::PublicKey.new("11111111111111111111111111111111")
369
+ pk2 = SolanaWalletAdapter::PublicKey.new([0] * 32)
370
+ pk1 == pk2 # => true (System Program address)
371
+ ```
372
+
373
+ ---
374
+
375
+ ### 6. Networks
376
+
377
+ ```ruby
378
+ net = SolanaWalletAdapter::Network::MainnetBeta
379
+ net.serialize # => "mainnet-beta"
380
+ net.rpc_url # => "https://api.mainnet-beta.solana.com"
381
+
382
+ SolanaWalletAdapter::Network::Devnet.rpc_url
383
+ # => "https://api.devnet.solana.com"
384
+
385
+ SolanaWalletAdapter::Network::Testnet.rpc_url
386
+ # => "https://api.testnet.solana.com"
387
+ ```
388
+
389
+ ---
390
+
391
+ ## Writing a custom adapter
392
+
393
+ Subclass `BaseMessageSignerWalletAdapter` (for sign + message), or
394
+ `BaseSignerWalletAdapter` (sign only), or `BaseWalletAdapter` (metadata only):
395
+
396
+ ```ruby
397
+ # typed: strict
398
+ # app/solana/my_custom_wallet_adapter.rb
399
+
400
+ class MyCustomWalletAdapter < SolanaWalletAdapter::BaseMessageSignerWalletAdapter
401
+ extend T::Sig
402
+
403
+ sig { override.returns(String) }
404
+ def name = "MyWallet"
405
+
406
+ sig { override.returns(String) }
407
+ def url = "https://mywallet.example.com"
408
+
409
+ sig { override.returns(String) }
410
+ def icon = "data:image/svg+xml;base64,PHN2Zy..."
411
+
412
+ sig { override.returns(SolanaWalletAdapter::WalletReadyState) }
413
+ def ready_state = SolanaWalletAdapter::WalletReadyState::Unsupported
414
+
415
+ sig { override.returns(T.nilable(SolanaWalletAdapter::PublicKey)) }
416
+ def public_key = nil
417
+
418
+ sig { override.returns(T::Boolean) }
419
+ def connecting? = false
420
+
421
+ sig { override.returns(SolanaWalletAdapter::SupportedTransactionVersions) }
422
+ def supported_transaction_versions
423
+ Set[
424
+ SolanaWalletAdapter::TransactionVersion::Legacy,
425
+ SolanaWalletAdapter::TransactionVersion::Version0,
426
+ ].freeze
427
+ end
428
+
429
+ sig { override.void }
430
+ def connect
431
+ raise SolanaWalletAdapter::WalletNotReadyError,
432
+ "#{name} connection is initiated in the browser"
433
+ end
434
+
435
+ sig { override.void }
436
+ def disconnect; end
437
+
438
+ sig do
439
+ override
440
+ .params(
441
+ transaction: SolanaWalletAdapter::Transaction,
442
+ rpc_url: String,
443
+ options: SolanaWalletAdapter::SendTransactionOptions,
444
+ )
445
+ .returns(String)
446
+ end
447
+ def send_transaction(transaction, rpc_url, options = SolanaWalletAdapter::SendTransactionOptions.new)
448
+ raise SolanaWalletAdapter::WalletNotConnectedError
449
+ end
450
+
451
+ sig { override.params(transaction: SolanaWalletAdapter::Transaction).returns(SolanaWalletAdapter::Transaction) }
452
+ def sign_transaction(transaction)
453
+ raise SolanaWalletAdapter::WalletNotConnectedError
454
+ end
455
+
456
+ sig { override.params(transactions: T::Array[SolanaWalletAdapter::Transaction]).returns(T::Array[SolanaWalletAdapter::Transaction]) }
457
+ def sign_all_transactions(transactions)
458
+ raise SolanaWalletAdapter::WalletNotConnectedError
459
+ end
460
+
461
+ sig { override.params(message: String).returns(String) }
462
+ def sign_message(message)
463
+ raise SolanaWalletAdapter::WalletNotConnectedError
464
+ end
465
+ end
466
+ ```
467
+
468
+ Register it:
469
+
470
+ ```ruby
471
+ # config/initializers/solana_wallet_adapter.rb
472
+ SolanaWalletAdapter::WalletRegistry.register(MyCustomWalletAdapter)
473
+ ```
474
+
475
+ ---
476
+
477
+ ## Error reference
478
+
479
+ All errors inherit from `SolanaWalletAdapter::WalletError < StandardError`.
480
+
481
+ | Class | When raised |
482
+ |---|---|
483
+ | `WalletNotReadyError` | Wallet not installed / not in a connectable state |
484
+ | `WalletLoadError` | Wallet extension failed to load |
485
+ | `WalletConfigError` | Adapter misconfiguration |
486
+ | `WalletConnectionError` | Connection attempt failed |
487
+ | `WalletDisconnectedError` | Wallet disconnected unexpectedly |
488
+ | `WalletDisconnectionError` | Explicit disconnect request failed |
489
+ | `WalletAccountError` | Wallet returned an unexpected account |
490
+ | `WalletPublicKeyError` | Wallet returned an invalid public key |
491
+ | `WalletKeypairError` | Keypair-level error |
492
+ | `WalletNotConnectedError` | Operation requires a connected wallet |
493
+ | `WalletSendTransactionError` | Sending a transaction failed |
494
+ | `WalletSignTransactionError` | Signing a transaction failed |
495
+ | `WalletSignMessageError` | Signing a message failed / signature invalid |
496
+ | `WalletSignInError` | SIWS flow failed (mismatch or bad signature) |
497
+ | `WalletTimeoutError` | Operation timed out |
498
+ | `WalletWindowBlockedError` | Popup window was blocked |
499
+ | `WalletWindowClosedError` | User closed the wallet popup |
500
+
501
+ All errors accept an optional second argument `cause_error`:
502
+
503
+ ```ruby
504
+ raise SolanaWalletAdapter::WalletConnectionError.new("Could not connect", original_exception)
505
+ ```
506
+
507
+ ---
508
+
509
+ ## API reference
510
+
511
+ ### `WalletRegistry`
512
+
513
+ ```ruby
514
+ WalletRegistry.register(*adapter_classes) # register one or more adapter classes
515
+ WalletRegistry.all # => Array of adapter classes
516
+ WalletRegistry.find("Phantom") # => PhantomWalletAdapter class or nil
517
+ WalletRegistry.to_json_array # => Array<Hash> ready for JSON serialisation
518
+ WalletRegistry.reset! # clear all (useful in tests)
519
+ ```
520
+
521
+ ### `SignatureVerifier`
522
+
523
+ ```ruby
524
+ verifier = SolanaWalletAdapter::SignatureVerifier.new
525
+
526
+ # Returns true/false
527
+ verifier.verify(public_key: pk, message: msg, signature: sig)
528
+
529
+ # Raises WalletSignMessageError on failure
530
+ verifier.verify!(public_key: pk, message: msg, signature: sig)
531
+
532
+ # Raises WalletSignInError on failure
533
+ verifier.verify_sign_in!(input: sign_in_input, output: sign_in_output)
534
+ ```
535
+
536
+ ### `SignInInput`
537
+
538
+ ```ruby
539
+ input = SolanaWalletAdapter::SignInInput.new(
540
+ domain: "example.com", # required
541
+ address: "9noX...", # required – Base58 public key
542
+ statement: "Sign in to MyApp", # optional
543
+ uri: "https://example.com/", # optional
544
+ version: "1", # default "1"
545
+ nonce: "abc123", # optional but recommended
546
+ issued_at: Time.now.utc.iso8601, # optional
547
+ expiration_time: 10.minutes.from_now.utc.iso8601, # optional
548
+ not_before: nil, # optional
549
+ request_id: nil, # optional
550
+ resources: [], # optional Array<String>
551
+ )
552
+
553
+ input.to_message # => canonical SIWS message string to sign
554
+ ```
555
+
556
+ ### `PublicKey`
557
+
558
+ ```ruby
559
+ SolanaWalletAdapter::PublicKey.new(base58_string)
560
+ SolanaWalletAdapter::PublicKey.new(binary_string) # 32-byte binary
561
+ SolanaWalletAdapter::PublicKey.new(integer_array) # 32-element Array<Integer>
562
+
563
+ pk.to_base58 # => String
564
+ pk.to_bytes # => Array<Integer>
565
+ pk.bytes # => String (binary)
566
+ pk == other_pk # => Boolean
567
+ ```
568
+
569
+ ### `Transaction`
570
+
571
+ ```ruby
572
+ SolanaWalletAdapter::Transaction.new(wire_bytes, version: nil)
573
+
574
+ tx.versioned? # => true for Version0, false for Legacy/nil
575
+ tx.serialize # => wire_bytes
576
+ tx.version # => TransactionVersion or nil
577
+ ```
578
+
579
+ ### `SendTransactionOptions`
580
+
581
+ ```ruby
582
+ SolanaWalletAdapter::SendTransactionOptions.new(
583
+ signers: [], # Array<String> additional signer keypairs
584
+ preflight_commitment: "confirmed", # String or nil
585
+ skip_preflight: false,
586
+ max_retries: nil,
587
+ min_context_slot: nil,
588
+ )
589
+ ```
590
+
591
+ ---
592
+
593
+ ## Development
594
+
595
+ ```bash
596
+ bundle install
597
+ bundle exec rspec # run tests
598
+ bundle exec srb tc # Sorbet type-check
599
+ ```
600
+
601
+ To add more wallet adapters, copy `lib/solana_wallet_adapter/wallets/phantom.rb`
602
+ as a template and register the new class in the initializer.
603
+
604
+ ---
605
+
606
+ ## License
607
+
608
+ Apache-2.0 — same as the original TypeScript library.
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ require "sorbet-runtime"
5
+ require "set"
6
+
7
+ # Core infrastructure
8
+ require_relative "solana_wallet_adapter/version"
9
+ require_relative "solana_wallet_adapter/errors"
10
+ require_relative "solana_wallet_adapter/network"
11
+ require_relative "solana_wallet_adapter/public_key"
12
+ require_relative "solana_wallet_adapter/transaction"
13
+ require_relative "solana_wallet_adapter/wallet_ready_state"
14
+ require_relative "solana_wallet_adapter/event_emitter"
15
+
16
+ # Adapter hierarchy
17
+ require_relative "solana_wallet_adapter/adapter"
18
+ require_relative "solana_wallet_adapter/signer_adapter"
19
+ require_relative "solana_wallet_adapter/message_signer_adapter"
20
+
21
+ # Server-side signature verification
22
+ require_relative "solana_wallet_adapter/signature_verifier"
23
+
24
+ # Wallet registry
25
+ require_relative "solana_wallet_adapter/wallet_registry"
26
+
27
+ # Wallet adapter implementations
28
+ require_relative "solana_wallet_adapter/wallets/phantom"
29
+ require_relative "solana_wallet_adapter/wallets/solflare"
30
+ require_relative "solana_wallet_adapter/wallets/ledger"
31
+ require_relative "solana_wallet_adapter/wallets/coinbase"
32
+ require_relative "solana_wallet_adapter/wallets/walletconnect"
33
+
34
+ # Rails integration (only when Rails is available)
35
+ if defined?(Rails)
36
+ require_relative "solana_wallet_adapter/controller_helpers"
37
+ require_relative "solana_wallet_adapter/view_helpers"
38
+ require_relative "solana_wallet_adapter/railtie"
39
+ end