solana_ruby_wallet_adapter 0.1.1 → 0.1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7a0e9caf19e2a18ea8c6c910d69b21a97391c7f52ad77997a069ef8dd8665c4c
4
- data.tar.gz: cf3ec248114950685f094a702eb0a9b4a89eea60086f8d5547a78d71c4a24514
3
+ metadata.gz: 688ccabfbcf81a0b1760ffddb3ad8e60698a64a0521ce469ee89c180ef877cbe
4
+ data.tar.gz: a774975e1fa73ee4cac0f5799f19cd70a112da3ec2ec55dbe24aae413735277c
5
5
  SHA512:
6
- metadata.gz: 76a89389d97d4708aa3257bce24933e8d439651efc6aa7be58a02f39276e10baadcec6e1c5a69faecae3fea1428aae106bf48b6231fa4fa4dc5311cf2a77e520
7
- data.tar.gz: 2e963b0f4e6c5aa70033d1563cccecfefca59bd6dbffc576d07bb16be1bf16cbfa0a8183fde81ee66be3bdce5d10223163d8b02b2f7bad2874ccc8346c394705
6
+ metadata.gz: c3bf635e569af35e0b8fcdbbcae0d5cc7791f4d1eee2db9a9167215f14b0afd2a75c9fe409d5390c8847c536b6ea10c46bad3a3b5f85e1dcf2f3c5b978ca8126
7
+ data.tar.gz: 643cca94b8474106490d02551d29004a6bf28f97f0874e6c1230deff25ef84168ac4c8d349d7f9e19c23d31a71c1f42e1c05a51a5e15aadf240efb36f78e9b15
data/README.md CHANGED
@@ -21,8 +21,9 @@ Provides:
21
21
  - [Simple message signing](#2-simple-message-signing-authentication)
22
22
  - [Sign-In-With-Solana (SIWS)](#3-sign-in-with-solana-siws)
23
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)
24
+ - [Server-built stake transaction (React-free Rails + Stimulus)](#5-server-built-stake-transaction-react-free-rails--stimulus)
25
+ - [Working with public keys](#6-working-with-public-keys)
26
+ - [Networks](#7-networks)
26
27
  4. [Writing a custom adapter](#writing-a-custom-adapter)
27
28
  5. [Error reference](#error-reference)
28
29
  6. [API reference](#api-reference)
@@ -349,7 +350,242 @@ signature = adapter.send(:send_raw_transaction, raw_bytes, rpc_url, options)
349
350
 
350
351
  ---
351
352
 
352
- ### 5. Working with public keys
353
+ ### 5. Server-built stake transaction (React-free Rails + Stimulus)
354
+
355
+ This example shows how to build a Solana staking UI without React.
356
+ The transaction is constructed and partially signed on the server using
357
+ [solana-ruby-kit](https://github.com/pzupan/solana-ruby-kit), then sent to
358
+ the browser for the user's wallet signature. This gem seeds the wallet
359
+ picker and handles any server-side signature work.
360
+
361
+ **Flow:**
362
+ 1. Rails layout seeds registered wallet metadata into `window.__SOLANA_WALLETS__`.
363
+ 2. A Stimulus controller detects the wallet, connects, and collects the SOL amount.
364
+ 3. The browser POSTs `{ public_key, sol }` to a Rails endpoint.
365
+ 4. The Rails controller builds a `createAccount + initialize + delegate` transaction,
366
+ partially signs it with the generated stake-account keypair, and returns
367
+ the wire bytes as Base64.
368
+ 5. The Stimulus controller passes the bytes to the wallet for the user's signature,
369
+ then broadcasts the fully-signed transaction.
370
+
371
+ #### Layout — seed wallet metadata
372
+
373
+ ```erb
374
+ <%# app/views/layouts/application.html.erb %>
375
+ <head>
376
+ <%# ... %>
377
+ <script>window.__SOLANA_WALLETS__ = <%= solana_wallets_json.html_safe %>;</script>
378
+ </head>
379
+ ```
380
+
381
+ #### Initializer — register adapters
382
+
383
+ ```ruby
384
+ # config/initializers/solana_wallet_adapter.rb
385
+ SolanaWalletAdapter::WalletRegistry.register(
386
+ SolanaWalletAdapter::Wallets::PhantomWalletAdapter,
387
+ SolanaWalletAdapter::Wallets::SolflareWalletAdapter,
388
+ SolanaWalletAdapter::Wallets::LedgerWalletAdapter,
389
+ SolanaWalletAdapter::Wallets::CoinbaseWalletAdapter,
390
+ SolanaWalletAdapter::Wallets::WalletConnectWalletAdapter,
391
+ )
392
+ ```
393
+
394
+ #### Rails controller — build and partially sign the transaction
395
+
396
+ ```ruby
397
+ # app/controllers/staking_controller.rb
398
+ require 'base64'
399
+
400
+ class StakingController < ApplicationController
401
+ VOTE_ACCOUNT = '26RGqX3mezgYDxJnGh94gnMM4L2k9grH1eWcTSCHnaxR'
402
+ RPC_URL = 'https://api.mainnet-beta.solana.com'
403
+
404
+ # POST /staking/prepare
405
+ def prepare
406
+ public_key = params.require(:public_key)
407
+ lamports = (params.require(:sol).to_f * 1_000_000_000).to_i
408
+
409
+ rpc = Solana::Ruby::Kit::Rpc::Client.new(RPC_URL)
410
+ blockhash_result = rpc.get_latest_blockhash
411
+ blockhash = blockhash_result.value.blockhash
412
+ last_valid = blockhash_result.value.last_valid_block_height
413
+
414
+ owner = Solana::Ruby::Kit::Addresses::Address.new(public_key)
415
+ stake_kp = Solana::Ruby::Kit::Keys.generate_key_pair
416
+ stake_address = Solana::Ruby::Kit::Addresses::Address.new(
417
+ Solana::Ruby::Kit::Encoding::Base58.encode(stake_kp.verify_key.to_bytes)
418
+ )
419
+
420
+ create_ixs = Solana::Ruby::Kit::Programs::StakeProgram.create_account_instructions(
421
+ from: owner,
422
+ stake_account: stake_address,
423
+ authorized: owner,
424
+ lamports: lamports
425
+ )
426
+
427
+ delegate_ix = Solana::Ruby::Kit::Programs::StakeProgram.delegate_instruction(
428
+ stake_account: stake_address,
429
+ vote_account: Solana::Ruby::Kit::Addresses::Address.new(VOTE_ACCOUNT),
430
+ authorized: owner
431
+ )
432
+
433
+ message = Solana::Ruby::Kit::TransactionMessages::TransactionMessage.new(
434
+ version: :legacy,
435
+ instructions: create_ixs + [delegate_ix],
436
+ fee_payer: owner,
437
+ lifetime_constraint: Solana::Ruby::Kit::TransactionMessages::BlockhashLifetimeConstraint.new(
438
+ blockhash: blockhash,
439
+ last_valid_block_height: last_valid
440
+ )
441
+ )
442
+
443
+ tx = Solana::Ruby::Kit::Transactions.compile_transaction_message(message)
444
+ tx = Solana::Ruby::Kit::Transactions.partially_sign_transaction([stake_kp.signing_key], tx)
445
+
446
+ wire_bytes = Solana::Ruby::Kit::Transactions.wire_encode_transaction(tx)
447
+ render json: { transaction: Base64.strict_encode64(wire_bytes) }
448
+ rescue => e
449
+ render json: { error: e.message }, status: :unprocessable_entity
450
+ end
451
+ end
452
+ ```
453
+
454
+ #### Route
455
+
456
+ ```ruby
457
+ # config/routes.rb
458
+ post '/staking/prepare', to: 'staking#prepare'
459
+ ```
460
+
461
+ #### ViewComponent — Stimulus markup
462
+
463
+ ```erb
464
+ <%# app/components/stake_button/show_component.html.erb %>
465
+ <div data-controller="stake">
466
+ <button
467
+ class="btn btn-outline-primary w-100 mb-3"
468
+ data-action="click->stake#connectWallet"
469
+ data-stake-target="connectButton">
470
+ Select Wallet
471
+ </button>
472
+
473
+ <input
474
+ type="number"
475
+ class="form-control mb-3"
476
+ data-stake-target="input"
477
+ disabled
478
+ value="1"
479
+ min="0.01"
480
+ step="0.01"
481
+ />
482
+
483
+ <button
484
+ class="btn btn-primary w-100"
485
+ data-action="click->stake#stake"
486
+ data-stake-target="stakeButton"
487
+ disabled>
488
+ Stake Now
489
+ </button>
490
+
491
+ <p class="mt-2 small text-muted" data-stake-target="status"></p>
492
+ </div>
493
+ ```
494
+
495
+ #### Stimulus controller — wallet connection and signing
496
+
497
+ ```js
498
+ // app/javascript/controllers/stake_controller.js
499
+ import { Controller } from "@hotwired/stimulus"
500
+ import { Connection, Transaction } from "@solana/web3.js"
501
+
502
+ const RPC_URL = "https://api.mainnet-beta.solana.com"
503
+
504
+ export default class extends Controller {
505
+ static targets = ["connectButton", "input", "stakeButton", "status"]
506
+
507
+ connect() {
508
+ this.provider = null
509
+ this.publicKey = null
510
+ this.connection = new Connection(RPC_URL, "confirmed")
511
+ this.detectWallet()
512
+ }
513
+
514
+ detectWallet() {
515
+ if (window.phantom?.solana?.isPhantom) this.provider = window.phantom.solana
516
+ else if (window.solana?.isPhantom) this.provider = window.solana
517
+ else if (window.solflare?.isSolflare) this.provider = window.solflare
518
+ }
519
+
520
+ async connectWallet() {
521
+ if (!this.provider) this.detectWallet()
522
+ if (!this.provider) {
523
+ this.setStatus("No Solana wallet found. Please install Phantom or Solflare.")
524
+ return
525
+ }
526
+ try {
527
+ this.setStatus("Connecting…")
528
+ const resp = await this.provider.connect()
529
+ this.publicKey = resp.publicKey.toString()
530
+ this.connectButtonTarget.textContent =
531
+ `${this.publicKey.slice(0, 6)}…${this.publicKey.slice(-4)}`
532
+ this.inputTarget.disabled = false
533
+ this.stakeButtonTarget.disabled = false
534
+ this.setStatus("")
535
+ } catch (e) {
536
+ this.setStatus(`Connect failed: ${e.message}`)
537
+ }
538
+ }
539
+
540
+ async stake() {
541
+ if (!this.publicKey) return
542
+
543
+ const sol = parseFloat(this.inputTarget.value || "0")
544
+ if (sol <= 0) { this.setStatus("Enter a valid SOL amount."); return }
545
+
546
+ this.stakeButtonTarget.disabled = true
547
+ this.setStatus("Building transaction…")
548
+
549
+ try {
550
+ const csrfToken = document.querySelector("meta[name='csrf-token']").content
551
+ const resp = await fetch("/staking/prepare", {
552
+ method: "POST",
553
+ headers: { "Content-Type": "application/json", "X-CSRF-Token": csrfToken },
554
+ body: JSON.stringify({ public_key: this.publicKey, sol }),
555
+ })
556
+ const data = await resp.json()
557
+ if (data.error) throw new Error(data.error)
558
+
559
+ this.setStatus("Awaiting wallet signature…")
560
+ const txBytes = Uint8Array.from(atob(data.transaction), c => c.charCodeAt(0))
561
+ const tx = Transaction.from(txBytes)
562
+ const signedTx = await this.provider.signTransaction(tx)
563
+
564
+ this.setStatus("Broadcasting…")
565
+ const sig = await this.connection.sendRawTransaction(signedTx.serialize())
566
+ await this.connection.confirmTransaction(sig, "confirmed")
567
+ this.setStatus(`Staked! Tx: ${sig.slice(0, 20)}…`)
568
+ } catch (e) {
569
+ this.setStatus(`Error: ${e.message}`)
570
+ this.stakeButtonTarget.disabled = false
571
+ }
572
+ }
573
+
574
+ setStatus(msg) {
575
+ if (this.hasStatusTarget) this.statusTarget.textContent = msg
576
+ }
577
+ }
578
+ ```
579
+
580
+ > **Note:** `@solana/web3.js` is still needed in the browser to deserialize the
581
+ > server-built transaction (`Transaction.from`) and broadcast it via
582
+ > `sendRawTransaction`. All React and `@solana/wallet-adapter-react*` packages
583
+ > can be removed from `package.json`.
584
+
585
+ ---
586
+
587
+ ### 6. Working with public keys
588
+
353
589
 
354
590
  ```ruby
355
591
  # From a Base58 string (most common – what wallets return)
@@ -372,7 +608,7 @@ pk1 == pk2 # => true (System Program address)
372
608
 
373
609
  ---
374
610
 
375
- ### 6. Networks
611
+ ### 7. Networks
376
612
 
377
613
  ```ruby
378
614
  net = SolanaWalletAdapter::Network::MainnetBeta
@@ -36,6 +36,8 @@ module SolanaWalletAdapter
36
36
 
37
37
  # Input parameters for a SIWS sign-in request.
38
38
  class SignInInput < T::Struct
39
+ extend T::Sig
40
+
39
41
  # The domain presenting the sign-in request (e.g. "example.com").
40
42
  const :domain, String
41
43
 
@@ -2,5 +2,5 @@
2
2
  # typed: strict
3
3
 
4
4
  module SolanaWalletAdapter
5
- VERSION = "0.1.1"
5
+ VERSION = "0.1.2"
6
6
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solana_ruby_wallet_adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - pzupan
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-04-09 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: sorbet-runtime
@@ -177,7 +177,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
177
177
  - !ruby/object:Gem::Version
178
178
  version: '0'
179
179
  requirements: []
180
- rubygems_version: 3.6.2
180
+ rubygems_version: 4.0.10
181
181
  specification_version: 4
182
182
  summary: Modular Solana wallet adapters for Ruby on Rails
183
183
  test_files: []