artq 0.2.0 → 0.3.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c0f00a597b6533cbdce4aa3286531d8533f9d3c0cf57b8147b7160d4b1d206d3
4
- data.tar.gz: e192cfb230111adb5dd69a1213a1446a9dc629fc853cacb8b10e5d80ac723e8e
3
+ metadata.gz: 173bf8cbe3bc0e9db811e19b585bd04bc23956b28b1b4cb055c1697288472d7e
4
+ data.tar.gz: 90c2751c1028ca2986e12825f83d8afbb9055ce864228f5a611a9110481f1439
5
5
  SHA512:
6
- metadata.gz: 6bf4be0a1990ce7c1d6d6e2c30b936cc0a145401b2992b32e4f2c83153881746fdaab77de434a38409bc17c6c0cbb93ee4673a589767197b314b11b935078426
7
- data.tar.gz: 65db2f2e2d112926111cb05398dd992a28d64f793a6f5d955b01a4cf03f1f25d9334d998b16088302479a07e6a3191712bc41d932c1b87d821f5d68b3e7b9974
6
+ metadata.gz: 339f0a49d30a544ce53892ec7f3c1a04a3ddb47d279ddc938d995ae8efc7c25356db9920a44f06d61a5d3482efe9975a965ba791ca6c6603cc427a6353914ab1
7
+ data.tar.gz: db571cdfcd4e29e3486199ac775a5494e1f4a42256d7d7449c8adbc3fdf7d4259356863dd3f440b60b94bcc30cf104c365c727965226eefe6a9e1ee849aa23da
data/Manifest.txt CHANGED
@@ -5,4 +5,6 @@ Rakefile
5
5
  bin/artq
6
6
  lib/artq.rb
7
7
  lib/artq/contract.rb
8
+ lib/artq/layers.rb
9
+ lib/artq/tokens.rb
8
10
  lib/artq/version.rb
data/README.md CHANGED
@@ -184,7 +184,7 @@ and inline svg images in the base64 format get "cut" from the metadata and "past
184
184
  ```
185
185
 
186
186
 
187
- #### Bonus - Case No. 3 - "On-Blockchain" Layers (Incl. Metadata & Images)
187
+ #### Case No. 3 - "On-Blockchain" Layers (Incl. Metadata & Images)
188
188
 
189
189
 
190
190
  Note: Some "on-blockchain" pixel art collections
@@ -339,7 +339,42 @@ such as
339
339
  and many more.
340
340
 
341
341
 
342
- Tip: See the [**Art Factory Sandbox**](https://github.com/pixelartexchange/artfactory.sandbox) for more art collections with "on-blockchain" layers.
342
+ Tip: For more art collections with "on-blockchain" layers see the [**Art Factory Sandbox »**](https://github.com/pixelartexchange/artfactory.sandbox)
343
+
344
+
345
+
346
+
347
+
348
+ ### Using the ArtQ Machinery in Your Own Scripts
349
+
350
+
351
+ Yes, you can. Let's try the (crypto) marcs:
352
+
353
+
354
+ ``` ruby
355
+ require 'artq'
356
+
357
+ marcs_eth = "0xe9b91d537c3aa5a3fa87275fbd2e4feaaed69bd0"
358
+
359
+ marcs = ArtQ::Contract.new( marcs_eth )
360
+
361
+ n = 0
362
+ m = 0
363
+ res = marcs.traitData( n, m ) ## note: return binary blob (for n,m-index)
364
+ pp res
365
+ #=> ["\x89PNG..."]
366
+
367
+ res = marcs.traitDetails( n, m ) ## note: returns tuple (name, mimetype, hide?)
368
+ pp res
369
+ #=> ["Zombie", "image/png", false]
370
+
371
+
372
+
373
+ ## or with convenience download_layers helper
374
+ ArtQ.download_layers( marcs_eth, outdir: './marcs' )
375
+ ```
376
+
377
+
343
378
 
344
379
 
345
380
 
data/lib/artq/contract.rb CHANGED
@@ -50,13 +50,12 @@ class Contract
50
50
 
51
51
 
52
52
 
53
- def self.rpc
54
- @rpc ||= JsonRpc.new( ENV['INFURA_URI'] )
55
- end
53
+ ## note: forward to Ethlite.config.rpc
54
+ ## keep config here as a convenience shortcut - why? why not?
55
+ def self.rpc() Ethlite.config.rpc; end
56
+ def self.rpc=(value) Ethlite.config.rpc = value; end
57
+
56
58
 
57
- def self.rpc=(value)
58
- @rpc = value.is_a?( String ) ? JsonRpc.new( value ) : value
59
- end
60
59
 
61
60
 
62
61
  def initialize( contract_address )
@@ -66,6 +65,7 @@ class Contract
66
65
  ######
67
66
  # private helper to call method
68
67
  def _do_call( eth, args )
68
+ puts " calling method >#{eth.name}< with args >#{args}<... "
69
69
  eth.do_call( self.class.rpc, @contract_address, args )
70
70
  end
71
71
 
@@ -0,0 +1,100 @@
1
+
2
+ module ArtQ
3
+
4
+
5
+ ## use alternate Encoding::BINARY - why? why not?
6
+ ## or just use .b e.g. "GIF87".b or such !!!
7
+ JPGSIG = "\xFF\xD8\xFF".force_encoding( Encoding::ASCII_8BIT )
8
+ PNGSIG = "\x89PNG".force_encoding( Encoding::ASCII_8BIT )
9
+ GIF87SIG = "GIF87".force_encoding( Encoding::ASCII_8BIT )
10
+ GIF89SIG = "GIF89".force_encoding( Encoding::ASCII_8BIT )
11
+
12
+
13
+
14
+ def self.download_layers( contract_address,
15
+ outdir: "./#{contract_address}" )
16
+
17
+ puts "==> download layers for art collection contract @ >#{contract_address}<:"
18
+
19
+ c = Contract.new( contract_address )
20
+
21
+ ## get some metadata - why? why not?
22
+ name = c.name
23
+ symbol = c.symbol
24
+ totalSupply = c.totalSupply
25
+
26
+ traitDetails = []
27
+ n=0
28
+ loop do
29
+ m=0
30
+ loop do
31
+ rec = c.traitDetails( n, m )
32
+ break if rec == ['','',false] ## note: assume end-of-list if all values are zeros / defaults.
33
+
34
+ traitDetails << [[n,m], rec ]
35
+ m += 1
36
+ sleep( 0.5 )
37
+ end
38
+ break if m==0
39
+ n += 1
40
+ end
41
+
42
+
43
+ ## todo/check: include or drop hide (of any use?) - why? why not?
44
+ headers = ['index', 'name', 'type', 'hide']
45
+ recs = []
46
+ traitDetails.each do |t|
47
+ recs << [ t[0].join('/'),
48
+ t[1][0],
49
+ t[1][1],
50
+ t[1][2].to_s]
51
+ end
52
+
53
+ buf = String.new('')
54
+ buf << headers.join( ', ' )
55
+ buf << "\n"
56
+ recs.each do |rec|
57
+ buf << rec.join( ', ' )
58
+ buf << "\n"
59
+ end
60
+
61
+ write_text( "#{outdir}/cache/layers.csv", buf )
62
+
63
+ #####
64
+ # try to download all images
65
+ traitDetails.each_with_index do |t,i|
66
+ puts " [#{i+1}/#{traitDetails.size}] downloading #{t[1][1]} >#{t[1][0]}<..."
67
+
68
+ n,m = t[0]
69
+ data = c.traitData( n, m )
70
+
71
+ basename = "#{n}_#{m}"
72
+ if data.start_with?( PNGSIG )
73
+ puts "BINGO!! it's a png blob - #{data.size} byte(s)"
74
+ write_blob( "#{outdir}/cache/#{basename}.png", data )
75
+ elsif data.start_with?( GIF87SIG ) || data.start_with?( GIF89SIG )
76
+ puts "BINGO!! it's a gif blob - #{data.size} byte(s)"
77
+ write_blob( "#{outdir}/cache/#{basename}.gif", data )
78
+ elsif data.start_with?( JPGSIG )
79
+ puts "BINGO!! it's a jpg blob - #{data.size} byte(s)"
80
+ write_blob( "#{outdir}/cache/#{basename}.jpg", data )
81
+ elsif data.index( /<svg[^>]*?>/i ) ## add more markers to find - why? why not?
82
+ puts "BINGO!! it's a svg (text) blob - #{data.size} byte(s)"
83
+ ## todo/check - save text as binary blob 1:1 - why? why not?
84
+ write_blob( "#{outdir}/cache/#{basename}.svg", data )
85
+ else
86
+ puts "!! ERROR - unknown image format; sorry"
87
+ exit 1
88
+ end
89
+ sleep( 0.5 )
90
+ end
91
+
92
+
93
+ puts " name: >#{name}<"
94
+ puts " symbol: >#{symbol}<"
95
+ puts " totalSupply: >#{totalSupply}<"
96
+ puts
97
+ puts " traitDetails:"
98
+ pp traitDetails
99
+ end
100
+ end # module ArtQ
@@ -0,0 +1,107 @@
1
+
2
+ module ArtQ
3
+
4
+
5
+
6
+ class Meta ## check: change/rename to MetaToken or such - why? why not?
7
+ def self.parse( tokenURI )
8
+ if tokenURI.start_with?( 'data:application/json;base64' )
9
+
10
+ str = tokenURI.sub( 'data:application/json;base64', '' )
11
+ str = Base64.decode64( str )
12
+ data = JSON.parse( str )
13
+
14
+ ## check for image_data - and replace if base64 encoded
15
+ image_data = data['image_data']
16
+ svg_image_data = data['svg_image_data']
17
+
18
+ if svg_image_data && svg_image_data.start_with?( 'data:image/svg+xml;base64' )
19
+ data['svg_image_data'] = '...'
20
+ data['image_data'] = '...' if image_data
21
+ ## note: prefer svg_image_data if present over image_data - why? why not?
22
+ str = svg_image_data.sub( 'data:image/svg+xml;base64', '' )
23
+ image_data = Base64.decode64( str )
24
+ elsif image_data && image_data.start_with?( 'data:image/svg+xml;base64' )
25
+ data['image_data'] = '...'
26
+ str = image_data.sub( 'data:image/svg+xml;base64', '' )
27
+ image_data = Base64.decode64( str )
28
+ else
29
+ ## assume no inline image_data ??
30
+ end
31
+
32
+ new( data, image_data )
33
+ else
34
+ new ## use new({},nil) - why? why not?
35
+ end
36
+ end
37
+
38
+ def initialize( data={},
39
+ image_data=nil )
40
+ @data = data
41
+ @image_data = image_data
42
+ end
43
+
44
+ def data() @data; end
45
+ def image_data() @image_data; end
46
+ end # class Meta
47
+
48
+
49
+
50
+ ## download on-blockchain token metadata and (inline) images
51
+ def self.download_tokens( contract_address,
52
+ outdir: "./#{contract_address}",
53
+ ids: (0..99) )
54
+
55
+ puts "==> download token (on-blockchain) metadata and (inline) images for art collection contract @ >#{contract_address}<:"
56
+
57
+ c = Contract.new( contract_address )
58
+
59
+ ## get some metadata - why? why not?
60
+ name = c.name
61
+ symbol = c.symbol
62
+ totalSupply = c.totalSupply
63
+
64
+
65
+ warns = [] ## collect all warnings
66
+
67
+ tokenIds = ids
68
+ tokenIds.each do |tokenId|
69
+ tokenURI = c.tokenURI( tokenId )
70
+ sleep( 0.5 )
71
+
72
+
73
+ puts " tokenId #{tokenId}:"
74
+ meta = Meta.parse( tokenURI )
75
+ if meta.data.empty?
76
+ ## todo/check: raise TypeError or such or return nil - why? why not?
77
+ warns << "token no. #{tokenId} metadata not 'on-blockchain'? expected json base64-encoded; got:"
78
+ puts "!! WARN - " + warns[-1]
79
+ pp tokenURI
80
+ else
81
+ path = "#{outdir}/token/#{tokenId}.json"
82
+ write_json( path, meta.data )
83
+
84
+ if meta.image_data
85
+ ## assume svg for now - always - why? why not?
86
+ path = "#{outdir}/token-i/#{tokenId}.svg"
87
+ write_text( path, meta.image_data )
88
+ else
89
+ warns << "token no. #{tokenId} (inline) image data not found in 'on-blockchain' metadata; got:"
90
+ puts "!! WARN - " + warns[-1]
91
+ pp meta.data
92
+ end
93
+ end
94
+ end
95
+
96
+ if warns.size > 0
97
+ puts "!!! #{warns.size} WARNING(S):"
98
+ pp warns
99
+ end
100
+
101
+ puts
102
+ puts " name: >#{name}<"
103
+ puts " symbol: >#{symbol}<"
104
+ puts " totalSupply: >#{totalSupply}<"
105
+ end
106
+
107
+ end # module ArtQ
data/lib/artq/version.rb CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
 
3
3
  module ArtQ
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.1'
5
5
  end # module ArtQ
6
6
 
7
7
 
data/lib/artq.rb CHANGED
@@ -5,16 +5,25 @@ require 'optparse'
5
5
  ## our own code
6
6
  require_relative 'artq/version' # let version go first
7
7
  require_relative 'artq/contract'
8
+ require_relative 'artq/layers'
9
+ require_relative 'artq/tokens'
8
10
 
9
11
 
10
12
 
11
13
  module ArtQ
12
14
 
15
+ class Tool
13
16
 
14
17
 
18
+ def self.is_addr?( str )
19
+ ## e.g.
20
+ ## must for now start with 0x (or 0X)
21
+ ## and than 40 hexdigits (20 bytes)
22
+ ## e.g. 0xe21ebcd28d37a67757b9bc7b290f4c4928a430b1
23
+ str.match( /\A0x[0-9a-f]{40}\z/i )
24
+ end
15
25
 
16
26
 
17
- class Tool
18
27
 
19
28
  def self.main( args=ARGV )
20
29
  puts "==> welcome to artq tool with args:"
@@ -49,14 +58,38 @@ class Tool
49
58
  exit
50
59
  end
51
60
 
52
- contract_address = args[0] ## todo/check - use collection_name/slug or such?
61
+
62
+ ## todo/check - use collection_name/slug or such?
63
+ contract_address = nil
64
+ outdir = nil
65
+
66
+ if is_addr?( args[0] ) ## do nothing; it's an address
67
+ contract_address = args[0]
68
+ outdir = "./tmp/#{contract_address}"
69
+ else ## try reading collection.yml config
70
+ config_path = "./#{args[0]}/collection.yml"
71
+ if File.exist?( config_path )
72
+ config = read_yaml( config_path )
73
+ contract_address = config['token']['contract']
74
+ outdir = "./#{args[0]}"
75
+ else
76
+ puts "!! ERROR - no config found for collection >#{contract_address}<; sorry"
77
+ exit 1
78
+ end
79
+ end
80
+
81
+
53
82
  command = args[1] || 'info'
54
83
 
55
84
 
56
85
  if ['i','inf','info'].include?( command )
57
86
  do_info( contract_address )
58
87
  elsif ['l', 'layer', 'layers'].include?( command )
59
- do_layers( contract_address )
88
+ ## note: outdir - save into cache for now
89
+ do_layers( contract_address, outdir: outdir )
90
+ elsif ['t', 'token', 'tokens'].include?( command )
91
+ ## note: outdir - saves into token (metadata) & token-i (images)
92
+ do_tokens( contract_address, outdir: outdir )
60
93
  else
61
94
  puts "!! ERROR - unknown command >#{command}<, sorry"
62
95
  end
@@ -77,11 +110,11 @@ class Tool
77
110
  symbol = c.symbol
78
111
  totalSupply = c.totalSupply
79
112
 
80
- meta = []
113
+ recs = []
81
114
  tokenIds = (0..2)
82
115
  tokenIds.each do |tokenId|
83
116
  tokenURI = c.tokenURI( tokenId )
84
- meta << [tokenId, tokenURI]
117
+ recs << [tokenId, tokenURI]
85
118
  end
86
119
 
87
120
  puts " name: >#{name}<"
@@ -89,122 +122,34 @@ class Tool
89
122
  puts " totalSupply: >#{totalSupply}<"
90
123
  puts
91
124
  puts " tokenURIs #{tokenIds}:"
92
- meta.each do |tokenId, tokenURI|
125
+ recs.each do |tokenId, tokenURI|
93
126
  puts " tokenId #{tokenId}:"
94
- if tokenURI.start_with?( 'data:application/json;base64')
95
- ## on-blockchain!!!
96
- ## decode base64
97
-
98
- str = tokenURI.sub( 'data:application/json;base64', '' )
99
- str = Base64.decode64( str )
100
- data = JSON.parse( str )
101
-
102
-
103
- ## check for image_data - and replace if base64 encoded
104
- image_data = data['image_data']
105
-
106
- if image_data.start_with?( 'data:image/svg+xml;base64' )
107
- data['image_data'] = '...'
108
- str = image_data.sub( 'data:image/svg+xml;base64', '' )
109
- image_data = Base64.decode64( str )
110
- end
111
-
112
- pp data
127
+ meta = Meta.parse( tokenURI )
128
+ if meta.data.empty? ## assume "off-blockchain" if no "on-blockchain" data found
129
+ puts " #{tokenURI}"
130
+ else ## assume "on-blockchain" data
131
+ pp meta.data
113
132
  puts
114
133
  puts " image_data:"
115
- puts image_data
116
- else
117
- puts " #{tokenURI}"
134
+ puts meta.image_data
118
135
  end
119
136
  end
120
137
  end
121
138
 
122
139
 
123
140
 
124
- JPGSIG = "\xFF\xD8\xFF".force_encoding( Encoding::ASCII_8BIT )
125
- PNGSIG = "\x89PNG".force_encoding( Encoding::ASCII_8BIT )
126
- GIF87SIG = "GIF87".force_encoding( Encoding::ASCII_8BIT )
127
- GIF89SIG = "GIF89".force_encoding( Encoding::ASCII_8BIT )
128
-
129
-
130
- def self.do_layers( contract_address )
141
+ def self.do_layers( contract_address, outdir: )
131
142
  puts "==> query layers for art collection contract @ >#{contract_address}<:"
132
143
 
133
- c = Contract.new( contract_address )
134
-
135
- name = c.name
136
- symbol = c.symbol
137
- totalSupply = c.totalSupply
138
-
139
-
140
- traitDetails = []
141
- n=0
142
- loop do
143
- m=0
144
- loop do
145
- rec = c.traitDetails( n, m )
146
- break if rec == ['','',false]
147
-
148
- traitDetails << [[n,m], rec ]
149
- m += 1
150
- sleep( 0.5 )
151
- end
152
- break if m==0
153
- n += 1
154
- end
155
-
156
- headers = ['index', 'name', 'type', 'hide']
157
- recs = []
158
- traitDetails.each do |t|
159
- recs << [ t[0].join('/'),
160
- t[1][0],
161
- t[1][1],
162
- t[1][2].to_s]
163
- end
164
-
165
- buf = String.new('')
166
- buf << headers.join( ', ' )
167
- buf << "\n"
168
- recs.each do |rec|
169
- buf << rec.join( ', ' )
170
- buf << "\n"
171
- end
172
-
173
- outdir = "./tmp/#{contract_address}"
174
- write_text( "#{outdir}/layers.csv", buf )
175
-
176
- #####
177
- # try to download all images
178
- traitDetails.each do |t|
179
- n,m = t[0]
180
- data = c.traitData( n, m )
181
-
182
- basename = "#{n}_#{m}"
183
- if data.start_with?( PNGSIG )
184
- puts "BINGO!! it's a png blob"
185
- write_blob( "#{outdir}/#{basename}.png", data )
186
- elsif data.start_with?( GIF87SIG ) || data.start_with?( GIF89SIG )
187
- puts "BINGO!! it's a gif blob"
188
- write_blob( "#{outdir}/#{basename}.gif", data )
189
- elsif data.start_with?( JPGSIG )
190
- puts "BINGO!! it's a jpg blob"
191
- write_blob( "#{outdir}/#{basename}.jpg", data )
192
- else
193
- puts "!! ERROR - unknown image format; sorry"
194
- exit 1
195
- end
196
- sleep( 0.5 )
197
- end
198
-
199
-
200
- puts " name: >#{name}<"
201
- puts " symbol: >#{symbol}<"
202
- puts " totalSupply: >#{totalSupply}<"
203
- puts
204
- puts " traitDetails:"
205
- pp traitDetails
144
+ ArtQ.download_layers( contract_address,
145
+ outdir: outdir )
206
146
  end
207
147
 
148
+ def self.do_tokens( contract_address, outdir: )
149
+ puts "==> query inline 'on-blockchain' token metadata & images for art collection contract @ >#{contract_address}<:"
150
+ ArtQ.download_tokens( contract_address,
151
+ outdir: outdir )
152
+ end
208
153
 
209
154
  end # class Tool
210
155
  end # module ArtQ
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: artq
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gerald Bauer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-23 00:00:00.000000000 Z
11
+ date: 2022-12-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ethlite
@@ -76,6 +76,8 @@ files:
76
76
  - bin/artq
77
77
  - lib/artq.rb
78
78
  - lib/artq/contract.rb
79
+ - lib/artq/layers.rb
80
+ - lib/artq/tokens.rb
79
81
  - lib/artq/version.rb
80
82
  homepage: https://github.com/pixelartexchange/artbase
81
83
  licenses: