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.
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module SolanaWalletAdapter
5
+ # Mixin for adapters that can sign transactions locally (without sending).
6
+ # Mirrors SignerWalletAdapterProps from @solana/wallet-adapter-base.
7
+ module SignerWalletAdapter
8
+ extend T::Sig
9
+ extend T::Helpers
10
+
11
+ requires_ancestor { BaseWalletAdapter }
12
+
13
+ # Sign a single transaction and return the signed bytes.
14
+ sig do
15
+ abstract
16
+ .params(transaction: Transaction)
17
+ .returns(Transaction)
18
+ end
19
+ def sign_transaction(transaction); end
20
+
21
+ # Sign multiple transactions and return them in the same order.
22
+ sig do
23
+ abstract
24
+ .params(transactions: T::Array[Transaction])
25
+ .returns(T::Array[Transaction])
26
+ end
27
+ def sign_all_transactions(transactions); end
28
+ end
29
+
30
+ # Abstract base class that combines BaseWalletAdapter with signing.
31
+ # Mirrors BaseSignerWalletAdapter from @solana/wallet-adapter-base.
32
+ class BaseSignerWalletAdapter < BaseWalletAdapter
33
+ extend T::Sig
34
+ extend T::Helpers
35
+ include SignerWalletAdapter
36
+
37
+ abstract!
38
+
39
+ # send_transaction default implementation: sign then forward raw bytes
40
+ # to the Solana RPC via Net::HTTP. Subclasses may override.
41
+ sig do
42
+ override
43
+ .params(
44
+ transaction: Transaction,
45
+ rpc_url: String,
46
+ options: SendTransactionOptions
47
+ )
48
+ .returns(String)
49
+ end
50
+ def send_transaction(transaction, rpc_url, options = SendTransactionOptions.new)
51
+ raise WalletNotConnectedError unless connected?
52
+
53
+ if transaction.versioned?
54
+ unless supported_transaction_versions
55
+ raise WalletSendTransactionError,
56
+ "Sending versioned transactions isn't supported by this wallet"
57
+ end
58
+ unless T.must(supported_transaction_versions).include?(T.must(transaction.version))
59
+ raise WalletSendTransactionError,
60
+ "Sending transaction version #{transaction.version&.serialize} isn't supported by this wallet"
61
+ end
62
+ end
63
+
64
+ signed = begin
65
+ sign_transaction(transaction)
66
+ rescue WalletSignTransactionError
67
+ raise
68
+ rescue => e
69
+ raise WalletSendTransactionError.new(e.message, e)
70
+ end
71
+
72
+ send_raw_transaction(signed.serialize, rpc_url, options)
73
+ rescue WalletError => e
74
+ emit(:error, e)
75
+ raise
76
+ end
77
+
78
+ private
79
+
80
+ # Submits serialised transaction bytes to the RPC endpoint and returns
81
+ # the transaction signature string.
82
+ sig { params(raw_bytes: String, rpc_url: String, options: SendTransactionOptions).returns(String) }
83
+ def send_raw_transaction(raw_bytes, rpc_url, options)
84
+ require "net/http"
85
+ require "json"
86
+ require "base64"
87
+
88
+ encoded = Base64.strict_encode64(raw_bytes)
89
+
90
+ config = [encoded, { encoding: "base64" }]
91
+ config[1]["skipPreflight"] = options.skip_preflight if options.skip_preflight
92
+ config[1]["preflightCommitment"] = options.preflight_commitment if options.preflight_commitment
93
+ config[1]["maxRetries"] = options.max_retries if options.max_retries
94
+ config[1]["minContextSlot"] = options.min_context_slot if options.min_context_slot
95
+
96
+ body = JSON.generate({
97
+ jsonrpc: "2.0",
98
+ id: 1,
99
+ method: "sendTransaction",
100
+ params: config,
101
+ })
102
+
103
+ uri = URI(rpc_url)
104
+ response = Net::HTTP.post(uri, body, "Content-Type" => "application/json")
105
+ result = JSON.parse(response.body)
106
+
107
+ if (err = result["error"])
108
+ raise WalletSendTransactionError, err["message"] || err.inspect
109
+ end
110
+
111
+ result.dig("result") || raise(WalletSendTransactionError, "Unexpected RPC response: #{result.inspect}")
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module SolanaWalletAdapter
5
+ # Mirrors the TransactionVersion type in @solana/web3.js.
6
+ # Solana supports "legacy" transactions and versioned transactions (0+).
7
+ class TransactionVersion < T::Enum
8
+ enums do
9
+ Legacy = new("legacy")
10
+ Version0 = new(0)
11
+ end
12
+ end
13
+
14
+ # Mirrors SupportedTransactionVersions – a frozen set of accepted versions.
15
+ # nil means "all versions supported" (equivalent to null/undefined in TS).
16
+ SupportedTransactionVersions = T.type_alias { T.nilable(T::Set[TransactionVersion]) }
17
+
18
+ # Lightweight wrapper around raw serialised Solana transaction bytes.
19
+ # The gem does not attempt to parse the transaction wire format; that
20
+ # responsibility belongs to a dedicated Solana RPC client gem.
21
+ class Transaction
22
+ extend T::Sig
23
+
24
+ sig { returns(String) }
25
+ attr_reader :wire_bytes
26
+
27
+ sig { returns(T.nilable(TransactionVersion)) }
28
+ attr_reader :version
29
+
30
+ sig { params(wire_bytes: String, version: T.nilable(TransactionVersion)).void }
31
+ def initialize(wire_bytes, version: nil)
32
+ @wire_bytes = T.let(wire_bytes, String)
33
+ @version = T.let(version, T.nilable(TransactionVersion))
34
+ end
35
+
36
+ sig { returns(T::Boolean) }
37
+ def versioned?
38
+ !version.nil? && version != TransactionVersion::Legacy
39
+ end
40
+
41
+ sig { returns(String) }
42
+ def serialize
43
+ wire_bytes
44
+ end
45
+ end
46
+
47
+ # Options forwarded to the RPC sendTransaction call.
48
+ class SendTransactionOptions < T::Struct
49
+ extend T::Sig
50
+
51
+ # Additional signers that should co-sign the transaction before sending.
52
+ const :signers, T::Array[String], default: []
53
+
54
+ # Preflight commitment level. Maps to Solana's Commitment type.
55
+ const :preflight_commitment, T.nilable(String), default: nil
56
+
57
+ # Whether to skip preflight simulation.
58
+ const :skip_preflight, T::Boolean, default: false
59
+
60
+ # Maximum number of retries.
61
+ const :max_retries, T.nilable(Integer), default: nil
62
+
63
+ # Minimum context slot for the transaction.
64
+ const :min_context_slot, T.nilable(Integer), default: nil
65
+ end
66
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module SolanaWalletAdapter
5
+ VERSION = "0.1.1"
6
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module SolanaWalletAdapter
5
+ # Helpers mixed into ActionView::Base when the Railtie loads.
6
+ module ViewHelpers
7
+ extend T::Sig
8
+
9
+ # Returns a JSON array of all registered wallet adapter metadata.
10
+ # Useful for seeding a JS wallet picker component.
11
+ #
12
+ # Example (in an ERB layout):
13
+ # <script>
14
+ # window.__SOLANA_WALLETS__ = <%= solana_wallets_json %>;
15
+ # </script>
16
+ sig { returns(String) }
17
+ def solana_wallets_json
18
+ require "json"
19
+ JSON.generate(WalletRegistry.to_json_array)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module SolanaWalletAdapter
5
+ # Mirrors the WalletReadyState enum from @solana/wallet-adapter-base.
6
+ #
7
+ # +Installed+ – The wallet browser extension / app was detected.
8
+ # +NotDetected+ – No wallet detected in the current environment.
9
+ # +Loadable+ – The wallet is always available (e.g. iOS deep-link redirect).
10
+ # +Unsupported+ – The platform cannot support this wallet (server-side, etc.).
11
+ class WalletReadyState < T::Enum
12
+ enums do
13
+ Installed = new("Installed")
14
+ NotDetected = new("NotDetected")
15
+ Loadable = new("Loadable")
16
+ Unsupported = new("Unsupported")
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module SolanaWalletAdapter
5
+ # A registry that maps wallet names to their adapter classes.
6
+ # Provides a single place to look up all known wallet adapters and to
7
+ # filter them by ready-state for use in frontend JSON serialisation.
8
+ #
9
+ # Usage:
10
+ # # Register your adapters (typically in an initializer)
11
+ # SolanaWalletAdapter::WalletRegistry.register(
12
+ # SolanaWalletAdapter::Wallets::PhantomWalletAdapter,
13
+ # SolanaWalletAdapter::Wallets::SolflareWalletAdapter,
14
+ # )
15
+ #
16
+ # # Query
17
+ # WalletRegistry.all # => [PhantomWalletAdapter, ...]
18
+ # WalletRegistry.find("Phantom") # => PhantomWalletAdapter
19
+ class WalletRegistry
20
+ extend T::Sig
21
+
22
+ @adapters = T.let(
23
+ {},
24
+ T::Hash[String, T.class_of(BaseWalletAdapter)]
25
+ )
26
+
27
+ class << self
28
+ extend T::Sig
29
+
30
+ # Register one or more adapter classes.
31
+ sig { params(adapter_classes: T::Array[T.class_of(BaseWalletAdapter)]).void }
32
+ def register(*adapter_classes)
33
+ adapter_classes.each do |klass|
34
+ instance = klass.new
35
+ @adapters[instance.name] = klass
36
+ end
37
+ end
38
+
39
+ # Return all registered adapter classes in registration order.
40
+ sig { returns(T::Array[T.class_of(BaseWalletAdapter)]) }
41
+ def all
42
+ @adapters.values
43
+ end
44
+
45
+ # Find an adapter class by wallet name.
46
+ sig { params(name: String).returns(T.nilable(T.class_of(BaseWalletAdapter))) }
47
+ def find(name)
48
+ @adapters[name]
49
+ end
50
+
51
+ # Serialise all adapters to an array of hashes suitable for JSON.
52
+ # Instantiates each adapter once to read metadata.
53
+ sig { returns(T::Array[T::Hash[String, T.untyped]]) }
54
+ def to_json_array
55
+ @adapters.values.map { |klass| klass.new.to_h }
56
+ end
57
+
58
+ # Remove all registered adapters. Useful in tests.
59
+ sig { void }
60
+ def reset!
61
+ @adapters.clear
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module SolanaWalletAdapter
5
+ module Wallets
6
+ class CoinbaseWalletAdapter < BaseMessageSignerWalletAdapter
7
+ extend T::Sig
8
+
9
+ COINBASE_ICON = T.let(
10
+ "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAyNCIgaGVpZ2h0PSIxMDI0IiB2aWV3Qm94PSIwIDAgMTAyNCAxMDI0IiBm" \
11
+ "aWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8Y2lyY2xlIGN4PSI1MTIiIGN5" \
12
+ "PSI1MTIiIHI9IjUxMiIgZmlsbD0iIzAwNTJGRiIvPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVs" \
13
+ "ZT0iZXZlbm9kZCIgZD0iTTE1MiA1MTJDMTUyIDcxMC44MjMgMzEzLjE3NyA4NzIgNTEyIDg3MkM3MTAuODIzIDg3" \
14
+ "MiA4NzIgNzEwLjgyMyA4NzIgNTEyQzg3MiAzMTMuMTc3IDcxMC44MjMgMTUyIDUxMiAxNTJDMzEzLjE3NyAxNTIg" \
15
+ "MTUyIDMxMy4xNzcgMTUyIDUxMlpNNDIwIDM5NkM0MDYuNzQ1IDM5NiAzOTYgNDA2Ljc0NSAzOTYgNDIwVjYwNEMz" \
16
+ "OTYgNjE3LjI1NSA0MDYuNzQ1IDYyOCA0MjAgNjI4SDYwNEM2MTcuMjU1IDYyOCA2MjggNjE3LjI1NSA2MjggNjA0" \
17
+ "VjQyMEM2MjggNDA2Ljc0NSA2MTcuMjU1IDM5NiA2MDQgMzk2SDQyMFoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=",
18
+ String
19
+ )
20
+
21
+ sig { override.returns(String) }
22
+ def name = "Coinbase Wallet"
23
+
24
+ sig { override.returns(String) }
25
+ def url = "https://chrome.google.com/webstore/detail/coinbase-wallet-extension/hnfanknocfeofbddgcijnmhnfnkdnaad"
26
+
27
+ sig { override.returns(String) }
28
+ def icon = COINBASE_ICON
29
+
30
+ sig { override.returns(WalletReadyState) }
31
+ def ready_state = WalletReadyState::Unsupported
32
+
33
+ sig { override.returns(T.nilable(PublicKey)) }
34
+ def public_key = nil
35
+
36
+ sig { override.returns(T::Boolean) }
37
+ def connecting? = false
38
+
39
+ sig { override.returns(SupportedTransactionVersions) }
40
+ def supported_transaction_versions
41
+ Set[TransactionVersion::Legacy, TransactionVersion::Version0].freeze
42
+ end
43
+
44
+ sig { override.void }
45
+ def connect
46
+ raise WalletNotReadyError, "#{name} connection is initiated in the browser"
47
+ end
48
+
49
+ sig { override.void }
50
+ def disconnect; end
51
+
52
+ sig do
53
+ override
54
+ .params(transaction: Transaction, rpc_url: String, options: SendTransactionOptions)
55
+ .returns(String)
56
+ end
57
+ def send_transaction(transaction, rpc_url, options = SendTransactionOptions.new)
58
+ raise WalletNotConnectedError, "send_transaction must use a pre-signed transaction server-side"
59
+ end
60
+
61
+ sig { override.params(transaction: Transaction).returns(Transaction) }
62
+ def sign_transaction(transaction)
63
+ raise WalletNotConnectedError, "sign_transaction must be performed client-side"
64
+ end
65
+
66
+ sig { override.params(transactions: T::Array[Transaction]).returns(T::Array[Transaction]) }
67
+ def sign_all_transactions(transactions)
68
+ raise WalletNotConnectedError, "sign_all_transactions must be performed client-side"
69
+ end
70
+
71
+ sig { override.params(message: String).returns(String) }
72
+ def sign_message(message)
73
+ raise WalletNotConnectedError, "sign_message must be performed client-side"
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module SolanaWalletAdapter
5
+ module Wallets
6
+ class LedgerWalletAdapter < BaseSignerWalletAdapter
7
+ extend T::Sig
8
+
9
+ LEDGER_ICON = T.let(
10
+ "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMzUgMzUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2Zy" \
11
+ "I+PGcgZmlsbD0iI2ZmZiI+PHBhdGggZD0ibTIzLjU4OCAweC0xNnYyMS41ODNoMjEuNnYtMTZhNS41ODUgNS41OD" \
12
+ "UgMCAwIDAgLTUuNi01LjU4M3oiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDUuNzM5KSIvPjxwYXRoIGQ9Im04LjM0Mi" \
13
+ "AwaC0yLjc1N2E1LjU4NSA1LjU4NSAwIDAgMCAtNS41ODUgNS41ODV2Mi43NTdoOC4zNDJ6Ii8+PHBhdGggZD0ibTAg" \
14
+ "Ny41OWg4LjM0MnY4LjM0MmgtOC4zNDJ6IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIDUuNzM5KSIvPjxwYXRoIGQ9" \
15
+ "Im0xNS4xOCAyMy40NTFoMi43NTdhNS41ODUgNS41ODUgMCAwIDAgNS41ODUtNS42di0yLjY3MWgtOC4zNDJ6IiB0" \
16
+ "cmFuc2Zvcm09InRyYW5zbGF0ZSgxMS40NzggMTEuNDc4KSIvPjxwYXRoIGQ9Im03LjU5IDE1LjE4aDguMzQydjgu" \
17
+ "MzQyaC04LjM0MnoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDUuNzM5IDExLjQ3OCkiLz48cGF0aCBkPSJtMCAxNS4x" \
18
+ "OHYyLjc1N2E1LjU4NSA1LjU4NSAwIDAgMCA1LjU4NSA1LjU4NWgyLjc1N3YtOC4zNDJ6IiB0cmFuc2Zvcm09InRy" \
19
+ "YW5zbGF0ZSgwIDExLjQ3OCkiLz48L2c+PC9zdmc+",
20
+ String
21
+ )
22
+
23
+ sig { override.returns(String) }
24
+ def name = "Ledger"
25
+
26
+ sig { override.returns(String) }
27
+ def url = "https://ledger.com"
28
+
29
+ sig { override.returns(String) }
30
+ def icon = LEDGER_ICON
31
+
32
+ sig { override.returns(WalletReadyState) }
33
+ def ready_state = WalletReadyState::Unsupported
34
+
35
+ sig { override.returns(T.nilable(PublicKey)) }
36
+ def public_key = nil
37
+
38
+ sig { override.returns(T::Boolean) }
39
+ def connecting? = false
40
+
41
+ sig { override.returns(SupportedTransactionVersions) }
42
+ def supported_transaction_versions
43
+ Set[TransactionVersion::Legacy, TransactionVersion::Version0].freeze
44
+ end
45
+
46
+ sig { override.void }
47
+ def connect
48
+ raise WalletNotReadyError, "#{name} connection is initiated in the browser via USB/Bluetooth"
49
+ end
50
+
51
+ sig { override.void }
52
+ def disconnect; end
53
+
54
+ sig do
55
+ override
56
+ .params(transaction: Transaction, rpc_url: String, options: SendTransactionOptions)
57
+ .returns(String)
58
+ end
59
+ def send_transaction(transaction, rpc_url, options = SendTransactionOptions.new)
60
+ raise WalletNotConnectedError, "send_transaction must use a pre-signed transaction server-side"
61
+ end
62
+
63
+ sig { override.params(transaction: Transaction).returns(Transaction) }
64
+ def sign_transaction(transaction)
65
+ raise WalletNotConnectedError, "sign_transaction must be performed client-side by the Ledger device"
66
+ end
67
+
68
+ sig { override.params(transactions: T::Array[Transaction]).returns(T::Array[Transaction]) }
69
+ def sign_all_transactions(transactions)
70
+ raise WalletNotConnectedError, "sign_all_transactions must be performed client-side"
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module SolanaWalletAdapter
5
+ module Wallets
6
+ # Server-side metadata adapter for the Phantom wallet.
7
+ #
8
+ # In a Rails app the "connecting" lifecycle is handled in the browser by
9
+ # the Phantom extension / app. This class provides:
10
+ # - Wallet metadata (name, icon, url) for frontend consumption.
11
+ # - A stub implementation of the abstract interface so instances can be
12
+ # registered in WalletRegistry and serialised to JSON.
13
+ # - Optional override points for custom server-side send_transaction /
14
+ # sign_message if you choose to proxy those calls through the server.
15
+ #
16
+ # For a full round-trip flow:
17
+ # 1. Render wallet metadata to the frontend via WalletRegistry.to_json_array.
18
+ # 2. The browser connects to Phantom and obtains a public key + signature.
19
+ # 3. POST both to a Rails controller.
20
+ # 4. Verify server-side with SignatureVerifier.
21
+ class PhantomWalletAdapter < BaseMessageSignerWalletAdapter
22
+ extend T::Sig
23
+
24
+ PHANTOM_ICON = T.let(
25
+ "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDgi" \
26
+ "IGhlaWdodD0iMTA4IiB2aWV3Qm94PSIwIDAgMTA4IDEwOCIgZmlsbD0ibm9uZSI+CjxyZWN0IHdpZHRoPSIxMDgi" \
27
+ "IGhlaWdodD0iMTA4IiByeD0iMjYiIGZpbGw9IiNBQjlGRjIiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBj" \
28
+ "bGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik00Ni41MjY3IDY5LjkyMjlDNDIuMDA1NCA3Ni44NTA5IDM0LjQyOTIgODUu" \
29
+ "NjE4MiAyNC4zNDggODUuNjE4MkMxOS41ODI0IDg1LjYxODIgMTUgODMuNjU2MyAxNSA3NS4xMzQyQzE1IDUzLjQz" \
30
+ "MDUgNDQuNjMyNiAxOS44MzI3IDcyLjEyNjggMTkuODMyN0M4Ny43NjggMTkuODMyNyA5NCAzMC42ODQ2IDk0IDQz" \
31
+ "LjAwNzlDOTQgNTguODI1OCA4My43MzU1IDc2LjkxMjIgNzMuNTMyMSA3Ni45MTIyQzcwLjI5MzkgNzYuOTEyMiA2" \
32
+ "OC43MDUzIDc1LjEzNDIgNjguNzA1MyA3Mi4zMTRDNjguNzA1MyA3MS41NzgzIDY4LjgyNzUgNzAuNzgxMiA2OS4w" \
33
+ "NzE5IDY5LjkyMjlDNjUuNTg5MyA3NS44Njk5IDU4Ljg2ODUgODEuMzg3OCA1Mi41NzU0IDgxLjM4NzhDNDcuOTkz" \
34
+ "IDgxLjM4NzggNDUuNjcxMyA3OC41MDYzIDQ1LjY3MTMgNzQuNDU5OEM0NS42NzEzIDcyLjk4ODQgNDUuOTc2OCA3" \
35
+ "MS40NTU2IDQ2LjUyNjcgNjkuOTIyOVpNODMuNjc2MSA0Mi41Nzk0QzgzLjY3NjEgNDYuMTcwNCA4MS41NTc1IDQ3" \
36
+ "Ljk2NTggNzkuMTg3NSA0Ny45NjU4Qzc2Ljc4MTYgNDcuOTY1OCA3NC42OTg5IDQ2LjE3MDQgNzQuNjk4OSA0Mi41" \
37
+ "Nzk0Qzc0LjY5ODkgMzguOTg4NSA3Ni43ODE2IDM3LjE5MzEgNzkuMTg3NSAzNy4xOTMxQzgxLjU1NzUgMzcuMTkz" \
38
+ "MSA4My42NzYxIDM4Ljk4ODUgODMuNjc2MSA0Mi41Nzk0Wk03MC4yMTAzIDQyLjU3OTVDNzAuMjEwMyA0Ni4xNzA0" \
39
+ "IDY4LjA5MTYgNDcuOTY1OCA2NS43MjE2IDQ3Ljk2NThDNjMuMzE1NyA0Ny45NjU4IDYxLjIzMyA0Ni4xNzA0IDYx" \
40
+ "LjIzMyA0Mi41Nzk1QzYxLjIzMyAzOC45ODg1IDYzLjMxNTcgMzcuMTkzMSA2NS43MjE2IDM3LjE5MzFDNjguMDkx" \
41
+ "NiAzNy4xOTMxIDcwLjIxMDMgMzguOTg4NSA3MC4yMTAzIDQyLjU3OTVaIiBmaWxsPSIjRkZGREY4Ii8+Cjwvc3Zn" \
42
+ "Pg==",
43
+ String
44
+ )
45
+
46
+ sig { override.returns(String) }
47
+ def name = "Phantom"
48
+
49
+ sig { override.returns(String) }
50
+ def url = "https://phantom.app"
51
+
52
+ sig { override.returns(String) }
53
+ def icon = PHANTOM_ICON
54
+
55
+ sig { override.returns(WalletReadyState) }
56
+ def ready_state = WalletReadyState::Unsupported
57
+
58
+ sig { override.returns(T.nilable(PublicKey)) }
59
+ def public_key = nil
60
+
61
+ sig { override.returns(T::Boolean) }
62
+ def connecting? = false
63
+
64
+ sig { override.returns(SupportedTransactionVersions) }
65
+ def supported_transaction_versions
66
+ Set[TransactionVersion::Legacy, TransactionVersion::Version0].freeze
67
+ end
68
+
69
+ # Server-side adapters do not initiate connections directly.
70
+ sig { override.void }
71
+ def connect
72
+ raise WalletNotReadyError,
73
+ "PhantomWalletAdapter is a server-side adapter; connection is initiated in the browser"
74
+ end
75
+
76
+ sig { override.void }
77
+ def disconnect
78
+ # no-op on server side
79
+ end
80
+
81
+ # send_transaction is inherited from BaseSignerWalletAdapter and will
82
+ # forward raw bytes to the RPC endpoint. Override here if you need
83
+ # custom logic (e.g. a different commitment level for Phantom).
84
+ sig do
85
+ override
86
+ .params(
87
+ transaction: Transaction,
88
+ rpc_url: String,
89
+ options: SendTransactionOptions
90
+ )
91
+ .returns(String)
92
+ end
93
+ def send_transaction(transaction, rpc_url, options = SendTransactionOptions.new)
94
+ raise WalletNotConnectedError,
95
+ "send_transaction must be called with a pre-signed transaction on the server side"
96
+ end
97
+
98
+ # Server-side adapters cannot sign – signing is done in the browser.
99
+ sig { override.params(transaction: Transaction).returns(Transaction) }
100
+ def sign_transaction(transaction)
101
+ raise WalletNotConnectedError,
102
+ "sign_transaction must be performed client-side by the Phantom extension"
103
+ end
104
+
105
+ sig { override.params(transactions: T::Array[Transaction]).returns(T::Array[Transaction]) }
106
+ def sign_all_transactions(transactions)
107
+ raise WalletNotConnectedError,
108
+ "sign_all_transactions must be performed client-side by the Phantom extension"
109
+ end
110
+
111
+ sig { override.params(message: String).returns(String) }
112
+ def sign_message(message)
113
+ raise WalletNotConnectedError,
114
+ "sign_message must be performed client-side by the Phantom extension"
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module SolanaWalletAdapter
5
+ module Wallets
6
+ class SolflareWalletAdapter < BaseMessageSignerWalletAdapter
7
+ extend T::Sig
8
+
9
+ SOLFLARE_ICON = T.let(
10
+ "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIGlkPSJTIiB4bWxucz0i" \
11
+ "aHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PHN0eWxlPi5jbHMt" \
12
+ "MXtmaWxsOiMwMjA1MGE7c3Ryb2tlOiNmZmVmNDY7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOi41" \
13
+ "cHg7fS5jbHMtMntmaWxsOiNmZmVmNDY7fTwvc3R5bGU+PC9kZWZzPjxyZWN0IGNsYXNzPSJjbHMtMiIgeD0iMCIg" \
14
+ "d2lkdGg9IjUwIiBoZWlnaHQ9IjUwIiByeD0iMTIiIHJ5PSIxMiIvPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTI0" \
15
+ "LjIzLDI2LjQybDIuNDYtMi4zOCw0LjU5LDEuNWMzLjAxLDEsNC41MSwyLjg0LDQuNTEsNS40MywwLDEuOTYtLjc1" \
16
+ "LDMuMjYtMi4yNSw0LjkzbC0uNDYuNS4xNy0xLjE3Yy42Ny00LjI2LS41OC02LjA5LTQuNzItNy40M2wtNC4zLTEu" \
17
+ "MzhoMFpNMTguMDUsMTEuODVsMTIuNTIsNC4xNy0yLjcxLDIuNTktNi41MS0yLjE3Yy0yLjI1LS43NS0zLjAxLTEu" \
18
+ "OTYtMy4zLTQuNTF2LS4wOGgwWk0xNy4zLDMzLjA2bDIuODQtMi43MSw1LjM0LDEuNzVjMi44LjkyLDMuNzYsMi4x" \
19
+ "MywzLjQ2LDUuMThsLTExLjY1LTQuMjJoMFpNMTMuNzEsMjAuOTVjMC0uNzkuNDItMS41NCwxLjEzLTIuMTcuNzUs" \
20
+ "MS4wOSwyLjA1LDIuMDUsNC4wOSwyLjcxbDQuNDIsMS40Ni0yLjQ2LDIuMzgtNC4zNC0xLjQyYy0yLS42Ny0yLjg0" \
21
+ "LTEuNjctMi44NC0yLjk2TTI2LjgyLDQyLjg3YzkuMTgtNi4wOSwxNC4xMS0xMC4yMywxNC4xMS0xNS4zMiwwLTMu" \
22
+ "MzgtMi01LjI2LTYuNDMtNi43MmwtMy4zNC0xLjEzLDkuMTQtOC43Ny0xLjg0LTEuOTYtMi43MSwyLjM4LTEyLjgx" \
23
+ "LTQuMjJjLTMuOTcsMS4yOS04Ljk3LDUuMDktOC45Nyw4Ljg5LDAsLjQyLjA0LjgzLjE3LDEuMjktMy4zLDEuODgt" \
24
+ "NC42MywzLjYzLTQuNjMsNS44LDAsMi4wNSwxLjA5LDQuMDksNC41NSw1LjIybDIuNzUuOTItOS41Miw5LjE0LDEu" \
25
+ "ODQsMS45NiwyLjk2LTIuNzEsMTQuNzMsNS4yMmgwWiIvPjwvc3ZnPg==",
26
+ String
27
+ )
28
+
29
+ sig { override.returns(String) }
30
+ def name = "Solflare"
31
+
32
+ sig { override.returns(String) }
33
+ def url = "https://solflare.com"
34
+
35
+ sig { override.returns(String) }
36
+ def icon = SOLFLARE_ICON
37
+
38
+ sig { override.returns(WalletReadyState) }
39
+ def ready_state = WalletReadyState::Unsupported
40
+
41
+ sig { override.returns(T.nilable(PublicKey)) }
42
+ def public_key = nil
43
+
44
+ sig { override.returns(T::Boolean) }
45
+ def connecting? = false
46
+
47
+ sig { override.returns(SupportedTransactionVersions) }
48
+ def supported_transaction_versions
49
+ Set[TransactionVersion::Legacy, TransactionVersion::Version0].freeze
50
+ end
51
+
52
+ sig { override.void }
53
+ def connect
54
+ raise WalletNotReadyError, "#{name} connection is initiated in the browser"
55
+ end
56
+
57
+ sig { override.void }
58
+ def disconnect; end
59
+
60
+ sig do
61
+ override
62
+ .params(transaction: Transaction, rpc_url: String, options: SendTransactionOptions)
63
+ .returns(String)
64
+ end
65
+ def send_transaction(transaction, rpc_url, options = SendTransactionOptions.new)
66
+ raise WalletNotConnectedError, "send_transaction must use a pre-signed transaction server-side"
67
+ end
68
+
69
+ sig { override.params(transaction: Transaction).returns(Transaction) }
70
+ def sign_transaction(transaction)
71
+ raise WalletNotConnectedError, "sign_transaction must be performed client-side"
72
+ end
73
+
74
+ sig { override.params(transactions: T::Array[Transaction]).returns(T::Array[Transaction]) }
75
+ def sign_all_transactions(transactions)
76
+ raise WalletNotConnectedError, "sign_all_transactions must be performed client-side"
77
+ end
78
+
79
+ sig { override.params(message: String).returns(String) }
80
+ def sign_message(message)
81
+ raise WalletNotConnectedError, "sign_message must be performed client-side"
82
+ end
83
+ end
84
+ end
85
+ end