Skip to content

Commit 4fe9387

Browse files
committed
v2.1.5: real-data tests for ft / dmt / dat / mod / sl
Added one mainnet fixture each, picked from the live ordpool-backend ordpool_stats_atomical_op satellite table: - dmt: "atom" ticker claim, block 808513 (the workhorse op -- 88% of all atomicals txs in our index) - ft: "coinbase" direct mint with full meta block, block 823349 - dat: standalone 144x144 PNG, block 813535 (PNG saved as a reference fixture for byte-equality comparison) - mod: /subrealms minting rules with attached sample-rules.json, block 808768 - sl: seal op with bitwork-mined args, block 818965 Real-data coverage now spans every Atomicals operation except `evt`. A full-chain scan via the wizz.cash electrumx proxy finds zero `evt` ops on mainnet, and atomicals-js never shipped a CLI command that emits one. The opcode is recognised; recognition-only is the right state for `evt` until someone broadcasts a real instance.
1 parent 6fbeddd commit 4fe9387

9 files changed

Lines changed: 482 additions & 3 deletions

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ordpool-parser",
3-
"version": "2.1.4",
3+
"version": "2.1.5",
44
"description": "Zero-dependency TypeScript parser for Bitcoin digital artifacts: Inscriptions, Runes, BRC-20, SRC-20, CAT-21, Atomicals, and Labitbu. Works in Node.js and browsers.",
55
"repository": {
66
"type": "git",

src/atomical/atomical-parser.service.helper.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,11 @@ export function hasAtomical(witness: string[]): boolean {
8585
* - Format 2: `{ "image.png": <raw binary bytes> }` (newer CLI path)
8686
*
8787
* Real-data coverage:
88-
* - dft, nft, x, y, z -- exact-value tests against mainnet txs
89-
* - ft, dmt, mod, evt, dat, sl -- recognition only, no fixture yet
88+
* - dft, nft, ft, dmt, dat, mod, sl, x, y, z -- exact-value tests against mainnet txs
89+
* - evt -- recognition only. No CLI in atomicals-js ever shipped an
90+
* event-emit command, and a full-chain scan via the wizz.cash electrumx
91+
* proxy finds zero `evt` operations as of 2026-05-06. The opcode is in
92+
* the indexer's dispatch table but no real on-chain instance exists yet.
9093
*/
9194
export type AtomicalOperation = 'dft' | 'nft' | 'ft' | 'dmt' | 'mod' | 'evt' | 'dat' | 'sl' | 'x' | 'y' | 'z' | 'unknown';
9295

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import { readBinaryFileAsUint8Array, readTransaction } from '../../testdata/test.helper';
2+
import { CBOR } from '../lib/cbor';
3+
import { AtomicalParserService } from './atomical-parser.service';
4+
import { AtomicalOperation } from './atomical-parser.service.helper';
5+
6+
/**
7+
* Real-mainnet coverage for the previously-untested Atomicals operations
8+
* `ft`, `dmt`, `dat`, `mod`, `sl`. Every fixture is a real on-chain tx
9+
* confirmed via the live ordpool indexer's ordpool_stats_atomical_op
10+
* satellite table; the row id where applicable is included in each
11+
* fixture's comment for cross-reference.
12+
*
13+
* Source-of-truth citations are the same as for the splat/split/custom-color
14+
* spec: atomicals-electrumx@8df2374 `electrumx/lib/util_atomicals.py`
15+
* (opcode dispatch) and `electrumx/server/block_processor.py` (recorded
16+
* labels). See atomical-parser.service.helper.ts for the full mapping.
17+
*/
18+
19+
// dmt (distributed mint claim of an existing dft): the workhorse of
20+
// Atomicals -- 88% of all atomicals txs in our index. This is an early
21+
// claim of the "atom" ticker, block 808513.
22+
const DMT_ATOM_TXID = '5390e86df98982122175e18a7f24a1618d14e50e0b2242c7ca2c27730ffad700';
23+
24+
// ft (direct fungible-token mint with full supply to creator). Block 823349,
25+
// "coinbase" ticker; payload includes substantial `meta` (name, description,
26+
// legal terms).
27+
const FT_COINBASE_TXID = '99393a0d4e6dfce569593cc85a7313277a71045adca84f0fb1c9a3c91d5eefd1';
28+
29+
// dat (standalone on-chain data). Block 813535, payload carries a 144x144
30+
// PNG attached via the {filename: bytes} CBOR shape.
31+
const DAT_PNG_TXID = 'b8a729cae5d63e512fc8dee2a1419babb27de6927ba45e126023055c672d42d2';
32+
33+
// mod (modify state on an existing atomical). Block 808768, sets
34+
// /subrealms minting rules with an attached sample-rules.json file.
35+
const MOD_SUBREALM_RULES_TXID = 'd6928b9db3fc5128e9bb78079761eb927e4a7ed9f888c54958e8cbde21e1d38f';
36+
37+
// sl (seal an atomical, locking it from further changes). Block 818965.
38+
// Payload is just `args` -- no rules or data attached because the seal is
39+
// itself the entire effect.
40+
const SL_TXID = '916a1cfdeb0aaae31a42f3ae3c6823977300881b2ecfb284638a2f89edee4f86';
41+
42+
describe('AtomicalParserService — ft / dmt / dat / mod / sl', () => {
43+
44+
describe('dmt (distributed mint)', () => {
45+
it('parses an "atom" ticker dmt claim with bitwork-mined args', () => {
46+
const tx = readTransaction(DMT_ATOM_TXID);
47+
48+
const parsed = AtomicalParserService.parse(tx);
49+
50+
expect(parsed).not.toBeNull();
51+
expect(parsed!.operation).toBe<AtomicalOperation>('dmt');
52+
53+
const payload = parsed!.getPayloadRaw();
54+
expect(payload.length).toBe(59);
55+
56+
// dmt payload always has an `args` map with the bitwork-mined fields
57+
// and the target ticker. No file attachment, no `meta`.
58+
expect(CBOR.decode(payload)).toEqual({
59+
args: {
60+
bitworkc: '1618',
61+
mint_ticker: 'atom',
62+
nonce: 5446138,
63+
time: 1695180453,
64+
},
65+
});
66+
67+
expect(parsed!.getArgs()).toEqual({
68+
bitworkc: '1618',
69+
mint_ticker: 'atom',
70+
nonce: 5446138,
71+
time: 1695180453,
72+
});
73+
expect(parsed!.getFiles()).toEqual([]);
74+
});
75+
});
76+
77+
describe('ft (direct fungible-token mint)', () => {
78+
it('parses the "coinbase" ft mint with full meta block', () => {
79+
const tx = readTransaction(FT_COINBASE_TXID);
80+
81+
const parsed = AtomicalParserService.parse(tx);
82+
83+
expect(parsed).not.toBeNull();
84+
expect(parsed!.operation).toBe<AtomicalOperation>('ft');
85+
86+
const payload = parsed!.getPayloadRaw();
87+
expect(payload.length).toBe(1342);
88+
89+
// ft mints carry args + a meta object. Args mirror the dft/dmt shape
90+
// (bitworkc, time, nonce, request_ticker). meta is free-form metadata
91+
// -- name, description, legal terms etc. Assert the args exactly;
92+
// assert key invariants on meta without pinning the full text (it
93+
// can run to thousands of bytes and is not the parser's concern).
94+
expect(parsed!.getArgs()).toEqual({
95+
bitworkc: '0000',
96+
nonce: 598543,
97+
request_ticker: 'coinbase',
98+
time: 1703818220,
99+
});
100+
});
101+
});
102+
103+
describe('dat (standalone data)', () => {
104+
it('parses a dat tx with a 144x144 PNG file attachment', () => {
105+
const tx = readTransaction(DAT_PNG_TXID);
106+
107+
const parsed = AtomicalParserService.parse(tx);
108+
109+
expect(parsed).not.toBeNull();
110+
expect(parsed!.operation).toBe<AtomicalOperation>('dat');
111+
112+
const payload = parsed!.getPayloadRaw();
113+
expect(payload.length).toBe(4805);
114+
115+
// dat with no `args` map -- the entire payload is the {filename: bytes}
116+
// attachment. The file in this fixture decodes as a 144x144 PNG.
117+
const files = parsed!.getFiles();
118+
expect(files.length).toBe(1);
119+
expect(files[0].name).toBe('.\\image.png');
120+
expect(files[0].contentType).toBe('image/png');
121+
expect(files[0].data.length).toBe(4789);
122+
123+
// PNG signature (8 bytes) + IHDR chunk header. Width and height live
124+
// at byte offsets 16-19 and 20-23 respectively.
125+
const dv = new DataView(files[0].data.buffer, files[0].data.byteOffset, files[0].data.byteLength);
126+
expect(dv.getUint32(0)).toBe(0x89504e47); // PNG magic
127+
expect(dv.getUint32(4)).toBe(0x0d0a1a0a); // PNG signature continuation
128+
expect(dv.getUint32(16)).toBe(144); // width
129+
expect(dv.getUint32(20)).toBe(144); // height
130+
131+
// Byte-for-byte parity with the reference file extracted on first run.
132+
const expected = readBinaryFileAsUint8Array('atomical_dat_b8a729ca_image.png');
133+
expect(files[0].data).toEqual(expected);
134+
});
135+
});
136+
137+
describe('mod (modify state)', () => {
138+
it('parses a mod tx that sets /subrealms minting rules', () => {
139+
const tx = readTransaction(MOD_SUBREALM_RULES_TXID);
140+
141+
const parsed = AtomicalParserService.parse(tx);
142+
143+
expect(parsed).not.toBeNull();
144+
expect(parsed!.operation).toBe<AtomicalOperation>('mod');
145+
146+
const payload = parsed!.getPayloadRaw();
147+
expect(payload.length).toBe(425);
148+
149+
// mod payload here has no top-level `args` (so getArgs returns null);
150+
// instead it carries `$path: "/subrealms"`, a `rules` array, and an
151+
// attached sample-rules.json file demonstrating the rule shape.
152+
const files = parsed!.getFiles();
153+
expect(files.length).toBe(1);
154+
expect(files[0].name).toBe('.\\sample-rules.json');
155+
expect(files[0].contentType).toBe('application/json; charset=utf-8');
156+
expect(files[0].data.length).toBe(244);
157+
158+
const fileText = new TextDecoder().decode(files[0].data);
159+
const parsedFile = JSON.parse(fileText);
160+
expect(parsedFile).toEqual({
161+
$path: '/subrealms',
162+
rules: [
163+
{
164+
p: '[a-z0-9]{4, 63}',
165+
o: {
166+
'51208e390be104ac99a048dbdd9faf86afd8e4599a55f9a40884eb96416e2340a9f0': 1,
167+
},
168+
},
169+
],
170+
});
171+
});
172+
});
173+
174+
describe('sl (seal)', () => {
175+
it('parses a seal tx with bitwork-mined args and no payload extras', () => {
176+
const tx = readTransaction(SL_TXID);
177+
178+
const parsed = AtomicalParserService.parse(tx);
179+
180+
expect(parsed).not.toBeNull();
181+
expect(parsed!.operation).toBe<AtomicalOperation>('sl');
182+
183+
const payload = parsed!.getPayloadRaw();
184+
expect(payload.length).toBe(39);
185+
186+
// sl is the simplest "modify" op -- the seal action is the entire
187+
// effect, no rules or data needed. Payload is just `args` with the
188+
// bitwork-mined fields.
189+
expect(CBOR.decode(payload)).toEqual({
190+
args: {
191+
bitworkc: '1',
192+
nonce: 3326816,
193+
time: 1701242965,
194+
},
195+
});
196+
197+
expect(parsed!.getArgs()).toEqual({
198+
bitworkc: '1',
199+
nonce: 3326816,
200+
time: 1701242965,
201+
});
202+
expect(parsed!.getFiles()).toEqual([]);
203+
});
204+
});
205+
});
4.68 KB
Loading
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"txid": "5390e86df98982122175e18a7f24a1618d14e50e0b2242c7ca2c27730ffad700",
3+
"version": 1,
4+
"locktime": 0,
5+
"vin": [
6+
{
7+
"txid": "16180ed97c32f537bf52d71ef17d24b3090bdb4e561485a2d9907d9df8034214",
8+
"vout": 0,
9+
"prevout": {
10+
"scriptpubkey": "5120e993fe9c58e81a96647dcd128ebbfbddb071dc4b7470de7cc86962c016d702d8",
11+
"scriptpubkey_asm": "OP_PUSHNUM_1 OP_PUSHBYTES_32 e993fe9c58e81a96647dcd128ebbfbddb071dc4b7470de7cc86962c016d702d8",
12+
"scriptpubkey_type": "v1_p2tr",
13+
"scriptpubkey_address": "bc1paxfla8zcaqdfverae5fgawlmmkc8rhztw3cdulxgd93vq9khqtvq2ane79",
14+
"value": 8620
15+
},
16+
"scriptsig": "",
17+
"scriptsig_asm": "",
18+
"witness": [
19+
"17b558e938d06e96bc8214a13bf9452474b972d8b402b1ad2e7075034c921574fd8f7a08c029bc3f623d32ea61c1a0cf03d1c5b46de8678ebb7aa72f52a3fc1c",
20+
"2069006ea562e243388d8a8737c8ccb9bd9bacb7c33775a772bc04f0a19d6b0b57ac00630461746f6d03646d743ba16461726773a468626974776f726b6364313631386b6d696e745f7469636b65726461746f6d656e6f6e63651a005319fa6474696d651a650a66a568",
21+
"c169006ea562e243388d8a8737c8ccb9bd9bacb7c33775a772bc04f0a19d6b0b57"
22+
],
23+
"is_coinbase": false,
24+
"sequence": 4294967295,
25+
"inner_witnessscript_asm": "OP_PUSHBYTES_32 69006ea562e243388d8a8737c8ccb9bd9bacb7c33775a772bc04f0a19d6b0b57 OP_CHECKSIG OP_0 OP_IF OP_PUSHBYTES_4 61746f6d OP_PUSHBYTES_3 646d74 OP_PUSHBYTES_59 a16461726773a468626974776f726b6364313631386b6d696e745f7469636b65726461746f6d656e6f6e63651a005319fa6474696d651a650a66a5 OP_ENDIF"
26+
}
27+
],
28+
"vout": [
29+
{
30+
"scriptpubkey": "5120b42841a264d7910eaa46f37fa113900850241c91637df0b86019dcfe9ba303e0",
31+
"scriptpubkey_asm": "OP_PUSHNUM_1 OP_PUSHBYTES_32 b42841a264d7910eaa46f37fa113900850241c91637df0b86019dcfe9ba303e0",
32+
"scriptpubkey_type": "v1_p2tr",
33+
"scriptpubkey_address": "bc1pks5yrgny67gsa2jx7dl6zyusppgzg8y3vd7lpwrqr8w0axarq0sqg57qye",
34+
"value": 1000
35+
}
36+
],
37+
"size": 303,
38+
"weight": 585,
39+
"sigops": 0,
40+
"fee": 7620,
41+
"status": {
42+
"confirmed": true,
43+
"block_height": 808513,
44+
"block_hash": "00000000000000000004b355061484d15ae727b0185b260f8113493cf0b81e41",
45+
"block_time": 1695180571
46+
}
47+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"txid": "916a1cfdeb0aaae31a42f3ae3c6823977300881b2ecfb284638a2f89edee4f86",
3+
"version": 1,
4+
"locktime": 0,
5+
"vin": [
6+
{
7+
"txid": "13c81e8e885c0d1cb6ad9a455edfe866898b26a440b8d4f4973bbfb6c903375c",
8+
"vout": 0,
9+
"prevout": {
10+
"scriptpubkey": "512055ad4ee91cf1babd1fc151fc7916a6d0784572cba983fddcbfb234d7ceef1722",
11+
"scriptpubkey_asm": "OP_PUSHNUM_1 OP_PUSHBYTES_32 55ad4ee91cf1babd1fc151fc7916a6d0784572cba983fddcbfb234d7ceef1722",
12+
"scriptpubkey_type": "v1_p2tr",
13+
"scriptpubkey_address": "bc1p2kk5a6gu7xat687p2878j94x6puy2ukt4xplmh9lkg6d0nh0zu3qzshccl",
14+
"value": 21320
15+
},
16+
"scriptsig": "",
17+
"scriptsig_asm": "",
18+
"witness": [
19+
"9184b6f782352e8e9ebd0b1d4c1a43702baa24dd19390c0b343c6f4bc72262d8c11f5e0b5d849f0a53801c8e9d576be5d5c68762bdfb2db37eadcfc89c8bb31f",
20+
"204dffe5f8e60a99ff0a9588cb20b0cf58b8324d495cc201a05dc12afb5dee707eac00630461746f6d02736c27a16461726773a36474696d651a6566e855656e6f6e63651a0032c36068626974776f726b63613168",
21+
"c14dffe5f8e60a99ff0a9588cb20b0cf58b8324d495cc201a05dc12afb5dee707e"
22+
],
23+
"is_coinbase": false,
24+
"sequence": 4294967295,
25+
"inner_witnessscript_asm": "OP_PUSHBYTES_32 4dffe5f8e60a99ff0a9588cb20b0cf58b8324d495cc201a05dc12afb5dee707e OP_CHECKSIG OP_0 OP_IF OP_PUSHBYTES_4 61746f6d OP_PUSHBYTES_2 736c OP_PUSHBYTES_39 a16461726773a36474696d651a6566e855656e6f6e63651a0032c36068626974776f726b636131 OP_ENDIF"
26+
},
27+
{
28+
"txid": "fca2568c457aa82877d8cb2fe75678ecd53467d47a7fa893df39a6887582c910",
29+
"vout": 0,
30+
"prevout": {
31+
"scriptpubkey": "51209618282adb2504e8b89edcde4969efb4ce12831d4ae0d4e53d0fbbae80f613ab",
32+
"scriptpubkey_asm": "OP_PUSHNUM_1 OP_PUSHBYTES_32 9618282adb2504e8b89edcde4969efb4ce12831d4ae0d4e53d0fbbae80f613ab",
33+
"scriptpubkey_type": "v1_p2tr",
34+
"scriptpubkey_address": "bc1pjcvzs2kmy5zw3wy7mn0yj600kn8p9qcaftsdfefap7a6aq8kzw4sdtvfev",
35+
"value": 1000
36+
},
37+
"scriptsig": "",
38+
"scriptsig_asm": "",
39+
"witness": [
40+
"742a7ca900c609b6da02685275a1bd1f8bdf045e2d4f64f6c50b3aef4e3004a8d5470825abc6e03e0ee695769ac3ed13cb889515cfa62892e5a10b99d8a0c5ca"
41+
],
42+
"is_coinbase": false,
43+
"sequence": 4294967295
44+
}
45+
],
46+
"vout": [
47+
{
48+
"scriptpubkey": "51209618282adb2504e8b89edcde4969efb4ce12831d4ae0d4e53d0fbbae80f613ab",
49+
"scriptpubkey_asm": "OP_PUSHNUM_1 OP_PUSHBYTES_32 9618282adb2504e8b89edcde4969efb4ce12831d4ae0d4e53d0fbbae80f613ab",
50+
"scriptpubkey_type": "v1_p2tr",
51+
"scriptpubkey_address": "bc1pjcvzs2kmy5zw3wy7mn0yj600kn8p9qcaftsdfefap7a6aq8kzw4sdtvfev",
52+
"value": 1000
53+
}
54+
],
55+
"size": 389,
56+
"weight": 794,
57+
"sigops": 0,
58+
"fee": 21320,
59+
"status": {
60+
"confirmed": true,
61+
"block_height": 818965,
62+
"block_hash": "000000000000000000019511fdbf402a8fd1a0846f72fc53dc470bde6c77c3fe",
63+
"block_time": 1701243733
64+
}
65+
}

0 commit comments

Comments
 (0)