trident_assistant 0.1.0
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 +7 -0
- data/.rubocop.yml +132 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +106 -0
- data/LICENSE.txt +21 -0
- data/README.md +47 -0
- data/Rakefile +16 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/exe/ta +6 -0
- data/lib/trident_assistant/api/collectible.rb +81 -0
- data/lib/trident_assistant/api/collection.rb +61 -0
- data/lib/trident_assistant/api/metadata.rb +26 -0
- data/lib/trident_assistant/api/order.rb +77 -0
- data/lib/trident_assistant/api.rb +27 -0
- data/lib/trident_assistant/cli/base.rb +55 -0
- data/lib/trident_assistant/cli/collectible.rb +100 -0
- data/lib/trident_assistant/cli/collection.rb +81 -0
- data/lib/trident_assistant/cli/metadata.rb +119 -0
- data/lib/trident_assistant/cli/nfo.rb +101 -0
- data/lib/trident_assistant/cli/order.rb +77 -0
- data/lib/trident_assistant/cli/utils.rb +23 -0
- data/lib/trident_assistant/cli.rb +45 -0
- data/lib/trident_assistant/client.rb +116 -0
- data/lib/trident_assistant/utils/memo.rb +62 -0
- data/lib/trident_assistant/utils/metadata.rb +117 -0
- data/lib/trident_assistant/utils.rb +88 -0
- data/lib/trident_assistant/version.rb +5 -0
- data/lib/trident_assistant.rb +18 -0
- metadata +92 -0
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TridentAssistant
|
4
|
+
class API
|
5
|
+
# api for order
|
6
|
+
module Order
|
7
|
+
EXCHANGE_ASSET_ID = "965e5c6e-434c-3fa9-b780-c50f43cd955c"
|
8
|
+
MINIMUM_AMOUNT = 0.000_000_01
|
9
|
+
|
10
|
+
def orders(**kwargs)
|
11
|
+
client.get(
|
12
|
+
"api/orders",
|
13
|
+
headers: {
|
14
|
+
Authorization: "Bearer #{mixin_bot.access_token("GET", "/me")}"
|
15
|
+
},
|
16
|
+
params: {
|
17
|
+
collection_id: kwargs[:collection_id],
|
18
|
+
metahash: kwargs[:metahash],
|
19
|
+
state: kwargs[:state],
|
20
|
+
type: kwargs[:type]
|
21
|
+
}
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def order(id)
|
26
|
+
client
|
27
|
+
.get(
|
28
|
+
"api/orders/#{id}",
|
29
|
+
headers: {
|
30
|
+
Authorization: "Bearer #{mixin_bot.access_token("GET", "/me")}"
|
31
|
+
}
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
def fill_order(order_id)
|
36
|
+
info = order order_id
|
37
|
+
raise "Order state: #{info["state"]}" if info["state"] != "open"
|
38
|
+
|
39
|
+
memo = TridentAssistant::Utils::Memo.new(type: "F", order_id: order_id, token_id: info["token_id"])
|
40
|
+
|
41
|
+
trace_id = SecureRandom.uuid
|
42
|
+
mixin_bot.create_multisig_transaction(
|
43
|
+
keystore[:pin],
|
44
|
+
{
|
45
|
+
asset_id: info["asset_id"],
|
46
|
+
trace_id: trace_id,
|
47
|
+
amount: info["price"],
|
48
|
+
memo: memo.encode,
|
49
|
+
receivers: TridentAssistant::Utils::TRIDENT_MTG[:members],
|
50
|
+
threshold: TridentAssistant::Utils::TRIDENT_MTG[:threshold]
|
51
|
+
}
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
def cancel_order(order_id)
|
56
|
+
info = order order_id
|
57
|
+
raise "Order maker: #{info["maker"]["id"]}" if info.dig("maker", "id") != mixin_bot.client_id
|
58
|
+
raise "Order state: #{info["state"]}" if info["state"] != "open"
|
59
|
+
|
60
|
+
memo = TridentAssistant::Utils::Memo.new(type: "C", order_id: order_id, token_id: info["token_id"])
|
61
|
+
|
62
|
+
trace_id = SecureRandom.uuid
|
63
|
+
mixin_bot.create_multisig_transaction(
|
64
|
+
keystore[:pin],
|
65
|
+
{
|
66
|
+
asset_id: info["asset_id"],
|
67
|
+
trace_id: trace_id,
|
68
|
+
amount: info["price"],
|
69
|
+
memo: memo.encode,
|
70
|
+
receivers: TridentAssistant::Utils::TRIDENT_MTG[:members],
|
71
|
+
threshold: TridentAssistant::Utils::TRIDENT_MTG[:threshold]
|
72
|
+
}
|
73
|
+
)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./client"
|
4
|
+
require_relative "./api/collection"
|
5
|
+
require_relative "./api/collectible"
|
6
|
+
require_relative "./api/metadata"
|
7
|
+
require_relative "./api/order"
|
8
|
+
|
9
|
+
module TridentAssistant
|
10
|
+
# APIs of Trident server
|
11
|
+
class API
|
12
|
+
attr_reader :mixin_bot, :client, :keystore
|
13
|
+
|
14
|
+
def initialize(**args)
|
15
|
+
@client = Client.new endpoint: args[:endpoint]
|
16
|
+
return if args[:keystore].blank?
|
17
|
+
|
18
|
+
@keystore = TridentAssistant::Utils.parse_json args[:keystore]
|
19
|
+
@mixin_bot = TridentAssistant::Utils.mixin_bot_from_keystore args[:keystore]
|
20
|
+
end
|
21
|
+
|
22
|
+
include TridentAssistant::API::Collectible
|
23
|
+
include TridentAssistant::API::Collection
|
24
|
+
include TridentAssistant::API::Metadata
|
25
|
+
include TridentAssistant::API::Order
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../api"
|
4
|
+
|
5
|
+
module TridentAssistant
|
6
|
+
module CLI
|
7
|
+
# Base class of CLI
|
8
|
+
class Base < Thor
|
9
|
+
# https://github.com/Shopify/cli-ui
|
10
|
+
UI = ::CLI::UI
|
11
|
+
|
12
|
+
attr_reader :keystore, :bot, :client, :api
|
13
|
+
|
14
|
+
def initialize(*args)
|
15
|
+
super
|
16
|
+
|
17
|
+
endpoint =
|
18
|
+
if options[:endpoint].present?
|
19
|
+
options[:endpoint]
|
20
|
+
else
|
21
|
+
{
|
22
|
+
prod: "https://thetrident.one",
|
23
|
+
test: "https://trident-test.onrender.com",
|
24
|
+
dev: "http://localhost:3000"
|
25
|
+
}[options[:environment].to_sym]
|
26
|
+
end
|
27
|
+
@api =
|
28
|
+
begin
|
29
|
+
TridentAssistant::API.new(
|
30
|
+
endpoint: endpoint,
|
31
|
+
keystore: options[:keystore]
|
32
|
+
)
|
33
|
+
rescue JSON::ParserError
|
34
|
+
log UI.fmt("{{x}} falied to parse keystore.json: #{options[:keystore]}")
|
35
|
+
rescue StandardError => e
|
36
|
+
log UI.fmt "{{x}} Failed to initialize Mixin bot, maybe your keystore is incorrect. #{e.inspect}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def log(obj)
|
43
|
+
if options[:pretty]
|
44
|
+
if obj.is_a? String
|
45
|
+
puts obj
|
46
|
+
else
|
47
|
+
ap obj
|
48
|
+
end
|
49
|
+
else
|
50
|
+
puts obj.inspect
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./base"
|
4
|
+
|
5
|
+
module TridentAssistant
|
6
|
+
module CLI
|
7
|
+
# CLI to query Trident and Mixin APIs
|
8
|
+
class Collectible < Base
|
9
|
+
desc "index", "query collectibles in wallet"
|
10
|
+
option :state, type: :string, aliases: "s", required: false, default: :unspent,
|
11
|
+
desc: "keystore or keystore.json file of Mixin bot"
|
12
|
+
option :keystore, type: :string, aliases: "k", required: true, desc: "keystore or keystore.json file of Mixin bot"
|
13
|
+
def index
|
14
|
+
r = api.mixin_bot.collectibles state: options[:state]
|
15
|
+
|
16
|
+
log r["data"]
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "show COLLECTION TOKEN", "query collectible"
|
20
|
+
option :keystore, type: :string, aliases: "k", required: true, desc: "keystore or keystore.json file of Mixin bot"
|
21
|
+
def show(collection, token)
|
22
|
+
token_id = MixinBot::Utils::Nfo.new(collection: collection, token: token).unique_token_id
|
23
|
+
r = api.mixin_bot.collectible token_id
|
24
|
+
|
25
|
+
log r["data"]
|
26
|
+
end
|
27
|
+
|
28
|
+
desc "deposit COLLECTION TOKEN", "deposit NFT"
|
29
|
+
option :keystore, type: :string, aliases: "k", required: true, desc: "keystore or keystore.json file of Mixin bot"
|
30
|
+
def deposit(collection, token)
|
31
|
+
log api.deposit collection, token
|
32
|
+
log UI.fmt("{{v}} successfully transfer NFT")
|
33
|
+
rescue StandardError => e
|
34
|
+
log UI.fmt("{{x}} failed: #{e.inspect} #{e.backtrace.join("\n")}")
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "airdrop COLLECTION, TOKEN", "airdrop NFT"
|
38
|
+
option :receiver, type: :string, aliases: "r", required: false, desc: "receiver ID of airdrop"
|
39
|
+
option :start, type: :string, aliases: "s", required: false, desc: "start time of airdrop"
|
40
|
+
option :keystore, type: :string, aliases: "k", required: true, desc: "keystore or keystore.json file of Mixin bot"
|
41
|
+
def airdrop(collection, token)
|
42
|
+
log api.airdrop collection, token, receiver_id: options[:receiver], start_at: options[:start]
|
43
|
+
log UI.fmt("{{v}} successfully transfer NFT")
|
44
|
+
rescue StandardError => e
|
45
|
+
log UI.fmt("{{x}} failed: #{e.inspect} #{e.backtrace.join("\n")}")
|
46
|
+
end
|
47
|
+
|
48
|
+
desc "withdraw COLLECTION TOKEN", "withdraw NFT"
|
49
|
+
option :keystore, type: :string, aliases: "k", required: true, desc: "keystore or keystore.json file of Mixin bot"
|
50
|
+
def withdraw(collection, token)
|
51
|
+
log api.withdraw collection, token
|
52
|
+
log UI.fmt("{{v}} successfully invoked withdraw")
|
53
|
+
rescue StandardError => e
|
54
|
+
log UI.fmt("{{x}} failed: #{e.inspect} #{e.backtrace.join("\n")}")
|
55
|
+
end
|
56
|
+
|
57
|
+
desc "bulkairdrop DIR", "Airdrop NFT in bulk"
|
58
|
+
option :keystore, type: :string, aliases: "k", required: true, desc: "keystore or keystore.json file of Mixin bot"
|
59
|
+
def bulkairdrop(dir)
|
60
|
+
raise "#{dir} is not a directory" unless Dir.exist?(dir)
|
61
|
+
|
62
|
+
Dir.glob("#{dir}/*.json").each do |file|
|
63
|
+
log UI.fmt("{{v}} found #{file}")
|
64
|
+
data = TridentAssistant::Utils.parse_json file
|
65
|
+
metadata = TridentAssistant::Utils.parse_metadata data
|
66
|
+
log UI.fmt("{{v}} metadata parsed")
|
67
|
+
metadata.validate!
|
68
|
+
log UI.fmt("{{v}} metadata validated")
|
69
|
+
|
70
|
+
if data.dig("_airdrop", "hash").present?
|
71
|
+
log UI.fmt("{{v}} NFT already transferred")
|
72
|
+
next
|
73
|
+
end
|
74
|
+
|
75
|
+
receiver_id = data.dig("_airdrop", "receiver_id")
|
76
|
+
start_at = data.dig("_airdrop", "start_at")
|
77
|
+
log UI.fmt("{{v}} airdrop receiver_id: #{receiver_id}")
|
78
|
+
log UI.fmt("{{v}} airdrop start_at: #{start_at}")
|
79
|
+
|
80
|
+
r = api.airdrop metadata.collection["id"], metadata.token["id"], receiver_id: receiver_id, start_at: start_at
|
81
|
+
log r["data"]
|
82
|
+
data["_airdrop"] ||= {}
|
83
|
+
data["_airdrop"]["hash"] = r["data"]["hash"]
|
84
|
+
log UI.fmt("{{v}} successfully transfer NFT ##{metadata.token["id"]} #{metadata.collection["id"]}")
|
85
|
+
rescue TridentAssistant::Utils::Metadata::InvalidFormatError, JSON::ParserError, Client::RequestError,
|
86
|
+
MixinBot::Error, RuntimeError => e
|
87
|
+
log UI.fmt("{{x}} #{file} failed to airdrop: #{e.inspect}")
|
88
|
+
next
|
89
|
+
ensure
|
90
|
+
File.write file, data.to_json
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def _airdrop
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./base"
|
4
|
+
|
5
|
+
module TridentAssistant
|
6
|
+
module CLI
|
7
|
+
# CLI tool of collection
|
8
|
+
class Collection < Base
|
9
|
+
class InvalidError < TridentAssistant::Error; end
|
10
|
+
|
11
|
+
option :keystore, type: :string, aliases: "k", required: true, desc: "keystore or keystore.json file of Mixin bot"
|
12
|
+
desc "index", "query all collections"
|
13
|
+
def index
|
14
|
+
log api.collections
|
15
|
+
end
|
16
|
+
|
17
|
+
option :name, type: :string, aliases: "n", desc: "collection Name"
|
18
|
+
option :description, type: :string, aliases: "d", desc: "collection Description"
|
19
|
+
option :icon, type: :string, aliases: "i", desc: "collection Icon"
|
20
|
+
option :url, type: :string, aliases: "u", desc: "collection External URL"
|
21
|
+
option :split, type: :string, aliases: "s", desc: "collection Split"
|
22
|
+
option :keystore, type: :string, aliases: "k", required: true, desc: "keystore or keystore.json file of Mixin bot"
|
23
|
+
desc "create", "create a new collection"
|
24
|
+
def create
|
25
|
+
name = options[:name] || UI.ask("Please input collection name")
|
26
|
+
raise InvalidError, "Name cannot be blank" if name.blank?
|
27
|
+
|
28
|
+
description = options[:description] || UI.ask("Please input collection description")
|
29
|
+
raise InvalidError, "Description cannot be blank" if description.blank?
|
30
|
+
|
31
|
+
icon = options[:icon] || UI.ask("Please input icon file or icon url")
|
32
|
+
icon_file = File.open(icon) if File.exist? icon
|
33
|
+
|
34
|
+
external_url = options[:url] || UI.ask("Please input collection external url, start with https:// or http://")
|
35
|
+
split = options[:split] || UI.ask("Please input collection split", default: "0.0")
|
36
|
+
payload = {
|
37
|
+
name: name,
|
38
|
+
description: description,
|
39
|
+
external_url: external_url,
|
40
|
+
split: split
|
41
|
+
}
|
42
|
+
|
43
|
+
if icon_file.present?
|
44
|
+
payload[:icon_base64] = Base64.strict_encode64(icon_file.read)
|
45
|
+
else
|
46
|
+
payload[:icon_url] = icon
|
47
|
+
end
|
48
|
+
|
49
|
+
log api.create_collection(**payload)
|
50
|
+
rescue InvalidError => e
|
51
|
+
log UI.fmt("{{x}} failed: #{e.inspect}")
|
52
|
+
ensure
|
53
|
+
icon_file&.close
|
54
|
+
end
|
55
|
+
|
56
|
+
desc "update ID", "update collection"
|
57
|
+
option :description, type: :string, aliases: "d", desc: "collection Description"
|
58
|
+
option :icon, type: :string, aliases: "i", desc: "collection Icon"
|
59
|
+
option :url, type: :string, aliases: "u", desc: "collection External URL"
|
60
|
+
option :keystore, type: :string, aliases: "k", required: true, desc: "keystore or keystore.json file of Mixin bot"
|
61
|
+
def update(id)
|
62
|
+
payload = {}
|
63
|
+
payload[:description] = options[:description] if options[:description].present?
|
64
|
+
payload[:external_url] = options[:url] if options[:url].present?
|
65
|
+
if options[:icon].present? && File.exist?(options[:icon])
|
66
|
+
icon = File.open options[:icon]
|
67
|
+
payload[:icon_base64] = Base64.strict_encode64(icon.read)
|
68
|
+
end
|
69
|
+
log api.update_collection(id, **payload)
|
70
|
+
ensure
|
71
|
+
icon&.close
|
72
|
+
end
|
73
|
+
|
74
|
+
option :keystore, type: :string, aliases: "k", required: true, desc: "keystore or keystore.json file of Mixin bot"
|
75
|
+
desc "show ID", "query a collection"
|
76
|
+
def show(id)
|
77
|
+
log api.collection id
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./base"
|
4
|
+
|
5
|
+
module TridentAssistant
|
6
|
+
module CLI
|
7
|
+
# CLI to generate metadata
|
8
|
+
class Metadata < Base
|
9
|
+
class InvalidError < TridentAssistant::Error; end
|
10
|
+
|
11
|
+
desc "new", "generate a new metadata"
|
12
|
+
option :keystore, type: :string, aliases: "k", required: true, desc: "keystore or keystore.json file of Mixin bot"
|
13
|
+
def new
|
14
|
+
creator_id = api.mixin_bot.client_id
|
15
|
+
creator_name = api.mixin_bot.me["full_name"]
|
16
|
+
creator_royalty = UI.ask("Please input creator royalty, 0.0 ~ 0.1", default: "0.0")
|
17
|
+
raise InvalidError, "Royalty must in 0.0 ~ 0.1" unless (0..0.1).include?(creator_royalty.to_f)
|
18
|
+
|
19
|
+
collection_id = UI.ask("Please input collection ID")
|
20
|
+
collection = api.collection collection_id
|
21
|
+
raise InvalidError, "Cannot find collection #{collection_id}" if collection.blank?
|
22
|
+
|
23
|
+
if collection["creator"]&.[]("id") != api.mixin_bot.client_id
|
24
|
+
raise InvalidError,
|
25
|
+
"Unauthorized to mint in #{collection_id}"
|
26
|
+
end
|
27
|
+
|
28
|
+
token_id = UI.ask("Please input token ID", default: "1")
|
29
|
+
token_name = UI.ask("Please input token name")
|
30
|
+
token_description = UI.ask("Please input token description")
|
31
|
+
|
32
|
+
token_icon_url = UI.ask("Please input token icon url or local file dir")
|
33
|
+
if File.file? token_icon_url
|
34
|
+
begin
|
35
|
+
file = File.open token_icon_url
|
36
|
+
res = api.mixin_bot.upload_attachment file
|
37
|
+
token_icon_url = res["view_url"]
|
38
|
+
ensure
|
39
|
+
file&.close
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
token_media_url = UI.ask("Please input token media url or local file dir")
|
44
|
+
if File.file? token_media_url
|
45
|
+
begin
|
46
|
+
file = File.open token_media_url
|
47
|
+
res = api.mixin_bot.upload_attachment file
|
48
|
+
token_media_url = res["view_url"]
|
49
|
+
token_media_hash = SHA3::Digest::SHA256.hexdigest file.read
|
50
|
+
ensure
|
51
|
+
file&.close
|
52
|
+
end
|
53
|
+
elsif token_media_url =~ URI::DEFAULT_PARSER.make_regexp
|
54
|
+
token_media_hash = TridentAssistant::Utils.hash_from_url(token_media_url)
|
55
|
+
end
|
56
|
+
|
57
|
+
metadata = TridentAssistant::Utils::Metadata.new(
|
58
|
+
creator: {
|
59
|
+
id: creator_id,
|
60
|
+
name: creator_name,
|
61
|
+
royalty: creator_royalty
|
62
|
+
},
|
63
|
+
collection: {
|
64
|
+
id: collection_id,
|
65
|
+
name: collection["name"],
|
66
|
+
description: collection["description"],
|
67
|
+
icon: {
|
68
|
+
url: collection["icon"]&.[]("url")
|
69
|
+
},
|
70
|
+
split: collection["split"].to_s
|
71
|
+
},
|
72
|
+
token: {
|
73
|
+
id: token_id,
|
74
|
+
name: token_name,
|
75
|
+
description: token_description,
|
76
|
+
icon: {
|
77
|
+
url: token_icon_url
|
78
|
+
},
|
79
|
+
media: {
|
80
|
+
url: token_media_url,
|
81
|
+
hash: token_media_hash.to_s
|
82
|
+
}
|
83
|
+
},
|
84
|
+
checksum: {
|
85
|
+
fields: ["creator.id", "creator.royalty", "collection.id", "collection.name",
|
86
|
+
"collection.split", "token.id", "token.name", "token.media.hash"],
|
87
|
+
algorithm: "sha256"
|
88
|
+
}
|
89
|
+
)
|
90
|
+
|
91
|
+
File.write "#{token_id}_#{collection_id}.json", metadata.json.to_json
|
92
|
+
log metadata.json
|
93
|
+
end
|
94
|
+
|
95
|
+
desc "show METAHASH", "query metadata via metahash"
|
96
|
+
option :keystore, type: :string, aliases: "k", required: true, desc: "keystore or keystore.json file of Mixin bot"
|
97
|
+
def show(metahash)
|
98
|
+
log api.metadata metahash
|
99
|
+
end
|
100
|
+
|
101
|
+
desc "upload", "upload metadata to Trident"
|
102
|
+
option :metadata, type: :string, aliases: "m", required: true, desc: "metadata or metadata.json file"
|
103
|
+
option :keystore, type: :string, aliases: "k", required: true, desc: "keystore or keystore.json file of Mixin bot"
|
104
|
+
def upload
|
105
|
+
metadata = TridentAssistant::Utils.parse_metadata options[:metadata]
|
106
|
+
log UI.fmt("{{v}} metadata parsed")
|
107
|
+
|
108
|
+
metadata.validate!
|
109
|
+
log UI.fmt("{{v}} metadata validated")
|
110
|
+
|
111
|
+
# upload metadata
|
112
|
+
api.upload_metadata metadata: metadata.json, metahash: metadata.metahash
|
113
|
+
log UI.fmt("{{v}} metadata uploaded: #{options[:endpoint]}/api/collectibles/#{metadata.metahash}")
|
114
|
+
rescue JSON::ParserError, Client::RequestError, InvalidError => e
|
115
|
+
log UI.fmt("{{x}} #{e.inspect}")
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./base"
|
4
|
+
|
5
|
+
module TridentAssistant
|
6
|
+
module CLI
|
7
|
+
# CLI to mint NFTs
|
8
|
+
class NFO < Base
|
9
|
+
desc "mint", "Mint NFT from NFO"
|
10
|
+
option :metadata, type: :string, aliases: "m", required: true, desc: "metadata or metadata.json file"
|
11
|
+
option :keystore, type: :string, aliases: "k", required: true, desc: "keystore or keystore.json file of Mixin bot"
|
12
|
+
def mint
|
13
|
+
_mint options[:metadata]
|
14
|
+
rescue JSON::ParserError, Client::RequestError, TridentAssistant::Utils::Metadata::InvalidFormatError,
|
15
|
+
MixinBot::Error, RuntimeError => e
|
16
|
+
log UI.fmt("{{x}} #{e.inspect}")
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "bulkmint DIR", "Mint NFT in bulk"
|
20
|
+
option :keystore, type: :string, aliases: "k", required: true, desc: "keystore or keystore.json file of Mixin bot"
|
21
|
+
def bulkmint(dir)
|
22
|
+
raise "#{dir} is not a directory" unless Dir.exist?(dir)
|
23
|
+
|
24
|
+
Dir.glob("#{dir}/*.json").each do |file|
|
25
|
+
log UI.fmt("{{v}} found #{file}")
|
26
|
+
_mint file
|
27
|
+
rescue TridentAssistant::Utils::Metadata::InvalidFormatError, JSON::ParserError, Client::RequestError,
|
28
|
+
MixinBot::Error, RuntimeError => e
|
29
|
+
log UI.fmt("{{x}} #{file} failed: #{e.inspect}")
|
30
|
+
next
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def _mint(raw)
|
37
|
+
# parse metadata
|
38
|
+
data = TridentAssistant::Utils.parse_json raw
|
39
|
+
metadata = TridentAssistant::Utils.parse_metadata data
|
40
|
+
log UI.fmt("{{v}} metadata parsed")
|
41
|
+
|
42
|
+
# validate metadata
|
43
|
+
metadata.validate!
|
44
|
+
log UI.fmt("{{v}} metadata validated")
|
45
|
+
|
46
|
+
raise "Creator ID incompatible with keystore" if metadata.creator[:id] != api.mixin_bot.client_id
|
47
|
+
|
48
|
+
token_id = MixinBot::Utils::Nfo.new(collection: metadata.collection[:id],
|
49
|
+
token: metadata.token[:id]).unique_token_id
|
50
|
+
collectible =
|
51
|
+
begin
|
52
|
+
api.mixin_bot.collectible token_id
|
53
|
+
rescue MixinBot::NotFoundError
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
if collectible.present?
|
57
|
+
log UI.fmt("{{v}} already minted: #{token_id}")
|
58
|
+
return
|
59
|
+
end
|
60
|
+
|
61
|
+
# upload metadata
|
62
|
+
if data.dig("_mint", "metahash").blank?
|
63
|
+
api.upload_metadata metadata: metadata.json, metahash: metadata.metahash
|
64
|
+
data["_mint"] ||= {}
|
65
|
+
data["_mint"]["metahash"] = metadata.metahash
|
66
|
+
end
|
67
|
+
log UI.fmt("{{v}} metadata uploaded: #{options[:endpoint]}/api/collectibles/#{metadata.metahash}")
|
68
|
+
|
69
|
+
# pay to NFO
|
70
|
+
trace_id = data.dig("_mint", "trace_id") || SecureRandom.uuid
|
71
|
+
memo = api.mixin_bot.nft_memo metadata.collection[:id], metadata.token[:id].to_i, metadata.metahash
|
72
|
+
payment =
|
73
|
+
api.mixin_bot.create_multisig_transaction(
|
74
|
+
api.keystore[:pin],
|
75
|
+
{
|
76
|
+
asset_id: TridentAssistant::Utils::MINT_ASSET_ID,
|
77
|
+
trace_id: trace_id,
|
78
|
+
amount: TridentAssistant::Utils::MINT_AMOUNT,
|
79
|
+
memo: memo,
|
80
|
+
receivers: TridentAssistant::Utils::NFO_MTG[:members],
|
81
|
+
threshold: TridentAssistant::Utils::NFO_MTG[:threshold]
|
82
|
+
}
|
83
|
+
)
|
84
|
+
|
85
|
+
data["_mint"]["trace_id"] = trace_id
|
86
|
+
if payment["errors"].blank?
|
87
|
+
log UI.fmt("{{v}} NFT mint payment paid: #{payment["data"]}")
|
88
|
+
data["_mint"]["token_id"] = token_id
|
89
|
+
end
|
90
|
+
|
91
|
+
log UI.fmt("{{v}} NFT successfully minted")
|
92
|
+
ensure
|
93
|
+
if File.file? raw
|
94
|
+
File.write raw, data.to_json
|
95
|
+
else
|
96
|
+
log data
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./base"
|
4
|
+
|
5
|
+
module TridentAssistant
|
6
|
+
module CLI
|
7
|
+
# CLI to transfer to Trident MTG
|
8
|
+
class Order < Base
|
9
|
+
EXCHANGE_ASSET_ID = "965e5c6e-434c-3fa9-b780-c50f43cd955c"
|
10
|
+
MINIMUM_AMOUNT = 0.000_000_01
|
11
|
+
|
12
|
+
desc "index", "list orders"
|
13
|
+
option :keystore, type: :string, aliases: "k", required: true, desc: "keystore or keystore.json file of Mixin bot"
|
14
|
+
option :collection, type: :string, aliases: "c", required: false, desc: "collection ID"
|
15
|
+
option :metahash, type: :string, aliases: "m", required: false, desc: "metahash"
|
16
|
+
option :type, type: :string, aliases: "t", required: false, desc: "ask | bid | auction"
|
17
|
+
option :state, type: :string, aliases: "s", required: false, desc: "open | completed"
|
18
|
+
def index
|
19
|
+
log api.orders(
|
20
|
+
collection_id: options[:collection],
|
21
|
+
metahash: options[:metahash],
|
22
|
+
state: options[:state],
|
23
|
+
type: options[:type]
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "show ID", "query order"
|
28
|
+
option :keystore, type: :string, aliases: "k", required: true, desc: "keystore or keystore.json file of Mixin bot"
|
29
|
+
def show(id)
|
30
|
+
log api.order id
|
31
|
+
end
|
32
|
+
|
33
|
+
desc "sell", "sell NFT at fixed price"
|
34
|
+
def ask; end
|
35
|
+
|
36
|
+
desc "auction", "auction NFT"
|
37
|
+
def auction; end
|
38
|
+
|
39
|
+
desc "bid", "bid NFT"
|
40
|
+
def bid; end
|
41
|
+
|
42
|
+
desc "fill ID", "fill order"
|
43
|
+
option :keystore, type: :string, aliases: "k", required: true, desc: "keystore or keystore.json file of Mixin bot"
|
44
|
+
def fill(id)
|
45
|
+
log api.fill_order id
|
46
|
+
end
|
47
|
+
|
48
|
+
desc "cancel ID", "cancel order"
|
49
|
+
option :keystore, type: :string, aliases: "k", required: true, desc: "keystore or keystore.json file of Mixin bot"
|
50
|
+
def cancel(id)
|
51
|
+
log api.cancel_order id
|
52
|
+
end
|
53
|
+
|
54
|
+
desc "deposit TOKEN", "deposit NFT"
|
55
|
+
def deposit(token)
|
56
|
+
log api.deposit_nft token
|
57
|
+
end
|
58
|
+
|
59
|
+
desc "withdraw TOKEN", "withdraw NFT"
|
60
|
+
option :keystore, type: :string, aliases: "k", required: true, desc: "keystore or keystore.json file of Mixin bot"
|
61
|
+
def withdraw(token)
|
62
|
+
payment =
|
63
|
+
api.mixin_bot.create_multisig_transaction(
|
64
|
+
keystore[:pin],
|
65
|
+
asset_id: EXCHANGE_ASSET_ID,
|
66
|
+
amount: MINIMUM_AMOUNT,
|
67
|
+
receivers: TridentAssistant::Utils::TRIDENT_MTG[:members],
|
68
|
+
threshold: TridentAssistant::Utils::TRIDENT_MTG[:threshold],
|
69
|
+
memo: TridentAssistant::Utils::Memo.new(type: "W", token_id: token).encode,
|
70
|
+
trace_id: SecureRandom.uuid
|
71
|
+
)
|
72
|
+
|
73
|
+
log UI.fmt("{{v}} payment: #{payment}")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./base"
|
4
|
+
|
5
|
+
module TridentAssistant
|
6
|
+
module CLI
|
7
|
+
# CLI for utils
|
8
|
+
class Utils < Base
|
9
|
+
desc "hash INPUT", "Hash a string or file using sha256"
|
10
|
+
option :file, type: :boolean, aliases: "f", default: false, desc: "Hash a file"
|
11
|
+
def hash(_input)
|
12
|
+
content =
|
13
|
+
if options[:file]
|
14
|
+
File.read options[:input]
|
15
|
+
else
|
16
|
+
options[:input].to_s
|
17
|
+
end
|
18
|
+
|
19
|
+
log SHA3::Digest::SHA256.hexdigest(content)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|