artq 0.2.0 → 0.3.1

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
  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: