@smartledger/bsv 5.5.3 → 6.0.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 +115 -0
- package/README.md +38 -38
- package/bin/cli.js +35 -5
- package/bsv-covenant.min.js +6 -6
- package/bsv-gdaf.min.js +2 -2
- package/bsv-ltp.min.js +2 -2
- package/bsv-shamir.min.js +1 -1
- package/bsv-smartcontract.min.js +6 -6
- package/bsv-statuslist.min.js +5 -5
- package/bsv-vcjwt.min.js +1 -1
- package/bsv.bundle.js +2 -2
- package/bsv.d.ts +283 -10
- package/bsv.min.js +2 -2
- 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/index.js +4 -0
- package/lib/crypto/ecdsa.js +13 -0
- package/lib/crypto/shamir.js +16 -4
- package/lib/gdaf/did-resolver.js +3 -1
- package/lib/gdaf/smartledger-anchor.js +154 -12
- package/lib/ltp/anchor.js +16 -3
- package/lib/ltp/obligation.js +56 -8
- package/lib/ltp/proof.js +4 -2
- package/lib/ltp/right.js +86 -11
- package/lib/ordinals/README.md +170 -0
- package/lib/ordinals/bsv20.js +183 -0
- package/lib/ordinals/index.js +49 -0
- package/lib/ordinals/inscription.js +159 -0
- package/lib/ordinals/ordlock.js +535 -0
- package/lib/smart_contract/builder.js +27 -3
- package/lib/smart_contract/covenant.js +37 -10
- package/lib/smart_contract/covenant_builder.js +7 -5
- package/lib/smart_contract/index.js +28 -15
- package/lib/smart_contract/preimage.js +7 -7
- package/lib/smart_contract/pushtx.js +47 -7
- package/lib/smart_contract/token.js +69 -1
- package/lib/spv/headerchain.js +77 -0
- package/lib/spv/index.js +16 -0
- package/lib/spv/merkleproof.js +108 -0
- package/lib/statuslist/index.js +65 -24
- package/lib/vcjwt/index.js +6 -0
- package/package.json +1 -1
- package/test/credentials-test.js +22 -19
- package/test/crypto/shamir.js +9 -1
- package/test/crypto/shamir_rngcap.js +29 -0
- package/test/gdaf/anchor_spv.js +163 -0
- package/test/ltp/verify_failclosed.js +134 -0
- package/test/ordinals/bsv20.js +116 -0
- package/test/ordinals/inscription.js +71 -0
- package/test/ordinals/ordlock.js +482 -0
- package/test/smart_contract/extract_field.js +61 -0
- package/test/smart_contract/nonenforcing_guard.js +44 -0
- package/test/smart_contract/ordinal_transfer.js +83 -0
- package/test/smart_contract/sighash_marketplace.js +110 -0
- package/test/spv/headerchain.js +86 -0
- package/test/spv/merkleproof.js +133 -0
- package/test/statuslist/failclosed.js +78 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
/**
|
|
3
|
+
* 1Sat Ordinals inscriptions.
|
|
4
|
+
*
|
|
5
|
+
* An inscription is content (a content-type + a body) carried on a 1-satoshi output
|
|
6
|
+
* behind an inert data envelope, appended to a normal locking script (P2PKH by
|
|
7
|
+
* default). The envelope is never executed, so spending is controlled entirely by the
|
|
8
|
+
* base lock while the inscription rides on the satoshi:
|
|
9
|
+
*
|
|
10
|
+
* <lockingScript>
|
|
11
|
+
* OP_FALSE OP_IF
|
|
12
|
+
* "ord"
|
|
13
|
+
* OP_1 <contentType>
|
|
14
|
+
* OP_0 <content>
|
|
15
|
+
* OP_ENDIF
|
|
16
|
+
*
|
|
17
|
+
* This matches the widely-used 1Sat Ordinals format (js-1sat-ord).
|
|
18
|
+
*/
|
|
19
|
+
var bsv = require('../..')
|
|
20
|
+
|
|
21
|
+
var ORD = Buffer.from('ord', 'utf8')
|
|
22
|
+
|
|
23
|
+
function scriptClass () { return bsv.Script }
|
|
24
|
+
function op () { return bsv.Opcode }
|
|
25
|
+
|
|
26
|
+
/** Resolve a base locking script from a Script, an Address, or an address string. */
|
|
27
|
+
function resolveLock (params) {
|
|
28
|
+
var Script = scriptClass()
|
|
29
|
+
if (params.lock) {
|
|
30
|
+
if (params.lock instanceof Script) return params.lock
|
|
31
|
+
if (Buffer.isBuffer(params.lock)) return Script.fromBuffer(params.lock)
|
|
32
|
+
if (typeof params.lock === 'string') return Script.fromHex(params.lock)
|
|
33
|
+
throw new Error('lock must be a Script, Buffer, or hex string')
|
|
34
|
+
}
|
|
35
|
+
if (params.address) {
|
|
36
|
+
var addr = (params.address instanceof bsv.Address)
|
|
37
|
+
? params.address
|
|
38
|
+
: bsv.Address.fromString(String(params.address))
|
|
39
|
+
return Script.buildPublicKeyHashOut(addr)
|
|
40
|
+
}
|
|
41
|
+
throw new Error('buildInscription requires an address or a lock script')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function toBuf (v, enc) {
|
|
45
|
+
if (Buffer.isBuffer(v)) return v
|
|
46
|
+
if (v == null) return Buffer.alloc(0)
|
|
47
|
+
return Buffer.from(String(v), enc || 'utf8')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Build a 1Sat Ordinals inscription locking script: a base lock followed by the
|
|
52
|
+
* inert `OP_FALSE OP_IF ... OP_ENDIF` envelope carrying the content.
|
|
53
|
+
*
|
|
54
|
+
* @param {object} params
|
|
55
|
+
* @param {string|Buffer} params.contentType e.g. 'text/plain', 'image/png'
|
|
56
|
+
* @param {string|Buffer} params.content the inscription body
|
|
57
|
+
* @param {Script|Buffer|string} [params.lock] base locking script (overrides address)
|
|
58
|
+
* @param {Address|string} [params.address] P2PKH owner (used if no `lock`)
|
|
59
|
+
* @returns {Script} the full inscription locking script
|
|
60
|
+
*/
|
|
61
|
+
function buildInscription (params) {
|
|
62
|
+
params = params || {}
|
|
63
|
+
var Opcode = op()
|
|
64
|
+
var lock = resolveLock(params)
|
|
65
|
+
var contentType = toBuf(params.contentType || 'text/plain')
|
|
66
|
+
var content = toBuf(params.content)
|
|
67
|
+
|
|
68
|
+
// Clone the base lock so we never mutate the caller's Script.
|
|
69
|
+
var s = scriptClass().fromBuffer(lock.toBuffer())
|
|
70
|
+
s.add(Opcode.OP_FALSE)
|
|
71
|
+
s.add(Opcode.OP_IF)
|
|
72
|
+
s.add(ORD)
|
|
73
|
+
s.add(Opcode.OP_1)
|
|
74
|
+
s.add(contentType)
|
|
75
|
+
s.add(Opcode.OP_0)
|
|
76
|
+
s.add(content)
|
|
77
|
+
s.add(Opcode.OP_ENDIF)
|
|
78
|
+
return s
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function chunkIsOp (chunk, opcodenum) {
|
|
82
|
+
return chunk && chunk.opcodenum === opcodenum && (chunk.buf == null)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Parse a 1Sat Ordinals inscription out of a locking script.
|
|
87
|
+
* @param {Script|Buffer|string} script
|
|
88
|
+
* @returns {null|{ contentType: string, content: Buffer, contentText: string, lock: Script }}
|
|
89
|
+
* null if the script carries no inscription envelope.
|
|
90
|
+
*/
|
|
91
|
+
function parseInscription (script) {
|
|
92
|
+
var Script = scriptClass()
|
|
93
|
+
var Opcode = op()
|
|
94
|
+
var s = (script instanceof Script) ? script
|
|
95
|
+
: Buffer.isBuffer(script) ? Script.fromBuffer(script)
|
|
96
|
+
: Script.fromHex(script)
|
|
97
|
+
var chunks = s.chunks
|
|
98
|
+
|
|
99
|
+
// Find `OP_FALSE OP_IF "ord"`.
|
|
100
|
+
var start = -1
|
|
101
|
+
for (var i = 0; i + 2 < chunks.length; i++) {
|
|
102
|
+
if (chunkIsOp(chunks[i], Opcode.OP_FALSE) &&
|
|
103
|
+
chunkIsOp(chunks[i + 1], Opcode.OP_IF) &&
|
|
104
|
+
chunks[i + 2].buf && chunks[i + 2].buf.equals(ORD)) {
|
|
105
|
+
start = i
|
|
106
|
+
break
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (start === -1) return null
|
|
110
|
+
|
|
111
|
+
// Everything before the envelope is the base lock.
|
|
112
|
+
var lock = new Script()
|
|
113
|
+
for (var k = 0; k < start; k++) lock.chunks.push(chunks[k])
|
|
114
|
+
|
|
115
|
+
// Walk fields after "ord" until OP_ENDIF: OP_1 => content-type, OP_0 => body.
|
|
116
|
+
var contentType = Buffer.alloc(0)
|
|
117
|
+
var content = Buffer.alloc(0)
|
|
118
|
+
for (var j = start + 3; j < chunks.length; j++) {
|
|
119
|
+
var c = chunks[j]
|
|
120
|
+
if (chunkIsOp(c, Opcode.OP_ENDIF)) break
|
|
121
|
+
if (chunkIsOp(c, Opcode.OP_1) && chunks[j + 1] && chunks[j + 1].buf) {
|
|
122
|
+
contentType = chunks[j + 1].buf; j++
|
|
123
|
+
} else if (chunkIsOp(c, Opcode.OP_0) && chunks[j + 1] && chunks[j + 1].buf) {
|
|
124
|
+
content = chunks[j + 1].buf; j++
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
contentType: contentType.toString('utf8'),
|
|
130
|
+
content: content,
|
|
131
|
+
contentText: content.toString('utf8'),
|
|
132
|
+
lock: lock
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** True if the script carries an inscription envelope. */
|
|
137
|
+
function isInscription (script) {
|
|
138
|
+
try { return parseInscription(script) !== null } catch (e) { return false }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Build the 1-satoshi Transaction.Output carrying an inscription.
|
|
143
|
+
* @returns {Transaction.Output}
|
|
144
|
+
*/
|
|
145
|
+
function createInscriptionOutput (params) {
|
|
146
|
+
params = params || {}
|
|
147
|
+
var satoshis = params.satoshis != null ? params.satoshis : 1
|
|
148
|
+
return new bsv.Transaction.Output({
|
|
149
|
+
script: buildInscription(params),
|
|
150
|
+
satoshis: satoshis
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
module.exports = {
|
|
155
|
+
buildInscription: buildInscription,
|
|
156
|
+
parseInscription: parseInscription,
|
|
157
|
+
isInscription: isInscription,
|
|
158
|
+
createInscriptionOutput: createInscriptionOutput
|
|
159
|
+
}
|
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
/**
|
|
3
|
+
* OrdLock — a trustless "list an ordinal for sale" covenant for 1Sat Ordinals.
|
|
4
|
+
*
|
|
5
|
+
* A seller locks the 1-sat ordinal UTXO behind a script with two spend paths:
|
|
6
|
+
*
|
|
7
|
+
* PURCHASE anyone may take the ordinal, but ONLY by paying the seller. The buyer
|
|
8
|
+
* builds the spend, the covenant authenticates its BIP-143 preimage with
|
|
9
|
+
* OP_PUSH_TX and requires that the transaction's outputs are exactly
|
|
10
|
+
* `buyerOutputs || payBlob || trailingOutputs` — where `payBlob` (the
|
|
11
|
+
* required payment output(s)) is hard-baked into the locking script. The
|
|
12
|
+
* buyer is free to choose where the ordinal goes and to add funding inputs
|
|
13
|
+
* / change (SIGHASH_ALL|ANYONECANPAY): the ONE thing they cannot do is take
|
|
14
|
+
* the ordinal without recreating the required payment(s) byte-for-byte.
|
|
15
|
+
*
|
|
16
|
+
* CANCEL the seller reclaims the listing at any time with an ordinary ECDSA
|
|
17
|
+
* signature over their own public key (a P2PKH gate inside the covenant).
|
|
18
|
+
*
|
|
19
|
+
* `payBlob` may pin MORE THAN ONE output — the concatenation of a seller payment plus
|
|
20
|
+
* any royalty / marketplace-fee outputs — so a purchase atomically pays every party or
|
|
21
|
+
* fails. This is the widely-deployed 1Sat Ordinals OrdinalLock pattern, generalized and
|
|
22
|
+
* built on the audited configurable-SIGHASH OP_PUSH_TX core (SmartContract.PushTx).
|
|
23
|
+
* Every script this module emits is exercised through the consensus interpreter.
|
|
24
|
+
*
|
|
25
|
+
* The listing is self-describing: `parseOrdLock(script)` recovers the seller, the pinned
|
|
26
|
+
* payment output(s), the total price, and any inline inscription — so an indexer, wallet,
|
|
27
|
+
* or marketplace UI can read a listing straight off-chain, and `buildPurchaseTx` can
|
|
28
|
+
* assemble a complete, signed purchase knowing only the listing UTXO and the buyer's coins.
|
|
29
|
+
*
|
|
30
|
+
* Layout (locking script):
|
|
31
|
+
* OP_IF
|
|
32
|
+
* <sellerPKH> ... // CANCEL: OP_DUP OP_HASH160 <pkh> OP_EQUALVERIFY OP_CHECKSIG
|
|
33
|
+
* OP_ELSE
|
|
34
|
+
* ...OP_PUSH_TX core... // PURCHASE: authenticate preimage, bind outputs to payBlob
|
|
35
|
+
* OP_ENDIF
|
|
36
|
+
* [OP_FALSE OP_IF "ord" … OP_ENDIF] // optional inert inscription envelope
|
|
37
|
+
*
|
|
38
|
+
* Requires post-Genesis limits: call SmartContract.enableGenesis() before verifying.
|
|
39
|
+
*/
|
|
40
|
+
var bsv = require('../..')
|
|
41
|
+
var P = require('../smart_contract/pushtx')
|
|
42
|
+
var H = require('../smart_contract/covenant_helpers')
|
|
43
|
+
var inscription = require('./inscription')
|
|
44
|
+
|
|
45
|
+
var Script = bsv.Script
|
|
46
|
+
var Opcode = bsv.Opcode
|
|
47
|
+
var BufferReader = bsv.encoding.BufferReader
|
|
48
|
+
var BN = bsv.crypto.BN
|
|
49
|
+
|
|
50
|
+
// OrdLock commits to the FULL output set (so payments can be pinned) but leaves the
|
|
51
|
+
// input set open for the buyer's funding — SIGHASH_ALL|ANYONECANPAY|FORKID (0xc1).
|
|
52
|
+
var ORDLOCK_SIGHASH = P.SIGHASH_ALL_ANYONECANPAY_FORKID
|
|
53
|
+
|
|
54
|
+
/** Push a possibly-empty buffer MINIMALDATA-cleanly (empty => OP_0, i.e. empty vector). */
|
|
55
|
+
function pushData (s, buf) { return (buf && buf.length) ? s.add(Buffer.from(buf)) : s.add(Opcode.OP_0) }
|
|
56
|
+
|
|
57
|
+
/** Serialize a Transaction.Output to its on-wire bytes (8-byte value || varint || script). */
|
|
58
|
+
function serializeOutput (out) { return out.toBufferWriter().toBuffer() }
|
|
59
|
+
|
|
60
|
+
/** Coerce a Script, Buffer, or hex string to a Script. */
|
|
61
|
+
function coerceScript (v) {
|
|
62
|
+
if (v instanceof Script) return v
|
|
63
|
+
if (Buffer.isBuffer(v)) return Script.fromBuffer(v)
|
|
64
|
+
return Script.fromHex(v)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Coerce an Address, PublicKey, PrivateKey, or address string to an Address. */
|
|
68
|
+
function toAddress (v) {
|
|
69
|
+
if (v instanceof bsv.Address) return v
|
|
70
|
+
if (v && typeof v.toAddress === 'function') return v.toAddress() // PublicKey / PrivateKey
|
|
71
|
+
return bsv.Address.fromString(String(v))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Resolve a 20-byte HASH160 owner commitment from an Address/PublicKey/key or 20-byte Buffer. */
|
|
75
|
+
function resolvePubKeyHash (owner) {
|
|
76
|
+
if (Buffer.isBuffer(owner)) {
|
|
77
|
+
if (owner.length !== 20) throw new Error('seller pubkeyhash buffer must be 20 bytes (HASH160)')
|
|
78
|
+
return owner
|
|
79
|
+
}
|
|
80
|
+
if (owner instanceof bsv.PublicKey) return bsv.crypto.Hash.sha256ripemd160(owner.toBuffer())
|
|
81
|
+
return toAddress(owner).hashBuffer
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Build the seller's payment Output (P2PKH) from an address/pubkey/key and a price in sats. */
|
|
85
|
+
function payOutputFor (payTo, price) {
|
|
86
|
+
if (payTo instanceof bsv.Transaction.Output) return payTo
|
|
87
|
+
return new bsv.Transaction.Output({
|
|
88
|
+
script: Script.buildPublicKeyHashOut(toAddress(payTo)), satoshis: price
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Coerce one payment spec to a Transaction.Output. */
|
|
93
|
+
function outputFromSpec (spec) {
|
|
94
|
+
if (spec instanceof bsv.Transaction.Output) return spec
|
|
95
|
+
if (Buffer.isBuffer(spec)) return bsv.Transaction.Output.fromBufferReader(new BufferReader(spec))
|
|
96
|
+
var to = spec.payTo != null ? spec.payTo : spec.address
|
|
97
|
+
var amt = spec.satoshis != null ? spec.satoshis : spec.price
|
|
98
|
+
if (to == null || amt == null) throw new Error('payment spec needs {address|payTo, satoshis|price}')
|
|
99
|
+
return payOutputFor(to, amt)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Resolve the required payment output(s) for a listing, in the order they must be
|
|
104
|
+
* recreated. Accepts (in priority order): `payOutputs` (array of Output/spec/Buffer),
|
|
105
|
+
* a single `payOutput`, or `price`+`payTo` (defaulting the recipient to `seller`) with
|
|
106
|
+
* optional `royalties` appended.
|
|
107
|
+
*/
|
|
108
|
+
function resolvePayOutputs (params) {
|
|
109
|
+
if (params.payOutputs) {
|
|
110
|
+
if (!params.payOutputs.length) throw new Error('payOutputs must be a non-empty array')
|
|
111
|
+
return params.payOutputs.map(outputFromSpec)
|
|
112
|
+
}
|
|
113
|
+
if (params.payOutput != null) return [outputFromSpec(params.payOutput)]
|
|
114
|
+
if (params.price != null) {
|
|
115
|
+
var arr = [payOutputFor(params.payTo != null ? params.payTo : params.seller, params.price)]
|
|
116
|
+
if (params.royalties) params.royalties.forEach(function (r) { arr.push(outputFromSpec(r)) })
|
|
117
|
+
return arr
|
|
118
|
+
}
|
|
119
|
+
throw new Error('buildOrdLock requires a price, a payOutput, or payOutputs')
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** The pinned payment bytes = concatenation of every required output's serialization. */
|
|
123
|
+
function payBlobFrom (outputs) { return Buffer.concat(outputs.map(serializeOutput)) }
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Build an OrdLock listing (locking) script.
|
|
127
|
+
*
|
|
128
|
+
* @param {object} params
|
|
129
|
+
* @param {Address|PublicKey|string|Buffer} params.seller who may CANCEL the listing
|
|
130
|
+
* (an address, public key, or 20-byte HASH160). Also the default payment recipient.
|
|
131
|
+
* @param {number} [params.price] asking price in satoshis (simple single-payment form).
|
|
132
|
+
* @param {Address|PublicKey|string|Transaction.Output} [params.payTo] payment recipient
|
|
133
|
+
* for `price` (default: `seller`).
|
|
134
|
+
* @param {Array} [params.royalties] extra payment specs appended after the `price` output
|
|
135
|
+
* (each: Transaction.Output, {address,satoshis}, or a serialized-output Buffer).
|
|
136
|
+
* @param {Array} [params.payOutputs] pin an explicit ordered list of payment outputs,
|
|
137
|
+
* overriding price/payTo/royalties (seller + royalty + marketplace-fee, etc.).
|
|
138
|
+
* @param {Transaction.Output|Buffer} [params.payOutput] pin a single explicit output.
|
|
139
|
+
* @param {object} [params.inscription] if given ({contentType, content}), an inscription
|
|
140
|
+
* envelope is appended so a fresh inscribe+list share one output.
|
|
141
|
+
* @returns {Script} the OrdLock locking script.
|
|
142
|
+
*/
|
|
143
|
+
function buildOrdLock (params) {
|
|
144
|
+
params = params || {}
|
|
145
|
+
if (params.seller == null) throw new Error('buildOrdLock requires a seller')
|
|
146
|
+
var sellerPKH = resolvePubKeyHash(params.seller)
|
|
147
|
+
var payBlob = payBlobFrom(resolvePayOutputs(params))
|
|
148
|
+
|
|
149
|
+
var s = new Script()
|
|
150
|
+
s.add(Opcode.OP_IF)
|
|
151
|
+
// CANCEL: seller signs with their key; standard P2PKH gate.
|
|
152
|
+
s.add(Opcode.OP_DUP).add(Opcode.OP_HASH160)
|
|
153
|
+
s.add(Buffer.from(sellerPKH)).add(Opcode.OP_EQUALVERIFY).add(Opcode.OP_CHECKSIG)
|
|
154
|
+
s.add(Opcode.OP_ELSE)
|
|
155
|
+
// PURCHASE: stack (after OP_IF pops the false marker) = [.., trailing, buyerOuts, preimage]
|
|
156
|
+
s.add(Opcode.OP_DUP)
|
|
157
|
+
P.pushTxCore(s, { sighashType: ORDLOCK_SIGHASH }) // authenticate the preimage
|
|
158
|
+
s.add(Opcode.OP_VERIFY)
|
|
159
|
+
P.assertSighashType(s, ORDLOCK_SIGHASH) // pin the flag (defense-in-depth); leaves preimage
|
|
160
|
+
P.extractHashOutputs(s) // preimage -> committed hashOutputs
|
|
161
|
+
s.add(Opcode.OP_TOALTSTACK) // park committedHO; main: [trailing, buyerOuts]
|
|
162
|
+
s.add(Buffer.from(payBlob)).add(Opcode.OP_CAT) // buyerOuts || payBlob
|
|
163
|
+
s.add(Opcode.OP_SWAP).add(Opcode.OP_CAT) // (buyerOuts || payBlob) || trailing
|
|
164
|
+
s.add(Opcode.OP_HASH256)
|
|
165
|
+
s.add(Opcode.OP_FROMALTSTACK).add(Opcode.OP_EQUAL) // == committed hashOutputs ?
|
|
166
|
+
s.add(Opcode.OP_ENDIF)
|
|
167
|
+
|
|
168
|
+
if (params.inscription) {
|
|
169
|
+
var env = inscription.buildInscription({
|
|
170
|
+
lock: new Script(), // envelope only; the covenant above is the real lock
|
|
171
|
+
contentType: params.inscription.contentType,
|
|
172
|
+
content: params.inscription.content
|
|
173
|
+
})
|
|
174
|
+
env.chunks.forEach(function (c) { s.chunks.push(c) })
|
|
175
|
+
}
|
|
176
|
+
return s
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function chunkIsOp (chunk, opcodenum) {
|
|
180
|
+
return chunk && chunk.opcodenum === opcodenum && (chunk.buf == null)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Split a pinned payBlob back into its constituent Transaction.Outputs. */
|
|
184
|
+
function splitPayBlob (blob) {
|
|
185
|
+
var br = new BufferReader(blob)
|
|
186
|
+
var outs = []
|
|
187
|
+
while (!br.eof()) outs.push(bsv.Transaction.Output.fromBufferReader(br))
|
|
188
|
+
return outs
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Parse an OrdLock listing script into its economic terms. Returns null if `script`
|
|
193
|
+
* is not a recognizable OrdLock (so it doubles as a detector).
|
|
194
|
+
*
|
|
195
|
+
* @param {Script|Buffer|string} script
|
|
196
|
+
* @returns {null|{
|
|
197
|
+
* seller: { pubKeyHash: Buffer, address: string },
|
|
198
|
+
* payOutputs: Array<{ satoshis: number, script: Script, address: string|null }>,
|
|
199
|
+
* payBlob: Buffer,
|
|
200
|
+
* totalPrice: number,
|
|
201
|
+
* inscription: null|{ contentType: string, content: Buffer, contentText: string }
|
|
202
|
+
* }}
|
|
203
|
+
*/
|
|
204
|
+
function parseOrdLock (script) {
|
|
205
|
+
try {
|
|
206
|
+
var s = coerceScript(script)
|
|
207
|
+
var chunks = s.chunks
|
|
208
|
+
// Locate the top-level OP_IF … OP_ELSE … OP_ENDIF of the covenant.
|
|
209
|
+
var depth = 0
|
|
210
|
+
var ifIdx = -1
|
|
211
|
+
var elseIdx = -1
|
|
212
|
+
var endIdx = -1
|
|
213
|
+
for (var i = 0; i < chunks.length; i++) {
|
|
214
|
+
if (chunkIsOp(chunks[i], Opcode.OP_IF)) { if (depth === 0) ifIdx = i; depth++ } else if (chunkIsOp(chunks[i], Opcode.OP_ELSE) && depth === 1) { elseIdx = i } else if (chunkIsOp(chunks[i], Opcode.OP_ENDIF)) { depth--; if (depth === 0) { endIdx = i; break } }
|
|
215
|
+
}
|
|
216
|
+
if (ifIdx === -1 || elseIdx === -1 || endIdx === -1) return null
|
|
217
|
+
|
|
218
|
+
// sellerPKH: the sole 20-byte push in the CANCEL branch (after OP_HASH160).
|
|
219
|
+
var sellerPKH = null
|
|
220
|
+
for (var c = ifIdx + 1; c < elseIdx; c++) {
|
|
221
|
+
if (chunks[c].buf && chunks[c].buf.length === 20) { sellerPKH = chunks[c].buf; break }
|
|
222
|
+
}
|
|
223
|
+
if (!sellerPKH) return null
|
|
224
|
+
|
|
225
|
+
// payBlob: the constant pushed after the LAST OP_TOALTSTACK in the PURCHASE branch
|
|
226
|
+
// (an earlier OP_TOALTSTACK inside pushTxCore parks the pubkey; ours parks the
|
|
227
|
+
// committed hashOutputs and is immediately followed by the payBlob push, then
|
|
228
|
+
// OP_CAT OP_SWAP OP_CAT OP_HASH256).
|
|
229
|
+
var payBlob = null
|
|
230
|
+
for (var d = elseIdx + 1; d < endIdx; d++) {
|
|
231
|
+
if (chunkIsOp(chunks[d], Opcode.OP_TOALTSTACK) &&
|
|
232
|
+
chunks[d + 1] && chunks[d + 1].buf &&
|
|
233
|
+
chunkIsOp(chunks[d + 2], Opcode.OP_CAT) &&
|
|
234
|
+
chunkIsOp(chunks[d + 3], Opcode.OP_SWAP)) {
|
|
235
|
+
payBlob = chunks[d + 1].buf // keep scanning is unnecessary — this tail is unique
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (!payBlob) return null
|
|
239
|
+
|
|
240
|
+
var payOutputs = splitPayBlob(payBlob).map(function (o) {
|
|
241
|
+
var addr = null
|
|
242
|
+
try { if (o.script.isPublicKeyHashOut()) addr = o.script.toAddress().toString() } catch (e) {}
|
|
243
|
+
return { satoshis: o.satoshis, script: o.script, address: addr }
|
|
244
|
+
})
|
|
245
|
+
var totalPrice = payOutputs.reduce(function (a, o) { return a + o.satoshis }, 0)
|
|
246
|
+
|
|
247
|
+
var insc = null
|
|
248
|
+
var parsedInsc = inscription.parseInscription(s)
|
|
249
|
+
if (parsedInsc) {
|
|
250
|
+
insc = { contentType: parsedInsc.contentType, content: parsedInsc.content, contentText: parsedInsc.contentText }
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
seller: { pubKeyHash: sellerPKH, address: bsv.Address.fromPublicKeyHash(sellerPKH).toString() },
|
|
255
|
+
payOutputs: payOutputs,
|
|
256
|
+
payBlob: payBlob,
|
|
257
|
+
totalPrice: totalPrice,
|
|
258
|
+
inscription: insc
|
|
259
|
+
}
|
|
260
|
+
} catch (e) {
|
|
261
|
+
return null
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** True if `script` is a recognizable OrdLock listing. */
|
|
266
|
+
function isOrdLock (script) { return parseOrdLock(script) !== null }
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Build the 1-sat Transaction.Output that lists an ordinal for sale under an OrdLock.
|
|
270
|
+
* @returns {Transaction.Output}
|
|
271
|
+
*/
|
|
272
|
+
function listInscriptionOutput (params) {
|
|
273
|
+
params = params || {}
|
|
274
|
+
var satoshis = params.satoshis != null ? params.satoshis : 1
|
|
275
|
+
return new bsv.Transaction.Output({ script: buildOrdLock(params), satoshis: satoshis })
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Build the unlocking script that PURCHASES a listed ordinal.
|
|
280
|
+
*
|
|
281
|
+
* The caller must first build `spend` with the ordinal's OrdLock input at
|
|
282
|
+
* `inputIndex`, and its OUTPUTS arranged as:
|
|
283
|
+
* [ ...buyerOutputs, <pinned payment output(s)>, ...trailingOutputs ]
|
|
284
|
+
* where the pinned block (starting at `payoutIndex`, `payoutCount` outputs long) is the
|
|
285
|
+
* required payment(s) recreated EXACTLY as the listing pinned them. `payoutCount` is
|
|
286
|
+
* auto-derived from the listing script when omitted. Everything before the block
|
|
287
|
+
* (typically the 1-sat ordinal sent to the buyer) and everything after it (change, data)
|
|
288
|
+
* is unconstrained by the covenant.
|
|
289
|
+
*
|
|
290
|
+
* IMPORTANT ORDERING: this grinds the spend's nLockTime to a valid OP_PUSH_TX
|
|
291
|
+
* signature, which changes the sighash of EVERY input. Call `purchase()` BEFORE
|
|
292
|
+
* signing any funding inputs the buyer adds.
|
|
293
|
+
*
|
|
294
|
+
* @param {object} params
|
|
295
|
+
* @param {Transaction} params.spend the spend tx (outputs already set).
|
|
296
|
+
* @param {Script} params.lockingScript the OrdLock script being spent.
|
|
297
|
+
* @param {number} [params.satoshis=1] satoshis on the listing UTXO.
|
|
298
|
+
* @param {number} [params.inputIndex=0] the OrdLock input's index.
|
|
299
|
+
* @param {number} [params.payoutIndex=1] index of the first pinned payment output.
|
|
300
|
+
* @param {number} [params.payoutCount] number of pinned outputs (auto-derived if omitted).
|
|
301
|
+
* @param {boolean} [params.validate=true] assert the pinned block matches the listing.
|
|
302
|
+
* @param {object} [params.grind] options forwarded to PushTx.grind.
|
|
303
|
+
* @returns {Script} the unlocking script (also assigned onto the input).
|
|
304
|
+
*/
|
|
305
|
+
function purchase (params) {
|
|
306
|
+
params = params || {}
|
|
307
|
+
var spend = params.spend
|
|
308
|
+
var lockingScript = params.lockingScript
|
|
309
|
+
if (!spend || !lockingScript) throw new Error('purchase requires { spend, lockingScript }')
|
|
310
|
+
var inputIndex = params.inputIndex || 0
|
|
311
|
+
var payoutIndex = params.payoutIndex != null ? params.payoutIndex : 1
|
|
312
|
+
var satoshis = params.satoshis != null ? params.satoshis : 1
|
|
313
|
+
var outs = spend.outputs || []
|
|
314
|
+
|
|
315
|
+
var parsed = parseOrdLock(lockingScript)
|
|
316
|
+
var payoutCount = params.payoutCount != null ? params.payoutCount
|
|
317
|
+
: (parsed ? parsed.payOutputs.length : 1)
|
|
318
|
+
if (payoutIndex + payoutCount > outs.length) {
|
|
319
|
+
throw new Error('purchase: spend has fewer outputs than the pinned block requires ' +
|
|
320
|
+
'(need ' + (payoutIndex + payoutCount) + ', have ' + outs.length + ')')
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Fail-fast: the pinned block must recreate the listing's required payment(s) exactly.
|
|
324
|
+
if (params.validate !== false && parsed) {
|
|
325
|
+
var pinned = Buffer.concat(outs.slice(payoutIndex, payoutIndex + payoutCount).map(serializeOutput))
|
|
326
|
+
if (!pinned.equals(parsed.payBlob)) {
|
|
327
|
+
throw new Error('purchase: outputs [' + payoutIndex + '..' + (payoutIndex + payoutCount - 1) +
|
|
328
|
+
'] do not match the listing payment — the seller (or a royalty/fee payee) would not be paid')
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
var buyerOuts = Buffer.concat(outs.slice(0, payoutIndex).map(serializeOutput))
|
|
333
|
+
var trailing = Buffer.concat(outs.slice(payoutIndex + payoutCount).map(serializeOutput))
|
|
334
|
+
|
|
335
|
+
var grindOpts = Object.assign({ sighashType: ORDLOCK_SIGHASH }, params.grind || {})
|
|
336
|
+
var g = P.grind(spend, inputIndex, lockingScript, satoshis, grindOpts)
|
|
337
|
+
|
|
338
|
+
var us = new Script()
|
|
339
|
+
pushData(us, trailing)
|
|
340
|
+
pushData(us, buyerOuts)
|
|
341
|
+
us.add(Buffer.from(g.preimage))
|
|
342
|
+
us.add(Opcode.OP_FALSE) // select the OP_ELSE (purchase) branch
|
|
343
|
+
spend.inputs[inputIndex].setScript(us)
|
|
344
|
+
return us
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Build the unlocking script that CANCELS a listing (seller reclaims the ordinal).
|
|
349
|
+
* The seller signs the spend over the OrdLock locking script with their own key.
|
|
350
|
+
*
|
|
351
|
+
* @param {object} params
|
|
352
|
+
* @param {PrivateKey} params.privateKey the seller's key (must hash to sellerPKH).
|
|
353
|
+
* @param {Transaction} params.spend the reclaim tx (outputs already set).
|
|
354
|
+
* @param {Script} params.lockingScript the OrdLock script being spent.
|
|
355
|
+
* @param {number} [params.satoshis=1] satoshis on the listing UTXO.
|
|
356
|
+
* @param {number} [params.inputIndex=0] the OrdLock input's index.
|
|
357
|
+
* @param {number} [params.sighashType] signature flag (default SIGHASH_ALL|FORKID).
|
|
358
|
+
* @returns {Script} the unlocking script (also assigned onto the input).
|
|
359
|
+
*/
|
|
360
|
+
function cancel (params) {
|
|
361
|
+
params = params || {}
|
|
362
|
+
var privateKey = params.privateKey
|
|
363
|
+
var spend = params.spend
|
|
364
|
+
var lockingScript = params.lockingScript
|
|
365
|
+
if (!privateKey || !spend || !lockingScript) {
|
|
366
|
+
throw new Error('cancel requires { privateKey, spend, lockingScript }')
|
|
367
|
+
}
|
|
368
|
+
var inputIndex = params.inputIndex || 0
|
|
369
|
+
var satoshis = params.satoshis != null ? params.satoshis : 1
|
|
370
|
+
var sighashType = params.sighashType != null ? params.sighashType : H.SIGHASH
|
|
371
|
+
|
|
372
|
+
var sig = H.signInput(spend, privateKey, inputIndex, lockingScript, satoshis, sighashType)
|
|
373
|
+
var us = new Script()
|
|
374
|
+
us.add(Buffer.from(sig))
|
|
375
|
+
us.add(privateKey.toPublicKey().toBuffer())
|
|
376
|
+
us.add(Opcode.OP_TRUE) // select the OP_IF (cancel) branch
|
|
377
|
+
spend.inputs[inputIndex].setScript(us)
|
|
378
|
+
return us
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** Add an input spending `outpoint` under `script` worth `satoshis`, script-sig empty. */
|
|
382
|
+
function addSpendInput (tx, outpoint, script, satoshis) {
|
|
383
|
+
tx.addInput(new bsv.Transaction.Input({
|
|
384
|
+
prevTxId: outpoint.txid || outpoint.prevTxId,
|
|
385
|
+
outputIndex: outpoint.outputIndex != null ? outpoint.outputIndex : outpoint.vout,
|
|
386
|
+
script: Script.empty()
|
|
387
|
+
}), coerceScript(script), satoshis)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** Sign input `idx` as P2PKH with `coin.privateKey` over the finalized tx (ALL|FORKID). */
|
|
391
|
+
function signP2PKHInput (tx, idx, coin) {
|
|
392
|
+
var sc = coerceScript(coin.script)
|
|
393
|
+
var sig = H.signInput(tx, coin.privateKey, idx, sc, coin.satoshis, P.SIGHASH_ALL_FORKID)
|
|
394
|
+
var us = new Script()
|
|
395
|
+
us.add(Buffer.from(sig))
|
|
396
|
+
us.add(coin.privateKey.toPublicKey().toBuffer())
|
|
397
|
+
tx.inputs[idx].setScript(us)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Assemble a COMPLETE, signed LISTING transaction: move a 1-sat ordinal held under a
|
|
402
|
+
* P2PKH (its `privateKey`) into an OrdLock listing output, paying the fee from separate
|
|
403
|
+
* funding coins. The ordinal's satoshi (input 0's first sat) maps to output 0 (the
|
|
404
|
+
* listing) under ordinal FIFO, so the inscribed sat is preserved into the listing.
|
|
405
|
+
*
|
|
406
|
+
* Accepts all the OrdLock term params of `buildOrdLock` (seller, price, royalties,
|
|
407
|
+
* payOutputs, inscription) plus:
|
|
408
|
+
* @param {object} params.ordinal the ordinal UTXO: { txid|prevTxId, outputIndex|vout,
|
|
409
|
+
* script (P2PKH), satoshis=1, privateKey }.
|
|
410
|
+
* @param {Array} [params.funding] P2PKH fee coins: { ...outpoint, script, satoshis, privateKey }.
|
|
411
|
+
* @param {number} [params.fee=500] miner fee (paid from funding).
|
|
412
|
+
* @param {Address|PublicKey|string} [params.changeAddress] change recipient
|
|
413
|
+
* (default: the ordinal owner's address).
|
|
414
|
+
* @returns {{ tx: Transaction, listingScript: Script, listingOutpoint: {txid,outputIndex} }}
|
|
415
|
+
*/
|
|
416
|
+
function buildListingTx (params) {
|
|
417
|
+
params = params || {}
|
|
418
|
+
var ordinal = params.ordinal
|
|
419
|
+
if (!ordinal || !ordinal.script || !ordinal.privateKey) {
|
|
420
|
+
throw new Error('buildListingTx requires ordinal { script, privateKey, txid, outputIndex }')
|
|
421
|
+
}
|
|
422
|
+
var ordSats = ordinal.satoshis != null ? ordinal.satoshis : 1
|
|
423
|
+
var lock = buildOrdLock(params)
|
|
424
|
+
|
|
425
|
+
var funding = params.funding || []
|
|
426
|
+
var fee = params.fee != null ? params.fee : 500
|
|
427
|
+
var fundingTotal = funding.reduce(function (a, f) { return a + f.satoshis }, 0)
|
|
428
|
+
var change = fundingTotal - fee
|
|
429
|
+
if (change < 0) {
|
|
430
|
+
throw new Error('buildListingTx: insufficient funding — need ' + fee + ' sat fee, funded ' + fundingTotal)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
var tx = new bsv.Transaction()
|
|
434
|
+
addSpendInput(tx, ordinal, ordinal.script, ordSats) // input 0 = the ordinal (P2PKH)
|
|
435
|
+
funding.forEach(function (f) { addSpendInput(tx, f, f.script, f.satoshis) })
|
|
436
|
+
|
|
437
|
+
tx.addOutput(new bsv.Transaction.Output({ script: lock, satoshis: ordSats })) // output 0 = listing
|
|
438
|
+
if (change > 0) {
|
|
439
|
+
tx.addOutput(payOutputFor(params.changeAddress || ordinal.privateKey.toAddress(), change))
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
signP2PKHInput(tx, 0, ordinal)
|
|
443
|
+
funding.forEach(function (f, i) { signP2PKHInput(tx, 1 + i, f) })
|
|
444
|
+
|
|
445
|
+
return { tx: tx, listingScript: lock, listingOutpoint: { txid: tx.hash, outputIndex: 0 } }
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Assemble a COMPLETE, signed purchase transaction from a listing UTXO and the buyer's
|
|
450
|
+
* P2PKH funding coins. The required payment(s) are read straight off the listing script
|
|
451
|
+
* (or overridden via `payOutputs`), so the buyer needs only the listing outpoint.
|
|
452
|
+
*
|
|
453
|
+
* Output layout: [ ordinal -> buyer, <pinned payment output(s)>, change -> buyer ].
|
|
454
|
+
* The ordinal's satoshi (input 0's first sat) maps to output 0 under ordinal FIFO, so it
|
|
455
|
+
* lands with the buyer; every pinned payee is paid; the fee comes from the funding coins.
|
|
456
|
+
*
|
|
457
|
+
* @param {object} params
|
|
458
|
+
* @param {object} params.listing { txid|prevTxId, outputIndex|vout, script, satoshis=1 }.
|
|
459
|
+
* @param {Address|PublicKey|string} params.ordinalDestination where the ordinal goes.
|
|
460
|
+
* @param {Array} params.funding buyer coins: { txid|prevTxId, outputIndex|vout, script,
|
|
461
|
+
* satoshis, privateKey } (P2PKH).
|
|
462
|
+
* @param {Array} [params.payOutputs] override the pinned payment(s) (must still match the
|
|
463
|
+
* listing byte-for-byte, else the covenant rejects).
|
|
464
|
+
* @param {number} [params.fee=500] miner fee in satoshis (paid from funding).
|
|
465
|
+
* @param {Address|PublicKey|string} [params.changeAddress] change recipient
|
|
466
|
+
* (default: ordinalDestination). Change omitted if zero.
|
|
467
|
+
* @param {object} [params.grind] options forwarded to PushTx.grind.
|
|
468
|
+
* @returns {Transaction} the fully-built, signed purchase transaction.
|
|
469
|
+
*/
|
|
470
|
+
function buildPurchaseTx (params) {
|
|
471
|
+
params = params || {}
|
|
472
|
+
var listing = params.listing
|
|
473
|
+
if (!listing || !listing.script) throw new Error('buildPurchaseTx requires listing.script')
|
|
474
|
+
if (!params.ordinalDestination) throw new Error('buildPurchaseTx requires ordinalDestination')
|
|
475
|
+
if (!params.funding || !params.funding.length) throw new Error('buildPurchaseTx requires funding coins')
|
|
476
|
+
|
|
477
|
+
var lockingScript = coerceScript(listing.script)
|
|
478
|
+
var parsed = parseOrdLock(lockingScript)
|
|
479
|
+
if (!parsed) throw new Error('buildPurchaseTx: listing.script is not a parseable OrdLock')
|
|
480
|
+
var ordSats = listing.satoshis != null ? listing.satoshis : 1
|
|
481
|
+
|
|
482
|
+
var payOuts = params.payOutputs
|
|
483
|
+
? params.payOutputs.map(outputFromSpec)
|
|
484
|
+
: parsed.payOutputs.map(function (p) {
|
|
485
|
+
return new bsv.Transaction.Output({ script: p.script, satoshis: p.satoshis })
|
|
486
|
+
})
|
|
487
|
+
var paidTotal = payOuts.reduce(function (a, o) { return a + o.satoshis }, 0)
|
|
488
|
+
|
|
489
|
+
var fee = params.fee != null ? params.fee : 500
|
|
490
|
+
var fundingTotal = params.funding.reduce(function (a, f) { return a + f.satoshis }, 0)
|
|
491
|
+
var change = fundingTotal - paidTotal - fee
|
|
492
|
+
if (change < 0) {
|
|
493
|
+
throw new Error('buildPurchaseTx: insufficient funding — need ' + (paidTotal + fee) +
|
|
494
|
+
' sat (payments ' + paidTotal + ' + fee ' + fee + '), funded ' + fundingTotal)
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
var tx = new bsv.Transaction()
|
|
498
|
+
addSpendInput(tx, listing, lockingScript, ordSats) // input 0 = the listing (covenant)
|
|
499
|
+
params.funding.forEach(function (f) { addSpendInput(tx, f, f.script, f.satoshis) })
|
|
500
|
+
|
|
501
|
+
tx.addOutput(payOutputFor(params.ordinalDestination, ordSats)) // output 0 = ordinal -> buyer
|
|
502
|
+
payOuts.forEach(function (o) { tx.addOutput(o) }) // pinned payment(s)
|
|
503
|
+
if (change > 0) {
|
|
504
|
+
tx.addOutput(payOutputFor(params.changeAddress || params.ordinalDestination, change))
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Grind + covenant unlock on input 0 (fixes nLockTime for ALL inputs).
|
|
508
|
+
purchase({
|
|
509
|
+
spend: tx,
|
|
510
|
+
lockingScript: lockingScript,
|
|
511
|
+
satoshis: ordSats,
|
|
512
|
+
inputIndex: 0,
|
|
513
|
+
payoutIndex: 1,
|
|
514
|
+
payoutCount: payOuts.length,
|
|
515
|
+
grind: params.grind
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
// Now sign the P2PKH funding inputs over the finalized transaction.
|
|
519
|
+
params.funding.forEach(function (f, i) { signP2PKHInput(tx, 1 + i, f) })
|
|
520
|
+
|
|
521
|
+
return tx
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
module.exports = {
|
|
525
|
+
ORDLOCK_SIGHASH: ORDLOCK_SIGHASH,
|
|
526
|
+
buildOrdLock: buildOrdLock,
|
|
527
|
+
parseOrdLock: parseOrdLock,
|
|
528
|
+
isOrdLock: isOrdLock,
|
|
529
|
+
listInscriptionOutput: listInscriptionOutput,
|
|
530
|
+
payOutputFor: payOutputFor,
|
|
531
|
+
purchase: purchase,
|
|
532
|
+
cancel: cancel,
|
|
533
|
+
buildListingTx: buildListingTx,
|
|
534
|
+
buildPurchaseTx: buildPurchaseTx
|
|
535
|
+
}
|