solana_ruby_wallet_adapter 0.1.1 → 0.1.3
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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 51811ed20a2a99f1c970eb5fefbaef2d8186355df44758ea919964df3ac561af
|
|
4
|
+
data.tar.gz: 8c8b14961ddcf9838ebfdaea5a73f42cff8a683ee76bd4603a69eeaa630f4f13
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3de9b50d9b861222d103dcaf7f06616a26f8447bed66bf4d433afbce15e2326732f52a29a91c4bbc740e832f56a9d0546c9f44983aa14ef3c3d6a3521e406741
|
|
7
|
+
data.tar.gz: 8d2d223f9be47268939f5c4b99d3d20cca149f22a9ebd771c036191167277e7da466837a6576dda773708a664ee781dd5840a0656363d9bc03707f635ef2df47
|
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
|
-
- [
|
|
25
|
-
- [
|
|
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,313 @@ signature = adapter.send(:send_raw_transaction, raw_bytes, rpc_url, options)
|
|
|
349
350
|
|
|
350
351
|
---
|
|
351
352
|
|
|
352
|
-
### 5.
|
|
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 `/staking/prepare`.
|
|
365
|
+
4. The Rails controller builds a `createAccount + 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
|
+
6. The signed wire bytes are POSTed to `/staking/submit`.
|
|
370
|
+
7. Rails verifies every Ed25519 signature via `WalletStandard`, then broadcasts
|
|
371
|
+
to the cluster and returns the transaction signature.
|
|
372
|
+
|
|
373
|
+
#### Environment — RPC credentials
|
|
374
|
+
|
|
375
|
+
Store your RPC API key in `.env` (already gitignored by Rails):
|
|
376
|
+
|
|
377
|
+
```
|
|
378
|
+
# .env
|
|
379
|
+
HELIUS_API_KEY=your-api-key-here
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
#### Initializer — configure solana-ruby-kit and register adapters
|
|
383
|
+
|
|
384
|
+
```ruby
|
|
385
|
+
# config/initializers/solana_ruby_kit.rb
|
|
386
|
+
Solana::Ruby::Kit.configure do |config|
|
|
387
|
+
if Rails.env.production?
|
|
388
|
+
config.rpc_url = "https://mainnet.helius-rpc.com/?api-key=#{ENV.fetch('HELIUS_API_KEY')}"
|
|
389
|
+
config.ws_url = "wss://mainnet.helius-rpc.com/?api-key=#{ENV.fetch('HELIUS_API_KEY')}"
|
|
390
|
+
else
|
|
391
|
+
config.rpc_url = "https://devnet.helius-rpc.com/?api-key=#{ENV.fetch('HELIUS_API_KEY')}"
|
|
392
|
+
config.ws_url = "wss://devnet.helius-rpc.com/?api-key=#{ENV.fetch('HELIUS_API_KEY')}"
|
|
393
|
+
end
|
|
394
|
+
config.commitment = :confirmed
|
|
395
|
+
config.timeout = 30
|
|
396
|
+
end
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
```ruby
|
|
400
|
+
# config/initializers/solana_wallet_adapter.rb
|
|
401
|
+
SolanaWalletAdapter::WalletRegistry.register(
|
|
402
|
+
SolanaWalletAdapter::Wallets::PhantomWalletAdapter,
|
|
403
|
+
SolanaWalletAdapter::Wallets::SolflareWalletAdapter,
|
|
404
|
+
SolanaWalletAdapter::Wallets::LedgerWalletAdapter,
|
|
405
|
+
SolanaWalletAdapter::Wallets::CoinbaseWalletAdapter,
|
|
406
|
+
SolanaWalletAdapter::Wallets::WalletConnectWalletAdapter,
|
|
407
|
+
)
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
#### Layout — seed wallet metadata
|
|
411
|
+
|
|
412
|
+
```erb
|
|
413
|
+
<%# app/views/layouts/application.html.erb %>
|
|
414
|
+
<head>
|
|
415
|
+
<%# ... %>
|
|
416
|
+
<script>window.__SOLANA_WALLETS__ = <%= solana_wallets_json.html_safe %>;</script>
|
|
417
|
+
</head>
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
#### Rails controller — build, verify, and broadcast
|
|
421
|
+
|
|
422
|
+
`Solana::Ruby::Kit.rpc_client` picks up the URL configured in the initializer.
|
|
423
|
+
`WalletStandard.verify_signed_transaction!` decodes the wire bytes returned by
|
|
424
|
+
the browser wallet and verifies every Ed25519 signature before broadcasting.
|
|
425
|
+
|
|
426
|
+
```ruby
|
|
427
|
+
# app/controllers/staking_controller.rb
|
|
428
|
+
require 'base64'
|
|
429
|
+
|
|
430
|
+
class StakingController < ApplicationController
|
|
431
|
+
VOTE_ACCOUNT = '26RGqX3mezgYDxJnGh94gnMM4L2k9grH1eWcTSCHnaxR'
|
|
432
|
+
|
|
433
|
+
# POST /staking/prepare
|
|
434
|
+
# Builds and partially signs a stake transaction server-side.
|
|
435
|
+
# Returns a base64-encoded wire transaction ready for the user's wallet to sign.
|
|
436
|
+
def prepare
|
|
437
|
+
public_key = params.require(:public_key)
|
|
438
|
+
lamports = (params.require(:sol).to_f * 1_000_000_000).to_i
|
|
439
|
+
|
|
440
|
+
rpc = Solana::Ruby::Kit.rpc_client
|
|
441
|
+
blockhash_result = rpc.get_latest_blockhash
|
|
442
|
+
blockhash = blockhash_result.value.blockhash
|
|
443
|
+
last_valid = blockhash_result.value.last_valid_block_height
|
|
444
|
+
|
|
445
|
+
owner = Solana::Ruby::Kit::Addresses::Address.new(public_key)
|
|
446
|
+
stake_kp = Solana::Ruby::Kit::Keys.generate_key_pair
|
|
447
|
+
stake_address = Solana::Ruby::Kit::Addresses::Address.new(
|
|
448
|
+
Solana::Ruby::Kit::Addresses.encode_address(stake_kp.verify_key.to_bytes)
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
create_ixs = Solana::Ruby::Kit::Programs::StakeProgram.create_account_instructions(
|
|
452
|
+
from: owner,
|
|
453
|
+
stake_account: stake_address,
|
|
454
|
+
authorized: owner,
|
|
455
|
+
lamports: lamports
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
delegate_ix = Solana::Ruby::Kit::Programs::StakeProgram.delegate_instruction(
|
|
459
|
+
stake_account: stake_address,
|
|
460
|
+
vote_account: Solana::Ruby::Kit::Addresses::Address.new(VOTE_ACCOUNT),
|
|
461
|
+
authorized: owner
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
message = Solana::Ruby::Kit::TransactionMessages::TransactionMessage.new(
|
|
465
|
+
version: :legacy,
|
|
466
|
+
instructions: create_ixs + [delegate_ix],
|
|
467
|
+
fee_payer: owner,
|
|
468
|
+
lifetime_constraint: Solana::Ruby::Kit::TransactionMessages::BlockhashLifetimeConstraint.new(
|
|
469
|
+
blockhash: blockhash,
|
|
470
|
+
last_valid_block_height: last_valid
|
|
471
|
+
)
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
tx = Solana::Ruby::Kit::Transactions.compile_transaction_message(message)
|
|
475
|
+
tx = Solana::Ruby::Kit::Transactions.partially_sign_transaction([stake_kp.signing_key], tx)
|
|
476
|
+
|
|
477
|
+
wire_bytes = Solana::Ruby::Kit::Transactions.wire_encode_transaction(tx)
|
|
478
|
+
render json: { transaction: Base64.strict_encode64(wire_bytes) }
|
|
479
|
+
rescue => e
|
|
480
|
+
render json: { error: e.message }, status: :unprocessable_entity
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
# POST /staking/submit
|
|
484
|
+
# Receives a wallet-signed transaction (base64 wire bytes), verifies every
|
|
485
|
+
# Ed25519 signature server-side via WalletStandard, then broadcasts to the
|
|
486
|
+
# cluster and returns the transaction signature.
|
|
487
|
+
def submit
|
|
488
|
+
signed_b64 = params.require(:signed_transaction)
|
|
489
|
+
wire_bytes = Base64.strict_decode64(signed_b64)
|
|
490
|
+
|
|
491
|
+
tx = Solana::Ruby::Kit::WalletStandard.verify_signed_transaction!(wire_bytes)
|
|
492
|
+
Solana::Ruby::Kit::Transactions.assert_fully_signed_transaction!(tx)
|
|
493
|
+
|
|
494
|
+
rpc = Solana::Ruby::Kit.rpc_client
|
|
495
|
+
sig = rpc.send_transaction(signed_b64)
|
|
496
|
+
|
|
497
|
+
render json: { signature: sig.value }
|
|
498
|
+
rescue => e
|
|
499
|
+
render json: { error: e.message }, status: :unprocessable_entity
|
|
500
|
+
end
|
|
501
|
+
end
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
#### Routes
|
|
505
|
+
|
|
506
|
+
```ruby
|
|
507
|
+
# config/routes.rb
|
|
508
|
+
post '/staking/prepare', to: 'staking#prepare'
|
|
509
|
+
post '/staking/submit', to: 'staking#submit'
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
#### ViewComponent — Stimulus markup
|
|
513
|
+
|
|
514
|
+
```erb
|
|
515
|
+
<%# app/components/stake_button/show_component.html.erb %>
|
|
516
|
+
<div data-controller="stake">
|
|
517
|
+
<button
|
|
518
|
+
class="btn btn-outline-primary w-100 mb-3"
|
|
519
|
+
data-action="click->stake#connectWallet"
|
|
520
|
+
data-stake-target="connectButton">
|
|
521
|
+
Select Wallet
|
|
522
|
+
</button>
|
|
523
|
+
|
|
524
|
+
<input
|
|
525
|
+
type="number"
|
|
526
|
+
class="form-control mb-3"
|
|
527
|
+
data-stake-target="input"
|
|
528
|
+
disabled
|
|
529
|
+
value="1"
|
|
530
|
+
min="0.01"
|
|
531
|
+
step="0.01"
|
|
532
|
+
/>
|
|
533
|
+
|
|
534
|
+
<button
|
|
535
|
+
class="btn btn-primary w-100"
|
|
536
|
+
data-action="click->stake#stake"
|
|
537
|
+
data-stake-target="stakeButton"
|
|
538
|
+
disabled>
|
|
539
|
+
Stake Now
|
|
540
|
+
</button>
|
|
541
|
+
|
|
542
|
+
<p class="mt-2 small text-muted" data-stake-target="status"></p>
|
|
543
|
+
</div>
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
#### Stimulus controller — wallet connection and signing
|
|
547
|
+
|
|
548
|
+
The signed transaction is POSTed back to Rails for server-side verification and
|
|
549
|
+
broadcasting. No RPC credentials are needed in the browser.
|
|
550
|
+
|
|
551
|
+
```js
|
|
552
|
+
// app/javascript/controllers/stake_controller.js
|
|
553
|
+
import { Controller } from "@hotwired/stimulus"
|
|
554
|
+
import { Transaction } from "@solana/web3.js"
|
|
555
|
+
|
|
556
|
+
export default class extends Controller {
|
|
557
|
+
static targets = ["connectButton", "input", "stakeButton", "status"]
|
|
558
|
+
|
|
559
|
+
connect() {
|
|
560
|
+
this.provider = null
|
|
561
|
+
this.publicKey = null
|
|
562
|
+
this.detectWallet()
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
detectWallet() {
|
|
566
|
+
if (window.phantom?.solana?.isPhantom) this.provider = window.phantom.solana
|
|
567
|
+
else if (window.solana?.isPhantom) this.provider = window.solana
|
|
568
|
+
else if (window.solflare?.isSolflare) this.provider = window.solflare
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
async connectWallet() {
|
|
572
|
+
if (!this.provider) this.detectWallet()
|
|
573
|
+
if (!this.provider) {
|
|
574
|
+
this.setStatus("No Solana wallet found. Please install Phantom or Solflare.")
|
|
575
|
+
return
|
|
576
|
+
}
|
|
577
|
+
try {
|
|
578
|
+
this.setStatus("Connecting…")
|
|
579
|
+
const resp = await this.provider.connect()
|
|
580
|
+
this.publicKey = resp.publicKey.toString()
|
|
581
|
+
this.connectButtonTarget.textContent =
|
|
582
|
+
`${this.publicKey.slice(0, 6)}…${this.publicKey.slice(-4)}`
|
|
583
|
+
this.inputTarget.disabled = false
|
|
584
|
+
this.stakeButtonTarget.disabled = false
|
|
585
|
+
this.setStatus("")
|
|
586
|
+
} catch (e) {
|
|
587
|
+
this.setStatus(`Connect failed: ${e.message}`)
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
async stake() {
|
|
592
|
+
if (!this.publicKey) return
|
|
593
|
+
|
|
594
|
+
const sol = parseFloat(this.inputTarget.value || "0")
|
|
595
|
+
if (sol <= 0) { this.setStatus("Enter a valid SOL amount."); return }
|
|
596
|
+
|
|
597
|
+
this.stakeButtonTarget.disabled = true
|
|
598
|
+
this.setStatus("Building transaction…")
|
|
599
|
+
|
|
600
|
+
try {
|
|
601
|
+
const csrfToken = document.querySelector("meta[name='csrf-token']").content
|
|
602
|
+
const resp = await fetch("/staking/prepare", {
|
|
603
|
+
method: "POST",
|
|
604
|
+
headers: { "Content-Type": "application/json", "X-CSRF-Token": csrfToken },
|
|
605
|
+
body: JSON.stringify({ public_key: this.publicKey, sol }),
|
|
606
|
+
})
|
|
607
|
+
const data = await resp.json()
|
|
608
|
+
if (data.error) throw new Error(data.error)
|
|
609
|
+
|
|
610
|
+
this.setStatus("Awaiting wallet signature…")
|
|
611
|
+
const txBytes = Uint8Array.from(atob(data.transaction), c => c.charCodeAt(0))
|
|
612
|
+
const tx = Transaction.from(txBytes)
|
|
613
|
+
const signedTx = await this.provider.signTransaction(tx)
|
|
614
|
+
|
|
615
|
+
this.setStatus("Broadcasting…")
|
|
616
|
+
const signedB64 = btoa(String.fromCharCode(...signedTx.serialize()))
|
|
617
|
+
const submitResp = await fetch("/staking/submit", {
|
|
618
|
+
method: "POST",
|
|
619
|
+
headers: { "Content-Type": "application/json", "X-CSRF-Token": csrfToken },
|
|
620
|
+
body: JSON.stringify({ signed_transaction: signedB64 }),
|
|
621
|
+
})
|
|
622
|
+
const submitData = await submitResp.json()
|
|
623
|
+
if (submitData.error) throw new Error(submitData.error)
|
|
624
|
+
|
|
625
|
+
this.setStatus(`Staked! Tx: ${submitData.signature.slice(0, 20)}…`)
|
|
626
|
+
} catch (e) {
|
|
627
|
+
this.setStatus(`Error: ${e.message}`)
|
|
628
|
+
this.stakeButtonTarget.disabled = false
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
setStatus(msg) {
|
|
633
|
+
if (this.hasStatusTarget) this.statusTarget.textContent = msg
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
#### esbuild — IIFE format required
|
|
639
|
+
|
|
640
|
+
`@solana/web3.js` uses `import.meta` internally. Bundle as IIFE and define
|
|
641
|
+
`import.meta.url` away so the bundle runs as a plain script (no `type="module"`
|
|
642
|
+
needed):
|
|
643
|
+
|
|
644
|
+
```json
|
|
645
|
+
// package.json
|
|
646
|
+
"build": "esbuild app/javascript/application.js --bundle --sourcemap --format=iife --define:global=globalThis --define:import.meta.url=undefined --outdir=app/assets/builds --public-path=/assets"
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
> **Note:** `@solana/web3.js` is still needed in the browser solely to
|
|
650
|
+
> deserialize the server-built transaction (`Transaction.from`) before passing it
|
|
651
|
+
> to the wallet for signing. Broadcasting is handled server-side by
|
|
652
|
+
> `WalletStandard` + `rpc.send_transaction`, so `Connection` and `sendRawTransaction`
|
|
653
|
+
> are not used. All React and `@solana/wallet-adapter-react*` packages can be
|
|
654
|
+
> removed from `package.json`.
|
|
655
|
+
|
|
656
|
+
---
|
|
657
|
+
|
|
658
|
+
### 6. Working with public keys
|
|
659
|
+
|
|
353
660
|
|
|
354
661
|
```ruby
|
|
355
662
|
# From a Base58 string (most common – what wallets return)
|
|
@@ -372,7 +679,7 @@ pk1 == pk2 # => true (System Program address)
|
|
|
372
679
|
|
|
373
680
|
---
|
|
374
681
|
|
|
375
|
-
###
|
|
682
|
+
### 7. Networks
|
|
376
683
|
|
|
377
684
|
```ruby
|
|
378
685
|
net = SolanaWalletAdapter::Network::MainnetBeta
|
|
@@ -28,7 +28,7 @@ module SolanaWalletAdapter
|
|
|
28
28
|
extend T::Sig
|
|
29
29
|
|
|
30
30
|
# Register one or more adapter classes.
|
|
31
|
-
sig { params(adapter_classes: T
|
|
31
|
+
sig { params(adapter_classes: T.class_of(BaseWalletAdapter)).void }
|
|
32
32
|
def register(*adapter_classes)
|
|
33
33
|
adapter_classes.each do |klass|
|
|
34
34
|
instance = klass.new
|
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.
|
|
4
|
+
version: 0.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- pzupan
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
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:
|
|
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: []
|