artq 0.0.1 → 0.2.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 +4 -4
- data/Manifest.txt +2 -0
- data/README.md +331 -2
- data/Rakefile +1 -1
- data/bin/artq +17 -0
- data/lib/artq/contract.rb +73 -0
- data/lib/artq/version.rb +1 -1
- data/lib/artq.rb +209 -1
- metadata +10 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c0f00a597b6533cbdce4aa3286531d8533f9d3c0cf57b8147b7160d4b1d206d3
|
4
|
+
data.tar.gz: e192cfb230111adb5dd69a1213a1446a9dc629fc853cacb8b10e5d80ac723e8e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6bf4be0a1990ce7c1d6d6e2c30b936cc0a145401b2992b32e4f2c83153881746fdaab77de434a38409bc17c6c0cbb93ee4673a589767197b314b11b935078426
|
7
|
+
data.tar.gz: 65db2f2e2d112926111cb05398dd992a28d64f793a6f5d955b01a4cf03f1f25d9334d998b16088302479a07e6a3191712bc41d932c1b87d821f5d68b3e7b9974
|
data/Manifest.txt
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# ArtQ
|
2
2
|
|
3
|
-
artq - query (ethereum) blockchain contracts / services for data about art collections via json-rpc
|
3
|
+
artq - query (ethereum) blockchain contracts / services for (meta) data about art collections via json-rpc
|
4
4
|
|
5
5
|
|
6
6
|
* home :: [github.com/pixelartexchange/artbase](https://github.com/pixelartexchange/artbase)
|
@@ -10,9 +10,338 @@ artq - query (ethereum) blockchain contracts / services for data about art colle
|
|
10
10
|
|
11
11
|
|
12
12
|
|
13
|
+
|
13
14
|
## Usage
|
14
15
|
|
15
|
-
|
16
|
+
|
17
|
+
### Step 0: Setup JSON RPC Client
|
18
|
+
|
19
|
+
The ArtQ command line tool
|
20
|
+
gets the eth node uri via the INFURA_URI enviroment variable / key for now.
|
21
|
+
Set the environment variable / key
|
22
|
+
depending on your operating system (OS) e.g.:
|
23
|
+
|
24
|
+
```
|
25
|
+
set INFURA_URI=https://mainnet.infura.io/v3/<YOUR_KEY_HERE>
|
26
|
+
```
|
27
|
+
|
28
|
+
|
29
|
+
### Query (Token) Collection Info
|
30
|
+
|
31
|
+
To use the artq command line tool pass in the art collection contract address in the hex (string) format.
|
32
|
+
|
33
|
+
|
34
|
+
#### Case No. 1 - "Off-Blockchain" Token Metadata
|
35
|
+
|
36
|
+
Let's try Moonbirds - an "off-blockchain" pixel art collection -
|
37
|
+
with the token contract / service at [0x23581767a106ae21c074b2276d25e5c3e136a68b](https://etherscan.io/address/0x23581767a106ae21c074b2276d25e5c3e136a68b):
|
38
|
+
|
39
|
+
```
|
40
|
+
$ artq 0x23581767a106ae21c074b2276d25e5c3e136a68b # or
|
41
|
+
$ artq 0x23581767a106ae21c074b2276d25e5c3e136a68b info
|
42
|
+
```
|
43
|
+
|
44
|
+
resulting in:
|
45
|
+
|
46
|
+
```
|
47
|
+
name: >Moonbirds<
|
48
|
+
symbol: >MOONBIRD<
|
49
|
+
totalSupply: >10000<
|
50
|
+
|
51
|
+
tokenURIs 0..2:
|
52
|
+
tokenId 0:
|
53
|
+
https://live---metadata-5covpqijaa-uc.a.run.app/metadata/0
|
54
|
+
tokenId 1:
|
55
|
+
https://live---metadata-5covpqijaa-uc.a.run.app/metadata/1
|
56
|
+
tokenId 2:
|
57
|
+
https://live---metadata-5covpqijaa-uc.a.run.app/metadata/2
|
58
|
+
```
|
59
|
+
|
60
|
+
Note: By default the tokenURI method gets called / queried
|
61
|
+
for the first tokens (e.g. 0, 1, 2, etc.).
|
62
|
+
|
63
|
+
If you get a link back (e.g. starting with `https://` or `ipfs://`)
|
64
|
+
than the art collection is "off-blockchain" and
|
65
|
+
you MUST follow / request the link to get the token metadata.
|
66
|
+
|
67
|
+
|
68
|
+
For example if you request <https://live---metadata-5covpqijaa-uc.a.run.app/metadata/0>
|
69
|
+
you get back:
|
70
|
+
|
71
|
+
``` json
|
72
|
+
{"name":"#0",
|
73
|
+
"image":"https://live---metadata-5covpqijaa-uc.a.run.app/images/0",
|
74
|
+
"external_url":"https://moonbirds.xyz/",
|
75
|
+
"attributes":[
|
76
|
+
{"trait_type":"Eyes","value":"Angry"},
|
77
|
+
{"trait_type":"Outerwear","value":"Hoodie Down"},
|
78
|
+
{"trait_type":"Body","value":"Tabby"},
|
79
|
+
{"trait_type":"Feathers","value":"Gray"},
|
80
|
+
{"trait_type":"Background","value":"Green"},
|
81
|
+
{"trait_type":"Beak","value":"Small"}],
|
82
|
+
"x_debug":["orig:9650"]}
|
83
|
+
```
|
84
|
+
|
85
|
+
|
86
|
+
|
87
|
+
#### Case No. 2 - "On-Blockchain" Token Metadata (With Inline Image)
|
88
|
+
|
89
|
+
|
90
|
+
|
91
|
+
Let's try The Saudis - an "on-blockchain" pixel art collection -
|
92
|
+
with the token contract / service at [0xe21ebcd28d37a67757b9bc7b290f4c4928a430b1](https://etherscan.io/address/0xe21ebcd28d37a67757b9bc7b290f4c4928a430b1):
|
93
|
+
|
94
|
+
```
|
95
|
+
$ artq 0xe21ebcd28d37a67757b9bc7b290f4c4928a430b1 # or
|
96
|
+
$ artq 0xe21ebcd28d37a67757b9bc7b290f4c4928a430b1 info
|
97
|
+
```
|
98
|
+
|
99
|
+
resulting in:
|
100
|
+
|
101
|
+
```
|
102
|
+
name: >The Saudis<
|
103
|
+
symbol: >SAUD<
|
104
|
+
totalSupply: >5555<
|
105
|
+
|
106
|
+
tokenURIs 0..2:
|
107
|
+
tokenId 0:
|
108
|
+
```
|
109
|
+
``` json
|
110
|
+
{"name":"The Saudis #0",
|
111
|
+
"description":"Max Bidding",
|
112
|
+
"image_data": "...",
|
113
|
+
"external_url":"https://token.thesaudisnft.com/0",
|
114
|
+
"attributes":
|
115
|
+
[{"trait_type":"Head", "value":"Light 1"},
|
116
|
+
{"trait_type":"Hair", "value":"Bald"},
|
117
|
+
{"trait_type":"Facial Hair", "value":"Normal Brown Beard & Mustache"},
|
118
|
+
{"trait_type":"Headwear", "value":"Haram Police Cap"},
|
119
|
+
{"trait_type":"Eyewear", "value":"Regular Pixel Shades"},
|
120
|
+
{"trait_type":"Mouthpiece", "value":"None"}]}
|
121
|
+
```
|
122
|
+
|
123
|
+
```
|
124
|
+
tokenId 1:
|
125
|
+
```
|
126
|
+
``` json
|
127
|
+
{"name":"The Saudis #1",
|
128
|
+
"description":"Max Bidding",
|
129
|
+
"image_data": "...",
|
130
|
+
"external_url":"https://token.thesaudisnft.com/1",
|
131
|
+
"attributes":
|
132
|
+
[{"trait_type":"Head", "value":"Light 1"},
|
133
|
+
{"trait_type":"Hair", "value":"Long Widow's Peak"},
|
134
|
+
{"trait_type":"Facial Hair", "value":"Messy White Beard"},
|
135
|
+
{"trait_type":"Headwear", "value":"Brown Shemagh & Agal"},
|
136
|
+
{"trait_type":"Eyewear", "value":"Big Purple Shades"},
|
137
|
+
{"trait_type":"Mouthpiece", "value":"None"}]}
|
138
|
+
```
|
139
|
+
|
140
|
+
```
|
141
|
+
tokenId 2:
|
142
|
+
```
|
143
|
+
``` json
|
144
|
+
{"name":"The Saudis #2 🛢" ,
|
145
|
+
"description":"Max Bidding",
|
146
|
+
"image_data": "...",
|
147
|
+
"external_url":"https://token.thesaudisnft.com/2",
|
148
|
+
"attributes":
|
149
|
+
[{"trait_type":"Head", "value":"Dark 1"},
|
150
|
+
{"trait_type":"Hair", "value":"Short Buzz Cut"},
|
151
|
+
{"trait_type":"Facial Hair", "value":"Gradient Beard"},
|
152
|
+
{"trait_type":"Headwear", "value":"Brown Shemagh & Agal"},
|
153
|
+
{"trait_type":"Eyewear", "value":"Big Pixel Shades"},
|
154
|
+
{"trait_type":"Mouthpiece", "value":"Shadowless Vape"}]}
|
155
|
+
```
|
156
|
+
|
157
|
+
Note: The artq command-line tool "auto-magically"
|
158
|
+
decodes "on-blockchain" metadata in the base64 format
|
159
|
+
and inline svg images in the base64 format get "cut" from the metadata and "pasted" decoded. Example for tokenId 0, that is, The Saudis #0:
|
160
|
+
|
161
|
+
``` xml
|
162
|
+
<svg xmlns="http://www.w3.org/2000/svg"
|
163
|
+
xmlns:xlink="http://www.w3.org/1999/xlink" image-rendering="pixelated"
|
164
|
+
height="336" width="336">
|
165
|
+
<foreignObject x="0" y="0" width="336" height="336">
|
166
|
+
<img xmlns="http://www.w3.org/1999/xhtml" height="336" width="336" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAJklEQVR42mNMbp32n4GGgHHUglELRi0YtWDUglELRi0YtWBoWAAAuD470bkESf4AAAAASUVORK5CYII="/>
|
167
|
+
</foreignObject>
|
168
|
+
<foreignObject x="0" y="0" width="336" height="336">
|
169
|
+
<img xmlns="http://www.w3.org/1999/xhtml" height="336" width="336" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAtklEQVRIiWNgGAXDHjASoeY/JfoJKfh/e2MDTklV/waCZjARYziPlCZWBVB5fD7EawHc8C/PrhNSRr4FlBhOlAWUAhZiFK3fdYqBgYGBIdDNDEOMEMDlA5TUUzZ1G4OHkRiKAmxi2ABRPsCWVPElX2SAMw6gaZxigMsCnJlH1b+BJMuJCiJkQGzQwMDgSKYwgBw0xPqEJAtIDR4GBgqDiJjSlGgf4Eg5BOsDUlMRMRUUCqB5KgIAHCwuITa/cDMAAAAASUVORK5CYII=" />
|
170
|
+
</foreignObject>
|
171
|
+
<foreignObject x="0" y="0" width="336" height="336">
|
172
|
+
<img xmlns="http://www.w3.org/1999/xhtml" height="336" width="336" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAaklEQVRIiWNgGAWjYBSMglEwBAAjPklnSZb/DAwMjDzCAgwbr7whywImItT8//L2Az4H4AUsRDqEoEGUWsDgLMnC8JSJFc6X/vebKhYwMiC5nlhDkQExcYAvIeBNJMRaADMI3TCChg8PAADd6xC7QG7FfAAAAABJRU5ErkJggg==" />
|
173
|
+
</foreignObject>
|
174
|
+
<foreignObject x="0" y="0" width="336" height="336">
|
175
|
+
<img xmlns="http://www.w3.org/1999/xhtml" height="336" width="336" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAcElEQVRIie2SsQ3AIAwEnwzDCpTMyiguGQEvQ5okQoljTNL6KiSes3gAnAlhIdu/nNOCoxAx5WvNlcyet40+CmccA0XXZrbcaIXQyuMm5gFBqEGEKyGmvFwRMLzBrC6tIvU3nGKu1LWcxeU4juP8YQdbhhrmTahwzwAAAABJRU5ErkJggg==" />
|
176
|
+
</foreignObject>
|
177
|
+
<foreignObject x="0" y="0" width="336" height="336">
|
178
|
+
<img xmlns="http://www.w3.org/1999/xhtml" height="336" width="336" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAATElEQVRIie2PwQoAIAhDXf//z+tQgYnWoVOwdxHm2NRMCPE/qBYkDQBfs1rpDuEk10SmV5QFPmh+k3lSfTv0UDAM7hNfGvVbkRDiZzpfrCH/gTYLqwAAAABJRU5ErkJggg==" />
|
179
|
+
</foreignObject>
|
180
|
+
<foreignObject x="0" y="0" width="336" height="336">
|
181
|
+
<img xmlns="http://www.w3.org/1999/xhtml" height="336" width="336" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAJklEQVRIie3NMQEAAAjDMMC/52ECvlRA00nqs3m9AwAAAAAAAJy1C7oDLddyCRYAAAAASUVORK5CYII=" />
|
182
|
+
</foreignObject>
|
183
|
+
</svg>
|
184
|
+
```
|
185
|
+
|
186
|
+
|
187
|
+
#### Bonus - Case No. 3 - "On-Blockchain" Layers (Incl. Metadata & Images)
|
188
|
+
|
189
|
+
|
190
|
+
Note: Some "on-blockchain" pixel art collections
|
191
|
+
include all layers, that is, metadata and images
|
192
|
+
to compose / make-up "on-blockchain" token images "on-demand / on-the-fly" from "trait" building blocks from scratch.
|
193
|
+
|
194
|
+
|
195
|
+
If the contract uses / supports:
|
196
|
+
|
197
|
+
- `traitDetails(uint256 _layerIndex, uint256 _traitIndex) returns (string _name, string _mimetype, bool _hide)` and
|
198
|
+
- `traitData(uint256 _layerIndex, uint256 _traitIndex) returns string`
|
199
|
+
|
200
|
+
than you can "auto-magically" download all "on-blockchain" layers, that is, all metadata triplets by repeatedly calling `traitDetails` starting
|
201
|
+
with index `0/0`, `0/1`, ..., `1/0`, `1/1`, ... and so on e.g.
|
202
|
+
|
203
|
+
- `traitDetails( 0, 0 )` => `["Rainbow Puke", "image/png", false]`
|
204
|
+
- `traitDetails( 0, 1 )` => `["Bubble Gum", "image/png", false]`
|
205
|
+
- ...
|
206
|
+
- `traitDetails( 1, 0 )` => `["Gold Chain", "image/png", false]`
|
207
|
+
- `traitDetails( 2, 1 )` => `["Bowtie", "image/png", false]`
|
208
|
+
- ...
|
209
|
+
|
210
|
+
|
211
|
+
and all images (as binary blobs) by calling `traitData` e.g.
|
212
|
+
|
213
|
+
- `traitData( 0, 0 )` => `"\x89PNG..."`
|
214
|
+
- `traitData( 0, 1 )` => `"\x89PNG..."`
|
215
|
+
- ...
|
216
|
+
|
217
|
+
and so on.
|
218
|
+
|
219
|
+
|
220
|
+
|
221
|
+
Let's try Mad Camels - an "on-blockchain" pixel art collection -
|
222
|
+
with the token contract / service at [0xad8474ba5a7f6abc52708f171f57fefc5cdc8c1c](https://etherscan.io/address/0xad8474ba5a7f6abc52708f171f57fefc5cdc8c1c):
|
223
|
+
|
224
|
+
|
225
|
+
```
|
226
|
+
$ artq 0xad8474ba5a7f6abc52708f171f57fefc5cdc8c1c layers
|
227
|
+
```
|
228
|
+
|
229
|
+
resulting in a temp(orary) directory holding
|
230
|
+
all images:
|
231
|
+
|
232
|
+
```
|
233
|
+
/0_0.png
|
234
|
+
0_1.png
|
235
|
+
...
|
236
|
+
1_0.png
|
237
|
+
1_1.png
|
238
|
+
...
|
239
|
+
```
|
240
|
+
|
241
|
+

|
242
|
+
|
243
|
+
|
244
|
+
and a datafile with all metadata in the comma-separated values (csv) format, that is, `layers.csv` e.g:
|
245
|
+
|
246
|
+
```
|
247
|
+
index, name, type, hide
|
248
|
+
0/0, Rainbow Puke, image/png, false
|
249
|
+
0/1, Bubble Gum, image/png, false
|
250
|
+
0/2, Vape, image/png, false
|
251
|
+
0/3, None, image/png, false
|
252
|
+
0/4, Cigarette, image/png, false
|
253
|
+
0/5, Pipe, image/png, false
|
254
|
+
1/0, Gold Chain, image/png, false
|
255
|
+
1/1, Bowtie, image/png, false
|
256
|
+
1/2, Gold Necklace, image/png, false
|
257
|
+
1/3, None, image/png, false
|
258
|
+
2/0, Eye Patch, image/png, false
|
259
|
+
2/1, Nerd Glasses, image/png, false
|
260
|
+
2/2, Blue Beams, image/png, false
|
261
|
+
2/3, Purple Eye Shadow, image/png, false
|
262
|
+
2/4, Gold Glasses, image/png, false
|
263
|
+
2/5, Holographic, image/png, false
|
264
|
+
2/6, Clown Eyes Red, image/png, false
|
265
|
+
2/7, Clown Eyes Green, image/png, false
|
266
|
+
2/8, Eye Mask, image/png, false
|
267
|
+
2/9, Laser Eye, image/png, false
|
268
|
+
2/10, VR, image/png, false
|
269
|
+
2/11, 3D Glasses, image/png, false
|
270
|
+
2/12, None, image/png, false
|
271
|
+
2/13, Yellow Glasses, image/png, false
|
272
|
+
2/14, Cool Glasses, image/png, false
|
273
|
+
2/15, Purple Glasses, image/png, false
|
274
|
+
2/16, Green Glasses, image/png, false
|
275
|
+
3/0, Diamond, image/png, false
|
276
|
+
3/1, Silver, image/png, false
|
277
|
+
3/2, Gold, image/png, false
|
278
|
+
3/3, None, image/png, false
|
279
|
+
4/0, Crown, image/png, false
|
280
|
+
4/1, Wireless Earphone, image/png, false
|
281
|
+
4/2, Flower, image/png, false
|
282
|
+
4/3, Fez, image/png, false
|
283
|
+
4/4, Fire, image/png, false
|
284
|
+
4/5, Beanie, image/png, false
|
285
|
+
4/6, Headphone, image/png, false
|
286
|
+
4/7, White Shemagh, image/png, false
|
287
|
+
4/8, Red And White Shemagh, image/png, false
|
288
|
+
4/9, Angle Ring, image/png, false
|
289
|
+
4/10, Blue Mohawk, image/png, false
|
290
|
+
4/11, Sombrero, image/png, false
|
291
|
+
4/12, Red Mohawk, image/png, false
|
292
|
+
4/13, Blue Bandana, image/png, false
|
293
|
+
4/14, Viking, image/png, false
|
294
|
+
4/15, Pilot Helmet, image/png, false
|
295
|
+
4/16, Top Hat, image/png, false
|
296
|
+
4/17, Captain Hat, image/png, false
|
297
|
+
4/18, Thief Hat, image/png, false
|
298
|
+
4/19, Orange Cap, image/png, false
|
299
|
+
4/20, Pirate Bandana, image/png, false
|
300
|
+
4/21, Knitted Cap, image/png, false
|
301
|
+
4/22, Purple Cap, image/png, false
|
302
|
+
4/23, Black Cap, image/png, false
|
303
|
+
4/24, Pirate Hat, image/png, false
|
304
|
+
4/25, None, image/png, false
|
305
|
+
4/26, Red Cap, image/png, false
|
306
|
+
4/27, Cop Hat, image/png, false
|
307
|
+
4/28, Cowboy Hat, image/png, false
|
308
|
+
4/29, Fedora, image/png, false
|
309
|
+
5/0, Mole, image/png, false
|
310
|
+
5/1, Pimple, image/png, false
|
311
|
+
5/2, None, image/png, false
|
312
|
+
6/0, Gold, image/png, false
|
313
|
+
6/1, Cyborg, image/png, false
|
314
|
+
6/2, Skeleton, image/png, false
|
315
|
+
6/3, Female, image/png, false
|
316
|
+
6/4, Robot, image/png, false
|
317
|
+
6/5, Zombie, image/png, false
|
318
|
+
6/6, Alien, image/png, false
|
319
|
+
6/7, Default, image/png, false
|
320
|
+
7/0, Desert, image/png, false
|
321
|
+
7/1, Cream, image/png, false
|
322
|
+
7/2, Pink, image/png, false
|
323
|
+
7/3, Purple, image/png, false
|
324
|
+
7/4, Green, image/png, false
|
325
|
+
7/5, Blue, image/png, false
|
326
|
+
```
|
327
|
+
|
328
|
+
|
329
|
+
Try some more art collections with "on-blockchain" layers
|
330
|
+
such as
|
331
|
+
[Long Live Kevin](https://etherscan.io/address/0x8ae5523f76a5711fb6bdca1566df3f4707aec1c4),
|
332
|
+
[Aliens vs Punks](https://etherscan.io/address/0x2612c0375c47ee510a1663169288f2e9eb912947),
|
333
|
+
[Chi Chis](https://etherscan.io/address/0x2204a94f96d39df3b6bc0298cf068c8c82dc8d61),
|
334
|
+
[Chopper](https://etherscan.io/address/0x090c8034e6706994945049e0ede1bbdf21498e6e),
|
335
|
+
[Inverse Punks](https://etherscan.io/address/0xf3a1befc9643f94551c24a766afb87383ef64dd4),
|
336
|
+
[Marcs](https://etherscan.io/address/0xe9b91d537c3aa5a3fa87275fbd2e4feaaed69bd0),
|
337
|
+
[Phunk Ape Origins](https://etherscan.io/address/0x9b66d03fc1eee61a512341058e95f1a68dc3a913),
|
338
|
+
[Punkin Spicies](https://etherscan.io/address/0x34625ecaa75c0ea33733a05c584f4cf112c10b6b),
|
339
|
+
and many more.
|
340
|
+
|
341
|
+
|
342
|
+
Tip: See the [**Art Factory Sandbox**](https://github.com/pixelartexchange/artfactory.sandbox) for more art collections with "on-blockchain" layers.
|
343
|
+
|
344
|
+
|
16
345
|
|
17
346
|
|
18
347
|
## License
|
data/Rakefile
CHANGED
@@ -6,7 +6,7 @@ Hoe.spec 'artq' do
|
|
6
6
|
|
7
7
|
self.version = ArtQ::VERSION
|
8
8
|
|
9
|
-
self.summary = "artq - query (ethereum) blockchain contracts / services for data about art collections via json-rpc"
|
9
|
+
self.summary = "artq - query (ethereum) blockchain contracts / services for (meta) data about art collections via json-rpc"
|
10
10
|
self.description = summary
|
11
11
|
|
12
12
|
self.urls = { home: 'https://github.com/pixelartexchange/artbase' }
|
data/bin/artq
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
###################
|
4
|
+
# == DEV TIPS:
|
5
|
+
#
|
6
|
+
# For local testing run like:
|
7
|
+
#
|
8
|
+
# ruby -Ilib bin/artq
|
9
|
+
#
|
10
|
+
# Set the executable bit in Linux. Example:
|
11
|
+
#
|
12
|
+
# % chmod a+x bin/artq
|
13
|
+
#
|
14
|
+
|
15
|
+
require 'artq'
|
16
|
+
|
17
|
+
ArtQ::Tool.main
|
@@ -0,0 +1,73 @@
|
|
1
|
+
|
2
|
+
module ArtQ
|
3
|
+
|
4
|
+
|
5
|
+
class Contract
|
6
|
+
|
7
|
+
## auto-add "well-known" methods for contract methods - why? why not?
|
8
|
+
METHODS = {
|
9
|
+
name: { inputs: [],
|
10
|
+
outputs: ['string'] },
|
11
|
+
symbol: { inputs: [],
|
12
|
+
outputs: ['string'] },
|
13
|
+
totalSupply: { inputs: [],
|
14
|
+
outputs: ['uint256'] },
|
15
|
+
tokenURI: { inputs: ['uint256'],
|
16
|
+
outputs: ['string'] },
|
17
|
+
|
18
|
+
traitData: { inputs: ['uint256', 'uint256'],
|
19
|
+
outputs: ['string'] },
|
20
|
+
traitDetails: { inputs: ['uint256', 'uint256'],
|
21
|
+
outputs: ['(string,string,bool)'] },
|
22
|
+
}
|
23
|
+
|
24
|
+
|
25
|
+
METHODS.each do |name, m|
|
26
|
+
eth = Ethlite::ContractMethod.new( name.to_s,
|
27
|
+
inputs: m[:inputs],
|
28
|
+
outputs: m[:outputs] )
|
29
|
+
|
30
|
+
arity = m[:inputs].size
|
31
|
+
if arity == 0
|
32
|
+
define_method( name ) do
|
33
|
+
args = []
|
34
|
+
_do_call( eth, args )
|
35
|
+
end
|
36
|
+
elsif arity == 1
|
37
|
+
define_method( name ) do |arg0|
|
38
|
+
args = [arg0]
|
39
|
+
_do_call( eth, args )
|
40
|
+
end
|
41
|
+
elsif arity == 2
|
42
|
+
define_method( name ) do |arg0,arg1|
|
43
|
+
args = [arg0, arg1]
|
44
|
+
_do_call( eth, args )
|
45
|
+
end
|
46
|
+
else
|
47
|
+
raise ArgumentError, "unsupported no. of arguments #{m[:inputs]} (arity); sorry"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
def self.rpc
|
54
|
+
@rpc ||= JsonRpc.new( ENV['INFURA_URI'] )
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.rpc=(value)
|
58
|
+
@rpc = value.is_a?( String ) ? JsonRpc.new( value ) : value
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
def initialize( contract_address )
|
63
|
+
@contract_address = contract_address
|
64
|
+
end
|
65
|
+
|
66
|
+
######
|
67
|
+
# private helper to call method
|
68
|
+
def _do_call( eth, args )
|
69
|
+
eth.do_call( self.class.rpc, @contract_address, args )
|
70
|
+
end
|
71
|
+
|
72
|
+
end # class Contract
|
73
|
+
end # module ArtQ
|
data/lib/artq/version.rb
CHANGED
data/lib/artq.rb
CHANGED
@@ -1,6 +1,214 @@
|
|
1
|
+
require 'ethlite'
|
2
|
+
require 'optparse'
|
1
3
|
|
2
4
|
|
3
|
-
|
5
|
+
## our own code
|
6
|
+
require_relative 'artq/version' # let version go first
|
7
|
+
require_relative 'artq/contract'
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
module ArtQ
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
|
17
|
+
class Tool
|
18
|
+
|
19
|
+
def self.main( args=ARGV )
|
20
|
+
puts "==> welcome to artq tool with args:"
|
21
|
+
pp args
|
22
|
+
|
23
|
+
options = {
|
24
|
+
}
|
25
|
+
|
26
|
+
parser = OptionParser.new do |opts|
|
27
|
+
|
28
|
+
opts.on("--rpc STRING",
|
29
|
+
"JSON RPC Host (default: nil)") do |str|
|
30
|
+
options[ :rpc] = str
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.on("-h", "--help", "Prints this help") do
|
34
|
+
puts opts
|
35
|
+
exit
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
parser.parse!( args )
|
40
|
+
puts "options:"
|
41
|
+
pp options
|
42
|
+
|
43
|
+
puts "args:"
|
44
|
+
pp args
|
45
|
+
|
46
|
+
if args.size < 1
|
47
|
+
puts "!! ERROR - no collection found - use <collection> <command>..."
|
48
|
+
puts ""
|
49
|
+
exit
|
50
|
+
end
|
51
|
+
|
52
|
+
contract_address = args[0] ## todo/check - use collection_name/slug or such?
|
53
|
+
command = args[1] || 'info'
|
54
|
+
|
55
|
+
|
56
|
+
if ['i','inf','info'].include?( command )
|
57
|
+
do_info( contract_address )
|
58
|
+
elsif ['l', 'layer', 'layers'].include?( command )
|
59
|
+
do_layers( contract_address )
|
60
|
+
else
|
61
|
+
puts "!! ERROR - unknown command >#{command}<, sorry"
|
62
|
+
end
|
63
|
+
|
64
|
+
puts "bye"
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
def self.do_info( contract_address )
|
72
|
+
puts "==> query info for art collection contract @ >#{contract_address}<:"
|
73
|
+
|
74
|
+
c = Contract.new( contract_address )
|
75
|
+
|
76
|
+
name = c.name
|
77
|
+
symbol = c.symbol
|
78
|
+
totalSupply = c.totalSupply
|
79
|
+
|
80
|
+
meta = []
|
81
|
+
tokenIds = (0..2)
|
82
|
+
tokenIds.each do |tokenId|
|
83
|
+
tokenURI = c.tokenURI( tokenId )
|
84
|
+
meta << [tokenId, tokenURI]
|
85
|
+
end
|
86
|
+
|
87
|
+
puts " name: >#{name}<"
|
88
|
+
puts " symbol: >#{symbol}<"
|
89
|
+
puts " totalSupply: >#{totalSupply}<"
|
90
|
+
puts
|
91
|
+
puts " tokenURIs #{tokenIds}:"
|
92
|
+
meta.each do |tokenId, tokenURI|
|
93
|
+
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
|
113
|
+
puts
|
114
|
+
puts " image_data:"
|
115
|
+
puts image_data
|
116
|
+
else
|
117
|
+
puts " #{tokenURI}"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
|
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 )
|
131
|
+
puts "==> query layers for art collection contract @ >#{contract_address}<:"
|
132
|
+
|
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
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
end # class Tool
|
210
|
+
end # module ArtQ
|
211
|
+
|
4
212
|
|
5
213
|
|
6
214
|
|
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.0
|
4
|
+
version: 0.2.0
|
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-
|
11
|
+
date: 2022-11-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ethlite
|
@@ -58,10 +58,11 @@ dependencies:
|
|
58
58
|
- - "~>"
|
59
59
|
- !ruby/object:Gem::Version
|
60
60
|
version: '3.23'
|
61
|
-
description: artq - query (ethereum) blockchain contracts / services for data
|
62
|
-
art collections via json-rpc
|
61
|
+
description: artq - query (ethereum) blockchain contracts / services for (meta) data
|
62
|
+
about art collections via json-rpc
|
63
63
|
email: wwwmake@googlegroups.com
|
64
|
-
executables:
|
64
|
+
executables:
|
65
|
+
- artq
|
65
66
|
extensions: []
|
66
67
|
extra_rdoc_files:
|
67
68
|
- CHANGELOG.md
|
@@ -72,7 +73,9 @@ files:
|
|
72
73
|
- Manifest.txt
|
73
74
|
- README.md
|
74
75
|
- Rakefile
|
76
|
+
- bin/artq
|
75
77
|
- lib/artq.rb
|
78
|
+
- lib/artq/contract.rb
|
76
79
|
- lib/artq/version.rb
|
77
80
|
homepage: https://github.com/pixelartexchange/artbase
|
78
81
|
licenses:
|
@@ -98,6 +101,6 @@ requirements: []
|
|
98
101
|
rubygems_version: 3.3.7
|
99
102
|
signing_key:
|
100
103
|
specification_version: 4
|
101
|
-
summary: artq - query (ethereum) blockchain contracts / services for data about
|
102
|
-
collections via json-rpc
|
104
|
+
summary: artq - query (ethereum) blockchain contracts / services for (meta) data about
|
105
|
+
art collections via json-rpc
|
103
106
|
test_files: []
|