From 72ee47de2d5e5ee01aa5387196c6df8c555efc18 Mon Sep 17 00:00:00 2001 From: Kohei Taniguchi Date: Wed, 30 Jul 2025 15:26:39 +0900 Subject: [PATCH 1/7] Add import_private_key metho to Glueby::Wallet It only supports ActiveRecordWalletAdapter so far. --- lib/glueby/internal/wallet.rb | 4 +++ .../wallet/abstract_wallet_adapter.rb | 11 ++++++++ .../wallet/active_record_wallet_adapter.rb | 22 ++++++++++++++++ lib/glueby/internal/wallet/errors.rb | 1 + lib/glueby/wallet.rb | 4 +++ .../active_record_wallet_adapter_spec.rb | 25 +++++++++++++++++++ spec/glueby/wallet_spec.rb | 13 ++++++++++ 7 files changed, 80 insertions(+) diff --git a/lib/glueby/internal/wallet.rb b/lib/glueby/internal/wallet.rb index 66a11be8..dec78f68 100644 --- a/lib/glueby/internal/wallet.rb +++ b/lib/glueby/internal/wallet.rb @@ -293,6 +293,10 @@ def fill_uncolored_inputs( [tx, fee, current_amount, provided_utxos] end + def import_private_key(key, label = nil) + wallet_adapter.import_private_key(id, key, label) + end + private def wallet_adapter diff --git a/lib/glueby/internal/wallet/abstract_wallet_adapter.rb b/lib/glueby/internal/wallet/abstract_wallet_adapter.rb index 9300e8cb..caf185d7 100644 --- a/lib/glueby/internal/wallet/abstract_wallet_adapter.rb +++ b/lib/glueby/internal/wallet/abstract_wallet_adapter.rb @@ -186,6 +186,17 @@ def get_addresses(wallet_id, label = nil) raise NotImplementedError, "You must implement #{self.class}##{__method__}" end + # Import an private key to the wallet + # + # @param [String] wallet_id - The private key is imported to. + # @param [Tapyrus::Key] key - The private key that is imported to the wallet. + # @param [String?] label - The label to filter the addresses. + # @return [bool] Whether the importing success or not. + # @raise [Errors::PrivateKeyAlreadyImported] If the private key already imported to the wallet. + def import_private_key(wallet_id, key, label = nil) + raise NotImplementedError, "You must implement #{self.class}##{__method__}" + end + # Create and returns pay to contract address # @param [String] wallet_id - The wallet id that is offered by `create_wallet()` method. # @param [String] contents - The data to be used for generating pay-to-contract address diff --git a/lib/glueby/internal/wallet/active_record_wallet_adapter.rb b/lib/glueby/internal/wallet/active_record_wallet_adapter.rb index 7ec891a2..397dac33 100644 --- a/lib/glueby/internal/wallet/active_record_wallet_adapter.rb +++ b/lib/glueby/internal/wallet/active_record_wallet_adapter.rb @@ -143,6 +143,28 @@ def get_addresses(wallet_id, label = nil) keys.map(&:address) end + def import_private_key(wallet_id, key, label = nil) + wallet = AR::Wallet.find_by(wallet_id: wallet_id) + ar_key = AR::Key.find_by( + wallet: wallet, + private_key: key.priv_key, + public_key: key.pubkey + ) + if ar_key + raise Errors::PrivateKeyAlreadyImported, "Key(pubkey: #{key.pubkey}) already imported in the wallet(#{wallet_id})" + else + AR::Key.create!( + wallet: wallet, + private_key: key.priv_key, + public_key: key.pubkey, + label: label, + purpose: :receive + ) + end + + true + end + def get_addresses_info(addresses) unless addresses.is_a?(Array) addresses = [addresses] diff --git a/lib/glueby/internal/wallet/errors.rb b/lib/glueby/internal/wallet/errors.rb index 70d2e048..0b13e97f 100644 --- a/lib/glueby/internal/wallet/errors.rb +++ b/lib/glueby/internal/wallet/errors.rb @@ -9,6 +9,7 @@ class WalletAlreadyCreated < Error; end class WalletNotFound < Error; end class InvalidSighashType < Error; end class InvalidSigner < Error; end + class PrivateKeyAlreadyImported < Error; end end end end diff --git a/lib/glueby/wallet.rb b/lib/glueby/wallet.rb index fc8f0aff..f4b2f851 100644 --- a/lib/glueby/wallet.rb +++ b/lib/glueby/wallet.rb @@ -35,6 +35,10 @@ def id @internal_wallet.id end + def import_private_key(key, label = nil) + @internal_wallet.import_private_key(key, label) + end + # @return [HashMap] hash of balances which key is color_id or empty string, and value is amount def balances(only_finalized = true) utxos = @internal_wallet.list_unspent(only_finalized) diff --git a/spec/glueby/internal/wallet/active_record_wallet_adapter_spec.rb b/spec/glueby/internal/wallet/active_record_wallet_adapter_spec.rb index c9c6246a..7b153013 100644 --- a/spec/glueby/internal/wallet/active_record_wallet_adapter_spec.rb +++ b/spec/glueby/internal/wallet/active_record_wallet_adapter_spec.rb @@ -333,6 +333,31 @@ end end + describe '#import_private_key' do + let(:key) { Tapyrus::Key.generate } + subject { adapter.import_private_key(wallet.wallet_id, key) } + + it 'create a new Key record' do + expect { subject }.to change { Glueby::Internal::Wallet::AR::Key.count }.by(1) + + ar_key = Glueby::Internal::Wallet::AR::Key.where(public_key: key.pubkey).first + expect(ar_key.private_key).to eq(key.priv_key) + expect(ar_key.script_pubkey).to eq(Tapyrus::Script.to_p2pkh(Tapyrus.hash160(key.pubkey)).to_hex) + expect(ar_key.label).to be_nil + expect(ar_key.purpose).to eq('receive') + end + + context 'specify label' do + subject { adapter.import_private_key(wallet.wallet_id, key, 'test_label') } + + it 'the new Key record has label' do + subject + ar_key = Glueby::Internal::Wallet::AR::Key.where(public_key: key.pubkey).first + expect(ar_key.label).to eq('test_label') + end + end + end + describe '#get_addresses_info' do subject { adapter.get_addresses_info(addresses) } diff --git a/spec/glueby/wallet_spec.rb b/spec/glueby/wallet_spec.rb index d9578f9c..8cad366e 100644 --- a/spec/glueby/wallet_spec.rb +++ b/spec/glueby/wallet_spec.rb @@ -75,6 +75,10 @@ def list_unspent(wallet_id, only_finalized = true, label = nil, color_id: nil) def token_utxos(wallet_id, color_id, only_finalized, per, page) [] end + + def import_private_key(wallet_id, key, label = nil) + true + end end before { Glueby::Internal::Wallet.wallet_adapter = TestWalletAdapter.new } @@ -160,4 +164,13 @@ def token_utxos(wallet_id, color_id, only_finalized, per, page) expect(Glueby::Internal::Wallet.wallet_adapter).to have_received(:list_unspent_with_count).with("wallet_id:1", only_finalized, nil, color_id: color_id, page: 1, per: 25) end end + + describe '#import_prifvate_keys' do + let(:key) { Tapyrus::Key.generate } + let(:wallet) { Glueby::Wallet.create } + + it 'returns true' do + expect(wallet.import_private_key(key, 'test-label')).to eq(true) + end + end end \ No newline at end of file From 8f9b873471999d1a2bc1957ad0a23e737d660549 Mon Sep 17 00:00:00 2001 From: Kohei Taniguchi Date: Thu, 31 Jul 2025 11:16:41 +0900 Subject: [PATCH 2/7] Fix typo --- lib/glueby/internal/wallet/abstract_wallet_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/glueby/internal/wallet/abstract_wallet_adapter.rb b/lib/glueby/internal/wallet/abstract_wallet_adapter.rb index caf185d7..37f4d10c 100644 --- a/lib/glueby/internal/wallet/abstract_wallet_adapter.rb +++ b/lib/glueby/internal/wallet/abstract_wallet_adapter.rb @@ -186,7 +186,7 @@ def get_addresses(wallet_id, label = nil) raise NotImplementedError, "You must implement #{self.class}##{__method__}" end - # Import an private key to the wallet + # Import a private key to the wallet # # @param [String] wallet_id - The private key is imported to. # @param [Tapyrus::Key] key - The private key that is imported to the wallet. From 524efaaa3b8a45207dcfabef4d09118af39066b1 Mon Sep 17 00:00:00 2001 From: Kohei Taniguchi Date: Thu, 31 Jul 2025 11:18:21 +0900 Subject: [PATCH 3/7] Change wallet adapter #import_private_key return to nil --- lib/glueby/internal/wallet/abstract_wallet_adapter.rb | 1 - lib/glueby/internal/wallet/active_record_wallet_adapter.rb | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/glueby/internal/wallet/abstract_wallet_adapter.rb b/lib/glueby/internal/wallet/abstract_wallet_adapter.rb index 37f4d10c..5fabad4d 100644 --- a/lib/glueby/internal/wallet/abstract_wallet_adapter.rb +++ b/lib/glueby/internal/wallet/abstract_wallet_adapter.rb @@ -191,7 +191,6 @@ def get_addresses(wallet_id, label = nil) # @param [String] wallet_id - The private key is imported to. # @param [Tapyrus::Key] key - The private key that is imported to the wallet. # @param [String?] label - The label to filter the addresses. - # @return [bool] Whether the importing success or not. # @raise [Errors::PrivateKeyAlreadyImported] If the private key already imported to the wallet. def import_private_key(wallet_id, key, label = nil) raise NotImplementedError, "You must implement #{self.class}##{__method__}" diff --git a/lib/glueby/internal/wallet/active_record_wallet_adapter.rb b/lib/glueby/internal/wallet/active_record_wallet_adapter.rb index 397dac33..f740fe57 100644 --- a/lib/glueby/internal/wallet/active_record_wallet_adapter.rb +++ b/lib/glueby/internal/wallet/active_record_wallet_adapter.rb @@ -161,8 +161,7 @@ def import_private_key(wallet_id, key, label = nil) purpose: :receive ) end - - true + nil end def get_addresses_info(addresses) From c168d436680cbf763a2ad1354fca19b8324e82c8 Mon Sep 17 00:00:00 2001 From: Kohei Taniguchi Date: Thu, 31 Jul 2025 13:27:35 +0900 Subject: [PATCH 4/7] Add test case --- .../active_record_wallet_adapter_spec.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/spec/glueby/internal/wallet/active_record_wallet_adapter_spec.rb b/spec/glueby/internal/wallet/active_record_wallet_adapter_spec.rb index 7b153013..2d3a03d9 100644 --- a/spec/glueby/internal/wallet/active_record_wallet_adapter_spec.rb +++ b/spec/glueby/internal/wallet/active_record_wallet_adapter_spec.rb @@ -356,6 +356,25 @@ expect(ar_key.label).to eq('test_label') end end + + context 'the private key is already in the wallet' do + before do + Glueby::Internal::Wallet::AR::Key.create!( + wallet: wallet, + private_key: key.priv_key, + public_key: key.pubkey, + purpose: :receive + ) + end + + it 'create a new Key record' do + expect { subject }.to raise_error( + Glueby::Internal::Wallet::Errors::PrivateKeyAlreadyImported, + "Key(pubkey: #{key.pubkey}) already imported in the wallet(#{wallet.wallet_id})" + ) + end + + end end describe '#get_addresses_info' do From 074ae743d4661218092722236d43a3f8051a15e5 Mon Sep 17 00:00:00 2001 From: Kohei Taniguchi Date: Thu, 31 Jul 2025 14:37:56 +0900 Subject: [PATCH 5/7] Add type check to #import_private_key --- .../wallet/active_record_wallet_adapter.rb | 3 +++ .../active_record_wallet_adapter_spec.rb | 19 +++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/glueby/internal/wallet/active_record_wallet_adapter.rb b/lib/glueby/internal/wallet/active_record_wallet_adapter.rb index f740fe57..16d32c4f 100644 --- a/lib/glueby/internal/wallet/active_record_wallet_adapter.rb +++ b/lib/glueby/internal/wallet/active_record_wallet_adapter.rb @@ -144,6 +144,9 @@ def get_addresses(wallet_id, label = nil) end def import_private_key(wallet_id, key, label = nil) + raise Glueby::ArgumentError, 'key should be a Tapyrus::Key' unless key.is_a?(Tapyrus::Key) + raise Glueby::ArgumentError, 'label should be a String' if label && !label.is_a?(String) + wallet = AR::Wallet.find_by(wallet_id: wallet_id) ar_key = AR::Key.find_by( wallet: wallet, diff --git a/spec/glueby/internal/wallet/active_record_wallet_adapter_spec.rb b/spec/glueby/internal/wallet/active_record_wallet_adapter_spec.rb index 2d3a03d9..1f0dc519 100644 --- a/spec/glueby/internal/wallet/active_record_wallet_adapter_spec.rb +++ b/spec/glueby/internal/wallet/active_record_wallet_adapter_spec.rb @@ -347,14 +347,30 @@ expect(ar_key.purpose).to eq('receive') end + context 'key is not an instance of Tapyrus::Key' do + let(:key) { 'invalid key' } + + it 'create a new Key record' do + expect { subject }.to raise_error(Glueby::ArgumentError, 'key should be a Tapyrus::Key') + end + end + context 'specify label' do - subject { adapter.import_private_key(wallet.wallet_id, key, 'test_label') } + let(:label) { 'test_label' } + subject { adapter.import_private_key(wallet.wallet_id, key, label) } it 'the new Key record has label' do subject ar_key = Glueby::Internal::Wallet::AR::Key.where(public_key: key.pubkey).first expect(ar_key.label).to eq('test_label') end + + context 'label is not an instance of String' do + let(:label) { 123 } + it 'create a new Key record' do + expect { subject }.to raise_error(Glueby::ArgumentError, 'label should be a String') + end + end end context 'the private key is already in the wallet' do @@ -373,7 +389,6 @@ "Key(pubkey: #{key.pubkey}) already imported in the wallet(#{wallet.wallet_id})" ) end - end end From 55f9cd584ac8544b13387986c13782650f027bf8 Mon Sep 17 00:00:00 2001 From: Kohei Taniguchi Date: Thu, 31 Jul 2025 14:46:05 +0900 Subject: [PATCH 6/7] Add caution document --- lib/glueby/internal/wallet/abstract_wallet_adapter.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/glueby/internal/wallet/abstract_wallet_adapter.rb b/lib/glueby/internal/wallet/abstract_wallet_adapter.rb index 5fabad4d..74fd15d9 100644 --- a/lib/glueby/internal/wallet/abstract_wallet_adapter.rb +++ b/lib/glueby/internal/wallet/abstract_wallet_adapter.rb @@ -188,6 +188,9 @@ def get_addresses(wallet_id, label = nil) # Import a private key to the wallet # + # CAUTION: Wallet Adapter doesn't rescan blockchain for the imported privatekey. + # So the UTXOs in processed blocks are not counted on the Glueby::BlockSyncer. + # # @param [String] wallet_id - The private key is imported to. # @param [Tapyrus::Key] key - The private key that is imported to the wallet. # @param [String?] label - The label to filter the addresses. From 9b5c875e14b775a674c1cde4af7e4d454ec72019 Mon Sep 17 00:00:00 2001 From: Kohei Taniguchi Date: Thu, 31 Jul 2025 14:47:10 +0900 Subject: [PATCH 7/7] fix type --- spec/glueby/wallet_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/glueby/wallet_spec.rb b/spec/glueby/wallet_spec.rb index 8cad366e..3105bcfb 100644 --- a/spec/glueby/wallet_spec.rb +++ b/spec/glueby/wallet_spec.rb @@ -165,7 +165,7 @@ def import_private_key(wallet_id, key, label = nil) end end - describe '#import_prifvate_keys' do + describe '#import_private_key' do let(:key) { Tapyrus::Key.generate } let(:wallet) { Glueby::Wallet.create }