class Glueby::Contract::Token

This class represents custom token issued by application user. Application users can

Examples:

alice = Glueby::Wallet.create bob = Glueby::Wallet.create

Use `Glueby::Internal::Wallet#receive_address` to generate the address of bob bob.internal_wallet.receive_address

> '1CY6TSSARn8rAFD9chCghX5B7j4PKR8S1a'

Issue token = Token.issue!(issuer: alice, amount: 100) token.amount(wallet: alice)

> 100

Send token.transfer!(sender: alice, receiver_address: '1CY6TSSARn8rAFD9chCghX5B7j4PKR8S1a', amount: 1) token.amount(wallet: alice)

> 99

token.amount(wallet: bob)

> 1

Burn token.burn!(sender: alice, amount: 10) token.amount(wallet: alice)

> 89

token.burn!(sender: alice) token.amount(wallet: alice)

> 0

Reissue token.reissue!(issuer: alice, amount: 100) token.amount(wallet: alice)

> 100

Attributes

color_id[R]

Public Class Methods

issue!(issuer:, token_type: Tapyrus::Color::TokenTypes::REISSUABLE, amount: 1) click to toggle source

Issue new token with specified amount and token type. REISSUABLE token can be reissued with reissue! method, and NON_REISSUABLE and NFT token can not. Amount is set to 1 when the token type is NFT

@param issuer [Glueby::Wallet] @param token_type [TokenTypes] @param amount [Integer] @return [Array<token, Array<tx>>] Tuple of tx array and token object @raise [InsufficientFunds] if wallet does not have enough TPC to send transaction. @raise [InvalidAmount] if amount is not positive integer. @raise [UnspportedTokenType] if token is not supported.

# File lib/glueby/contract/token.rb, line 63
def issue!(issuer:, token_type: Tapyrus::Color::TokenTypes::REISSUABLE, amount: 1)
  raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?

  txs, color_id = case token_type
                 when Tapyrus::Color::TokenTypes::REISSUABLE
                   issue_reissuable_token(issuer: issuer, amount: amount)
                 when Tapyrus::Color::TokenTypes::NON_REISSUABLE
                   issue_non_reissuable_token(issuer: issuer, amount: amount)
                 when Tapyrus::Color::TokenTypes::NFT
                   issue_nft_token(issuer: issuer)
                 else
                   raise Glueby::Contract::Errors::UnsupportedTokenType
                 end

  [new(color_id: color_id), txs]
end
new(color_id:) click to toggle source

Generate Token Instance @param color_id [String]

# File lib/glueby/contract/token.rb, line 223
def initialize(color_id:)
  @color_id = color_id
end
parse_from_payload(payload) click to toggle source

Restore token from payload @param payload [String] @return [Glueby::Contract::Token]

# File lib/glueby/contract/token.rb, line 210
def self.parse_from_payload(payload)
  color_id, script_pubkey = payload.unpack('a33a*')
  color_id = Tapyrus::Color::ColorIdentifier.parse_from_payload(color_id)
  if color_id.type == Tapyrus::Color::TokenTypes::REISSUABLE
    raise ArgumentError, 'script_pubkey should not be empty' if script_pubkey.empty?
    script_pubkey = Tapyrus::Script.parse_from_payload(script_pubkey)
    Glueby::Contract::AR::ReissuableToken.create!(color_id: color_id.to_hex, script_pubkey: script_pubkey.to_hex)
  end
  new(color_id: color_id)
end

Private Class Methods

issue_nft_token(issuer:) click to toggle source
# File lib/glueby/contract/token.rb, line 107
def issue_nft_token(issuer:)
  tx = create_issue_tx_for_nft_token(issuer: issuer)
  tx = issuer.internal_wallet.broadcast(tx)

  out_point = tx.inputs.first.out_point
  color_id = Tapyrus::Color::ColorIdentifier.nft(out_point)
  [[tx], color_id]
end
issue_non_reissuable_token(issuer:, amount:) click to toggle source
# File lib/glueby/contract/token.rb, line 98
def issue_non_reissuable_token(issuer:, amount:)
  tx = create_issue_tx_for_non_reissuable_token(issuer: issuer, amount: amount)
  tx = issuer.internal_wallet.broadcast(tx)

  out_point = tx.inputs.first.out_point
  color_id = Tapyrus::Color::ColorIdentifier.non_reissuable(out_point)
  [[tx], color_id]
end
issue_reissuable_token(issuer:, amount:) click to toggle source
# File lib/glueby/contract/token.rb, line 82
def issue_reissuable_token(issuer:, amount:)
  funding_tx = create_funding_tx(wallet: issuer)
  script_pubkey = funding_tx.outputs.first.script_pubkey
  color_id = Tapyrus::Color::ColorIdentifier.reissuable(script_pubkey)

  ActiveRecord::Base.transaction(joinable: false, requires_new: true) do
    # Store the script_pubkey for reissue the token.
    Glueby::Contract::AR::ReissuableToken.create!(color_id: color_id.to_hex, script_pubkey: script_pubkey.to_hex)

    funding_tx = issuer.internal_wallet.broadcast(funding_tx)
    tx = create_issue_tx_for_reissuable_token(funding_tx: funding_tx, issuer: issuer, amount: amount)
    tx = issuer.internal_wallet.broadcast(tx)
    [[funding_tx, tx], color_id]
  end
end

Public Instance Methods

amount(wallet:) click to toggle source

Return balance of token in the specified wallet. @param wallet [Glueby::Wallet] @return [Integer] amount of utxo value associated with this token.

# File lib/glueby/contract/token.rb, line 179
def amount(wallet:)
  # collect utxo associated with this address
  utxos = wallet.internal_wallet.list_unspent
  _, results = collect_colored_outputs(utxos, color_id)
  results.sum { |result| result[:amount] }
end
burn!(sender:, amount: 0) click to toggle source

Burn token If amount is not specified or 0, burn all token associated with the wallet.

@param sender [Glueby::Wallet] wallet to send this token @param amount [Integer] @raise [InsufficientFunds] if wallet does not have enough TPC to send transaction. @raise [InsufficientTokens] if wallet does not have enough token to send transaction. @raise [InvalidAmount] if amount is not positive integer.

# File lib/glueby/contract/token.rb, line 169
def burn!(sender:, amount: 0)
  raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?

  tx = create_burn_tx(color_id: color_id, sender: sender, amount: amount)
  sender.internal_wallet.broadcast(tx)
end
reissue!(issuer:, amount:) click to toggle source

Re-issue the token with specified amount. A wallet can issue the token only when it is REISSUABLE token. @param issuer [Glueby::Wallet] @param amount [Integer] @return [Array<String, tx>] Tuple of color_id and tx object @raise [InsufficientFunds] if wallet does not have enough TPC to send transaction. @raise [InvalidAmount] if amount is not positive integer. @raise [InvalidTokenType] if token is not reissuable. @raise [UnknownScriptPubkey] when token is reissuable but it doesn't know script pubkey to issue token.

# File lib/glueby/contract/token.rb, line 128
def reissue!(issuer:, amount:)
  raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?
  raise Glueby::Contract::Errors::InvalidTokenType unless token_type == Tapyrus::Color::TokenTypes::REISSUABLE

  if validate_reissuer(wallet: issuer)
    funding_tx = create_funding_tx(wallet: issuer, script: @script_pubkey)
    funding_tx = issuer.internal_wallet.broadcast(funding_tx)
    tx = create_reissue_tx(funding_tx: funding_tx, issuer: issuer, amount: amount, color_id: color_id)
    tx = issuer.internal_wallet.broadcast(tx)

    [color_id, tx]
  else
    raise Glueby::Contract::Errors::UnknownScriptPubkey
  end
end
script_pubkey() click to toggle source

Return the script_pubkey of the token from ActiveRecord @return [String] script_pubkey

# File lib/glueby/contract/token.rb, line 194
def script_pubkey
  @script_pubkey ||= Glueby::Contract::AR::ReissuableToken.script_pubkey(@color_id.to_hex)
end
to_payload() click to toggle source

Return serialized payload @return [String] payload

# File lib/glueby/contract/token.rb, line 200
def to_payload
  payload = +''
  payload << @color_id.to_payload
  payload << @script_pubkey.to_payload if script_pubkey
  payload
end
token_type() click to toggle source

Return token type @return [Tapyrus::Color::TokenTypes]

# File lib/glueby/contract/token.rb, line 188
def token_type
  color_id.type
end
transfer!(sender:, receiver_address:, amount: 1) click to toggle source

Send the token to other wallet

@param sender [Glueby::Wallet] wallet to send this token @param receiver_address [String] address to receive this token @param amount [Integer] @return [Array<String, tx>] Tuple of color_id and tx object @raise [InsufficientFunds] if wallet does not have enough TPC to send transaction. @raise [InsufficientTokens] if wallet does not have enough token to send. @raise [InvalidAmount] if amount is not positive integer.

# File lib/glueby/contract/token.rb, line 153
def transfer!(sender:, receiver_address:, amount: 1)
  raise Glueby::Contract::Errors::InvalidAmount unless amount.positive?

  tx = create_transfer_tx(color_id: color_id, sender: sender, receiver_address: receiver_address, amount: amount)
  sender.internal_wallet.broadcast(tx)
  [color_id, tx]
end

Private Instance Methods

validate_reissuer(wallet:) click to toggle source
Verify that wallet is the issuer of the reissuable token

 reutrn [Boolean]

# File lib/glueby/contract/token.rb, line 231
def validate_reissuer(wallet:)
  addresses = wallet.internal_wallet.get_addresses
  addresses.each do |address|
    decoded_address = Tapyrus.decode_base58_address(address)
    pubkey_hash_from_address = decoded_address[0]
    pubkey_hash_from_script = Tapyrus::Script.parse_from_payload(script_pubkey.chunks[2])
    if pubkey_hash_from_address == pubkey_hash_from_script.to_s
      return true
    end
  end
  false
end