glueby 0.3.0 → 0.4.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 +4 -4
- data/.github/workflows/ruby.yml +35 -0
- data/Gemfile +1 -0
- data/README.md +227 -16
- data/glueby.gemspec +3 -2
- data/lib/generators/glueby/contract/reissuable_token_generator.rb +26 -0
- data/lib/generators/glueby/contract/templates/initializer.rb.erb +7 -2
- data/lib/generators/glueby/contract/templates/key_table.rb.erb +4 -3
- data/lib/generators/glueby/contract/templates/reissuable_token_table.rb.erb +10 -0
- data/lib/generators/glueby/contract/templates/system_information_table.rb.erb +2 -2
- data/lib/generators/glueby/contract/templates/timestamp_table.rb.erb +1 -1
- data/lib/generators/glueby/contract/templates/utxo_table.rb.erb +3 -2
- data/lib/generators/glueby/contract/templates/wallet_table.rb.erb +2 -2
- data/lib/glueby/block_syncer.rb +98 -0
- data/lib/glueby/configuration.rb +62 -0
- data/lib/glueby/contract/active_record/reissuable_token.rb +26 -0
- data/lib/glueby/contract/active_record.rb +1 -0
- data/lib/glueby/contract/fee_estimator.rb +38 -0
- data/lib/glueby/contract/payment.rb +4 -4
- data/lib/glueby/contract/timestamp/syncer.rb +13 -0
- data/lib/glueby/contract/timestamp.rb +8 -6
- data/lib/glueby/contract/token.rb +70 -22
- data/lib/glueby/contract/tx_builder.rb +22 -19
- data/lib/glueby/contract.rb +2 -2
- data/lib/glueby/fee_provider/tasks.rb +141 -0
- data/lib/glueby/fee_provider.rb +73 -0
- data/lib/glueby/generator/migrate_generator.rb +1 -1
- data/lib/glueby/internal/wallet/abstract_wallet_adapter.rb +25 -6
- data/lib/glueby/internal/wallet/active_record/utxo.rb +1 -0
- data/lib/glueby/internal/wallet/active_record/wallet.rb +15 -5
- data/lib/glueby/internal/wallet/active_record_wallet_adapter/syncer.rb +14 -0
- data/lib/glueby/internal/wallet/active_record_wallet_adapter.rb +25 -8
- data/lib/glueby/internal/wallet/errors.rb +3 -0
- data/lib/glueby/internal/wallet/tapyrus_core_wallet_adapter.rb +42 -14
- data/lib/glueby/internal/wallet.rb +56 -13
- data/lib/glueby/railtie.rb +10 -0
- data/lib/glueby/version.rb +1 -1
- data/lib/glueby/wallet.rb +3 -2
- data/lib/glueby.rb +27 -12
- data/lib/tasks/glueby/block_syncer.rake +29 -0
- data/lib/tasks/glueby/contract/timestamp.rake +4 -26
- data/lib/tasks/glueby/fee_provider.rake +18 -0
- metadata +40 -16
- data/.travis.yml +0 -7
- data/lib/glueby/contract/fee_provider.rb +0 -21
- data/lib/tasks/glueby/contract/block_syncer.rake +0 -36
- data/lib/tasks/glueby/contract/wallet_adapter.rake +0 -42
@@ -1,15 +1,16 @@
|
|
1
1
|
class CreateUtxo < ActiveRecord::Migration<%= migration_version %>
|
2
2
|
def change
|
3
|
-
create_table :
|
3
|
+
create_table :glueby_utxos<%= table_options %> do |t|
|
4
4
|
t.string :txid
|
5
5
|
t.integer :index
|
6
6
|
t.bigint :value
|
7
7
|
t.string :script_pubkey
|
8
|
+
t.string :label, index: true
|
8
9
|
t.integer :status
|
9
10
|
t.belongs_to :key, null: true
|
10
11
|
t.timestamps
|
11
12
|
end
|
12
13
|
|
13
|
-
add_index :
|
14
|
+
add_index :glueby_utxos, [:txid, :index], unique: true
|
14
15
|
end
|
15
16
|
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
class CreateWallet < ActiveRecord::Migration<%= migration_version %>
|
2
2
|
def change
|
3
|
-
create_table :
|
3
|
+
create_table :glueby_wallets<%= table_options %> do |t|
|
4
4
|
t.string :wallet_id
|
5
5
|
t.timestamps
|
6
6
|
end
|
7
7
|
|
8
|
-
add_index :
|
8
|
+
add_index :glueby_wallets, [:wallet_id], unique: true
|
9
9
|
end
|
10
10
|
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Glueby
|
2
|
+
# You can use BlockSyncer when you need to synchronize the state of
|
3
|
+
# an application with the state of a blockchain. When BlockSyncer
|
4
|
+
# detects the generation of a new block, it executes the registered
|
5
|
+
# syncer code on a block-by-block or transaction-by-transaction basis.
|
6
|
+
# By using this, an application can detect that the issued transaction
|
7
|
+
# has been captured in blocks, receive a new remittance, and so on.
|
8
|
+
#
|
9
|
+
# # Syncer logic registration
|
10
|
+
#
|
11
|
+
# For registration, create a class that implements the method that performs
|
12
|
+
# synchronization processing and registers it in BlockSyncer. Implement
|
13
|
+
# methods with the following name in that class.
|
14
|
+
#
|
15
|
+
# Method name | Arguments | Call conditions
|
16
|
+
# ------------------ | --------------------- | ------------------------------
|
17
|
+
# block_sync (block) | block: Tapyrus::Block | When a new block is created
|
18
|
+
# block_tx (tx) | tx: Tapyrus::Tx | When a new block is created, it is executed for each tx contained in that block.
|
19
|
+
#
|
20
|
+
# @example Register a synchronous logic
|
21
|
+
# class Syncer
|
22
|
+
# def block_sync (block)
|
23
|
+
# # sync a block
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# def tx_sync (tx)
|
27
|
+
# # sync a tx
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
# BlockSyncer.register_syncer(Syncer)
|
31
|
+
#
|
32
|
+
# @example Unregister the synchronous logic
|
33
|
+
# BlockSyncer.unregister_syncer(Syncer)
|
34
|
+
#
|
35
|
+
# # Run BlockSyncer
|
36
|
+
#
|
37
|
+
# Run the `glueby: block_syncer: start` rake task periodically with a program
|
38
|
+
# for periodic execution such as cron. If it detects the generation of a new
|
39
|
+
# block when it is executed, the synchronization process will be executed.
|
40
|
+
# Determine the execution interval according to the requirements of the application.
|
41
|
+
class BlockSyncer
|
42
|
+
# @!attribute [r] height
|
43
|
+
# @return [Integer] The block height to be synced
|
44
|
+
attr_reader :height
|
45
|
+
|
46
|
+
class << self
|
47
|
+
# @!attribute r syncers
|
48
|
+
# @return [Array<Class>] The syncer classes that is registered
|
49
|
+
attr_reader :syncers
|
50
|
+
|
51
|
+
# Register syncer class
|
52
|
+
# @param [Class] syncer The syncer to be registered.
|
53
|
+
def register_syncer(syncer)
|
54
|
+
@syncers ||= []
|
55
|
+
@syncers << syncer
|
56
|
+
end
|
57
|
+
|
58
|
+
# Unregister syncer class
|
59
|
+
# @param [Class] syncer The syncer to be unregistered.
|
60
|
+
def unregister_syncer(syncer)
|
61
|
+
@syncers ||= []
|
62
|
+
@syncers.delete(syncer)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# @param [Integer] height The block height to be synced in the instance
|
67
|
+
def initialize(height)
|
68
|
+
@height = height
|
69
|
+
end
|
70
|
+
|
71
|
+
# Run a block synchronization
|
72
|
+
def run
|
73
|
+
return if self.class.syncers.nil?
|
74
|
+
|
75
|
+
self.class.syncers.each do |syncer|
|
76
|
+
instance = syncer.new
|
77
|
+
instance.block_sync(block) if instance.respond_to?(:block_sync)
|
78
|
+
|
79
|
+
if instance.respond_to?(:tx_sync)
|
80
|
+
block.transactions.each { |tx| instance.tx_sync(tx) }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def block
|
88
|
+
@block ||= begin
|
89
|
+
block = Glueby::Internal::RPC.client.getblock(block_hash, 0)
|
90
|
+
Tapyrus::Block.parse_from_payload(block.htb)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def block_hash
|
95
|
+
@block_hash ||= Glueby::Internal::RPC.client.getblockhash(height)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Glueby
|
2
|
+
# Global configuration on runtime
|
3
|
+
#
|
4
|
+
# The global configuration treats configurations for all modules in Glueby.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# Glueby.configure do |config|
|
8
|
+
# config.wallet_adapter = :activerecord
|
9
|
+
# config.rpc_config = { schema: 'http', host: '127.0.0.1', port: 12381, user: 'user', password: 'pass' }
|
10
|
+
# end
|
11
|
+
class Configuration
|
12
|
+
|
13
|
+
attr_reader :fee_provider_bears
|
14
|
+
alias_method :fee_provider_bears?, :fee_provider_bears
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@fee_provider_bears = false
|
18
|
+
end
|
19
|
+
|
20
|
+
# Specify wallet adapter.
|
21
|
+
# @param [Symbol] adapter - The adapter type :activerecord or :core is currently supported.
|
22
|
+
def wallet_adapter=(adapter)
|
23
|
+
case adapter
|
24
|
+
when :core
|
25
|
+
Glueby::Internal::Wallet.wallet_adapter = Glueby::Internal::Wallet::TapyrusCoreWalletAdapter.new
|
26
|
+
when :activerecord
|
27
|
+
Glueby::Internal::Wallet.wallet_adapter = Glueby::Internal::Wallet::ActiveRecordWalletAdapter.new
|
28
|
+
else
|
29
|
+
raise 'Not implemented'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Specify connection information to Tapyrus Core RPC.
|
34
|
+
# @param [Hash] config
|
35
|
+
# @option config [String] :schema - http or https
|
36
|
+
# @option config [String] :host - The host of the RPC endpoint
|
37
|
+
# @option config [Integer] :port - The port of the RPC endpoint
|
38
|
+
# @Option config [String] :user - The user for Basic Authorization of the RPC endpoint
|
39
|
+
# @Option config [String] :password - The password for Basic Authorization of the RPC endpoint
|
40
|
+
def rpc_config=(config)
|
41
|
+
Glueby::Internal::RPC.configure(config)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Use This to enable to use FeeProvider to supply inputs for fees on each transaction that is created on Glueby.
|
45
|
+
def fee_provider_bears!
|
46
|
+
@fee_provider_bears = true
|
47
|
+
end
|
48
|
+
|
49
|
+
# Use This to disable to use FeeProvider
|
50
|
+
def disable_fee_provider_bears!
|
51
|
+
@fee_provider_bears = false
|
52
|
+
end
|
53
|
+
|
54
|
+
# Specify FeeProvider configuration.
|
55
|
+
# @param [Hash] config
|
56
|
+
# @option config [Integer] :fixed_fee - The fee that Fee Provider pays on each transaction.
|
57
|
+
# @option config [Integer] :utxo_pool_size - Fee Provider tries to keep the number of utxo in utxo pool as this size using `glueby:fee_provider:manage_utxo_pool` rake task
|
58
|
+
def fee_provider_config=(config)
|
59
|
+
FeeProvider.configure(config)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Glueby
|
2
|
+
module Contract
|
3
|
+
module AR
|
4
|
+
class ReissuableToken < ::ActiveRecord::Base
|
5
|
+
|
6
|
+
# Get the script_pubkey corresponding to the color_id in Tapyrus::Script format
|
7
|
+
# @param [String] color_id
|
8
|
+
# @return [Tapyrus::Script]
|
9
|
+
def self.script_pubkey(color_id)
|
10
|
+
script_pubkey = Glueby::Contract::AR::ReissuableToken.where(color_id: color_id).pluck(:script_pubkey).first
|
11
|
+
if script_pubkey
|
12
|
+
Tapyrus::Script.parse_from_payload(script_pubkey.htb)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Check if the color_id is already stored
|
17
|
+
# @param [String] color_id
|
18
|
+
# @return [Boolean]
|
19
|
+
def self.saved?(color_id)
|
20
|
+
Glueby::Contract::AR::ReissuableToken.where(color_id: color_id).exists?
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Glueby
|
2
|
+
module Contract
|
3
|
+
module FeeEstimator
|
4
|
+
# @param [Tapyrus::Tx] tx - The target tx
|
5
|
+
# @return fee by tapyrus(not TPC).
|
6
|
+
def fee(tx)
|
7
|
+
return 0 if Glueby.configuration.fee_provider_bears?
|
8
|
+
estimate_fee(tx)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
# @private
|
14
|
+
# @abstract Override in subclasses. This is would be implemented an actual estimation logic.
|
15
|
+
# @param [Tapyrus::Tx] tx - The target tx
|
16
|
+
# @return fee by tapyrus(not TPC).
|
17
|
+
def estimate_fee(tx)
|
18
|
+
raise NotImplementedError
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class FixedFeeEstimator
|
23
|
+
include FeeEstimator
|
24
|
+
|
25
|
+
def initialize(fixed_fee: 10_000)
|
26
|
+
@fixed_fee = fixed_fee
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# @private
|
32
|
+
# @return fee by tapyrus(not TPC).
|
33
|
+
def estimate_fee(_tx)
|
34
|
+
@fixed_fee
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -32,11 +32,11 @@ module Glueby
|
|
32
32
|
extend Glueby::Contract::TxBuilder
|
33
33
|
|
34
34
|
class << self
|
35
|
-
def transfer(sender:, receiver_address:, amount:,
|
35
|
+
def transfer(sender:, receiver_address:, amount:, fee_estimator: FixedFeeEstimator.new)
|
36
36
|
raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?
|
37
37
|
|
38
38
|
tx = Tapyrus::Tx.new
|
39
|
-
dummy_fee =
|
39
|
+
dummy_fee = fee_estimator.fee(dummy_tx(tx))
|
40
40
|
|
41
41
|
sum, outputs = sender.internal_wallet.collect_uncolored_outputs(dummy_fee + amount)
|
42
42
|
fill_input(tx, outputs)
|
@@ -44,13 +44,13 @@ module Glueby
|
|
44
44
|
receiver_script = Tapyrus::Script.parse_from_addr(receiver_address)
|
45
45
|
tx.outputs << Tapyrus::TxOut.new(value: amount, script_pubkey: receiver_script)
|
46
46
|
|
47
|
-
fee =
|
47
|
+
fee = fee_estimator.fee(tx)
|
48
48
|
|
49
49
|
fill_change_tpc(tx, sender, sum - fee - amount)
|
50
50
|
|
51
51
|
tx = sender.internal_wallet.sign_tx(tx)
|
52
52
|
|
53
|
-
|
53
|
+
sender.internal_wallet.broadcast(tx)
|
54
54
|
end
|
55
55
|
end
|
56
56
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Glueby
|
2
|
+
module Contract
|
3
|
+
class Timestamp
|
4
|
+
class Syncer
|
5
|
+
def block_sync(block)
|
6
|
+
Glueby::Contract::AR::Timestamp
|
7
|
+
.where(txid: block.transactions.map(&:txid), status: :unconfirmed)
|
8
|
+
.update_all(status: :confirmed)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -10,15 +10,17 @@ module Glueby
|
|
10
10
|
class Timestamp
|
11
11
|
include Glueby::Contract::TxBuilder
|
12
12
|
|
13
|
+
autoload :Syncer, 'glueby/contract/timestamp/syncer'
|
14
|
+
|
13
15
|
module Util
|
14
16
|
include Glueby::Internal::Wallet::TapyrusCoreWalletAdapter::Util
|
15
17
|
module_function
|
16
18
|
|
17
|
-
def create_tx(wallet, prefix, data,
|
19
|
+
def create_tx(wallet, prefix, data, fee_estimator)
|
18
20
|
tx = Tapyrus::Tx.new
|
19
21
|
tx.outputs << Tapyrus::TxOut.new(value: 0, script_pubkey: create_script(prefix, data))
|
20
22
|
|
21
|
-
fee =
|
23
|
+
fee = fee_estimator.fee(dummy_tx(tx))
|
22
24
|
sum, outputs = wallet.internal_wallet.collect_uncolored_outputs(fee)
|
23
25
|
fill_input(tx, outputs)
|
24
26
|
|
@@ -50,7 +52,7 @@ module Glueby
|
|
50
52
|
|
51
53
|
# @param [String] content Data to be hashed and stored in blockchain.
|
52
54
|
# @param [String] prefix prefix of op_return data
|
53
|
-
# @param [Glueby::Contract::
|
55
|
+
# @param [Glueby::Contract::FeeEstimator] fee_estimator
|
54
56
|
# @param [Symbol] digest type which select of:
|
55
57
|
# - :sha256
|
56
58
|
# - :double_sha256
|
@@ -60,13 +62,13 @@ module Glueby
|
|
60
62
|
wallet:,
|
61
63
|
content:,
|
62
64
|
prefix: '',
|
63
|
-
|
65
|
+
fee_estimator: Glueby::Contract::FixedFeeEstimator.new,
|
64
66
|
digest: :sha256
|
65
67
|
)
|
66
68
|
@wallet = wallet
|
67
69
|
@content = content
|
68
70
|
@prefix = prefix
|
69
|
-
@
|
71
|
+
@fee_estimator = fee_estimator
|
70
72
|
@digest = digest
|
71
73
|
end
|
72
74
|
|
@@ -77,7 +79,7 @@ module Glueby
|
|
77
79
|
def save!
|
78
80
|
raise Glueby::Contract::Errors::TxAlreadyBroadcasted if @txid
|
79
81
|
|
80
|
-
@tx = create_tx(@wallet, @prefix, digest_content, @
|
82
|
+
@tx = create_tx(@wallet, @prefix, digest_content, @fee_estimator)
|
81
83
|
@txid = @wallet.internal_wallet.broadcast(@tx)
|
82
84
|
end
|
83
85
|
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
require 'active_record'
|
2
3
|
|
3
4
|
module Glueby
|
4
5
|
module Contract
|
@@ -55,14 +56,14 @@ module Glueby
|
|
55
56
|
# @param issuer [Glueby::Wallet]
|
56
57
|
# @param token_type [TokenTypes]
|
57
58
|
# @param amount [Integer]
|
58
|
-
# @return [
|
59
|
+
# @return [Array<token, Array<tx>>] Tuple of tx array and token object
|
59
60
|
# @raise [InsufficientFunds] if wallet does not have enough TPC to send transaction.
|
60
61
|
# @raise [InvalidAmount] if amount is not positive integer.
|
61
62
|
# @raise [UnspportedTokenType] if token is not supported.
|
62
63
|
def issue!(issuer:, token_type: Tapyrus::Color::TokenTypes::REISSUABLE, amount: 1)
|
63
64
|
raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?
|
64
65
|
|
65
|
-
txs, color_id
|
66
|
+
txs, color_id = case token_type
|
66
67
|
when Tapyrus::Color::TokenTypes::REISSUABLE
|
67
68
|
issue_reissuable_token(issuer: issuer, amount: amount)
|
68
69
|
when Tapyrus::Color::TokenTypes::NON_REISSUABLE
|
@@ -72,33 +73,44 @@ module Glueby
|
|
72
73
|
else
|
73
74
|
raise Glueby::Contract::Errors::UnsupportedTokenType
|
74
75
|
end
|
75
|
-
|
76
|
-
new(color_id: color_id,
|
76
|
+
|
77
|
+
[new(color_id: color_id), txs]
|
77
78
|
end
|
78
79
|
|
79
80
|
private
|
80
81
|
|
81
82
|
def issue_reissuable_token(issuer:, amount:)
|
82
|
-
|
83
|
-
funding_tx = create_funding_tx(wallet: issuer, amount: estimated_fee)
|
84
|
-
tx = create_issue_tx_for_reissuable_token(funding_tx: funding_tx, issuer: issuer, amount: amount)
|
83
|
+
funding_tx = create_funding_tx(wallet: issuer)
|
85
84
|
script_pubkey = funding_tx.outputs.first.script_pubkey
|
86
85
|
color_id = Tapyrus::Color::ColorIdentifier.reissuable(script_pubkey)
|
87
|
-
|
86
|
+
|
87
|
+
ActiveRecord::Base.transaction(joinable: false, requires_new: true) do
|
88
|
+
# Store the script_pubkey for reissue the token.
|
89
|
+
Glueby::Contract::AR::ReissuableToken.create!(color_id: color_id.to_hex, script_pubkey: script_pubkey.to_hex)
|
90
|
+
|
91
|
+
funding_tx = issuer.internal_wallet.broadcast(funding_tx)
|
92
|
+
tx = create_issue_tx_for_reissuable_token(funding_tx: funding_tx, issuer: issuer, amount: amount)
|
93
|
+
tx = issuer.internal_wallet.broadcast(tx)
|
94
|
+
[[funding_tx, tx], color_id]
|
95
|
+
end
|
88
96
|
end
|
89
97
|
|
90
98
|
def issue_non_reissuable_token(issuer:, amount:)
|
91
99
|
tx = create_issue_tx_for_non_reissuable_token(issuer: issuer, amount: amount)
|
100
|
+
tx = issuer.internal_wallet.broadcast(tx)
|
101
|
+
|
92
102
|
out_point = tx.inputs.first.out_point
|
93
103
|
color_id = Tapyrus::Color::ColorIdentifier.non_reissuable(out_point)
|
94
|
-
[[tx], color_id
|
104
|
+
[[tx], color_id]
|
95
105
|
end
|
96
106
|
|
97
107
|
def issue_nft_token(issuer:)
|
98
108
|
tx = create_issue_tx_for_nft_token(issuer: issuer)
|
109
|
+
tx = issuer.internal_wallet.broadcast(tx)
|
110
|
+
|
99
111
|
out_point = tx.inputs.first.out_point
|
100
112
|
color_id = Tapyrus::Color::ColorIdentifier.nft(out_point)
|
101
|
-
[[tx], color_id
|
113
|
+
[[tx], color_id]
|
102
114
|
end
|
103
115
|
end
|
104
116
|
|
@@ -108,6 +120,7 @@ module Glueby
|
|
108
120
|
# A wallet can issue the token only when it is REISSUABLE token.
|
109
121
|
# @param issuer [Glueby::Wallet]
|
110
122
|
# @param amount [Integer]
|
123
|
+
# @return [Array<String, tx>] Tuple of color_id and tx object
|
111
124
|
# @raise [InsufficientFunds] if wallet does not have enough TPC to send transaction.
|
112
125
|
# @raise [InvalidAmount] if amount is not positive integer.
|
113
126
|
# @raise [InvalidTokenType] if token is not reissuable.
|
@@ -115,12 +128,17 @@ module Glueby
|
|
115
128
|
def reissue!(issuer:, amount:)
|
116
129
|
raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?
|
117
130
|
raise Glueby::Contract::Errors::InvalidTokenType unless token_type == Tapyrus::Color::TokenTypes::REISSUABLE
|
118
|
-
raise Glueby::Contract::Errors::UnknownScriptPubkey unless @script_pubkey
|
119
131
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
132
|
+
if validate_reissuer(wallet: issuer)
|
133
|
+
funding_tx = create_funding_tx(wallet: issuer, script: @script_pubkey)
|
134
|
+
funding_tx = issuer.internal_wallet.broadcast(funding_tx)
|
135
|
+
tx = create_reissue_tx(funding_tx: funding_tx, issuer: issuer, amount: amount, color_id: color_id)
|
136
|
+
tx = issuer.internal_wallet.broadcast(tx)
|
137
|
+
|
138
|
+
[color_id, tx]
|
139
|
+
else
|
140
|
+
raise Glueby::Contract::Errors::UnknownScriptPubkey
|
141
|
+
end
|
124
142
|
end
|
125
143
|
|
126
144
|
# Send the token to other wallet
|
@@ -128,7 +146,7 @@ module Glueby
|
|
128
146
|
# @param sender [Glueby::Wallet] wallet to send this token
|
129
147
|
# @param receiver_address [String] address to receive this token
|
130
148
|
# @param amount [Integer]
|
131
|
-
# @return [
|
149
|
+
# @return [Array<String, tx>] Tuple of color_id and tx object
|
132
150
|
# @raise [InsufficientFunds] if wallet does not have enough TPC to send transaction.
|
133
151
|
# @raise [InsufficientTokens] if wallet does not have enough token to send.
|
134
152
|
# @raise [InvalidAmount] if amount is not positive integer.
|
@@ -137,6 +155,7 @@ module Glueby
|
|
137
155
|
|
138
156
|
tx = create_transfer_tx(color_id: color_id, sender: sender, receiver_address: receiver_address, amount: amount)
|
139
157
|
sender.internal_wallet.broadcast(tx)
|
158
|
+
[color_id, tx]
|
140
159
|
end
|
141
160
|
|
142
161
|
# Burn token
|
@@ -170,12 +189,19 @@ module Glueby
|
|
170
189
|
color_id.type
|
171
190
|
end
|
172
191
|
|
192
|
+
# Return the script_pubkey of the token from ActiveRecord
|
193
|
+
# @return [String] script_pubkey
|
194
|
+
def script_pubkey
|
195
|
+
@script_pubkey ||= Glueby::Contract::AR::ReissuableToken.script_pubkey(@color_id.to_hex)
|
196
|
+
end
|
197
|
+
|
173
198
|
# Return serialized payload
|
174
199
|
# @return [String] payload
|
175
200
|
def to_payload
|
176
201
|
payload = +''
|
177
202
|
payload << @color_id.to_payload
|
178
|
-
payload << @script_pubkey.to_payload if
|
203
|
+
payload << @script_pubkey.to_payload if script_pubkey
|
204
|
+
payload
|
179
205
|
end
|
180
206
|
|
181
207
|
# Restore token from payload
|
@@ -184,14 +210,36 @@ module Glueby
|
|
184
210
|
def self.parse_from_payload(payload)
|
185
211
|
color_id, script_pubkey = payload.unpack('a33a*')
|
186
212
|
color_id = Tapyrus::Color::ColorIdentifier.parse_from_payload(color_id)
|
187
|
-
|
188
|
-
|
213
|
+
if color_id.type == Tapyrus::Color::TokenTypes::REISSUABLE
|
214
|
+
raise ArgumentError, 'script_pubkey should not be empty' if script_pubkey.empty?
|
215
|
+
script_pubkey = Tapyrus::Script.parse_from_payload(script_pubkey)
|
216
|
+
Glueby::Contract::AR::ReissuableToken.create!(color_id: color_id.to_hex, script_pubkey: script_pubkey.to_hex)
|
217
|
+
end
|
218
|
+
new(color_id: color_id)
|
189
219
|
end
|
190
220
|
|
191
|
-
|
221
|
+
# Generate Token Instance
|
222
|
+
# @param color_id [String]
|
223
|
+
def initialize(color_id:)
|
192
224
|
@color_id = color_id
|
193
|
-
|
225
|
+
end
|
226
|
+
|
227
|
+
private
|
228
|
+
|
229
|
+
# Verify that wallet is the issuer of the reissuable token
|
230
|
+
# reutrn [Boolean]
|
231
|
+
def validate_reissuer(wallet:)
|
232
|
+
addresses = wallet.internal_wallet.get_addresses
|
233
|
+
addresses.each do |address|
|
234
|
+
decoded_address = Tapyrus.decode_base58_address(address)
|
235
|
+
pubkey_hash_from_address = decoded_address[0]
|
236
|
+
pubkey_hash_from_script = Tapyrus::Script.parse_from_payload(script_pubkey.chunks[2])
|
237
|
+
if pubkey_hash_from_address == pubkey_hash_from_script.to_s
|
238
|
+
return true
|
239
|
+
end
|
240
|
+
end
|
241
|
+
false
|
194
242
|
end
|
195
243
|
end
|
196
244
|
end
|
197
|
-
end
|
245
|
+
end
|