glueby 0.4.0 → 0.4.4
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 +387 -281
- data/glueby.gemspec +33 -32
- data/lib/generators/glueby/contract/templates/initializer.rb.erb +8 -3
- data/lib/generators/glueby/contract/templates/key_table.rb.erb +16 -15
- data/lib/generators/glueby/contract/templates/utxo_table.rb.erb +16 -15
- data/lib/glueby/block_syncer.rb +98 -0
- data/lib/glueby/contract/timestamp/syncer.rb +13 -0
- data/lib/glueby/contract/timestamp.rb +102 -100
- data/lib/glueby/contract/token.rb +244 -243
- data/lib/glueby/fee_provider/tasks.rb +140 -135
- data/lib/glueby/internal/wallet/abstract_wallet_adapter.rb +151 -145
- data/lib/glueby/internal/wallet/active_record/utxo.rb +51 -50
- data/lib/glueby/internal/wallet/active_record_wallet_adapter/syncer.rb +14 -0
- data/lib/glueby/internal/wallet/active_record_wallet_adapter.rb +151 -143
- data/lib/glueby/internal/wallet/tapyrus_core_wallet_adapter.rb +186 -183
- data/lib/glueby/internal/wallet.rb +162 -146
- data/lib/glueby/railtie.rb +10 -0
- data/lib/glueby/version.rb +3 -3
- data/lib/glueby.rb +39 -49
- data/lib/tasks/glueby/block_syncer.rake +29 -0
- data/lib/tasks/glueby/contract/timestamp.rake +39 -61
- data/lib/tasks/glueby/fee_provider.rake +18 -13
- metadata +26 -9
- data/lib/tasks/glueby/contract/block_syncer.rake +0 -36
- data/lib/tasks/glueby/contract/wallet_adapter.rake +0 -42
@@ -1,50 +1,51 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Glueby
|
4
|
-
module Internal
|
5
|
-
class Wallet
|
6
|
-
module AR
|
7
|
-
class Utxo < ::ActiveRecord::Base
|
8
|
-
belongs_to :key
|
9
|
-
|
10
|
-
validates :txid, uniqueness: { scope: :index, case_sensitive: false }
|
11
|
-
|
12
|
-
enum status: { init: 0, broadcasted: 1, finalized: 2 }
|
13
|
-
|
14
|
-
def color_id
|
15
|
-
script = Tapyrus::Script.parse_from_payload(script_pubkey.htb)
|
16
|
-
script.color_id&.to_hex
|
17
|
-
end
|
18
|
-
|
19
|
-
# Delete utxo spent by specified tx.
|
20
|
-
#
|
21
|
-
# @param [Tapyrus::Tx] the spending tx
|
22
|
-
def self.destroy_for_inputs(tx)
|
23
|
-
tx.inputs.each do |input|
|
24
|
-
Utxo.destroy_by(txid: input.out_point.txid, index: input.out_point.index)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
# Create utxo or update utxo for tx outputs
|
29
|
-
# if there is no key for script pubkey in an output, utxo for the output is not created.
|
30
|
-
#
|
31
|
-
# @param [Tapyrus::Tx] tx
|
32
|
-
def self.create_or_update_for_outputs(tx, status: :finalized)
|
33
|
-
tx.outputs.each.with_index do |output, index|
|
34
|
-
key = Key.key_for_output(output)
|
35
|
-
next unless key
|
36
|
-
|
37
|
-
utxo = Utxo.find_or_initialize_by(txid: tx.txid, index: index)
|
38
|
-
utxo.update!(
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Glueby
|
4
|
+
module Internal
|
5
|
+
class Wallet
|
6
|
+
module AR
|
7
|
+
class Utxo < ::ActiveRecord::Base
|
8
|
+
belongs_to :key
|
9
|
+
|
10
|
+
validates :txid, uniqueness: { scope: :index, case_sensitive: false }
|
11
|
+
|
12
|
+
enum status: { init: 0, broadcasted: 1, finalized: 2 }
|
13
|
+
|
14
|
+
def color_id
|
15
|
+
script = Tapyrus::Script.parse_from_payload(script_pubkey.htb)
|
16
|
+
script.color_id&.to_hex
|
17
|
+
end
|
18
|
+
|
19
|
+
# Delete utxo spent by specified tx.
|
20
|
+
#
|
21
|
+
# @param [Tapyrus::Tx] the spending tx
|
22
|
+
def self.destroy_for_inputs(tx)
|
23
|
+
tx.inputs.each do |input|
|
24
|
+
Utxo.destroy_by(txid: input.out_point.txid, index: input.out_point.index)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Create utxo or update utxo for tx outputs
|
29
|
+
# if there is no key for script pubkey in an output, utxo for the output is not created.
|
30
|
+
#
|
31
|
+
# @param [Tapyrus::Tx] tx
|
32
|
+
def self.create_or_update_for_outputs(tx, status: :finalized)
|
33
|
+
tx.outputs.each.with_index do |output, index|
|
34
|
+
key = Key.key_for_output(output)
|
35
|
+
next unless key
|
36
|
+
|
37
|
+
utxo = Utxo.find_or_initialize_by(txid: tx.txid, index: index)
|
38
|
+
utxo.update!(
|
39
|
+
label: key.label,
|
40
|
+
script_pubkey: output.script_pubkey.to_hex,
|
41
|
+
value: output.value,
|
42
|
+
status: status,
|
43
|
+
key: key
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Glueby
|
2
|
+
module Internal
|
3
|
+
class Wallet
|
4
|
+
class ActiveRecordWalletAdapter
|
5
|
+
class Syncer
|
6
|
+
def tx_sync(tx)
|
7
|
+
Glueby::Internal::Wallet::AR::Utxo.destroy_for_inputs(tx)
|
8
|
+
Glueby::Internal::Wallet::AR::Utxo.create_or_update_for_outputs(tx)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -1,143 +1,151 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'securerandom'
|
4
|
-
|
5
|
-
module Glueby
|
6
|
-
module Internal
|
7
|
-
class Wallet
|
8
|
-
# ActiveRecordWalletAdapter
|
9
|
-
#
|
10
|
-
# This class represents a wallet adapter that use Active Record to manage wallet and utxos.
|
11
|
-
# To use this wallet adapter, you should do the following steps:
|
12
|
-
#
|
13
|
-
# (1) Generate migrations for wallets, keys, utxos tables.
|
14
|
-
# The generator `glueby:contract:wallet_adapter` is available for migration.
|
15
|
-
# ```
|
16
|
-
# rails g glueby:contract:wallet_adapter
|
17
|
-
# ```
|
18
|
-
# this generator generates 3 files to create tables used by ActiveRecordWalletAdatper.
|
19
|
-
# then, migrate them
|
20
|
-
# ```
|
21
|
-
# $ rails db:migrate
|
22
|
-
# ```
|
23
|
-
#
|
24
|
-
# (2) Add configuration for activerecord
|
25
|
-
# ```
|
26
|
-
# config = {adapter: 'activerecord', schema: 'http', host: '127.0.0.1', port: 12381, user: 'user', password: 'pass'}
|
27
|
-
# Glueby::Wallet.configure(config)
|
28
|
-
# ```
|
29
|
-
#
|
30
|
-
# (3) Generate wallet and receive address
|
31
|
-
# ```
|
32
|
-
# alice_wallet = Glueby::Wallet.create
|
33
|
-
# address = alice_wallet.internal_wallet.receive_address
|
34
|
-
# ```
|
35
|
-
# `Glueby::Internal::Wallet#receive_address` returns Base58 encoded Tapyrus address like '1CY6TSSARn8rAFD9chCghX5B7j4PKR8S1a'.
|
36
|
-
#
|
37
|
-
# (4) Send TPC to created address and import into wallet.
|
38
|
-
# In general, ActiveRecordWalletAdapter handle only utxos generated by glueby.
|
39
|
-
# So, to start to use wallet, some external utxo should be imported into the wallet first.
|
40
|
-
# This step should be done by external transaction without Glueby::Wallet, such as 'sendtoaddress' or 'generatetoaddress' RPC command of Tapyrus Core
|
41
|
-
# ```
|
42
|
-
# $ tapyrus-cli sendtoaddress 1CY6TSSARn8rAFD9chCghX5B7j4PKR8S1a 1
|
43
|
-
# 1740af9f65ffd8537bdd06ccfa911bf1f4d6833222807d29c99d72b47838917d
|
44
|
-
# ```
|
45
|
-
#
|
46
|
-
# then, import into wallet by rake task `glueby:contract:wallet_adapter:import_tx` or `glueby:contract:wallet_adapter:import_block`
|
47
|
-
# ```
|
48
|
-
# $ rails "glueby:contract:wallet_adapter:import_tx[1740af9f65ffd8537bdd06ccfa911bf1f4d6833222807d29c99d72b47838917d]""
|
49
|
-
# ```
|
50
|
-
#
|
51
|
-
# (5) You are ready to use ActiveRecordWalletAdatper, check `Glueby::Internal::Wallet#list_unspent` or `Glueby::Wallet#balances`
|
52
|
-
# ```
|
53
|
-
# alice_wallet = Glueby::Wallet.create
|
54
|
-
# alice_wallet.balances
|
55
|
-
# ```
|
56
|
-
class ActiveRecordWalletAdapter < AbstractWalletAdapter
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
end
|
77
|
-
|
78
|
-
def
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
end
|
109
|
-
|
110
|
-
def
|
111
|
-
::
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
end
|
123
|
-
|
124
|
-
def
|
125
|
-
wallet = AR::Wallet.find_by(wallet_id: wallet_id)
|
126
|
-
key = wallet.keys.create(purpose: :
|
127
|
-
key.address
|
128
|
-
end
|
129
|
-
|
130
|
-
def
|
131
|
-
wallet = AR::Wallet.find_by(wallet_id: wallet_id)
|
132
|
-
key = wallet.keys.create(purpose: :
|
133
|
-
|
134
|
-
end
|
135
|
-
|
136
|
-
def
|
137
|
-
wallet = AR::Wallet.find_by(wallet_id: wallet_id)
|
138
|
-
wallet.keys.
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
module Glueby
|
6
|
+
module Internal
|
7
|
+
class Wallet
|
8
|
+
# ActiveRecordWalletAdapter
|
9
|
+
#
|
10
|
+
# This class represents a wallet adapter that use Active Record to manage wallet and utxos.
|
11
|
+
# To use this wallet adapter, you should do the following steps:
|
12
|
+
#
|
13
|
+
# (1) Generate migrations for wallets, keys, utxos tables.
|
14
|
+
# The generator `glueby:contract:wallet_adapter` is available for migration.
|
15
|
+
# ```
|
16
|
+
# rails g glueby:contract:wallet_adapter
|
17
|
+
# ```
|
18
|
+
# this generator generates 3 files to create tables used by ActiveRecordWalletAdatper.
|
19
|
+
# then, migrate them
|
20
|
+
# ```
|
21
|
+
# $ rails db:migrate
|
22
|
+
# ```
|
23
|
+
#
|
24
|
+
# (2) Add configuration for activerecord
|
25
|
+
# ```
|
26
|
+
# config = {adapter: 'activerecord', schema: 'http', host: '127.0.0.1', port: 12381, user: 'user', password: 'pass'}
|
27
|
+
# Glueby::Wallet.configure(config)
|
28
|
+
# ```
|
29
|
+
#
|
30
|
+
# (3) Generate wallet and receive address
|
31
|
+
# ```
|
32
|
+
# alice_wallet = Glueby::Wallet.create
|
33
|
+
# address = alice_wallet.internal_wallet.receive_address
|
34
|
+
# ```
|
35
|
+
# `Glueby::Internal::Wallet#receive_address` returns Base58 encoded Tapyrus address like '1CY6TSSARn8rAFD9chCghX5B7j4PKR8S1a'.
|
36
|
+
#
|
37
|
+
# (4) Send TPC to created address and import into wallet.
|
38
|
+
# In general, ActiveRecordWalletAdapter handle only utxos generated by glueby.
|
39
|
+
# So, to start to use wallet, some external utxo should be imported into the wallet first.
|
40
|
+
# This step should be done by external transaction without Glueby::Wallet, such as 'sendtoaddress' or 'generatetoaddress' RPC command of Tapyrus Core
|
41
|
+
# ```
|
42
|
+
# $ tapyrus-cli sendtoaddress 1CY6TSSARn8rAFD9chCghX5B7j4PKR8S1a 1
|
43
|
+
# 1740af9f65ffd8537bdd06ccfa911bf1f4d6833222807d29c99d72b47838917d
|
44
|
+
# ```
|
45
|
+
#
|
46
|
+
# then, import into wallet by rake task `glueby:contract:wallet_adapter:import_tx` or `glueby:contract:wallet_adapter:import_block`
|
47
|
+
# ```
|
48
|
+
# $ rails "glueby:contract:wallet_adapter:import_tx[1740af9f65ffd8537bdd06ccfa911bf1f4d6833222807d29c99d72b47838917d]""
|
49
|
+
# ```
|
50
|
+
#
|
51
|
+
# (5) You are ready to use ActiveRecordWalletAdatper, check `Glueby::Internal::Wallet#list_unspent` or `Glueby::Wallet#balances`
|
52
|
+
# ```
|
53
|
+
# alice_wallet = Glueby::Wallet.create
|
54
|
+
# alice_wallet.balances
|
55
|
+
# ```
|
56
|
+
class ActiveRecordWalletAdapter < AbstractWalletAdapter
|
57
|
+
|
58
|
+
autoload :Syncer, 'glueby/internal/wallet/active_record_wallet_adapter/syncer'
|
59
|
+
|
60
|
+
def create_wallet(wallet_id = nil)
|
61
|
+
wallet_id = SecureRandom.hex(16) unless wallet_id
|
62
|
+
begin
|
63
|
+
AR::Wallet.create!(wallet_id: wallet_id)
|
64
|
+
rescue ActiveRecord::RecordInvalid => _
|
65
|
+
raise Errors::WalletAlreadyCreated, "wallet_id '#{wallet_id}' is already exists"
|
66
|
+
end
|
67
|
+
wallet_id
|
68
|
+
end
|
69
|
+
|
70
|
+
def delete_wallet(wallet_id)
|
71
|
+
AR::Wallet.destroy_by(wallet_id: wallet_id)
|
72
|
+
end
|
73
|
+
|
74
|
+
def load_wallet(wallet_id)
|
75
|
+
raise Errors::WalletNotFound, "Wallet #{wallet_id} does not found" unless AR::Wallet.where(wallet_id: wallet_id).exists?
|
76
|
+
end
|
77
|
+
|
78
|
+
def unload_wallet(wallet_id)
|
79
|
+
end
|
80
|
+
|
81
|
+
def wallets
|
82
|
+
AR::Wallet.all.map(&:wallet_id).sort
|
83
|
+
end
|
84
|
+
|
85
|
+
def balance(wallet_id, only_finalized = true)
|
86
|
+
wallet = AR::Wallet.find_by(wallet_id: wallet_id)
|
87
|
+
utxos = wallet.utxos
|
88
|
+
utxos = utxos.where(status: :finalized) if only_finalized
|
89
|
+
utxos.sum(&:value)
|
90
|
+
end
|
91
|
+
|
92
|
+
def list_unspent(wallet_id, only_finalized = true, label = nil)
|
93
|
+
wallet = AR::Wallet.find_by(wallet_id: wallet_id)
|
94
|
+
utxos = wallet.utxos
|
95
|
+
utxos = utxos.where(status: :finalized) if only_finalized
|
96
|
+
utxos = utxos.where(label: label) if label && (label != :unlabeled)
|
97
|
+
utxos = utxos.where(label: nil) if label == :unlabeled
|
98
|
+
utxos.map do |utxo|
|
99
|
+
{
|
100
|
+
txid: utxo.txid,
|
101
|
+
vout: utxo.index,
|
102
|
+
script_pubkey: utxo.script_pubkey,
|
103
|
+
color_id: utxo.color_id,
|
104
|
+
amount: utxo.value,
|
105
|
+
finalized: utxo.status == 'finalized'
|
106
|
+
}
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def sign_tx(wallet_id, tx, prevtxs = [], sighashtype: Tapyrus::SIGHASH_TYPE[:all])
|
111
|
+
wallet = AR::Wallet.find_by(wallet_id: wallet_id)
|
112
|
+
wallet.sign(tx, prevtxs, sighashtype: sighashtype)
|
113
|
+
end
|
114
|
+
|
115
|
+
def broadcast(wallet_id, tx, &block)
|
116
|
+
::ActiveRecord::Base.transaction do
|
117
|
+
AR::Utxo.destroy_for_inputs(tx)
|
118
|
+
AR::Utxo.create_or_update_for_outputs(tx, status: :broadcasted)
|
119
|
+
block.call(tx) if block
|
120
|
+
Glueby::Internal::RPC.client.sendrawtransaction(tx.to_hex)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def receive_address(wallet_id, label = nil)
|
125
|
+
wallet = AR::Wallet.find_by(wallet_id: wallet_id)
|
126
|
+
key = wallet.keys.create(purpose: :receive, label: label)
|
127
|
+
key.address
|
128
|
+
end
|
129
|
+
|
130
|
+
def change_address(wallet_id)
|
131
|
+
wallet = AR::Wallet.find_by(wallet_id: wallet_id)
|
132
|
+
key = wallet.keys.create(purpose: :change)
|
133
|
+
key.address
|
134
|
+
end
|
135
|
+
|
136
|
+
def create_pubkey(wallet_id)
|
137
|
+
wallet = AR::Wallet.find_by(wallet_id: wallet_id)
|
138
|
+
key = wallet.keys.create(purpose: :receive)
|
139
|
+
Tapyrus::Key.new(pubkey: key.public_key)
|
140
|
+
end
|
141
|
+
|
142
|
+
def get_addresses(wallet_id, label = nil)
|
143
|
+
wallet = AR::Wallet.find_by(wallet_id: wallet_id)
|
144
|
+
keys = wallet.keys
|
145
|
+
keys = keys.where(label: label) if label
|
146
|
+
keys.map(&:address)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -1,183 +1,186 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'securerandom'
|
4
|
-
require 'bigdecimal'
|
5
|
-
|
6
|
-
module Glueby
|
7
|
-
module Internal
|
8
|
-
class Wallet
|
9
|
-
class TapyrusCoreWalletAdapter < AbstractWalletAdapter
|
10
|
-
module Util
|
11
|
-
module_function
|
12
|
-
|
13
|
-
# Convert TPC to tapyrus. 1 TPC is 10**8 tapyrus.
|
14
|
-
# @param [String] tpc
|
15
|
-
# @return [Integer] tapyrus
|
16
|
-
#
|
17
|
-
# Example) "0.00000010" to 10.
|
18
|
-
def tpc_to_tapyrus(tpc)
|
19
|
-
(BigDecimal(tpc) * 10**8).to_i
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
include Glueby::Internal::Wallet::TapyrusCoreWalletAdapter::Util
|
24
|
-
|
25
|
-
WALLET_PREFIX = 'wallet-'
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
#
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
confirmed
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
res.
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'securerandom'
|
4
|
+
require 'bigdecimal'
|
5
|
+
|
6
|
+
module Glueby
|
7
|
+
module Internal
|
8
|
+
class Wallet
|
9
|
+
class TapyrusCoreWalletAdapter < AbstractWalletAdapter
|
10
|
+
module Util
|
11
|
+
module_function
|
12
|
+
|
13
|
+
# Convert TPC to tapyrus. 1 TPC is 10**8 tapyrus.
|
14
|
+
# @param [String] tpc
|
15
|
+
# @return [Integer] tapyrus
|
16
|
+
#
|
17
|
+
# Example) "0.00000010" to 10.
|
18
|
+
def tpc_to_tapyrus(tpc)
|
19
|
+
(BigDecimal(tpc) * 10**8).to_i
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
include Glueby::Internal::Wallet::TapyrusCoreWalletAdapter::Util
|
24
|
+
|
25
|
+
WALLET_PREFIX = 'wallet-'
|
26
|
+
|
27
|
+
RPC_WALLET_ERROR_ERROR_CODE = -4 # Unspecified problem with wallet (key not found etc.)
|
28
|
+
RPC_WALLET_NOT_FOUND_ERROR_CODE = -18 # Invalid wallet specified
|
29
|
+
|
30
|
+
def create_wallet(wallet_id = nil)
|
31
|
+
wallet_id = SecureRandom.hex(16) unless wallet_id
|
32
|
+
begin
|
33
|
+
RPC.client.createwallet(wallet_name(wallet_id))
|
34
|
+
rescue Tapyrus::RPC::Error => ex
|
35
|
+
if ex.rpc_error && ex.rpc_error['code'] == RPC_WALLET_ERROR_ERROR_CODE && /Wallet wallet-#{wallet_id} already exists\./ =~ ex.rpc_error['message']
|
36
|
+
raise Errors::WalletAlreadyCreated, "Wallet #{wallet_id} has been already created."
|
37
|
+
else
|
38
|
+
raise ex
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
wallet_id
|
43
|
+
end
|
44
|
+
|
45
|
+
# On TapyrusCoreWallet, there are no way to delete real wallet via RPC.
|
46
|
+
# So, here just unload.
|
47
|
+
def delete_wallet(wallet_id)
|
48
|
+
unload_wallet(wallet_id)
|
49
|
+
end
|
50
|
+
|
51
|
+
def load_wallet(wallet_id)
|
52
|
+
RPC.client.loadwallet(wallet_name(wallet_id))
|
53
|
+
rescue Tapyrus::RPC::Error => ex
|
54
|
+
if ex.rpc_error && ex.rpc_error['code'] == RPC_WALLET_ERROR_ERROR_CODE && /Duplicate -wallet filename specified/ =~ ex.rpc_error['message']
|
55
|
+
raise Errors::WalletAlreadyLoaded, "Wallet #{wallet_id} has been already loaded."
|
56
|
+
elsif ex.rpc_error && ex.rpc_error['code'] == RPC_WALLET_NOT_FOUND_ERROR_CODE
|
57
|
+
raise Errors::WalletNotFound, "Wallet #{wallet_id} does not found"
|
58
|
+
else
|
59
|
+
raise ex
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def unload_wallet(wallet_id)
|
64
|
+
RPC.client.unloadwallet(wallet_name(wallet_id))
|
65
|
+
end
|
66
|
+
|
67
|
+
def wallets
|
68
|
+
RPC.client.listwallets.map do |wallet_name|
|
69
|
+
match = /\A#{WALLET_PREFIX}(?<wallet_id>[0-9A-Fa-f]{32})\z/.match(wallet_name)
|
70
|
+
next unless match
|
71
|
+
|
72
|
+
match[:wallet_id]
|
73
|
+
end.compact
|
74
|
+
end
|
75
|
+
|
76
|
+
def balance(wallet_id, only_finalized = true)
|
77
|
+
perform_as(wallet_id) do |client|
|
78
|
+
confirmed = tpc_to_tapyrus(client.getbalance)
|
79
|
+
return confirmed if only_finalized
|
80
|
+
|
81
|
+
confirmed + tpc_to_tapyrus(client.getunconfirmedbalance)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def list_unspent(wallet_id, only_finalized = true, label = nil)
|
86
|
+
perform_as(wallet_id) do |client|
|
87
|
+
min_conf = only_finalized ? 1 : 0
|
88
|
+
res = client.listunspent(min_conf)
|
89
|
+
|
90
|
+
res = res.filter { |i| i['label'] == label } if label && (label != :unlabeled)
|
91
|
+
res = res.filter { |i| i['label'] == "" } if label == :unlabeled
|
92
|
+
|
93
|
+
res.map do |i|
|
94
|
+
script = Tapyrus::Script.parse_from_payload(i['scriptPubKey'].htb)
|
95
|
+
color_id = if script.cp2pkh? || script.cp2sh?
|
96
|
+
Tapyrus::Color::ColorIdentifier.parse_from_payload(script.chunks[0].pushed_data).to_hex
|
97
|
+
end
|
98
|
+
{
|
99
|
+
txid: i['txid'],
|
100
|
+
vout: i['vout'],
|
101
|
+
script_pubkey: i['scriptPubKey'],
|
102
|
+
color_id: color_id,
|
103
|
+
amount: tpc_to_tapyrus(i['amount']),
|
104
|
+
finalized: i['confirmations'] != 0
|
105
|
+
}
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def sign_tx(wallet_id, tx, prevtxs = [], sighashtype: Tapyrus::SIGHASH_TYPE[:all])
|
111
|
+
perform_as(wallet_id) do |client|
|
112
|
+
res = client.signrawtransactionwithwallet(tx.to_hex, prevtxs, encode_sighashtype(sighashtype))
|
113
|
+
if res['complete']
|
114
|
+
Tapyrus::Tx.parse_from_payload(res['hex'].htb)
|
115
|
+
else
|
116
|
+
raise res['errors'].to_json
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def broadcast(wallet_id, tx, &block)
|
122
|
+
perform_as(wallet_id) do |client|
|
123
|
+
block.call(tx) if block
|
124
|
+
client.sendrawtransaction(tx.to_hex)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def receive_address(wallet_id, label = nil)
|
129
|
+
perform_as(wallet_id) do |client|
|
130
|
+
client.getnewaddress(label || '')
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def change_address(wallet_id)
|
135
|
+
perform_as(wallet_id) do |client|
|
136
|
+
client.getrawchangeaddress
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def create_pubkey(wallet_id)
|
141
|
+
perform_as(wallet_id) do |client|
|
142
|
+
address = client.getnewaddress('')
|
143
|
+
info = client.getaddressinfo(address)
|
144
|
+
Tapyrus::Key.new(pubkey: info['pubkey'])
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
def perform_as(wallet_id)
|
151
|
+
RPC.perform_as(wallet_name(wallet_id)) do |client|
|
152
|
+
begin
|
153
|
+
yield(client)
|
154
|
+
rescue Tapyrus::RPC::Error => ex
|
155
|
+
if ex.rpc_error && ex.rpc_error['code'] == RPC_WALLET_NOT_FOUND_ERROR_CODE
|
156
|
+
raise Errors::WalletUnloaded, "The wallet #{wallet_id} is unloaded. You should load before use it."
|
157
|
+
else
|
158
|
+
raise ex
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def wallet_name(wallet_id)
|
165
|
+
"#{WALLET_PREFIX}#{wallet_id}"
|
166
|
+
end
|
167
|
+
|
168
|
+
def encode_sighashtype(sighashtype)
|
169
|
+
type = case sighashtype & (~(Tapyrus::SIGHASH_TYPE[:anyonecanpay]))
|
170
|
+
when Tapyrus::SIGHASH_TYPE[:all] then 'ALL'
|
171
|
+
when Tapyrus::SIGHASH_TYPE[:none] then 'NONE'
|
172
|
+
when Tapyrus::SIGHASH_TYPE[:single] then 'SIGNLE'
|
173
|
+
else
|
174
|
+
raise Errors::InvalidSighashType, "Invalid sighash type '#{sighashtype}'"
|
175
|
+
end
|
176
|
+
|
177
|
+
if sighashtype & Tapyrus::SIGHASH_TYPE[:anyonecanpay] == 0x80
|
178
|
+
type += '|ANYONECANPAY'
|
179
|
+
end
|
180
|
+
|
181
|
+
type
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|