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 +4 -4
- data/README.md +240 -4
- data/lib/solana_wallet_adapter/message_signer_adapter.rb +2 -0
- data/lib/solana_wallet_adapter/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 688ccabfbcf81a0b1760ffddb3ad8e60698a64a0521ce469ee89c180ef877cbe
|
|
4
|
+
data.tar.gz: a774975e1fa73ee4cac0f5799f19cd70a112da3ec2ec55dbe24aae413735277c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
- [
|
|
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,242 @@ 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 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
|
-
###
|
|
611
|
+
### 7. Networks
|
|
376
612
|
|
|
377
613
|
```ruby
|
|
378
614
|
net = SolanaWalletAdapter::Network::MainnetBeta
|
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.2
|
|
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: []
|