@smartledger/bsv 7.2.0 → 7.3.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.
- package/CHANGELOG.md +92 -0
- package/README.md +38 -38
- package/bsv-gdaf.min.js +34 -34
- package/bsv-ltp.min.js +34 -34
- package/bsv-smartcontract.min.js +1 -1
- package/bsv.bundle.js +34 -34
- package/bsv.min.js +34 -34
- package/docs/MODULE_REFERENCE_COMPLETE.md +27 -27
- package/docs/advanced/UTXO_MANAGER_GUIDE.md +1 -1
- package/docs/getting-started/INSTALLATION.md +23 -23
- package/docs/getting-started/QUICK_START.md +7 -7
- package/docs/migration/FROM_BSV_1_5_6.md +5 -5
- package/lib/ordinals/README.md +37 -4
- package/lib/ordinals/bsv20.js +119 -12
- package/lib/ordinals/ordlock.js +27 -1
- package/package.json +1 -1
- package/test/ordinals/bsv20.js +98 -2
- package/test/ordinals/ordlock.js +71 -0
- package/version.js +1 -1
package/test/ordinals/bsv20.js
CHANGED
|
@@ -56,7 +56,9 @@ describe('Ordinals BSV-20 fungible tokens', function () {
|
|
|
56
56
|
})
|
|
57
57
|
|
|
58
58
|
it('preserves integer amounts larger than 2^53 exactly (string, never a JS number)', function () {
|
|
59
|
-
|
|
59
|
+
// uint64 max: far beyond 2^53, and the largest amount the spec allows. The value this
|
|
60
|
+
// used to test (26 digits) exceeded uint64 and would have been burned by an indexer.
|
|
61
|
+
var huge = '18446744073709551615'
|
|
60
62
|
var p = B.parseBsv20(B.buildMint({ address: address, tick: 'BIG', amt: huge }))
|
|
61
63
|
p.amt.should.equal(huge)
|
|
62
64
|
})
|
|
@@ -72,7 +74,7 @@ describe('Ordinals BSV-20 fungible tokens', function () {
|
|
|
72
74
|
B.parseBsv20(B.buildMint({ address: address, tick: 'ORDI', amt: '007' })).amt.should.equal('7')
|
|
73
75
|
B.parseBsv20(B.buildDeploy({ address: address, tick: 'ORDI', max: '000100' })).max.should.equal('100')
|
|
74
76
|
// canonicalization does not corrupt a huge value
|
|
75
|
-
var huge = '
|
|
77
|
+
var huge = '18446744073709551615'
|
|
76
78
|
B.parseBsv20(B.buildMint({ address: address, tick: 'ORDI', amt: '0' + huge })).amt.should.equal(huge)
|
|
77
79
|
})
|
|
78
80
|
|
|
@@ -123,4 +125,98 @@ describe('Ordinals BSV-20 fungible tokens', function () {
|
|
|
123
125
|
;(function () { B.buildMint({ tick: 'ORDI', amt: '1' }) }).should.throw(/address or a lock/)
|
|
124
126
|
})
|
|
125
127
|
})
|
|
128
|
+
|
|
129
|
+
// Checked against the BSV-20 / BSV-21 specification at docs.1satordinals.com.
|
|
130
|
+
describe('specification conformance', function () {
|
|
131
|
+
var OUTPOINT = '3b31'.repeat(16) + '_0'
|
|
132
|
+
var UINT64_MAX = '18446744073709551615'
|
|
133
|
+
|
|
134
|
+
it('accepts lim: 0, which the spec defines as unlimited', function () {
|
|
135
|
+
// "lim | No | String | Per-mint limit; 0 or omitted = unlimited"
|
|
136
|
+
B.parseBsv20(B.buildDeploy({ address: address, tick: 'ORDI', max: '21000000', lim: 0 }))
|
|
137
|
+
.lim.should.equal('0')
|
|
138
|
+
B.parseBsv20(B.buildDeploy({ address: address, tick: 'ORDI', max: '21000000', lim: '0' }))
|
|
139
|
+
.lim.should.equal('0')
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('accepts an amount of exactly uint64 max but rejects one above it', function () {
|
|
143
|
+
// "Amounts in BSV-21: strings representing uint64" — larger values are emitted
|
|
144
|
+
// happily as JSON and then discarded by indexers, burning the tokens.
|
|
145
|
+
B.parseBsv20(B.buildDeploy({ address: address, tick: 'ORDI', max: UINT64_MAX }))
|
|
146
|
+
.max.should.equal(UINT64_MAX)
|
|
147
|
+
;(function () {
|
|
148
|
+
B.buildDeploy({ address: address, tick: 'ORDI', max: '18446744073709551616' })
|
|
149
|
+
}).should.throw(/exceeds the uint64 maximum/)
|
|
150
|
+
;(function () {
|
|
151
|
+
B.buildMint({ address: address, tick: 'ORDI', amt: '9'.repeat(26) })
|
|
152
|
+
}).should.throw(/exceeds the uint64 maximum/)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('rejects a numeric amount past 2^53, which is already imprecise', function () {
|
|
156
|
+
(function () {
|
|
157
|
+
B.buildMint({ address: address, tick: 'ORDI', amt: 1e17 })
|
|
158
|
+
}).should.throw(/MAX_SAFE_INTEGER/)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('rejects non-string sym / icon instead of stringifying them', function () {
|
|
162
|
+
(function () {
|
|
163
|
+
B.buildDeployMint({ address: address, amt: '100', sym: {} })
|
|
164
|
+
}).should.throw(/sym must be a string, got an object/)
|
|
165
|
+
;(function () {
|
|
166
|
+
B.buildDeployMint({ address: address, amt: '100', icon: 42 })
|
|
167
|
+
}).should.throw(/icon must be a string/)
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it('requires icon to be an outpoint reference, as the spec defines it', function () {
|
|
171
|
+
(function () {
|
|
172
|
+
B.buildDeployMint({ address: address, amt: '100', icon: 'https://example.com/i.png' })
|
|
173
|
+
}).should.throw(/outpoint reference/)
|
|
174
|
+
B.parseBsv20(B.buildDeployMint({ address: address, amt: '100', icon: OUTPOINT }))
|
|
175
|
+
.icon.should.equal(OUTPOINT)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
// parseBsv20/isBsv20 are documented to report VALIDITY. They previously returned any
|
|
179
|
+
// JSON object carrying p:'bsv-20' and a string op, including payloads no indexer acts on.
|
|
180
|
+
describe('parseBsv20 enforces the validity it reports', function () {
|
|
181
|
+
function rejects (label, payload) {
|
|
182
|
+
it('rejects ' + label, function () {
|
|
183
|
+
var json = JSON.stringify(payload)
|
|
184
|
+
B.isBsv20(json).should.equal(false)
|
|
185
|
+
;(B.parseBsv20(json) === null).should.equal(true)
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
rejects('a transfer with no amount and no token', { p: 'bsv-20', op: 'transfer' })
|
|
189
|
+
rejects('a mint with no amount', { p: 'bsv-20', op: 'mint', tick: 'ORDI' })
|
|
190
|
+
rejects('a deploy with no max', { p: 'bsv-20', op: 'deploy', tick: 'ORDI' })
|
|
191
|
+
rejects('an operation the spec does not define', { p: 'bsv-20', op: 'not-an-op' })
|
|
192
|
+
rejects('a transfer naming both tick and id', { p: 'bsv-20', op: 'transfer', tick: 'A', id: OUTPOINT, amt: '1' })
|
|
193
|
+
rejects('an auth carrying amt, which the spec forbids', { p: 'bsv-20', op: 'auth', id: OUTPOINT, amt: '1' })
|
|
194
|
+
rejects('a deploy+auth carrying amt, which the spec forbids', { p: 'bsv-20', op: 'deploy+auth', amt: '1' })
|
|
195
|
+
rejects('a ticker longer than 4 bytes', { p: 'bsv-20', op: 'mint', tick: 'TOOLONG', amt: '1' })
|
|
196
|
+
rejects('a malformed token id', { p: 'bsv-20', op: 'transfer', id: 'nope', amt: '1' })
|
|
197
|
+
rejects('an amount above uint64', { p: 'bsv-20', op: 'mint', tick: 'A', amt: '9'.repeat(26) })
|
|
198
|
+
rejects('dec out of range', { p: 'bsv-20', op: 'deploy', tick: 'A', max: '1', dec: '19' })
|
|
199
|
+
|
|
200
|
+
it('accepts the spec operations this library does not yet emit', function () {
|
|
201
|
+
// Reading the chain is not the same as writing it: burn/auth/deploy+auth are valid
|
|
202
|
+
// BSV-21 and must be recognised even though there is no builder for them yet.
|
|
203
|
+
B.isBsv20(JSON.stringify({ p: 'bsv-20', op: 'burn', id: OUTPOINT, amt: '5' })).should.equal(true)
|
|
204
|
+
B.isBsv20(JSON.stringify({ p: 'bsv-20', op: 'auth', id: OUTPOINT })).should.equal(true)
|
|
205
|
+
B.isBsv20(JSON.stringify({ p: 'bsv-20', op: 'deploy+auth', sym: 'STABLE' })).should.equal(true)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it('tolerates a non-canonical on-chain amount when reading', function () {
|
|
209
|
+
// Our builder emits canonical amounts, but other people's payloads are not ours
|
|
210
|
+
// to reject over leading zeros.
|
|
211
|
+
B.isBsv20(JSON.stringify({ p: 'bsv-20', op: 'mint', tick: 'A', amt: '007' })).should.equal(true)
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it('still round-trips everything this library builds', function () {
|
|
215
|
+
B.isBsv20(B.buildDeploy({ address: address, tick: 'ORDI', max: '21000000' })).should.equal(true)
|
|
216
|
+
B.isBsv20(B.buildMint({ address: address, tick: 'ORDI', amt: '1' })).should.equal(true)
|
|
217
|
+
B.isBsv20(B.buildTransfer({ address: address, id: OUTPOINT, amt: '1' })).should.equal(true)
|
|
218
|
+
B.isBsv20(B.buildDeployMint({ address: address, amt: '1000' })).should.equal(true)
|
|
219
|
+
})
|
|
220
|
+
})
|
|
221
|
+
})
|
|
126
222
|
})
|
package/test/ordinals/ordlock.js
CHANGED
|
@@ -549,4 +549,75 @@ describe('Ordinals OrdLock — code-review regressions', function () {
|
|
|
549
549
|
})
|
|
550
550
|
Ord.parseOrdLock(lock).totalPrice.should.equal(777)
|
|
551
551
|
})
|
|
552
|
+
|
|
553
|
+
// parseOrdLock recovers terms from the script's shape. Shape alone proves nothing: a
|
|
554
|
+
// script wearing the same opcode arrangement without the OP_PUSH_TX covenant enforces no
|
|
555
|
+
// payment, and reporting a seller and a price for it invents a listing that does not
|
|
556
|
+
// exist. The terms are therefore verified by rebuilding the listing and comparing bytes.
|
|
557
|
+
describe('is a verifier, not a shape matcher', function () {
|
|
558
|
+
var attacker = bsv.PrivateKey.fromBuffer(Buffer.alloc(32, 9))
|
|
559
|
+
|
|
560
|
+
// Wears the fingerprint parseOrdLock keys off: a top-level OP_IF with a 20-byte push,
|
|
561
|
+
// and OP_TOALTSTACK <blob> OP_CAT OP_SWAP in the OP_ELSE branch. Enforces nothing.
|
|
562
|
+
function fabricatedListing () {
|
|
563
|
+
var payout = new bsv.Transaction.Output({
|
|
564
|
+
script: bsv.Script.buildPublicKeyHashOut(attacker.toAddress()),
|
|
565
|
+
satoshis: 100000000
|
|
566
|
+
})
|
|
567
|
+
var s = new bsv.Script()
|
|
568
|
+
s.add(bsv.Opcode.OP_IF)
|
|
569
|
+
s.add(attacker.toAddress().hashBuffer)
|
|
570
|
+
s.add(bsv.Opcode.OP_DROP)
|
|
571
|
+
s.add(bsv.Opcode.OP_ELSE)
|
|
572
|
+
s.add(bsv.Opcode.OP_TOALTSTACK)
|
|
573
|
+
s.add(payout.toBufferWriter().toBuffer())
|
|
574
|
+
s.add(bsv.Opcode.OP_CAT)
|
|
575
|
+
s.add(bsv.Opcode.OP_SWAP)
|
|
576
|
+
s.add(bsv.Opcode.OP_ENDIF)
|
|
577
|
+
s.add(bsv.Opcode.OP_TRUE)
|
|
578
|
+
return s
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
it('rejects a fabricated listing that enforces no payment', function () {
|
|
582
|
+
var fake = fabricatedListing()
|
|
583
|
+
Ord.isOrdLock(fake).should.equal(false)
|
|
584
|
+
;(Ord.parseOrdLock(fake) === null).should.equal(true)
|
|
585
|
+
})
|
|
586
|
+
|
|
587
|
+
it('proves the fabricated listing really is spendable for free', function () {
|
|
588
|
+
// Without this, the test above only shows we reject *something*.
|
|
589
|
+
var interp = new bsv.Script.Interpreter()
|
|
590
|
+
var unlock = new bsv.Script().add(bsv.Opcode.OP_1)
|
|
591
|
+
var spent = interp.verify(unlock, fabricatedListing(), new bsv.Transaction(), 0, 0)
|
|
592
|
+
spent.should.equal(true)
|
|
593
|
+
})
|
|
594
|
+
|
|
595
|
+
it('rejects a genuine listing with anything appended', function () {
|
|
596
|
+
var real = Ord.buildOrdLock({ seller: owner.toAddress(), price: 50000 })
|
|
597
|
+
var tampered = bsv.Script.fromHex(real.toHex()).add(bsv.Opcode.OP_NOP)
|
|
598
|
+
Ord.isOrdLock(tampered).should.equal(false)
|
|
599
|
+
})
|
|
600
|
+
|
|
601
|
+
it('still accepts every genuine listing shape', function () {
|
|
602
|
+
var simple = Ord.buildOrdLock({ seller: owner.toAddress(), price: 50000 })
|
|
603
|
+
Ord.parseOrdLock(simple).totalPrice.should.equal(50000)
|
|
604
|
+
|
|
605
|
+
var withInsc = Ord.buildOrdLock({
|
|
606
|
+
seller: owner.toAddress(),
|
|
607
|
+
price: 777,
|
|
608
|
+
inscription: { contentType: 'text/plain', content: 'for sale' }
|
|
609
|
+
})
|
|
610
|
+
var parsed = Ord.parseOrdLock(withInsc)
|
|
611
|
+
parsed.totalPrice.should.equal(777)
|
|
612
|
+
parsed.inscription.contentText.should.equal('for sale')
|
|
613
|
+
|
|
614
|
+
var multi = Ord.buildOrdLock({
|
|
615
|
+
seller: owner.toAddress(),
|
|
616
|
+
price: 1000,
|
|
617
|
+
royalties: [{ address: attacker.toAddress(), satoshis: 50 }]
|
|
618
|
+
})
|
|
619
|
+
Ord.parseOrdLock(multi).payOutputs.length.should.equal(2)
|
|
620
|
+
Ord.parseOrdLock(multi).totalPrice.should.equal(1050)
|
|
621
|
+
})
|
|
622
|
+
})
|
|
552
623
|
})
|
package/version.js
CHANGED