@smartledger/bsv 9.0.0 → 9.1.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.
- package/CHANGELOG.md +164 -0
- package/README.md +152 -1041
- package/STABILITY.md +134 -0
- package/bsv-gdaf.min.js +53 -48
- package/bsv-ltp.min.js +23 -18
- package/bsv-smartcontract.min.js +18 -18
- package/bsv.bundle.js +53 -48
- package/bsv.min.js +53 -48
- 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/docs/proposals/10.0.0-package-split.md +131 -0
- package/index.js +5 -0
- package/lib/block/merkleblock.js +53 -9
- package/lib/hdprivatekey.js +15 -0
- package/lib/script/interpreter.js +109 -1
- package/lib/smart_contract/builder.js +14 -0
- package/lib/util/deprecate.js +159 -0
- package/package.json +19 -82
- package/version.js +1 -1
- package/.mocharc.json +0 -5
- package/test/address.js +0 -629
- package/test/block/block.js +0 -239
- package/test/block/blockheader.js +0 -270
- package/test/block/merkleblock.js +0 -207
- package/test/build/bundle_crypto_shim.js +0 -104
- package/test/build/bundle_externals.js +0 -125
- package/test/build/bundle_smoke.js +0 -55
- package/test/build/esbuild_main.js +0 -65
- package/test/build/esm_wrapper.js +0 -97
- package/test/build/exports_resolution.js +0 -84
- package/test/build/version_sync.js +0 -14
- package/test/cli/smoke.js +0 -261
- package/test/consensus/base58-vectors.js +0 -120
- package/test/consensus/sv-script-vectors.js +0 -81
- package/test/consensus/sv-sighash-vectors.js +0 -51
- package/test/consensus/sv-tx-vectors.js +0 -48
- package/test/credentials-test.js +0 -332
- package/test/crypto/backend_selection.js +0 -126
- package/test/crypto/bn.js +0 -168
- package/test/crypto/ecdsa.js +0 -426
- package/test/crypto/elliptic-fixed.js +0 -61
- package/test/crypto/hash.browser.js +0 -121
- package/test/crypto/hash.js +0 -122
- package/test/crypto/point.js +0 -193
- package/test/crypto/random.js +0 -105
- package/test/crypto/security.js +0 -176
- package/test/crypto/shamir.js +0 -177
- package/test/crypto/shamir_rngcap.js +0 -29
- package/test/crypto/signature.js +0 -399
- package/test/data/bip69.json +0 -215
- package/test/data/bitcoin-sv/README.md +0 -170
- package/test/data/bitcoin-sv/base58_encode_decode.json +0 -14
- package/test/data/bitcoin-sv/base58_keys_invalid.json +0 -152
- package/test/data/bitcoin-sv/base58_keys_valid.json +0 -452
- package/test/data/bitcoin-sv/script_tests.json +0 -2591
- package/test/data/bitcoin-sv/sighash.json +0 -1003
- package/test/data/bitcoin-sv/tx_invalid.json +0 -285
- package/test/data/bitcoin-sv/tx_valid.json +0 -367
- package/test/data/bitcoind/base58_keys_invalid.json +0 -152
- package/test/data/bitcoind/base58_keys_valid.json +0 -452
- package/test/data/bitcoind/blocks.json +0 -27
- package/test/data/bitcoind/script_tests.json +0 -2244
- package/test/data/bitcoind/sig_canonical.json +0 -7
- package/test/data/bitcoind/sig_noncanonical.json +0 -22
- package/test/data/bitcoind/tx_invalid.json +0 -177
- package/test/data/bitcoind/tx_valid.json +0 -224
- package/test/data/blk86756-testnet.dat +0 -0
- package/test/data/blk86756-testnet.js +0 -12
- package/test/data/blk86756-testnet.json +0 -684
- package/test/data/brc220-batch-vector.json +0 -129
- package/test/data/ecdsa.json +0 -230
- package/test/data/merkleblocks.js +0 -486
- package/test/data/messages.json +0 -22
- package/test/data/sighash.json +0 -1004
- package/test/data/tx_creation.json +0 -85
- package/test/didweb/relationships.js +0 -144
- package/test/ecies/bitcore-ecies.js +0 -178
- package/test/ecies/electrum-ecies.js +0 -206
- package/test/encoding/base58.js +0 -131
- package/test/encoding/base58check.js +0 -145
- package/test/encoding/bufferreader.js +0 -328
- package/test/encoding/bufferwriter.js +0 -160
- package/test/encoding/varint.js +0 -104
- package/test/gdaf/anchor_no_key_leak.js +0 -146
- package/test/gdaf/anchor_spv.js +0 -163
- package/test/gdaf/canonicalization.js +0 -106
- package/test/gdaf/canonicalize.js +0 -140
- package/test/gdaf/zk_prover.js +0 -204
- package/test/hdkeys.js +0 -365
- package/test/hdprivatekey.js +0 -339
- package/test/hdpublickey.js +0 -293
- package/test/index.js +0 -16
- package/test/ltp/ids.js +0 -74
- package/test/ltp/right.js +0 -198
- package/test/ltp/verify_failclosed.js +0 -134
- package/test/message/message.js +0 -190
- package/test/mnemonic/data/fixtures.json +0 -300
- package/test/mnemonic/mnemonic.js +0 -277
- package/test/mnemonic/mocha.opts +0 -1
- package/test/mnemonic/pbkdf2.test.js +0 -43
- package/test/networks.js +0 -207
- package/test/notaryhash/batch_leaf.js +0 -140
- package/test/notaryhash/batch_vector.js +0 -282
- package/test/notaryhash/certificate.js +0 -249
- package/test/notaryhash/encoding.js +0 -186
- package/test/notaryhash/interop.js +0 -112
- package/test/notaryhash/merkle.js +0 -181
- package/test/notaryhash/script.js +0 -270
- package/test/notaryhash/verify.js +0 -342
- package/test/opcode.js +0 -186
- package/test/ordinals/bsv20.js +0 -337
- package/test/ordinals/inscription.js +0 -329
- package/test/ordinals/ordlock.js +0 -567
- package/test/privatekey.js +0 -540
- package/test/publickey.js +0 -411
- package/test/regressions.js +0 -215
- package/test/script/chronicle.js +0 -543
- package/test/script/defaults.js +0 -160
- package/test/script/genesis_limits.js +0 -203
- package/test/script/interpreter.js +0 -776
- package/test/script/script.js +0 -1259
- package/test/script/string_ops.js +0 -88
- package/test/security/fail_closed_contracts.js +0 -104
- package/test/security/threat_model_coverage.js +0 -31
- package/test/smart_contract/covenants.js +0 -207
- package/test/smart_contract/dsl_debugger.js +0 -92
- package/test/smart_contract/extract_field.js +0 -61
- package/test/smart_contract/nonenforcing_guard.js +0 -44
- package/test/smart_contract/ordinal_transfer.js +0 -71
- package/test/smart_contract/preimage.js +0 -98
- package/test/smart_contract/sighash_marketplace.js +0 -98
- package/test/smart_contract/token_generalized.js +0 -175
- package/test/spv/headerchain.js +0 -86
- package/test/spv/merkleproof.js +0 -133
- package/test/statuslist/failclosed.js +0 -78
- package/test/transaction/deserialize.js +0 -33
- package/test/transaction/input/input.js +0 -92
- package/test/transaction/input/multisig.js +0 -174
- package/test/transaction/input/multisigscripthash.js +0 -111
- package/test/transaction/input/publickey.js +0 -68
- package/test/transaction/input/publickeyhash.js +0 -59
- package/test/transaction/output.js +0 -185
- package/test/transaction/sighash.js +0 -91
- package/test/transaction/signature.js +0 -127
- package/test/transaction/transaction.js +0 -1299
- package/test/transaction/unspentoutput.js +0 -97
- package/test/types/dts_drift.js +0 -124
- package/test/types/surface_honesty.js +0 -102
- package/test/util/id.js +0 -72
- package/test/util/js.js +0 -76
- package/test/util/preconditions.js +0 -79
- package/test/vcjwt/interop.js +0 -126
package/test/ordinals/ordlock.js
DELETED
|
@@ -1,567 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
/* global describe, it, before, after */
|
|
4
|
-
|
|
5
|
-
// OrdLock marketplace covenant — list a 1Sat ordinal for sale behind a trustless
|
|
6
|
-
// "pay the seller or cancel" covenant. Every locking/unlocking pair below is driven
|
|
7
|
-
// through the consensus interpreter (the "if it emits Script, it's interpreter-
|
|
8
|
-
// verified" operating principle), including adversarial spends that MUST be rejected.
|
|
9
|
-
|
|
10
|
-
require('chai').should()
|
|
11
|
-
var bsv = require('../..')
|
|
12
|
-
var Ord = bsv.Ordinals
|
|
13
|
-
var SC = bsv.SmartContract
|
|
14
|
-
var Script = bsv.Script
|
|
15
|
-
var PrivateKey = bsv.PrivateKey
|
|
16
|
-
var Transaction = bsv.Transaction
|
|
17
|
-
|
|
18
|
-
var help = SC.CovenantHelpers
|
|
19
|
-
var verify = help.verify
|
|
20
|
-
var p2pkhOutput = help.p2pkhOutput
|
|
21
|
-
|
|
22
|
-
var ORD_SATS = 1
|
|
23
|
-
var PRICE = 100000
|
|
24
|
-
|
|
25
|
-
describe('Ordinals OrdLock marketplace', function () {
|
|
26
|
-
this.timeout(20000)
|
|
27
|
-
|
|
28
|
-
var seller = PrivateKey.fromRandom()
|
|
29
|
-
var buyer = PrivateKey.fromRandom()
|
|
30
|
-
|
|
31
|
-
// A funding tx that pays the listing UTXO, plus a spend consuming it at input 0.
|
|
32
|
-
function fundListing (lock) {
|
|
33
|
-
return help.fundAndSpend(lock, ORD_SATS, {}).spend
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Build the buyer's outputs: [ ordinal->buyer (index 0), payment->seller (index 1) ].
|
|
37
|
-
function buyerOutputs (payTo, price) {
|
|
38
|
-
return [
|
|
39
|
-
p2pkhOutput(buyer, ORD_SATS), // the ordinal goes to the buyer at output 0
|
|
40
|
-
Ord.payOutputFor(payTo || seller, price == null ? PRICE : price) // seller payment at 1
|
|
41
|
-
]
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
it('verifies a purchase that pays the seller', function () {
|
|
45
|
-
var lock = Ord.buildOrdLock({ seller: seller.toAddress(), price: PRICE })
|
|
46
|
-
var spend = fundListing(lock)
|
|
47
|
-
buyerOutputs().forEach(function (o) { spend.addOutput(o) })
|
|
48
|
-
Ord.purchaseOrdLock({ spend: spend, lockingScript: lock, satoshis: ORD_SATS })
|
|
49
|
-
verify(spend.inputs[0].script, lock, { tx: spend, satoshis: ORD_SATS }).ok.should.equal(true)
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
it('stays valid when the buyer adds a funding input and a change output', function () {
|
|
53
|
-
var lock = Ord.buildOrdLock({ seller: seller.toAddress(), price: PRICE })
|
|
54
|
-
var spend = fundListing(lock)
|
|
55
|
-
// outputs: [ordinal->buyer, payment->seller, change->buyer]
|
|
56
|
-
spend.addOutput(p2pkhOutput(buyer, ORD_SATS))
|
|
57
|
-
spend.addOutput(Ord.payOutputFor(seller, PRICE))
|
|
58
|
-
spend.addOutput(p2pkhOutput(buyer, 40000)) // buyer change, trailing
|
|
59
|
-
|
|
60
|
-
// Grind + build the unlock BEFORE adding the (ANYONECANPAY) funding input.
|
|
61
|
-
Ord.purchaseOrdLock({ spend: spend, lockingScript: lock, satoshis: ORD_SATS })
|
|
62
|
-
spend.addInput(new Transaction.Input({
|
|
63
|
-
prevTxId: '22'.repeat(32), outputIndex: 0, script: Script.empty()
|
|
64
|
-
}), p2pkhOutput(buyer, PRICE + 50000).script, PRICE + 50000)
|
|
65
|
-
|
|
66
|
-
verify(spend.inputs[0].script, lock, { tx: spend, satoshis: ORD_SATS }).ok.should.equal(true)
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
it('rejects a purchase that underpays the seller', function () {
|
|
70
|
-
var lock = Ord.buildOrdLock({ seller: seller.toAddress(), price: PRICE })
|
|
71
|
-
var spend = fundListing(lock)
|
|
72
|
-
spend.addOutput(p2pkhOutput(buyer, ORD_SATS))
|
|
73
|
-
spend.addOutput(Ord.payOutputFor(seller, PRICE))
|
|
74
|
-
Ord.purchaseOrdLock({ spend: spend, lockingScript: lock, satoshis: ORD_SATS })
|
|
75
|
-
|
|
76
|
-
// Tamper: shave the seller's payment after grinding → committed outputs change.
|
|
77
|
-
spend.outputs[1] = Ord.payOutputFor(seller, PRICE - 1)
|
|
78
|
-
verify(spend.inputs[0].script, lock, { tx: spend, satoshis: ORD_SATS }).ok.should.equal(false)
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
it('rejects a purchase that redirects the payment to someone else', function () {
|
|
82
|
-
var lock = Ord.buildOrdLock({ seller: seller.toAddress(), price: PRICE })
|
|
83
|
-
var spend = fundListing(lock)
|
|
84
|
-
spend.addOutput(p2pkhOutput(buyer, ORD_SATS))
|
|
85
|
-
spend.addOutput(Ord.payOutputFor(seller, PRICE))
|
|
86
|
-
Ord.purchaseOrdLock({ spend: spend, lockingScript: lock, satoshis: ORD_SATS })
|
|
87
|
-
|
|
88
|
-
spend.outputs[1] = Ord.payOutputFor(PrivateKey.fromRandom(), PRICE) // thief's address
|
|
89
|
-
verify(spend.inputs[0].script, lock, { tx: spend, satoshis: ORD_SATS }).ok.should.equal(false)
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
it('pays a distinct payTo recipient while the seller keeps cancel rights', function () {
|
|
93
|
-
var payee = PrivateKey.fromRandom()
|
|
94
|
-
var lock = Ord.buildOrdLock({ seller: seller.toAddress(), payTo: payee.toAddress(), price: PRICE })
|
|
95
|
-
var spend = fundListing(lock)
|
|
96
|
-
spend.addOutput(p2pkhOutput(buyer, ORD_SATS))
|
|
97
|
-
spend.addOutput(Ord.payOutputFor(payee, PRICE))
|
|
98
|
-
Ord.purchaseOrdLock({ spend: spend, lockingScript: lock, satoshis: ORD_SATS })
|
|
99
|
-
verify(spend.inputs[0].script, lock, { tx: spend, satoshis: ORD_SATS }).ok.should.equal(true)
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
it('purchase() fail-fasts (throws) when the payout output is wrong', function () {
|
|
103
|
-
var lock = Ord.buildOrdLock({ seller: seller.toAddress(), price: PRICE })
|
|
104
|
-
var spend = fundListing(lock)
|
|
105
|
-
spend.addOutput(p2pkhOutput(buyer, ORD_SATS))
|
|
106
|
-
spend.addOutput(Ord.payOutputFor(seller, PRICE - 1)) // underpay
|
|
107
|
-
var pinned = Ord.payOutputFor(seller, PRICE)
|
|
108
|
-
;(function () {
|
|
109
|
-
Ord.purchaseOrdLock({ spend: spend, lockingScript: lock, satoshis: ORD_SATS, payOutput: pinned })
|
|
110
|
-
}).should.throw(/would not be paid/)
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
it('lets the seller CANCEL the listing with their key', function () {
|
|
114
|
-
var lock = Ord.buildOrdLock({ seller: seller.toAddress(), price: PRICE })
|
|
115
|
-
var spend = fundListing(lock)
|
|
116
|
-
spend.addOutput(p2pkhOutput(seller, ORD_SATS)) // seller reclaims the ordinal
|
|
117
|
-
Ord.cancelOrdLock({ privateKey: seller, spend: spend, lockingScript: lock, satoshis: ORD_SATS })
|
|
118
|
-
verify(spend.inputs[0].script, lock, { tx: spend, satoshis: ORD_SATS }).ok.should.equal(true)
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
it('rejects a CANCEL signed by the wrong key', function () {
|
|
122
|
-
var lock = Ord.buildOrdLock({ seller: seller.toAddress(), price: PRICE })
|
|
123
|
-
var spend = fundListing(lock)
|
|
124
|
-
spend.addOutput(p2pkhOutput(buyer, ORD_SATS))
|
|
125
|
-
Ord.cancelOrdLock({ privateKey: buyer, spend: spend, lockingScript: lock, satoshis: ORD_SATS })
|
|
126
|
-
verify(spend.inputs[0].script, lock, { tx: spend, satoshis: ORD_SATS }).ok.should.equal(false)
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
it('listInscriptionOutput is a 1-sat output whose script cancels + purchases', function () {
|
|
130
|
-
var out = Ord.listInscriptionOutput({ seller: seller.toAddress(), price: PRICE })
|
|
131
|
-
out.satoshis.should.equal(1)
|
|
132
|
-
// Purchase path round-trips through the interpreter.
|
|
133
|
-
var lock = out.script
|
|
134
|
-
var spend = fundListing(lock)
|
|
135
|
-
spend.addOutput(p2pkhOutput(buyer, ORD_SATS))
|
|
136
|
-
spend.addOutput(Ord.payOutputFor(seller, PRICE))
|
|
137
|
-
Ord.purchaseOrdLock({ spend: spend, lockingScript: lock, satoshis: ORD_SATS })
|
|
138
|
-
verify(spend.inputs[0].script, lock, { tx: spend, satoshis: ORD_SATS }).ok.should.equal(true)
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
it('can inscribe + list in one output; the envelope is inert and parseable', function () {
|
|
142
|
-
var lock = Ord.buildOrdLock({
|
|
143
|
-
seller: seller.toAddress(),
|
|
144
|
-
price: PRICE,
|
|
145
|
-
inscription: { contentType: 'text/plain', content: 'for sale' }
|
|
146
|
-
})
|
|
147
|
-
// The inscription rides along and is recoverable.
|
|
148
|
-
Ord.isInscription(lock).should.equal(true)
|
|
149
|
-
Ord.parseInscription(lock).contentText.should.equal('for sale')
|
|
150
|
-
// ...and the covenant still enforces payment.
|
|
151
|
-
var spend = fundListing(lock)
|
|
152
|
-
spend.addOutput(p2pkhOutput(buyer, ORD_SATS))
|
|
153
|
-
spend.addOutput(Ord.payOutputFor(seller, PRICE))
|
|
154
|
-
Ord.purchaseOrdLock({ spend: spend, lockingScript: lock, satoshis: ORD_SATS })
|
|
155
|
-
verify(spend.inputs[0].script, lock, { tx: spend, satoshis: ORD_SATS }).ok.should.equal(true)
|
|
156
|
-
})
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
describe('Ordinals OrdLock — multi-output payments (royalties + marketplace fee)', function () {
|
|
160
|
-
this.timeout(20000)
|
|
161
|
-
|
|
162
|
-
var seller = PrivateKey.fromRandom()
|
|
163
|
-
var royalty = PrivateKey.fromRandom()
|
|
164
|
-
var market = PrivateKey.fromRandom()
|
|
165
|
-
var buyer = PrivateKey.fromRandom()
|
|
166
|
-
var ROY = 5000
|
|
167
|
-
var FEE = 2000
|
|
168
|
-
|
|
169
|
-
function multiLock () {
|
|
170
|
-
return Ord.buildOrdLock({
|
|
171
|
-
seller: seller.toAddress(),
|
|
172
|
-
price: PRICE,
|
|
173
|
-
royalties: [ { address: royalty.toAddress(), satoshis: ROY }, { address: market.toAddress(), satoshis: FEE } ]
|
|
174
|
-
})
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// The three pinned outputs, in order, that a purchase must recreate.
|
|
178
|
-
function pinnedOuts () {
|
|
179
|
-
return [ Ord.payOutputFor(seller, PRICE), Ord.payOutputFor(royalty, ROY), Ord.payOutputFor(market, FEE) ]
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
it('pays seller + royalty + marketplace fee atomically (auto-derived payoutCount)', function () {
|
|
183
|
-
var lock = multiLock()
|
|
184
|
-
var spend = help.fundAndSpend(lock, ORD_SATS, {}).spend
|
|
185
|
-
spend.addOutput(p2pkhOutput(buyer, ORD_SATS)) // ordinal -> buyer
|
|
186
|
-
pinnedOuts().forEach(function (o) { spend.addOutput(o) }) // the 3 pinned payments
|
|
187
|
-
spend.addOutput(p2pkhOutput(buyer, 10000)) // buyer change (trailing)
|
|
188
|
-
// No payoutCount passed — purchaseOrdLock reads it from the listing script.
|
|
189
|
-
Ord.purchaseOrdLock({ spend: spend, lockingScript: lock, satoshis: ORD_SATS })
|
|
190
|
-
verify(spend.inputs[0].script, lock, { tx: spend, satoshis: ORD_SATS }).ok.should.equal(true)
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
it('rejects a purchase that skips the royalty payment', function () {
|
|
194
|
-
var lock = multiLock()
|
|
195
|
-
var spend = help.fundAndSpend(lock, ORD_SATS, {}).spend
|
|
196
|
-
spend.addOutput(p2pkhOutput(buyer, ORD_SATS))
|
|
197
|
-
pinnedOuts().forEach(function (o) { spend.addOutput(o) })
|
|
198
|
-
Ord.purchaseOrdLock({ spend: spend, lockingScript: lock, satoshis: ORD_SATS, validate: false })
|
|
199
|
-
// Zero out the royalty (output index 2) after signing → covenant must reject.
|
|
200
|
-
spend.outputs[2] = Ord.payOutputFor(royalty, 1)
|
|
201
|
-
verify(spend.inputs[0].script, lock, { tx: spend, satoshis: ORD_SATS }).ok.should.equal(false)
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
it('rejects a purchase that redirects the marketplace fee', function () {
|
|
205
|
-
var lock = multiLock()
|
|
206
|
-
var spend = help.fundAndSpend(lock, ORD_SATS, {}).spend
|
|
207
|
-
spend.addOutput(p2pkhOutput(buyer, ORD_SATS))
|
|
208
|
-
pinnedOuts().forEach(function (o) { spend.addOutput(o) })
|
|
209
|
-
Ord.purchaseOrdLock({ spend: spend, lockingScript: lock, satoshis: ORD_SATS, validate: false })
|
|
210
|
-
spend.outputs[3] = Ord.payOutputFor(PrivateKey.fromRandom(), FEE) // thief keeps the fee
|
|
211
|
-
verify(spend.inputs[0].script, lock, { tx: spend, satoshis: ORD_SATS }).ok.should.equal(false)
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
it('purchaseOrdLock fail-fasts if a pinned payment is missing before broadcast', function () {
|
|
215
|
-
var lock = multiLock()
|
|
216
|
-
var spend = help.fundAndSpend(lock, ORD_SATS, {}).spend
|
|
217
|
-
spend.addOutput(p2pkhOutput(buyer, ORD_SATS))
|
|
218
|
-
spend.addOutput(Ord.payOutputFor(seller, PRICE))
|
|
219
|
-
spend.addOutput(Ord.payOutputFor(royalty, ROY))
|
|
220
|
-
spend.addOutput(Ord.payOutputFor(market, 1)) // underpays the fee
|
|
221
|
-
;(function () {
|
|
222
|
-
Ord.purchaseOrdLock({ spend: spend, lockingScript: lock, satoshis: ORD_SATS })
|
|
223
|
-
}).should.throw(/would not be paid/)
|
|
224
|
-
})
|
|
225
|
-
})
|
|
226
|
-
|
|
227
|
-
describe('Ordinals OrdLock — parseOrdLock (self-describing listings)', function () {
|
|
228
|
-
var seller = PrivateKey.fromRandom()
|
|
229
|
-
var royalty = PrivateKey.fromRandom()
|
|
230
|
-
|
|
231
|
-
it('recovers seller, payments, total price, and inscription', function () {
|
|
232
|
-
var lock = Ord.buildOrdLock({
|
|
233
|
-
seller: seller.toAddress(),
|
|
234
|
-
price: PRICE,
|
|
235
|
-
royalties: [ { address: royalty.toAddress(), satoshis: 7500 } ],
|
|
236
|
-
inscription: { contentType: 'image/png', content: 'PNGDATA' }
|
|
237
|
-
})
|
|
238
|
-
var p = Ord.parseOrdLock(lock)
|
|
239
|
-
p.should.be.an('object')
|
|
240
|
-
p.seller.address.should.equal(seller.toAddress().toString())
|
|
241
|
-
p.payOutputs.length.should.equal(2)
|
|
242
|
-
p.payOutputs[0].satoshis.should.equal(PRICE)
|
|
243
|
-
p.payOutputs[0].address.should.equal(seller.toAddress().toString())
|
|
244
|
-
p.payOutputs[1].satoshis.should.equal(7500)
|
|
245
|
-
p.payOutputs[1].address.should.equal(royalty.toAddress().toString())
|
|
246
|
-
p.totalPrice.should.equal(PRICE + 7500)
|
|
247
|
-
p.inscription.contentType.should.equal('image/png')
|
|
248
|
-
p.inscription.contentText.should.equal('PNGDATA')
|
|
249
|
-
})
|
|
250
|
-
|
|
251
|
-
it('isOrdLock distinguishes a listing from a plain P2PKH and an inscription', function () {
|
|
252
|
-
var lock = Ord.buildOrdLock({ seller: seller.toAddress(), price: PRICE })
|
|
253
|
-
Ord.isOrdLock(lock).should.equal(true)
|
|
254
|
-
Ord.isOrdLock(lock.toHex()).should.equal(true) // accepts hex too
|
|
255
|
-
Ord.isOrdLock(bsv.Script.buildPublicKeyHashOut(seller.toAddress())).should.equal(false)
|
|
256
|
-
Ord.isOrdLock(Ord.buildInscription({ address: seller.toAddress(), content: 'x' })).should.equal(false)
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
it('parseOrdLock returns null for a non-listing script', function () {
|
|
260
|
-
var isNull = Ord.parseOrdLock(bsv.Script.buildPublicKeyHashOut(seller.toAddress())) === null
|
|
261
|
-
isNull.should.equal(true)
|
|
262
|
-
})
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
describe('Ordinals OrdLock — buildPurchaseTx (end-to-end)', function () {
|
|
266
|
-
this.timeout(20000)
|
|
267
|
-
|
|
268
|
-
var seller = PrivateKey.fromRandom()
|
|
269
|
-
var royalty = PrivateKey.fromRandom()
|
|
270
|
-
var buyer = PrivateKey.fromRandom()
|
|
271
|
-
var fundKey = PrivateKey.fromRandom()
|
|
272
|
-
var fundScript = bsv.Script.buildPublicKeyHashOut(fundKey.toAddress())
|
|
273
|
-
|
|
274
|
-
function listing () {
|
|
275
|
-
var lock = Ord.buildOrdLock({
|
|
276
|
-
seller: seller.toAddress(), price: PRICE,
|
|
277
|
-
royalties: [ { address: royalty.toAddress(), satoshis: 5000 } ]
|
|
278
|
-
})
|
|
279
|
-
return { txid: 'a1'.repeat(32), outputIndex: 0, script: lock, satoshis: ORD_SATS }
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function fundingCoins (amounts) {
|
|
283
|
-
return amounts.map(function (sats, i) {
|
|
284
|
-
return { txid: 'f' + i + '0'.repeat(62), outputIndex: i, script: fundScript, satoshis: sats, privateKey: fundKey }
|
|
285
|
-
})
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// Verify every input of a built tx through the consensus interpreter.
|
|
289
|
-
function verifyAllInputs (tx, l, funding) {
|
|
290
|
-
var r0 = verify(tx.inputs[0].script, l.script, { tx: tx, satoshis: l.satoshis, inputIndex: 0 })
|
|
291
|
-
r0.ok.should.equal(true)
|
|
292
|
-
funding.forEach(function (f, i) {
|
|
293
|
-
var r = verify(tx.inputs[i + 1].script, fundScript, { tx: tx, satoshis: f.satoshis, inputIndex: i + 1 })
|
|
294
|
-
r.ok.should.equal(true)
|
|
295
|
-
})
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
it('assembles a fully-signed purchase from just the listing UTXO and buyer coins', function () {
|
|
299
|
-
var l = listing()
|
|
300
|
-
var funding = fundingCoins([80000, 60000])
|
|
301
|
-
var tx = Ord.buildPurchaseTx({
|
|
302
|
-
listing: l, ordinalDestination: buyer.toAddress(), funding: funding, fee: 500
|
|
303
|
-
})
|
|
304
|
-
// Layout: [ordinal->buyer, seller PRICE, royalty 5000, change].
|
|
305
|
-
tx.inputs.length.should.equal(3)
|
|
306
|
-
tx.outputs.length.should.equal(4)
|
|
307
|
-
tx.outputs[0].satoshis.should.equal(ORD_SATS)
|
|
308
|
-
tx.outputs[1].satoshis.should.equal(PRICE)
|
|
309
|
-
tx.outputs[2].satoshis.should.equal(5000)
|
|
310
|
-
// Value conservation: in 1+80000+60000 = 140001; out 1+100000+5000+change; fee 500.
|
|
311
|
-
tx.outputs[3].satoshis.should.equal(140001 - ORD_SATS - PRICE - 5000 - 500)
|
|
312
|
-
verifyAllInputs(tx, l, funding)
|
|
313
|
-
})
|
|
314
|
-
|
|
315
|
-
it('reads the required payments straight off the listing (buyer supplies no terms)', function () {
|
|
316
|
-
var l = listing()
|
|
317
|
-
var funding = fundingCoins([200000])
|
|
318
|
-
var tx = Ord.buildPurchaseTx({ listing: l, ordinalDestination: buyer.toAddress(), funding: funding, fee: 250 })
|
|
319
|
-
// The pinned payments came from parseOrdLock, and the covenant still validates.
|
|
320
|
-
verifyAllInputs(tx, l, funding)
|
|
321
|
-
tx.outputs[1].satoshis.should.equal(PRICE) // seller
|
|
322
|
-
tx.outputs[2].satoshis.should.equal(5000) // royalty
|
|
323
|
-
})
|
|
324
|
-
|
|
325
|
-
it('omits the change output when funding is exact', function () {
|
|
326
|
-
var l = listing()
|
|
327
|
-
var funding = fundingCoins([PRICE + 5000 + 500]) // exactly payments + fee
|
|
328
|
-
var tx = Ord.buildPurchaseTx({ listing: l, ordinalDestination: buyer.toAddress(), funding: funding, fee: 500 })
|
|
329
|
-
tx.outputs.length.should.equal(3) // ordinal + 2 payments, no change
|
|
330
|
-
verifyAllInputs(tx, l, funding)
|
|
331
|
-
})
|
|
332
|
-
|
|
333
|
-
it('throws on insufficient funding', function () {
|
|
334
|
-
var l = listing()
|
|
335
|
-
var funding = fundingCoins([1000]) // nowhere near PRICE + royalty + fee
|
|
336
|
-
;(function () {
|
|
337
|
-
Ord.buildPurchaseTx({ listing: l, ordinalDestination: buyer.toAddress(), funding: funding, fee: 500 })
|
|
338
|
-
}).should.throw(/insufficient funding/)
|
|
339
|
-
})
|
|
340
|
-
|
|
341
|
-
it('the covenant still binds — tampering any output after build breaks input 0', function () {
|
|
342
|
-
var l = listing()
|
|
343
|
-
var funding = fundingCoins([200000])
|
|
344
|
-
var tx = Ord.buildPurchaseTx({ listing: l, ordinalDestination: buyer.toAddress(), funding: funding, fee: 500 })
|
|
345
|
-
tx.outputs[1] = Ord.payOutputFor(PrivateKey.fromRandom(), PRICE) // steal the seller payment
|
|
346
|
-
verify(tx.inputs[0].script, l.script, { tx: tx, satoshis: l.satoshis, inputIndex: 0 }).ok.should.equal(false)
|
|
347
|
-
})
|
|
348
|
-
})
|
|
349
|
-
|
|
350
|
-
describe('Ordinals OrdLock — full lifecycle (list -> buy / cancel)', function () {
|
|
351
|
-
this.timeout(20000)
|
|
352
|
-
|
|
353
|
-
var owner = PrivateKey.fromRandom() // current ordinal owner / seller
|
|
354
|
-
var royalty = PrivateKey.fromRandom()
|
|
355
|
-
var buyer = PrivateKey.fromRandom()
|
|
356
|
-
var fundKey = PrivateKey.fromRandom()
|
|
357
|
-
var fundScript = bsv.Script.buildPublicKeyHashOut(fundKey.toAddress())
|
|
358
|
-
var ownerScript = bsv.Script.buildPublicKeyHashOut(owner.toAddress())
|
|
359
|
-
|
|
360
|
-
// The ordinal UTXO currently held by the owner under P2PKH.
|
|
361
|
-
function ordinalUtxo () {
|
|
362
|
-
return { txid: '0e'.repeat(32), outputIndex: 0, script: ownerScript, satoshis: ORD_SATS, privateKey: owner }
|
|
363
|
-
}
|
|
364
|
-
function fundCoin (sats, i) {
|
|
365
|
-
return { txid: 'f' + (i || 0) + '0'.repeat(62), outputIndex: 0, script: fundScript, satoshis: sats, privateKey: fundKey }
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
it('buildListingTx signs the ordinal + fee and preserves the sat into the listing', function () {
|
|
369
|
-
var funding = [fundCoin(20000)]
|
|
370
|
-
var out = Ord.buildListingTx({
|
|
371
|
-
ordinal: ordinalUtxo(), seller: owner.toAddress(), price: PRICE,
|
|
372
|
-
royalties: [{ address: royalty.toAddress(), satoshis: 4000 }],
|
|
373
|
-
funding: funding, fee: 500
|
|
374
|
-
})
|
|
375
|
-
// Output 0 is the listing (ordinal's sat), and it's a parseable OrdLock.
|
|
376
|
-
out.tx.outputs[0].satoshis.should.equal(ORD_SATS)
|
|
377
|
-
out.listingOutpoint.outputIndex.should.equal(0)
|
|
378
|
-
Ord.isOrdLock(out.tx.outputs[0].script).should.equal(true)
|
|
379
|
-
Ord.parseOrdLock(out.listingScript).totalPrice.should.equal(PRICE + 4000)
|
|
380
|
-
// Change = funding - fee.
|
|
381
|
-
out.tx.outputs[1].satoshis.should.equal(20000 - 500)
|
|
382
|
-
// Both inputs (ordinal P2PKH + funding P2PKH) verify.
|
|
383
|
-
verify(out.tx.inputs[0].script, ownerScript, { tx: out.tx, satoshis: ORD_SATS, inputIndex: 0 }).ok.should.equal(true)
|
|
384
|
-
verify(out.tx.inputs[1].script, fundScript, { tx: out.tx, satoshis: 20000, inputIndex: 1 }).ok.should.equal(true)
|
|
385
|
-
})
|
|
386
|
-
|
|
387
|
-
it('a listing created by buildListingTx can then be PURCHASED end-to-end', function () {
|
|
388
|
-
// 1) Seller lists.
|
|
389
|
-
var listed = Ord.buildListingTx({
|
|
390
|
-
ordinal: ordinalUtxo(), seller: owner.toAddress(), price: PRICE,
|
|
391
|
-
royalties: [{ address: royalty.toAddress(), satoshis: 4000 }],
|
|
392
|
-
funding: [fundCoin(20000)], fee: 500
|
|
393
|
-
})
|
|
394
|
-
// 2) Buyer purchases the freshly-created listing UTXO (terms read off its script).
|
|
395
|
-
var listing = {
|
|
396
|
-
txid: listed.listingOutpoint.txid,
|
|
397
|
-
outputIndex: listed.listingOutpoint.outputIndex,
|
|
398
|
-
script: listed.listingScript,
|
|
399
|
-
satoshis: ORD_SATS
|
|
400
|
-
}
|
|
401
|
-
var buyFunding = [fundCoin(200000, 1)]
|
|
402
|
-
var buyTx = Ord.buildPurchaseTx({
|
|
403
|
-
listing: listing, ordinalDestination: buyer.toAddress(), funding: buyFunding, fee: 500
|
|
404
|
-
})
|
|
405
|
-
// Ordinal -> buyer, seller + royalty paid, change back to buyer.
|
|
406
|
-
buyTx.outputs[0].satoshis.should.equal(ORD_SATS)
|
|
407
|
-
buyTx.outputs[1].satoshis.should.equal(PRICE)
|
|
408
|
-
buyTx.outputs[2].satoshis.should.equal(4000)
|
|
409
|
-
// Covenant input + funding input both verify through the interpreter.
|
|
410
|
-
verify(buyTx.inputs[0].script, listing.script, { tx: buyTx, satoshis: ORD_SATS, inputIndex: 0 }).ok.should.equal(true)
|
|
411
|
-
verify(buyTx.inputs[1].script, fundScript, { tx: buyTx, satoshis: 200000, inputIndex: 1 }).ok.should.equal(true)
|
|
412
|
-
})
|
|
413
|
-
|
|
414
|
-
it('a listing created by buildListingTx can instead be CANCELLED by the seller', function () {
|
|
415
|
-
var listed = Ord.buildListingTx({
|
|
416
|
-
ordinal: ordinalUtxo(), seller: owner.toAddress(), price: PRICE,
|
|
417
|
-
funding: [fundCoin(20000)], fee: 500
|
|
418
|
-
})
|
|
419
|
-
// Seller reclaims the ordinal from the listing UTXO.
|
|
420
|
-
var reclaim = new Transaction()
|
|
421
|
-
reclaim.addInput(new Transaction.Input({
|
|
422
|
-
prevTxId: listed.listingOutpoint.txid, outputIndex: 0, script: Script.empty()
|
|
423
|
-
}), listed.listingScript, ORD_SATS)
|
|
424
|
-
reclaim.addOutput(p2pkhOutput(owner, ORD_SATS))
|
|
425
|
-
Ord.cancelOrdLock({ privateKey: owner, spend: reclaim, lockingScript: listed.listingScript, satoshis: ORD_SATS })
|
|
426
|
-
verify(reclaim.inputs[0].script, listed.listingScript, { tx: reclaim, satoshis: ORD_SATS }).ok.should.equal(true)
|
|
427
|
-
})
|
|
428
|
-
|
|
429
|
-
it('buildListingTx throws when funding cannot cover the fee', function () {
|
|
430
|
-
;(function () {
|
|
431
|
-
Ord.buildListingTx({
|
|
432
|
-
ordinal: ordinalUtxo(), seller: owner.toAddress(), price: PRICE,
|
|
433
|
-
funding: [fundCoin(300)], fee: 500
|
|
434
|
-
})
|
|
435
|
-
}).should.throw(/insufficient funding/)
|
|
436
|
-
})
|
|
437
|
-
})
|
|
438
|
-
|
|
439
|
-
describe('Ordinals OrdLock — code-review regressions', function () {
|
|
440
|
-
this.timeout(20000)
|
|
441
|
-
|
|
442
|
-
var owner = PrivateKey.fromRandom()
|
|
443
|
-
var fundKey = PrivateKey.fromRandom()
|
|
444
|
-
var ownerScript = bsv.Script.buildPublicKeyHashOut(owner.toAddress())
|
|
445
|
-
var fundScript = bsv.Script.buildPublicKeyHashOut(fundKey.toAddress())
|
|
446
|
-
|
|
447
|
-
// Finding #1: buildListingTx must sign the ordinal input with the resolved value even
|
|
448
|
-
// when the caller omits ordinal.satoshis (documented to default to 1).
|
|
449
|
-
it('buildListingTx signs a valid ordinal input when ordinal.satoshis is omitted', function () {
|
|
450
|
-
var out = Ord.buildListingTx({
|
|
451
|
-
ordinal: { txid: '0e'.repeat(32), outputIndex: 0, script: ownerScript, privateKey: owner }, // no satoshis
|
|
452
|
-
seller: owner.toAddress(),
|
|
453
|
-
price: PRICE,
|
|
454
|
-
funding: [{ txid: 'f0'.repeat(32), outputIndex: 0, script: fundScript, satoshis: 20000, privateKey: fundKey }],
|
|
455
|
-
fee: 500
|
|
456
|
-
})
|
|
457
|
-
verify(out.tx.inputs[0].script, ownerScript, { tx: out.tx, satoshis: 1, inputIndex: 0 }).ok.should.equal(true)
|
|
458
|
-
})
|
|
459
|
-
|
|
460
|
-
// Finding #2: parsed addresses honor a network option instead of always being livenet.
|
|
461
|
-
it('parseOrdLock formats addresses for the requested network', function () {
|
|
462
|
-
var lock = Ord.buildOrdLock({ seller: owner.toAddress(), price: PRICE })
|
|
463
|
-
var live = Ord.parseOrdLock(lock).seller.address
|
|
464
|
-
var test = Ord.parseOrdLock(lock, { network: 'testnet' }).seller.address
|
|
465
|
-
live.should.not.equal(test)
|
|
466
|
-
bsv.Address.fromString(test).network.name.should.equal('testnet')
|
|
467
|
-
bsv.Address.fromString(live).network.name.should.equal('livenet')
|
|
468
|
-
})
|
|
469
|
-
|
|
470
|
-
// Finding #3: a funding coin missing satoshis fails fast (was a silent wrong-amount signature).
|
|
471
|
-
it('buildPurchaseTx throws when a funding coin has no satoshis', function () {
|
|
472
|
-
var lock = Ord.buildOrdLock({ seller: owner.toAddress(), price: PRICE })
|
|
473
|
-
;(function () {
|
|
474
|
-
Ord.buildPurchaseTx({
|
|
475
|
-
listing: { txid: 'a1'.repeat(32), outputIndex: 0, script: lock, satoshis: 1 },
|
|
476
|
-
ordinalDestination: fundKey.toAddress(),
|
|
477
|
-
funding: [{ txid: 'b1'.repeat(32), outputIndex: 0, script: fundScript, privateKey: fundKey }], // no satoshis
|
|
478
|
-
fee: 500
|
|
479
|
-
})
|
|
480
|
-
}).should.throw(/satoshis/)
|
|
481
|
-
})
|
|
482
|
-
|
|
483
|
-
// Finding #5: payOutputFor never silently ignores price when handed a full Output.
|
|
484
|
-
it('payOutputFor rejects a Transaction.Output (price would be ignored)', function () {
|
|
485
|
-
var o = new bsv.Transaction.Output({ script: ownerScript, satoshis: 5 })
|
|
486
|
-
;(function () { Ord.payOutputFor(o, PRICE) }).should.throw()
|
|
487
|
-
})
|
|
488
|
-
|
|
489
|
-
it('buildOrdLock still accepts a pre-built Output as payTo (uses its own value)', function () {
|
|
490
|
-
var lock = Ord.buildOrdLock({
|
|
491
|
-
seller: owner.toAddress(), price: 1,
|
|
492
|
-
payTo: new bsv.Transaction.Output({ script: ownerScript, satoshis: 777 })
|
|
493
|
-
})
|
|
494
|
-
Ord.parseOrdLock(lock).totalPrice.should.equal(777)
|
|
495
|
-
})
|
|
496
|
-
|
|
497
|
-
// parseOrdLock recovers terms from the script's shape. Shape alone proves nothing: a
|
|
498
|
-
// script wearing the same opcode arrangement without the OP_PUSH_TX covenant enforces no
|
|
499
|
-
// payment, and reporting a seller and a price for it invents a listing that does not
|
|
500
|
-
// exist. The terms are therefore verified by rebuilding the listing and comparing bytes.
|
|
501
|
-
describe('is a verifier, not a shape matcher', function () {
|
|
502
|
-
var attacker = bsv.PrivateKey.fromBuffer(Buffer.alloc(32, 9))
|
|
503
|
-
|
|
504
|
-
// Wears the fingerprint parseOrdLock keys off: a top-level OP_IF with a 20-byte push,
|
|
505
|
-
// and OP_TOALTSTACK <blob> OP_CAT OP_SWAP in the OP_ELSE branch. Enforces nothing.
|
|
506
|
-
function fabricatedListing () {
|
|
507
|
-
var payout = new bsv.Transaction.Output({
|
|
508
|
-
script: bsv.Script.buildPublicKeyHashOut(attacker.toAddress()),
|
|
509
|
-
satoshis: 100000000
|
|
510
|
-
})
|
|
511
|
-
var s = new bsv.Script()
|
|
512
|
-
s.add(bsv.Opcode.OP_IF)
|
|
513
|
-
s.add(attacker.toAddress().hashBuffer)
|
|
514
|
-
s.add(bsv.Opcode.OP_DROP)
|
|
515
|
-
s.add(bsv.Opcode.OP_ELSE)
|
|
516
|
-
s.add(bsv.Opcode.OP_TOALTSTACK)
|
|
517
|
-
s.add(payout.toBufferWriter().toBuffer())
|
|
518
|
-
s.add(bsv.Opcode.OP_CAT)
|
|
519
|
-
s.add(bsv.Opcode.OP_SWAP)
|
|
520
|
-
s.add(bsv.Opcode.OP_ENDIF)
|
|
521
|
-
s.add(bsv.Opcode.OP_TRUE)
|
|
522
|
-
return s
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
it('rejects a fabricated listing that enforces no payment', function () {
|
|
526
|
-
var fake = fabricatedListing()
|
|
527
|
-
Ord.isOrdLock(fake).should.equal(false)
|
|
528
|
-
;(Ord.parseOrdLock(fake) === null).should.equal(true)
|
|
529
|
-
})
|
|
530
|
-
|
|
531
|
-
it('proves the fabricated listing really is spendable for free', function () {
|
|
532
|
-
// Without this, the test above only shows we reject *something*.
|
|
533
|
-
var interp = new bsv.Script.Interpreter()
|
|
534
|
-
var unlock = new bsv.Script().add(bsv.Opcode.OP_1)
|
|
535
|
-
var spent = interp.verify(unlock, fabricatedListing(), new bsv.Transaction(), 0, 0)
|
|
536
|
-
spent.should.equal(true)
|
|
537
|
-
})
|
|
538
|
-
|
|
539
|
-
it('rejects a genuine listing with anything appended', function () {
|
|
540
|
-
var real = Ord.buildOrdLock({ seller: owner.toAddress(), price: 50000 })
|
|
541
|
-
var tampered = bsv.Script.fromHex(real.toHex()).add(bsv.Opcode.OP_NOP)
|
|
542
|
-
Ord.isOrdLock(tampered).should.equal(false)
|
|
543
|
-
})
|
|
544
|
-
|
|
545
|
-
it('still accepts every genuine listing shape', function () {
|
|
546
|
-
var simple = Ord.buildOrdLock({ seller: owner.toAddress(), price: 50000 })
|
|
547
|
-
Ord.parseOrdLock(simple).totalPrice.should.equal(50000)
|
|
548
|
-
|
|
549
|
-
var withInsc = Ord.buildOrdLock({
|
|
550
|
-
seller: owner.toAddress(),
|
|
551
|
-
price: 777,
|
|
552
|
-
inscription: { contentType: 'text/plain', content: 'for sale' }
|
|
553
|
-
})
|
|
554
|
-
var parsed = Ord.parseOrdLock(withInsc)
|
|
555
|
-
parsed.totalPrice.should.equal(777)
|
|
556
|
-
parsed.inscription.contentText.should.equal('for sale')
|
|
557
|
-
|
|
558
|
-
var multi = Ord.buildOrdLock({
|
|
559
|
-
seller: owner.toAddress(),
|
|
560
|
-
price: 1000,
|
|
561
|
-
royalties: [{ address: attacker.toAddress(), satoshis: 50 }]
|
|
562
|
-
})
|
|
563
|
-
Ord.parseOrdLock(multi).payOutputs.length.should.equal(2)
|
|
564
|
-
Ord.parseOrdLock(multi).totalPrice.should.equal(1050)
|
|
565
|
-
})
|
|
566
|
-
})
|
|
567
|
-
})
|