ryp 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5c71e065c00c656b3ebe2ae750ce7c37a7889224
4
- data.tar.gz: 951a67222fd17a78da92ac0b8c8db29b8e7201b7
3
+ metadata.gz: 1037a00adca2cc8fcf89fe3c60a39d69148a0237
4
+ data.tar.gz: 94b1576abe78e42e8b4254d2ed565a596a114e48
5
5
  SHA512:
6
- metadata.gz: 914b48005a4e4ac2b6c74086243f915a40ede8791eb08b59228fa67994c88bad23146f9c148963551ed9c5ba6265ef63f223f74f70949ca27068b7942c432ea2
7
- data.tar.gz: 935dc4d589eeeccf6b7556ab0296c562ee24f04e2f77a179b39cfc3e83d73531ff13af483447e20e00af68a3661c0742d32f6f3ba56de275b08b9f819f863d63
6
+ metadata.gz: 2c58d1451a91a553276ca729652e12a1d36ca54af568c671fffdda0ed6fcb36609d53e321c5405d93a788172ccc5ecfef2d42c17a9ff8f050784037fe46a0e6f
7
+ data.tar.gz: 89e5720f7891759a7d24e6eeecb4f068d8a1d17874b1a79c50e6e2356959ceafddef53fc3ed687a4e75ee7f451c127409be14f8c47f6979c8137e3bda66640a4
data/.gitignore CHANGED
@@ -1,5 +1,6 @@
1
1
  /.bundle/
2
2
  /.yardoc
3
+ /Gemfile.lock
3
4
  /_yardoc/
4
5
  /coverage/
5
6
  /doc/
data/.rspec CHANGED
@@ -1,3 +1,2 @@
1
1
  --format documentation
2
2
  --color
3
- --require spec_helper
@@ -1,5 +1,5 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - 2.4.2
5
- before_install: gem install bundler -v 1.16.0
4
+ - 2.3.3
5
+ before_install: gem install bundler -v 1.15.4
@@ -55,7 +55,7 @@ further defined and clarified by project maintainers.
55
55
  ## Enforcement
56
56
 
57
57
  Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
- reported by contacting the project team at m@mjw.io. All
58
+ reported by contacting the project team at matthew@coinbase.com. All
59
59
  complaints will be reviewed and investigated and will result in a response that
60
60
  is deemed necessary and appropriate to the circumstances. The project team is
61
61
  obligated to maintain confidentiality with regard to the reporter of an incident.
data/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/ryp`. To experiment with that code, run `bin/console` for an interactive prompt.
4
4
 
5
+ TODO: Delete this and the text above, and describe your gem
6
+
5
7
  ## Installation
6
8
 
7
9
  Add this line to your application's Gemfile:
@@ -20,6 +22,7 @@ Or install it yourself as:
20
22
 
21
23
  ## Usage
22
24
 
25
+ TODO: Write usage instructions here
23
26
 
24
27
  ## Development
25
28
 
@@ -33,7 +36,7 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERN
33
36
 
34
37
  ## License
35
38
 
36
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
39
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
37
40
 
38
41
  ## Code of Conduct
39
42
 
data/exe/ryp ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ require 'ryp'
3
+ client = Ryp::Command.new
4
+ command = ARGV[0]
5
+
6
+ if client.respond_to?(command)
7
+ Ryp::Command.start
8
+ else
9
+ client.info(command)
10
+ end
data/lib/ryp.rb CHANGED
@@ -1,5 +1,42 @@
1
- require "ryp/version"
1
+ require 'ryp/version'
2
+ require 'json'
2
3
 
3
4
  module Ryp
4
- # Your code goes here...
5
+ require 'ryp/client'
6
+ require 'ryp/command'
7
+ require 'ryp/content'
8
+ require 'ryp/errors'
9
+ require 'ryp/presenter'
10
+ require 'ryp/resource'
11
+
12
+ require 'ryp/bitcoin_address'
13
+ require 'ryp/bitcoin_transaction'
14
+
15
+ require 'ryp/ethereum_address'
16
+ require 'ryp/ethereum_transaction'
17
+
18
+ module DataSource
19
+ require 'ryp/data_source/base'
20
+ require 'ryp/data_source/block_cypher'
21
+ require 'ryp/data_source/blockchain_info'
22
+ end
23
+
24
+ def self.info(args, options = {})
25
+ options = options.each_with_object({}) { |(k,v), h| h[k.to_sym] = v }
26
+ content = Ryp::Content.discover(args, options)
27
+ presenter = Ryp::Presenter.new(content.network)
28
+
29
+ puts "[\"#{content.type}\"] on [\"#{content.network}\"]: #{content.value}" if options[:verbose]
30
+ content.fetch_data!
31
+ presenter.present(content)
32
+ rescue Ryp::UnknownContentType => e
33
+ puts "Unable to identify #{content.value} on the #{content.network} network"
34
+ end
35
+
36
+ def self.ticker_to_name(ticker)
37
+ {
38
+ eth: 'Ethereum',
39
+ btc: 'Bitcoin'
40
+ }[ticker.to_s.downcase.to_sym]
41
+ end
5
42
  end
@@ -0,0 +1,38 @@
1
+ module Ryp
2
+ class BitcoinAddress < Ryp::Resource
3
+
4
+ # https://www.blockcypher.com/dev/bitcoin/#address-balance-endpoint
5
+ def self.data_map
6
+ {
7
+ address: 'address',
8
+ total_received: 'total_received',
9
+ total_sent: 'total_sent',
10
+ balance: 'balance',
11
+ unconfirmed_balance: 'unconfirmed_balance',
12
+ transaction_count: 'n_tx',
13
+ unconfirmed_transaction_count: 'unconfirmed_n_tx',
14
+ }
15
+ end
16
+ attr_reader *data_map.keys
17
+
18
+ def total_received
19
+ @total_received.to_i / 1e8
20
+ end
21
+
22
+ def total_sent
23
+ @total_sent.to_i / 1e8
24
+ end
25
+
26
+ def balance
27
+ @balance.to_i / 1e8
28
+ end
29
+
30
+ def unconfirmed_balance
31
+ @unconfirmed_balance.to_i / 1e8
32
+ end
33
+
34
+ def url
35
+ "https://blockchain.info/address/#{address}"
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,31 @@
1
+ module Ryp
2
+ class BitcoinTransaction < Ryp::Resource
3
+
4
+ # https://www.blockcypher.com/dev/bitcoin/#transaction-hash-endpoint
5
+ def self.data_map
6
+ {
7
+ hash: 'hash',
8
+ height: 'block_height',
9
+ total: 'total',
10
+ fees: 'fees',
11
+ size: 'size',
12
+ received_at: 'received',
13
+ confirmed_at: 'confirmed',
14
+ confirmations: 'confirmations'
15
+ }
16
+ end
17
+ attr_reader *data_map.keys
18
+
19
+ def total
20
+ @total / 1e8
21
+ end
22
+
23
+ def fees
24
+ @fees / 1e8
25
+ end
26
+
27
+ def url
28
+ "https://blockchain.info/tx/#{hash}"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,42 @@
1
+ module Ryp
2
+ class Client
3
+ attr_reader :network, :client
4
+
5
+ # ETH: https://www.blockcypher.com/dev/ethereum/
6
+ # BTC: https://www.blockcypher.com/dev/bitcoin/
7
+
8
+ def initialize(network)
9
+ @network = network.to_s.downcase.to_sym
10
+ raise ArgumentError, 'Unknown Network' if network.nil? || network == :unknown
11
+ @client = send("#{network}_data_source")
12
+ end
13
+
14
+ def transaction(arg)
15
+ data_class('Transaction').new.tap do |transaction|
16
+ transaction.parse! client.transaction(arg)
17
+ end
18
+ end
19
+
20
+ def address(arg)
21
+ data_class('Address').new.tap do |address|
22
+ address.parse! client.address(arg)
23
+ end
24
+ end
25
+
26
+ # `content_type` accepts Address and Transaction
27
+ def data_class(content_type)
28
+ network_name = Ryp.ticker_to_name(network)
29
+ Ryp.const_get("#{network_name}#{content_type}")
30
+ end
31
+
32
+ private
33
+
34
+ def eth_data_source
35
+ @eth_client ||= Ryp::DataSource::BlockCypher.new(:eth)
36
+ end
37
+
38
+ def btc_data_source
39
+ @btc_client ||= Ryp::DataSource::BlockCypher.new(:btc)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,20 @@
1
+ require 'thor'
2
+
3
+ module Ryp
4
+ class Command < Thor
5
+
6
+ desc 'info <argument>', "Look up related information to any given argument"
7
+ option :network, aliases: :n
8
+ option :verbose, type: :boolean, aliases: :v
9
+ def info(argument)
10
+ Ryp.info(argument, options)
11
+ end
12
+
13
+ desc 'status', 'Report current status across all networks'
14
+ def status
15
+ puts 'Network Status: Not implemented yet'
16
+ end
17
+
18
+ default_command :info
19
+ end
20
+ end
@@ -0,0 +1,61 @@
1
+ module Ryp
2
+ class Content
3
+ attr_reader :type, :network, :value, :data
4
+
5
+ ETH_ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/
6
+ ETH_TRANSACTION_REGEX = /^0x([A-Fa-f0-9]{64})$/
7
+
8
+ BTC_ADDRESS_REGEX = /^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/
9
+ BTC_TRANSACTION_REGEX = /^[a-zA-Z0-9]{64}$/
10
+
11
+ MATCHERS = {
12
+ eth: {
13
+ address: ETH_ADDRESS_REGEX,
14
+ transaction: ETH_TRANSACTION_REGEX
15
+ },
16
+ btc: {
17
+ address: BTC_ADDRESS_REGEX,
18
+ transaction: BTC_TRANSACTION_REGEX
19
+ }
20
+ }
21
+
22
+ def initialize(type:, network:, value:)
23
+ @type = type
24
+ @network = network
25
+ @value = value
26
+ end
27
+
28
+ def self.discover(data, options = {})
29
+ value, network, type = *[data, determine_content(data, options)].flatten
30
+
31
+ new(type: type, network: network, value: data)
32
+ end
33
+
34
+ # Allow passing { network: 'n' } to specify a network
35
+ def self.determine_content(data, options = {})
36
+ specified_network = options[:network] && options[:network].downcase.to_sym
37
+
38
+ puts "Determining Content: #{[data, options].inspect}" if options[:verbose]
39
+ MATCHERS.each_pair do |network, matchers|
40
+ next if specified_network && specified_network != network
41
+
42
+ matchers.each_pair do |type, expression|
43
+ return [network, type] if data =~ expression
44
+ end
45
+ end
46
+
47
+ [specified_network || :unknown, :unknown]
48
+ end
49
+
50
+ def fetch_data!
51
+ raise Ryp::UnknownContentType, 'Unkown Content Type' if type == :unknown
52
+ @data = client.send(type, self)
53
+ rescue Ryp::BadResponseError => e
54
+ puts e
55
+ end
56
+
57
+ def client
58
+ @client ||= Ryp::Client.new(network)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,25 @@
1
+ require 'faraday'
2
+ require 'json'
3
+
4
+ module Ryp
5
+ module DataSource
6
+ class Base
7
+ def get(path)
8
+ response = connection.get(path)
9
+ if response.status != 200
10
+ raise BadResponseError, "#{response.status} response from server: #{response.body}"
11
+ end
12
+
13
+ JSON.parse(response.body)
14
+ end
15
+
16
+ def connection
17
+ @connection ||= Faraday.new(data_host)
18
+ end
19
+
20
+ def data_host
21
+ # Implemented in subclasses
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ require 'faraday'
2
+
3
+ module Ryp
4
+ module DataSource
5
+ class BlockCypher < Ryp::DataSource::Base
6
+ attr_reader :network
7
+
8
+ def initialize(network)
9
+ @network = network
10
+ end
11
+
12
+ def address(content)
13
+ get("v1/#{network}/main/addrs/#{content.value}")
14
+ end
15
+
16
+ def transaction(content)
17
+ get("v1/#{network}/main/txs/#{content.value}")
18
+ end
19
+
20
+ def data_host
21
+ 'https://api.blockcypher.com'
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ module Ryp
2
+ module DataSource
3
+ class BlockchainInfo < Ryp::DataSource::Base
4
+ def address(content)
5
+ get("rawaddr/#{content.value}")
6
+ end
7
+
8
+ def transaction(content)
9
+ get("rawtx/#{content.value}")
10
+ end
11
+
12
+ def data_host
13
+ 'https://blockchain.info'
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,6 @@
1
+ module Ryp
2
+ class RypError < StandardError; end
3
+ class UnknownContentType < RypError; end
4
+ class UnknownNetwork < RypError; end
5
+ class BadResponseError < RypError; end
6
+ end
@@ -0,0 +1,42 @@
1
+ module Ryp
2
+ class EthereumAddress < Ryp::Resource
3
+
4
+ # https://www.blockcypher.com/dev/ethereum/#address
5
+ def self.data_map
6
+ {
7
+ address: 'address',
8
+ total_received: 'total_received',
9
+ total_sent: 'total_sent',
10
+ balance: 'balance',
11
+ unconfirmed_balance: 'unconfirmed_balance',
12
+ transaction_count: 'n_tx',
13
+ unconfirmed_transaction_count: 'unconfirmed_n_tx'
14
+ }
15
+ end
16
+ attr_reader *data_map.keys
17
+
18
+ def hash
19
+ "0x#{@hash}"
20
+ end
21
+
22
+ def total_received
23
+ @total_received.to_i / 1e18
24
+ end
25
+
26
+ def total_sent
27
+ @total_sent.to_i / 1e18
28
+ end
29
+
30
+ def balance
31
+ @balance.to_i / 1e18
32
+ end
33
+
34
+ def unconfirmed_balance
35
+ @unconfirmed_balance.to_i / 1e18
36
+ end
37
+
38
+ def url
39
+ "https://etherscan.io/address/0x#{address}"
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,37 @@
1
+ module Ryp
2
+ class EthereumTransaction < Ryp::Resource
3
+
4
+ # https://www.blockcypher.com/dev/ethereum/#transaction
5
+ def self.data_map
6
+ {
7
+ hash: 'hash',
8
+ height: 'block_height',
9
+ total: 'total',
10
+ fees: 'fees',
11
+ size: 'size',
12
+ received_at: 'received',
13
+ confirmed_at: 'confirmed',
14
+ confirmations: 'confirmations',
15
+ gas_used: 'gas_used',
16
+ gas_price: 'gas_price'
17
+ }
18
+ end
19
+ attr_reader *data_map.keys
20
+
21
+ def hash
22
+ "0x#{@hash}"
23
+ end
24
+
25
+ def total
26
+ @total / 1e18
27
+ end
28
+
29
+ def fees
30
+ @fees / 1e18
31
+ end
32
+
33
+ def url
34
+ "https://etherscan.io/tx/#{hash}"
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ module Ryp
2
+ class Presenter
3
+ attr_reader :network
4
+
5
+ def initialize(network)
6
+ @network = network
7
+ end
8
+
9
+ def present(content)
10
+ puts "#{content.type.capitalize} on #{network.upcase}:\n\n"
11
+ data = content.data.to_h
12
+ url = data.delete(:url)
13
+ pretty_print data
14
+ puts "\n#{url}"
15
+ end
16
+
17
+ private
18
+
19
+ def pretty_print(object)
20
+ if object.is_a?(Hash)
21
+ object.each_pair do |key, value|
22
+ if value.is_a?(Array)
23
+ attribute = key.to_s.gsub(/-|_/, ' ').capitalize
24
+ puts ["#{attribute}:", value.map{|v| " - #{v}"}].join("\n")
25
+ else
26
+ attribute = key.to_s.gsub(/-|_/, ' ').capitalize
27
+ puts "#{attribute.ljust(25)}: #{value}"
28
+ end
29
+ end
30
+ elsif object.is_a?(Array)
31
+ object.each { |n| puts n }
32
+ else
33
+ puts object
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,24 @@
1
+ module Ryp
2
+ class Resource
3
+ attr_reader :raw
4
+
5
+ def parse!(data)
6
+ self.class.data_map.each_pair do |attribute, key|
7
+ instance_variable_set("@#{attribute}", data[key])
8
+ end
9
+ @raw = data
10
+ end
11
+
12
+ def attributes
13
+ Hash.new.tap do |attributes|
14
+ self.class.data_map.keys.each do |attribute|
15
+ attributes[attribute] = send(attribute)
16
+ end
17
+ end
18
+ end
19
+
20
+ def to_h
21
+ { url: url }.merge(attributes)
22
+ end
23
+ end
24
+ end
@@ -1,3 +1,3 @@
1
1
  module Ryp
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -1,4 +1,4 @@
1
-
1
+ # coding: utf-8
2
2
  lib = File.expand_path("../lib", __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require "ryp/version"
@@ -7,10 +7,10 @@ Gem::Specification.new do |spec|
7
7
  spec.name = "ryp"
8
8
  spec.version = Ryp::VERSION
9
9
  spec.authors = ["Matthew Werner"]
10
- spec.email = ["m@mjw.io"]
10
+ spec.email = ["matthew@coinbase.com"]
11
11
 
12
- spec.summary = %q{Cryptocurrency Tooling CLI}
13
- spec.description = %q{Tools you can use on the command line to investigate a cryptocurrency}
12
+ spec.summary = %q{Quick access cryptocurrency tools}
13
+ spec.description = %q{Get information about cryptocurrency from the command line}
14
14
  spec.homepage = "https://github.com/mwerner/ryp"
15
15
  spec.license = "MIT"
16
16
 
@@ -21,7 +21,10 @@ Gem::Specification.new do |spec|
21
21
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
22
  spec.require_paths = ["lib"]
23
23
 
24
- spec.add_development_dependency "bundler", "~> 1.16"
24
+ spec.add_dependency 'thor'
25
+ spec.add_dependency 'faraday'
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.15"
25
28
  spec.add_development_dependency "rake", "~> 10.0"
26
29
  spec.add_development_dependency "rspec", "~> 3.0"
27
30
  end
metadata CHANGED
@@ -1,29 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ryp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew Werner
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-12-27 00:00:00.000000000 Z
11
+ date: 2018-01-03 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: bundler
15
43
  requirement: !ruby/object:Gem::Requirement
16
44
  requirements:
17
45
  - - "~>"
18
46
  - !ruby/object:Gem::Version
19
- version: '1.16'
47
+ version: '1.15'
20
48
  type: :development
21
49
  prerelease: false
22
50
  version_requirements: !ruby/object:Gem::Requirement
23
51
  requirements:
24
52
  - - "~>"
25
53
  - !ruby/object:Gem::Version
26
- version: '1.16'
54
+ version: '1.15'
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: rake
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -52,10 +80,11 @@ dependencies:
52
80
  - - "~>"
53
81
  - !ruby/object:Gem::Version
54
82
  version: '3.0'
55
- description: Tools you can use on the command line to investigate a cryptocurrency
83
+ description: Get information about cryptocurrency from the command line
56
84
  email:
57
- - m@mjw.io
58
- executables: []
85
+ - matthew@coinbase.com
86
+ executables:
87
+ - ryp
59
88
  extensions: []
60
89
  extra_rdoc_files: []
61
90
  files:
@@ -69,7 +98,21 @@ files:
69
98
  - Rakefile
70
99
  - bin/console
71
100
  - bin/setup
101
+ - exe/ryp
72
102
  - lib/ryp.rb
103
+ - lib/ryp/bitcoin_address.rb
104
+ - lib/ryp/bitcoin_transaction.rb
105
+ - lib/ryp/client.rb
106
+ - lib/ryp/command.rb
107
+ - lib/ryp/content.rb
108
+ - lib/ryp/data_source/base.rb
109
+ - lib/ryp/data_source/block_cypher.rb
110
+ - lib/ryp/data_source/blockchain_info.rb
111
+ - lib/ryp/errors.rb
112
+ - lib/ryp/ethereum_address.rb
113
+ - lib/ryp/ethereum_transaction.rb
114
+ - lib/ryp/presenter.rb
115
+ - lib/ryp/resource.rb
73
116
  - lib/ryp/version.rb
74
117
  - ryp.gemspec
75
118
  homepage: https://github.com/mwerner/ryp
@@ -95,5 +138,5 @@ rubyforge_project:
95
138
  rubygems_version: 2.6.13
96
139
  signing_key:
97
140
  specification_version: 4
98
- summary: Cryptocurrency Tooling CLI
141
+ summary: Quick access cryptocurrency tools
99
142
  test_files: []