trident_assistant 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./cli/base"
4
+ require_relative "./cli/collectible"
5
+ require_relative "./cli/collection"
6
+ require_relative "./cli/metadata"
7
+ require_relative "./cli/nfo"
8
+ require_relative "./cli/order"
9
+ require_relative "./cli/utils"
10
+
11
+ module TridentAssistant
12
+ # CLI tool
13
+ module CLI
14
+ # Main commands of CLI
15
+ class Command < TridentAssistant::CLI::Base
16
+ class_option :pretty, type: :boolean, aliases: "-p", default: true, desc: "Print output in pretty"
17
+ class_option :environment, type: :string, aliases: "-e", default: "prod", desc: "prod | test | dev"
18
+ class_option :endpoint, type: :string, aliases: "-E", desc: "Specify an endpoint"
19
+
20
+ desc "version", "Display TridentAssistant version"
21
+ def version
22
+ log VERSION
23
+ end
24
+
25
+ def self.exit_on_failure?
26
+ true
27
+ end
28
+
29
+ desc "collectible", "commands for collectible"
30
+ subcommand "collectible", CLI::Collectible
31
+
32
+ desc "collection", "commands for collection"
33
+ subcommand "collection", CLI::Collection
34
+
35
+ desc "nfo", "commands for nfo"
36
+ subcommand "nfo", CLI::NFO
37
+
38
+ desc "metadata", "commands for metadata"
39
+ subcommand "metadata", CLI::Metadata
40
+
41
+ desc "order", "commands for order"
42
+ subcommand "order", CLI::Order
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TridentAssistant
4
+ # HTTP client to trident server
5
+ class Client
6
+ class HttpError < TridentAssistant::Error; end
7
+ class RequestError < TridentAssistant::Error; end
8
+
9
+ ENDPOINT = "https://thetrident.one"
10
+
11
+ attr_reader :endpoint
12
+
13
+ def initialize(**kwargs)
14
+ @endpoint = URI(kwargs[:endpoint] || ENDPOINT)
15
+ end
16
+
17
+ def get(path, **options)
18
+ request(:get, path, **options)
19
+ end
20
+
21
+ def post(path, **options)
22
+ request(:post, path, **options)
23
+ end
24
+
25
+ def put(path, **options)
26
+ request(:put, path, **options)
27
+ end
28
+
29
+ private
30
+
31
+ def request(verb, path, **options)
32
+ uri = uri_for path
33
+
34
+ options[:headers] ||= {}
35
+ options[:headers]["Content-Type"] ||= "application/json"
36
+
37
+ begin
38
+ response = HTTP.timeout(connect: 5, write: 10, read: 10).request(verb, uri, options)
39
+ rescue HTTP::Error => e
40
+ raise HttpError, e.message
41
+ end
42
+
43
+ raise RequestError, response.to_s unless response.status.success?
44
+
45
+ parse_response(response) do |parse_as, result|
46
+ case parse_as
47
+ when :json
48
+ break result if result["message"].blank?
49
+
50
+ raise result["message"]
51
+ else
52
+ result
53
+ end
54
+ end
55
+ end
56
+
57
+ def uri_for(path)
58
+ uri_options = {
59
+ scheme: endpoint.scheme,
60
+ host: endpoint.host,
61
+ port: endpoint.port,
62
+ path: path
63
+ }
64
+ Addressable::URI.new(uri_options)
65
+ end
66
+
67
+ def parse_response(response)
68
+ content_type = response.headers[:content_type]
69
+ parse_as = {
70
+ %r{^application/json} => :json,
71
+ %r{^image/.*} => :file,
72
+ %r{^text/html} => :xml,
73
+ %r{^text/plain} => :plain
74
+ }.each_with_object([]) { |match, memo| memo << match[1] if content_type =~ match[0] }.first || :plain
75
+
76
+ if parse_as == :plain
77
+ result = JSON.parse(response&.body&.to_s)
78
+ result && yield(:json, result)
79
+
80
+ yield(:plain, response.body)
81
+ end
82
+
83
+ case parse_as
84
+ when :json
85
+ result = JSON.parse(response.body.to_s)
86
+ when :file
87
+ extension =
88
+ if response.headers[:content_type] =~ %r{^image/.*}
89
+ {
90
+ "image/gif": ".gif",
91
+ "image/jpeg": ".jpg",
92
+ "image/png": ".png"
93
+ }[response.headers["content-type"]]
94
+ else
95
+ ""
96
+ end
97
+
98
+ begin
99
+ file = Tempfile.new(["mixin-file-", extension])
100
+ file.binmode
101
+ file.write(response.body)
102
+ ensure
103
+ file&.close
104
+ end
105
+
106
+ result = file
107
+ when :xml
108
+ result = Hash.from_xml(response.body.to_s)
109
+ else
110
+ result = response.body
111
+ end
112
+
113
+ yield(parse_as, result)
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TridentAssistant
4
+ module Utils
5
+ # build metadata of NFT
6
+ class Memo
7
+ attr_accessor :type, :token_id, :asset_id, :order_id, :price, :reserve_price, :receiver_id, :start_at,
8
+ :expire_at, :encoded, :decoded
9
+
10
+ def initialize(**kwargs)
11
+ @type = kwargs[:type]
12
+ @token_id = kwargs[:token_id]
13
+ @asset_id = kwargs[:asset_id]
14
+ @order_id = kwargs[:order_id]
15
+ @price = kwargs[:price]
16
+ @reserve_price = kwargs[:reserve_price]
17
+ @receiver_id = kwargs[:receiver_id]
18
+ @start_at = kwargs[:start_at]
19
+ @expire_at = kwargs[:expire_at]
20
+ @encoded = kwargs[:encoded]
21
+ end
22
+
23
+ def json
24
+ {
25
+ T: type,
26
+ N: token_id,
27
+ A: asset_id,
28
+ O: order_id,
29
+ P: price,
30
+ R: reserve_price,
31
+ RC: receiver_id,
32
+ S: start_at,
33
+ E: expire_at
34
+ }.compact
35
+ end
36
+
37
+ def encode
38
+ hash = {
39
+ T: type,
40
+ N: token_id && MixinBot::Utils::UUID.new(hex: token_id).packed,
41
+ A: asset_id && MixinBot::Utils::UUID.new(hex: asset_id).packed,
42
+ O: order_id && MixinBot::Utils::UUID.new(hex: order_id).packed,
43
+ P: price && format("%.8f", price.to_f).gsub(/(0)+\z/, ""),
44
+ R: reserve_price && format("%.8f", reserve_price.to_f).gsub(/(0)+\z/, ""),
45
+ RC: receiver_id && MixinBot::Utils::UUID.new(hex: receiver_id).packed,
46
+ S: start_at && Time.parse(start_at).to_i,
47
+ E: expire_at && Time.parse(start_at).to_i
48
+ }.compact
49
+
50
+ @encoded =
51
+ Base64.urlsafe_encode64(
52
+ MessagePack.pack(hash),
53
+ padding: false
54
+ )
55
+ end
56
+
57
+ def decode
58
+ json
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TridentAssistant
4
+ module Utils
5
+ # build metadata of NFT
6
+ class Metadata
7
+ class InvalidFormatError < TridentAssistant::Error; end
8
+
9
+ attr_reader :creator, :collection, :token, :checksum
10
+
11
+ def initialize(**kwargs)
12
+ @creator = kwargs[:creator] || {}
13
+ @collection = kwargs[:collection] || {}
14
+ @token = kwargs[:token] || {}
15
+ @checksum = kwargs[:checksum] || {}
16
+ end
17
+
18
+ def json
19
+ {
20
+ creator: creator,
21
+ collection: collection,
22
+ token: token,
23
+ checksum: checksum
24
+ }.compact.with_indifferent_access
25
+ end
26
+
27
+ def validate!
28
+ raise InvalidFormatError, creator unless creator_valid?
29
+ raise InvalidFormatError, collection unless collection_valid?
30
+ raise InvalidFormatError, token unless token_valid?
31
+ raise InvalidFormatError, checksum unless checksum_valid?
32
+ raise InvalidFormatError, "failed to validate hash" unless all_hash_valid?
33
+
34
+ true
35
+ end
36
+
37
+ def valid?
38
+ creator_valid? && collection_valid? && token_valid? && checksum_valid? && all_hash_valid?
39
+ end
40
+
41
+ def creator_valid?
42
+ return false unless creator.is_a?(Hash)
43
+
44
+ @creator = creator.with_indifferent_access
45
+ creator["id"].present? && creator["name"].present?
46
+ end
47
+
48
+ def collection_valid?
49
+ return false unless collection.is_a?(Hash)
50
+
51
+ @collection = collection.with_indifferent_access
52
+ return false unless collection["id"].match?(/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/)
53
+ return false if collection["name"].blank?
54
+
55
+ true
56
+ end
57
+
58
+ def token_valid?
59
+ return false unless token.is_a?(Hash)
60
+
61
+ @token = token.with_indifferent_access
62
+ return false unless token["id"].match?(/\A(?!0)\d+\z/)
63
+ return false if token["id"].to_i > 8**64
64
+ return false if token["name"].blank?
65
+
66
+ true
67
+ end
68
+
69
+ def checksum_valid?
70
+ return false unless checksum.is_a?(Hash)
71
+
72
+ @checksum = checksum.with_indifferent_access
73
+ checksum["algorithm"] && checksum["fields"].is_a?(Array)
74
+ end
75
+
76
+ def all_hash_valid?
77
+ checksum["fields"].each do |field|
78
+ value = json
79
+
80
+ field.split(".").each do |key|
81
+ next unless value.is_a?(Hash)
82
+
83
+ return false if key == "hash" && value["hash"] != TridentAssistant::Utils.hash_from_url(value["url"])
84
+
85
+ value = value[key]
86
+ end
87
+ end
88
+
89
+ true
90
+ end
91
+
92
+ def checksum_content
93
+ return unless checksum_valid?
94
+
95
+ checksum["fields"].map do |field|
96
+ value = json
97
+ field.split(".").each do |key|
98
+ value = (value[key] if value.is_a?(Hash))
99
+ end
100
+ value.to_s
101
+ end.join
102
+ end
103
+
104
+ def metahash
105
+ return unless valid?
106
+
107
+ alg = checksum["algorithm"]
108
+ case alg
109
+ when "sha256", "sha3-256"
110
+ SHA3::Digest::SHA256.hexdigest checksum_content
111
+ else
112
+ raise "algorithm #{alg} not supported!"
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./utils/memo"
4
+ require_relative "./utils/metadata"
5
+
6
+ module TridentAssistant
7
+ # Some useful methods
8
+ module Utils
9
+ MINT_ASSET_ID = "c94ac88f-4671-3976-b60a-09064f1811e8"
10
+ MINT_AMOUNT = 0.001
11
+ NFO_MTG = {
12
+ members: %w[
13
+ 047061e6-496d-4c35-b06b-b0424a8a400d
14
+ 4b188942-9fb0-4b99-b4be-e741a06d1ebf
15
+ 50115496-7247-4e2c-857b-ec8680756bee
16
+ a51006d0-146b-4b32-a2ce-7defbf0d7735
17
+ acf65344-c778-41ee-bacb-eb546bacfb9f
18
+ cf4abd9c-2cfa-4b5a-b1bd-e2b61a83fabd
19
+ dd655520-c919-4349-822f-af92fabdbdf4
20
+ ].sort,
21
+ threshold: 5
22
+ }.freeze
23
+ TRIDENT_MTG = {
24
+ members: %w[
25
+ 6f5a84ce-d663-451e-b413-2d0c84b7629d
26
+ 99216bb5-9787-49b6-b251-537362ce23eb
27
+ fcc1e3ec-97f1-41a7-89a3-20a0ec30a31f
28
+ 1a361145-66a7-48cd-ab6c-db9a42da4074
29
+ 9ba50819-159c-45e6-9bed-1eb2dded3360
30
+ ].sort,
31
+ threshold: 3
32
+ }.freeze
33
+
34
+ class << self
35
+ def hash_from_url(url)
36
+ return if url.to_s.blank?
37
+
38
+ content =
39
+ begin
40
+ URI.parse(url).open(&:read)
41
+ rescue OpenURI::HTTPError
42
+ ""
43
+ end
44
+
45
+ SHA3::Digest::SHA256.hexdigest content
46
+ end
47
+
48
+ def mixin_bot_from_keystore(keystore)
49
+ keystore = parse_json keystore if keystore.is_a?(String)
50
+
51
+ MixinBot::API.new(
52
+ client_id: keystore["client_id"],
53
+ session_id: keystore["session_id"],
54
+ pin_token: keystore["pin_token"],
55
+ private_key: keystore["private_key"]
56
+ )
57
+ end
58
+
59
+ def parse_metadata(input)
60
+ metadata =
61
+ case input
62
+ when String
63
+ TridentAssistant::Utils.parse_json input
64
+ when Hash
65
+ input
66
+ else
67
+ {}
68
+ end
69
+ TridentAssistant::Utils::Metadata.new(
70
+ creator: metadata["creator"],
71
+ collection: metadata["collection"],
72
+ token: metadata["token"],
73
+ checksum: metadata["checksum"]
74
+ )
75
+ end
76
+
77
+ def parse_json(input)
78
+ input =
79
+ if File.file? input
80
+ File.read input
81
+ else
82
+ input
83
+ end
84
+ JSON.parse(input).with_indifferent_access
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TridentAssistant
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mixin_bot"
4
+ require "open-uri"
5
+
6
+ # Trident SDK
7
+ module TridentAssistant
8
+ class Error < StandardError; end
9
+
10
+ def self.api
11
+ TridentAssistant::API.new
12
+ end
13
+ end
14
+
15
+ require_relative "trident_assistant/api"
16
+ require_relative "trident_assistant/cli"
17
+ require_relative "trident_assistant/utils"
18
+ require_relative "trident_assistant/version"
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: trident_assistant
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - an-lee
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-04-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mixin_bot
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.8'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.8'
27
+ description: A simple program to use Trident NFT
28
+ email:
29
+ - an.lee.work@gmail.com
30
+ executables:
31
+ - ta
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".rubocop.yml"
36
+ - CHANGELOG.md
37
+ - CODE_OF_CONDUCT.md
38
+ - Gemfile
39
+ - Gemfile.lock
40
+ - LICENSE.txt
41
+ - README.md
42
+ - Rakefile
43
+ - bin/console
44
+ - bin/setup
45
+ - exe/ta
46
+ - lib/trident_assistant.rb
47
+ - lib/trident_assistant/api.rb
48
+ - lib/trident_assistant/api/collectible.rb
49
+ - lib/trident_assistant/api/collection.rb
50
+ - lib/trident_assistant/api/metadata.rb
51
+ - lib/trident_assistant/api/order.rb
52
+ - lib/trident_assistant/cli.rb
53
+ - lib/trident_assistant/cli/base.rb
54
+ - lib/trident_assistant/cli/collectible.rb
55
+ - lib/trident_assistant/cli/collection.rb
56
+ - lib/trident_assistant/cli/metadata.rb
57
+ - lib/trident_assistant/cli/nfo.rb
58
+ - lib/trident_assistant/cli/order.rb
59
+ - lib/trident_assistant/cli/utils.rb
60
+ - lib/trident_assistant/client.rb
61
+ - lib/trident_assistant/utils.rb
62
+ - lib/trident_assistant/utils/memo.rb
63
+ - lib/trident_assistant/utils/metadata.rb
64
+ - lib/trident_assistant/version.rb
65
+ homepage: https://github.com/TheTridentOne/trident_assistant
66
+ licenses:
67
+ - MIT
68
+ metadata:
69
+ homepage_uri: https://github.com/TheTridentOne/trident_assistant
70
+ source_code_uri: https://github.com/TheTridentOne/trident_assistant
71
+ changelog_uri: https://github.com/TheTridentOne/trident_assistant
72
+ rubygems_mfa_required: 'true'
73
+ post_install_message:
74
+ rdoc_options: []
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: 2.6.0
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubygems_version: 3.3.4
89
+ signing_key:
90
+ specification_version: 4
91
+ summary: A simple program to use Trident NFT
92
+ test_files: []