Class: BTC::AssetTransactionBuilder
- Inherits:
-
Object
- Object
- BTC::AssetTransactionBuilder
- Defined in:
- lib/btcruby/open_assets/asset_transaction_builder.rb,
lib/btcruby/open_assets/asset_transaction_builder/errors.rb,
lib/btcruby/open_assets/asset_transaction_builder/result.rb,
lib/btcruby/open_assets/asset_transaction_builder/provider.rb
Overview
TODO:
- provide Asset ID(s) as input, or raw AssetTransactionInput objects
- provide raw unspents to pay mining fees
- provide asset change address
- provide btc change address
- provide issuance API, transfer API, payment API (plain btc outputs) Use TransactionBuilder internally.
Defined Under Namespace
Modules: Provider Classes: Error, InsufficientFundsError, MissingChangeAddressError, MissingUnspentOutputsError, Result
Instance Attribute Summary collapse
-
#asset_change_address ⇒ Object
Must be a subclass of a BTC::AssetAddress.
-
#asset_provider ⇒ Object
Provider of the pure bitcoin unspent outputs adopting AssetTransactionBuilder::Provider.
-
#asset_unspent_outputs ⇒ Object
Enumerable yielding BTC::AssetTransactionOutput instances with valid
transaction_hashandindexproperties. -
#bitcoin_change_address ⇒ Object
Must be a subclass of a BTC::BitcoinPaymentAddress.
-
#bitcoin_provider ⇒ Object
Provider of the pure bitcoin unspent outputs adopting TransactionBuilder::Provider.
-
#bitcoin_unspent_outputs ⇒ Object
Enumerable yielding BTC::TransactionOutput instances with valid
transaction_hashandindexproperties. -
#fee_rate ⇒ Object
Miner's fee per kilobyte (1000 bytes).
-
#metadata ⇒ Object
Metadata to embed in the marker output.
-
#network ⇒ Object
Network to validate provided addresses against.
-
#signer ⇒ Object
TransactionBuilder::Signer for all the inputs (bitcoins and assets).
Instance Method Summary collapse
- #build ⇒ Object
-
#initialize ⇒ AssetTransactionBuilder
constructor
A new instance of AssetTransactionBuilder.
- #internal_bitcoin_provider ⇒ Object
-
#issue_asset(source_script: nil, source_output: nil, amount: nil, script: nil, address: nil) ⇒ Object
Adds an issuance of some assets.
-
#issuing_asset_id ⇒ Object
Helpers.
- #make_asset_transaction_output(asset_id: nil, amount: nil, script: nil) ⇒ Object
- #make_output_for_asset_script(script) ⇒ Object
- #make_transaction_builder ⇒ Object
- #make_transaction_for_issues ⇒ Object
-
#send_bitcoin(output: nil, amount: nil, script: nil, address: nil) ⇒ Object
Adds a normal payment output.
-
#transfer_asset(asset_id: nil, amount: nil, script: nil, address: nil, provider: nil, unspent_outputs: nil, change_address: nil) ⇒ Object
Adds a transfer output.
- #validate_asset_change_address! ⇒ Object
-
#validate_bitcoin_change_address! ⇒ Object
Validation Methods.
Constructor Details
#initialize ⇒ AssetTransactionBuilder
Returns a new instance of AssetTransactionBuilder.
51 52 |
# File 'lib/btcruby/open_assets/asset_transaction_builder.rb', line 51 def initialize end |
Instance Attribute Details
#asset_change_address ⇒ Object
Must be a subclass of a BTC::AssetAddress
23 24 25 |
# File 'lib/btcruby/open_assets/asset_transaction_builder.rb', line 23 def asset_change_address @asset_change_address end |
#asset_provider ⇒ Object
Provider of the pure bitcoin unspent outputs adopting AssetTransactionBuilder::Provider.
If not specified, asset_unspent_outputs must be provided.
38 39 40 |
# File 'lib/btcruby/open_assets/asset_transaction_builder.rb', line 38 def asset_provider @asset_provider end |
#asset_unspent_outputs ⇒ Object
Enumerable yielding BTC::AssetTransactionOutput instances with valid transaction_hash and index properties.
30 31 32 |
# File 'lib/btcruby/open_assets/asset_transaction_builder.rb', line 30 def asset_unspent_outputs @asset_unspent_outputs end |
#bitcoin_change_address ⇒ Object
Must be a subclass of a BTC::BitcoinPaymentAddress
20 21 22 |
# File 'lib/btcruby/open_assets/asset_transaction_builder.rb', line 20 def bitcoin_change_address @bitcoin_change_address end |
#bitcoin_provider ⇒ Object
Provider of the pure bitcoin unspent outputs adopting TransactionBuilder::Provider.
If not specified, bitcoin_unspent_outputs must be provided.
34 35 36 |
# File 'lib/btcruby/open_assets/asset_transaction_builder.rb', line 34 def bitcoin_provider @bitcoin_provider end |
#bitcoin_unspent_outputs ⇒ Object
Enumerable yielding BTC::TransactionOutput instances with valid transaction_hash and index properties.
If not specified, bitcoin_unspent_outputs_provider is used if possible.
27 28 29 |
# File 'lib/btcruby/open_assets/asset_transaction_builder.rb', line 27 def bitcoin_unspent_outputs @bitcoin_unspent_outputs end |
#fee_rate ⇒ Object
Miner's fee per kilobyte (1000 bytes). Default is Transaction::DEFAULT_FEE_RATE
46 47 48 |
# File 'lib/btcruby/open_assets/asset_transaction_builder.rb', line 46 def fee_rate @fee_rate end |
#metadata ⇒ Object
Metadata to embed in the marker output. Default is nil (empty string).
49 50 51 |
# File 'lib/btcruby/open_assets/asset_transaction_builder.rb', line 49 def end |
#network ⇒ Object
Network to validate provided addresses against.
Default value is Network.default.
17 18 19 |
# File 'lib/btcruby/open_assets/asset_transaction_builder.rb', line 17 def network @network end |
#signer ⇒ Object
TransactionBuilder::Signer for all the inputs (bitcoins and assets).
If not provided, inputs will be left unsigned and result.unsigned_input_indexes will contain indexes of these inputs.
42 43 44 |
# File 'lib/btcruby/open_assets/asset_transaction_builder.rb', line 42 def signer @signer end |
Instance Method Details
#build ⇒ Object
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 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 |
# File 'lib/btcruby/open_assets/asset_transaction_builder.rb', line 137 def build validate_bitcoin_change_address! validate_asset_change_address! result = Result.new # We don't count assets_cost because outputs of these txs # will be consumed in the next transaction. Only add the fees. if self.issuing_asset_script issuing_tx, unsigned_input_indexes = make_transaction_for_issues result.fee = issuing_tx.fee result.transactions << issuing_tx result.unsigned_input_indexes << unsigned_input_indexes self.issuing_asset_output = AssetTransactionOutput.new(transaction_output: issuing_tx.outputs.first, verified: true) end # Prepare the target transaction txbuilder = make_transaction_builder if result.transactions.size > 0 txbuilder.parent_transactions = [result.transactions.last] end txbuilder.prepended_unspent_outputs ||= [] txbuilder.outputs = [] # Add issuance input and outputs first. consumed_asset_outputs = [] # used in inputs issue_outputs = [] transfer_outputs = [] if self.issued_assets.size > 0 txbuilder.prepended_unspent_outputs << self.issuing_asset_output.transaction_output self.issued_assets.each do |issue| atxo = make_asset_transaction_output(asset_id: issuing_asset_id, amount: issue[:amount], script: issue[:script]) issue_outputs << atxo end end # Move all transfers of the asset ID used on the issuing output to the top of the list. asset_id_on_the_issuing_input = self.issuing_asset_output ? self.issuing_asset_output.asset_id : nil if asset_id_on_the_issuing_input aid = asset_id_on_the_issuing_input.to_s self.transferred_assets = self.transferred_assets.sort do |a,b| # move the asset id used in the issue input to the top if a[:asset_id].to_s == aid -1 elsif b[:asset_id] == aid 1 else 0 # keep the order end end end consumed_outpoints = {} # "txid:index" => true self.transferred_assets.each do |transfer| # |aid,transfers| asset_id = transfer[:asset_id] transfers = [transfer] amount_required = 0 amount_provided = 0 if asset_id_on_the_issuing_input.to_s == asset_id.to_s amount_provided = self.issuing_asset_output.value end atxo = make_asset_transaction_output(asset_id: transfer[:asset_id], amount: transfer[:amount], script: transfer[:script]) amount_required += atxo.value transfer_outputs << atxo # Fill in enough unspent assets for this asset_id. # Use per-transfer provider if it's specified. Otherwise use global provider. provider = transfer[:provider] || self.asset_provider unspents = provider.asset_unspent_outputs(asset_id: asset_id, amount: amount_required).dup while amount_provided < amount_required autxo = unspents.shift if !autxo raise InsufficientFundsError, "Not enough outputs for asset #{asset_id.to_s} (#{amount_provided} available < #{amount_required} required)" end if !consumed_outpoints[oid = autxo.transaction_output.outpoint_id] # Only apply outputs with matching asset ids. if autxo.asset_id == asset_id raise ArgumentError, "Must be verified asset outputs to spend" if !autxo.verified? consumed_outpoints[oid] = true amount_provided += autxo.value consumed_asset_outputs << autxo txbuilder.prepended_unspent_outputs << autxo.transaction_output end end end # If the difference is > 0, add a change output change = amount_provided - amount_required if change > 0 # Use per-transfer change address if it's specified. Otherwise use global change address. change_addr = transfer[:change_address] || self.asset_change_address atxo = make_asset_transaction_output(asset_id: asset_id, amount: change, script: change_addr.script) transfer_outputs << atxo end end # each transfer # If we have an asset on the issuance input and it is never used in any transfer, # then we need to create a change output just for it. if asset_id_on_the_issuing_input && self.issuing_asset_output.value > 0 if !self.transferred_assets.map{|dict| dict[:asset_id].to_s }.uniq.include?(asset_id_on_the_issuing_input.to_s) atxo = make_asset_transaction_output(asset_id: self.issuing_asset_output.asset_id, amount: self.issuing_asset_output.value, script: self.asset_change_address.script) transfer_outputs << atxo end end all_asset_outputs = (issue_outputs + transfer_outputs) result.assets_cost = all_asset_outputs.inject(0){|sum, atxo| sum + atxo.transaction_output.value } marker = AssetMarker.new(quantities: all_asset_outputs.map{|atxo| atxo.value }, metadata: self.) # Now, add underlying issues, marker and transfer outputs issue_outputs.each do |atxo| txbuilder.outputs << atxo.transaction_output end txbuilder.outputs << marker.output transfer_outputs.each do |atxo| txbuilder.outputs << atxo.transaction_output end txresult = txbuilder.build tx = txresult.transaction atx = AssetTransaction.new(transaction: tx) BTC::Invariant(atx.outputs.size == all_asset_outputs.size + 1 + (txresult.change_amount > 0 ? 1 : 0), "Must have all asset outputs (with marker output and optional change output)"); if txresult.change_amount > 0 plain_change_output = atx.outputs.last BTC::Invariant(!plain_change_output.verified?, "Must have plain change output not verified"); BTC::Invariant(!plain_change_output.asset_id, "Must have plain change output not have asset id"); BTC::Invariant(!plain_change_output.value, "Must have plain change output not have asset amount"); plain_change_output.verified = true # to match the rest of outputs. end # Provide color info for each input consumed_asset_outputs.each_with_index do |atxo, i| atx.inputs[i].asset_id = atxo.asset_id atx.inputs[i].value = atxo.value atx.inputs[i].verified = true end atx.inputs[consumed_asset_outputs.size..-1].each do |input| input.asset_id = nil input.value = nil input.verified = true end # Provide color info for each output issue_outputs.each_with_index do |aout1, i| aout = atx.outputs[i] aout.asset_id = aout1.asset_id aout.value = aout1.value aout.verified = true end atx.outputs[issue_outputs.size].verified = true # make marker verified offset = 1 + issue_outputs.size # +1 for marker transfer_outputs.each_with_index do |aout1, i| aout = atx.outputs[i + offset] aout.asset_id = aout1.asset_id aout.value = aout1.value aout.verified = true end offset = 1 + issue_outputs.size + transfer_outputs.size # +1 for marker atx.outputs[offset..-1].each do |aout| aout.asset_id = nil aout.value = nil aout.verified = true end if txresult.change_amount == 0 atx.outputs.each do |aout| if !aout.marker? BTC::Invariant(aout.verified?, "Must be verified"); BTC::Invariant(!!aout.asset_id, "Must have asset id"); BTC::Invariant(aout.value && aout.value > 0, "Must have some asset amount"); end end end result.unsigned_input_indexes << txresult.unsigned_input_indexes result.transactions << tx result.asset_transaction = atx result end |
#internal_bitcoin_provider ⇒ Object
372 373 374 |
# File 'lib/btcruby/open_assets/asset_transaction_builder.rb', line 372 def internal_bitcoin_provider @internal_bitcoin_provider ||= (self.bitcoin_provider || TransactionBuilder::Provider.new{|txb| []}) end |
#issue_asset(source_script: nil, source_output: nil, amount: nil, script: nil, address: nil) ⇒ Object
Adds an issuance of some assets.
If script is specified, it is used to create an intermediate base transaction.
If output is specified, it must be a valid spendable output with transaction_id and index.
It can be regular TransactionOutput or verified AssetTransactionOutput.
amount must be > 0 - number of units to be issued
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/btcruby/open_assets/asset_transaction_builder.rb', line 63 def issue_asset(source_script: nil, source_output: nil, amount: nil, script: nil, address: nil) raise ArgumentError, "Either `source_script` or `source_output` must be specified" if !source_script && !source_output raise ArgumentError, "Both `source_script` and `source_output` cannot be specified" if source_script && source_output raise ArgumentError, "Either `script` or `address` must be specified" if !script && !address raise ArgumentError, "Both `script` and `address` cannot be specified" if script && address raise ArgumentError, "Amount must be greater than zero" if !amount || amount <= 0 if source_output && (!source_output.index || !source_output.transaction_hash) raise ArgumentError, "If `source_output` is specified, it must have valid `transaction_hash` and `index` attributes" end script ||= AssetAddress.parse(address).script # Ensure source output is a verified asset output. if source_output if source_output.is_a?(AssetTransactionOutput) raise ArgumentError, "Must be verified asset output to spend" if !source_output.verified? else source_output = AssetTransactionOutput.new(transaction_output: source_output, verified: true) end end # Set either the script or output only once. # All the remaining issuances must use the same script or output. if !self.issuing_asset_script && !self.issuing_asset_output self.issuing_asset_script = source_script self.issuing_asset_output = source_output else if self.issuing_asset_script != source_script || self.issuing_asset_output != source_output raise ArgumentError, "Can't issue more assets from a different source script or source output" end end self.issued_assets << {amount: amount, script: script} end |
#issuing_asset_id ⇒ Object
Helpers
336 337 338 |
# File 'lib/btcruby/open_assets/asset_transaction_builder.rb', line 336 def issuing_asset_id @issuing_asset_id ||= AssetID.new(script: @issuing_asset_script || @issuing_asset_output.transaction_output.script) end |
#make_asset_transaction_output(asset_id: nil, amount: nil, script: nil) ⇒ Object
340 341 342 343 |
# File 'lib/btcruby/open_assets/asset_transaction_builder.rb', line 340 def make_asset_transaction_output(asset_id: nil, amount: nil, script: nil) txout = make_output_for_asset_script(script) AssetTransactionOutput.new(transaction_output: txout, asset_id: asset_id, value: amount, verified: true) end |
#make_output_for_asset_script(script) ⇒ Object
355 356 357 358 359 360 |
# File 'lib/btcruby/open_assets/asset_transaction_builder.rb', line 355 def make_output_for_asset_script(script) txout = BTC::TransactionOutput.new(value: MAX_MONEY, script: script) txout.value = txout.dust_limit raise RuntimeError, "Sanity check: txout value must not be zero" if txout.value <= 0 txout end |
#make_transaction_builder ⇒ Object
362 363 364 365 366 367 368 369 370 |
# File 'lib/btcruby/open_assets/asset_transaction_builder.rb', line 362 def make_transaction_builder txbuilder = TransactionBuilder.new txbuilder.change_address = self.bitcoin_change_address txbuilder.signer = self.signer txbuilder.provider = self.internal_bitcoin_provider txbuilder.unspent_outputs = self.bitcoin_unspent_outputs txbuilder.fee_rate = self.fee_rate txbuilder end |
#make_transaction_for_issues ⇒ Object
345 346 347 348 349 350 351 352 353 |
# File 'lib/btcruby/open_assets/asset_transaction_builder.rb', line 345 def make_transaction_for_issues raise "Sanity check" if !self.issuing_asset_script txbuilder = make_transaction_builder txbuilder.outputs = [ make_output_for_asset_script(self.issuing_asset_script) ] result = txbuilder.build [result.transaction, result.unsigned_input_indexes] end |
#send_bitcoin(output: nil, amount: nil, script: nil, address: nil) ⇒ Object
Adds a normal payment output. Typically used for transfer
119 120 121 122 123 124 125 126 127 |
# File 'lib/btcruby/open_assets/asset_transaction_builder.rb', line 119 def send_bitcoin(output: nil, amount: nil, script: nil, address: nil) if !output raise ArgumentError, "Either `script` or `address` must be specified" if !script && !address raise ArgumentError, "Amount must be specified (>= 0)" if (!amount || amount < 0) script ||= address.public_address.script if address output = TransactionOutput.new(value: amount, script: script) end self.bitcoin_outputs << output end |
#transfer_asset(asset_id: nil, amount: nil, script: nil, address: nil, provider: nil, unspent_outputs: nil, change_address: nil) ⇒ Object
Adds a transfer output. May override per-builder unspents/provider/change address to allow multi-user swaps.
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/btcruby/open_assets/asset_transaction_builder.rb', line 100 def transfer_asset(asset_id: nil, amount: nil, script: nil, address: nil, provider: nil, unspent_outputs: nil, change_address: nil) raise ArgumentError, "AssetID must be provided" if !asset_id raise ArgumentError, "Either `script` or `address` must be specified" if !script && !address raise ArgumentError, "Both `script` and `address` cannot be specified" if script && address raise ArgumentError, "Amount must be greater than zero" if !amount || amount <= 0 provider = Provider.new{|atxbuilder| unspent_outputs } if unspent_outputs change_address = AssetAddress.parse(change_address) if change_address asset_id = AssetID.parse(asset_id) script ||= AssetAddress.parse(address).script self.transferred_assets << {asset_id: asset_id, amount: amount, script: script, provider: provider, change_address: change_address} end |
#validate_asset_change_address! ⇒ Object
386 387 388 389 390 391 392 |
# File 'lib/btcruby/open_assets/asset_transaction_builder.rb', line 386 def validate_asset_change_address! self.transferred_assets.each do |dict| addr = dict[:change_address] || self.asset_change_address raise ArgumentError, "Missing asset_change_address" if !addr raise ArgumentError, "asset_change_address must be an instance of BTC::AssetAddress" if !addr.is_a?(AssetAddress) end end |
#validate_bitcoin_change_address! ⇒ Object
Validation Methods
379 380 381 382 383 384 |
# File 'lib/btcruby/open_assets/asset_transaction_builder.rb', line 379 def validate_bitcoin_change_address! addr = self.bitcoin_change_address raise ArgumentError, "Missing bitcoin_change_address" if !addr raise ArgumentError, "bitcoin_change_address must be an instance of BTC::Address" if !addr.is_a?(Address) raise ArgumentError, "bitcoin_change_address must not be an instance of BTC::AssetAddress" if addr.is_a?(AssetAddress) end |