ecash-agora 0.1.1-rc

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.
@@ -0,0 +1,955 @@
1
+ // Copyright (c) 2024 The Bitcoin developers
2
+ // Distributed under the MIT software license, see the accompanying
3
+ // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
+ import { ALL_ANYONECANPAY_BIP143, ALL_BIP143, alpSend, DEFAULT_DUST_LIMIT, emppScript, flagSignature, OP_0, OP_0NOTEQUAL, OP_1, OP_12, OP_2, OP_2DUP, OP_2OVER, OP_2SWAP, OP_3, OP_3DUP, OP_8, OP_9, OP_ADD, OP_BIN2NUM, OP_CAT, OP_CHECKDATASIGVERIFY, OP_CHECKSIG, OP_CHECKSIGVERIFY, OP_CODESEPARATOR, OP_DIV, OP_DROP, OP_DUP, OP_ELSE, OP_ENDIF, OP_EQUAL, OP_EQUALVERIFY, OP_FROMALTSTACK, OP_GREATERTHANOREQUAL, OP_HASH160, OP_HASH256, OP_IF, OP_MOD, OP_NIP, OP_NOTIF, OP_NUM2BIN, OP_OVER, OP_PICK, OP_PUSHDATA1, OP_REVERSEBYTES, OP_ROT, OP_SHA256, OP_SIZE, OP_SPLIT, OP_SUB, OP_SWAP, OP_TOALTSTACK, OP_TUCK, OP_VERIFY, pushBytesOp, pushNumberOp, Script, sha256d, slpSend, strToBytes, WriterBytes, WriterLength, writeTxOutput, } from 'ecash-lib';
5
+ import { AGORA_LOKAD_ID } from './consts.js';
6
+ /**
7
+ * An Agora offer that can partially be accepted.
8
+ * In contrast to oneshot offers, these can be partially accepted, with the
9
+ * remainder sent back to a new UTXO with the same terms but reduced token
10
+ * amount.
11
+ * This is useful for fungible tokens, where the maker doesn't know upfront how
12
+ * many tokens the takers would like to acquire.
13
+ *
14
+ * The Script enforces that the taker re-creates an offer with the same terms
15
+ * with tokens he didn't buy.
16
+ * It calculates the required sats to accept the offer based on the price per
17
+ * token, and the number of tokens requested by the taker, and enforces the
18
+ * correct amount of satoshis are sent to the P2PKH of the maker of this offer.
19
+ *
20
+ * Offers can also be cancelled by the maker of the offer.
21
+ *
22
+ * One complication is the price calculation, due to eCash's limited precision
23
+ * and range (31-bits plus 1 sign bit) of its Script integers.
24
+ * We employ two strategies to increase precision and range:
25
+ * - "Scaling": We scale up values to the maximum representable, such that we
26
+ * make full use of the 31 bits available. Values that have been scaled up
27
+ * have the prefix "scaled", and the scale factor is "tokenScaleFactor". We
28
+ * only scale token amounts.
29
+ * - "Truncation": We cut off bytes at the "end" of numbers, essentially
30
+ * dividing them by 256 for each truncation, until they fit in 31 bits, so we
31
+ * can use arithmetic opcodes. Later we "un-truncate" values again by adding
32
+ * the bytes back. We use OP_CAT to un-truncate values, which doesn't care
33
+ * about the 31-bit limit. Values that have been truncated have the "trunc"
34
+ * prefix. We truncate both token amounts (by numTokenTruncBytes bytes) and
35
+ * sats amounts (by numSatsTruncBytes).
36
+ *
37
+ * Scaling and truncation can be combined, such that the token price is in
38
+ * "scaledTruncTokensPerTruncSat".
39
+ * Together, they give us a very large range of representable values, while
40
+ * keeping a decent precision.
41
+ *
42
+ * Ideally, eCash can eventually raise the maximum integer size to e.g. 64-bits,
43
+ * which would greatly increase the precision. The strategies employed are
44
+ * useful there too, we simply get a much more accurate price calculation.
45
+ **/
46
+ export class AgoraPartial {
47
+ constructor(params) {
48
+ this.truncTokens = params.truncTokens;
49
+ this.numTokenTruncBytes = params.numTokenTruncBytes;
50
+ this.tokenScaleFactor = params.tokenScaleFactor;
51
+ this.scaledTruncTokensPerTruncSat = params.scaledTruncTokensPerTruncSat;
52
+ this.numSatsTruncBytes = params.numSatsTruncBytes;
53
+ this.makerPk = params.makerPk;
54
+ this.minAcceptedScaledTruncTokens = params.minAcceptedScaledTruncTokens;
55
+ this.tokenId = params.tokenId;
56
+ this.tokenType = params.tokenType;
57
+ this.tokenProtocol = params.tokenProtocol;
58
+ this.scriptLen = params.scriptLen;
59
+ this.dustAmount = params.dustAmount;
60
+ }
61
+ /**
62
+ * Approximate good script parameters for the given offer params.
63
+ * Note: This is not guaranteed to be optimal and is done on a best-effort
64
+ * basis.
65
+ * @param params Offer params to approximate, see AgoraPartialParams for
66
+ * details.
67
+ * @param scriptIntegerBits How many bits Script integers have on the
68
+ * network. On XEC, this must be 32, but if it is raised in the
69
+ * future to e.g. 64-bit integers, this can be set to 64 to greatly
70
+ * increase accuracy.
71
+ **/
72
+ static approximateParams(params, scriptIntegerBits = 32n) {
73
+ if (params.offeredTokens < 1n) {
74
+ throw new Error('offeredTokens must be at least 1');
75
+ }
76
+ if (params.priceNanoSatsPerToken < 1n) {
77
+ throw new Error('priceNanoSatsPerToken must be at least 1');
78
+ }
79
+ if (params.minAcceptedTokens < 1n) {
80
+ throw new Error('minAcceptedTokens must be at least 1');
81
+ }
82
+ if (params.tokenProtocol == 'SLP' &&
83
+ params.offeredTokens > 0xffffffffffffffffn) {
84
+ throw new Error('For SLP, offeredTokens can be at most 0xffffffffffffffff');
85
+ }
86
+ if (params.tokenProtocol == 'ALP' &&
87
+ params.offeredTokens > 0xffffffffffffn) {
88
+ throw new Error('For ALP, offeredTokens can be at most 0xffffffffffff');
89
+ }
90
+ // Script uses 1 bit as sign bit, which we can't use in our calculation
91
+ const scriptIntegerWithoutSignBits = scriptIntegerBits - 1n;
92
+ // Max integer that can be represented in Script on the network
93
+ const maxScriptInt = (1n << scriptIntegerWithoutSignBits) - 1n;
94
+ // Edge case where price can be represented exactly,
95
+ // no need to introduce extra approximation.
96
+ const isPrecisePrice = 1000000000n % params.priceNanoSatsPerToken == 0n;
97
+ // The Script can only handle a maximum level of truncation
98
+ const maxTokenTruncBytes = params.tokenProtocol == 'SLP' ? 5 : 3;
99
+ const minTokenScaleFactor = isPrecisePrice
100
+ ? 1n
101
+ : params.minTokenScaleFactor ?? 1000n;
102
+ // If we can't represent the offered tokens in a script int, we truncate 8
103
+ // bits at a time until it fits.
104
+ let truncTokens = params.offeredTokens;
105
+ let numTokenTruncBytes = 0n;
106
+ while (truncTokens * minTokenScaleFactor > maxScriptInt &&
107
+ numTokenTruncBytes < maxTokenTruncBytes) {
108
+ truncTokens >>= 8n;
109
+ numTokenTruncBytes++;
110
+ }
111
+ // Required sats to fully accept the trade (rounded down)
112
+ const requiredSats = (params.offeredTokens * params.priceNanoSatsPerToken) / 1000000000n;
113
+ // For bigger trades (>=2^31 sats), we need also to truncate sats
114
+ let requiredTruncSats = requiredSats;
115
+ let numSatsTruncBytes = 0n;
116
+ while (requiredTruncSats > maxScriptInt) {
117
+ requiredTruncSats >>= 8n;
118
+ numSatsTruncBytes++;
119
+ }
120
+ // We scale up the token values to get some extra precision
121
+ let tokenScaleFactor = maxScriptInt / truncTokens;
122
+ // How many scaled trunc tokens can be gotten for each trunc sat.
123
+ // It is the inverse of the price specified by the user, and truncated +
124
+ // scaled as required by the Script.
125
+ const calcScaledTruncTokensPerTruncSat = () => ((1n << (8n * numSatsTruncBytes)) *
126
+ tokenScaleFactor *
127
+ 1000000000n) /
128
+ ((1n << (8n * numTokenTruncBytes)) * params.priceNanoSatsPerToken);
129
+ // For trades offering a few tokens for many sats, truncate the sats
130
+ // amounts some more to increase precision.
131
+ const minPriceInteger = params.minPriceInteger ?? 1000n;
132
+ // However, only truncate sats if tokenScaleFactor is well above
133
+ // scaledTruncTokensPerTruncSat, otherwise we lose precision because
134
+ // we're rounding up for the sats calculation in the Script.
135
+ const minScaleRatio = params.minScaleRatio ?? 1000n;
136
+ let scaledTruncTokensPerTruncSat = calcScaledTruncTokensPerTruncSat();
137
+ while (scaledTruncTokensPerTruncSat < minPriceInteger &&
138
+ scaledTruncTokensPerTruncSat * minScaleRatio < tokenScaleFactor) {
139
+ numSatsTruncBytes++;
140
+ scaledTruncTokensPerTruncSat = calcScaledTruncTokensPerTruncSat();
141
+ }
142
+ // Edge case where the sats calculation can go above the integer limit
143
+ if (truncTokens * tokenScaleFactor + scaledTruncTokensPerTruncSat - 1n >
144
+ maxScriptInt) {
145
+ if (truncTokens * tokenScaleFactor <=
146
+ scaledTruncTokensPerTruncSat) {
147
+ // Case where we just overshot the tokenScaleFactor
148
+ tokenScaleFactor /= 2n;
149
+ scaledTruncTokensPerTruncSat =
150
+ calcScaledTruncTokensPerTruncSat();
151
+ }
152
+ const maxTruncTokens = maxScriptInt - scaledTruncTokensPerTruncSat + 1n;
153
+ if (maxTruncTokens < 0n) {
154
+ throw new Error('Parameters cannot be represented in Script');
155
+ }
156
+ if (truncTokens > maxTruncTokens) {
157
+ // Case where truncTokens itself is close to maxScriptInt
158
+ tokenScaleFactor = 1n;
159
+ truncTokens = maxTruncTokens;
160
+ }
161
+ else {
162
+ // Case where scaled tokens would exceed maxScriptInt
163
+ tokenScaleFactor = maxTruncTokens / truncTokens;
164
+ }
165
+ // Recalculate price
166
+ scaledTruncTokensPerTruncSat = calcScaledTruncTokensPerTruncSat();
167
+ }
168
+ // Scale + truncate the minimum accepted tokens
169
+ let minAcceptedScaledTruncTokens = (params.minAcceptedTokens * tokenScaleFactor) >>
170
+ (8n * numTokenTruncBytes);
171
+ const agoraPartial = new AgoraPartial({
172
+ truncTokens,
173
+ numTokenTruncBytes: Number(numTokenTruncBytes),
174
+ tokenScaleFactor,
175
+ scaledTruncTokensPerTruncSat,
176
+ numSatsTruncBytes: Number(numSatsTruncBytes),
177
+ makerPk: params.makerPk,
178
+ minAcceptedScaledTruncTokens,
179
+ tokenId: params.tokenId,
180
+ tokenType: params.tokenType,
181
+ tokenProtocol: params.tokenProtocol,
182
+ scriptLen: 0x7f,
183
+ dustAmount: params.dustAmount ?? DEFAULT_DUST_LIMIT,
184
+ });
185
+ if (agoraPartial.minAcceptedTokens() < 1n) {
186
+ throw new Error('minAcceptedTokens too small, got truncated to 0');
187
+ }
188
+ agoraPartial.updateScriptLen();
189
+ return agoraPartial;
190
+ }
191
+ updateScriptLen() {
192
+ let measuredLength = this.script().cutOutCodesep(0).bytecode.length;
193
+ if (measuredLength >= 0x80) {
194
+ this.scriptLen = 0x80;
195
+ measuredLength = this.script().cutOutCodesep(0).bytecode.length;
196
+ }
197
+ this.scriptLen = measuredLength;
198
+ }
199
+ /**
200
+ * How many tokens are accually offered by the Script.
201
+ * This may differ from the offeredTokens in the AgoraPartialParams used to
202
+ * approximate this AgoraPartial.
203
+ **/
204
+ offeredTokens() {
205
+ return this.truncTokens << BigInt(8 * this.numTokenTruncBytes);
206
+ }
207
+ /**
208
+ * Actual minimum acceptable tokens of this Script.
209
+ * This may differ from the minAcceptedTokens in the AgoraPartialParams used
210
+ * to approximate this AgoraPartial.
211
+ **/
212
+ minAcceptedTokens() {
213
+ return ((this.minAcceptedScaledTruncTokens <<
214
+ BigInt(8 * this.numTokenTruncBytes)) /
215
+ this.tokenScaleFactor);
216
+ }
217
+ /**
218
+ * Calculate the actually asked satoshi amount for the given accepted number of tokens.
219
+ * This is the exact amount that has to be sent to makerPk's P2PKH address
220
+ * to accept the offer.
221
+ * `acceptedTokens` must have the lowest numTokenTruncBytes bytes set to 0,
222
+ * use prepareAcceptedTokens to do so.
223
+ **/
224
+ askedSats(acceptedTokens) {
225
+ const numSatsTruncBits = BigInt(8 * this.numSatsTruncBytes);
226
+ const numTokenTruncBits = BigInt(8 * this.numTokenTruncBytes);
227
+ const acceptedTruncTokens = acceptedTokens >> numTokenTruncBits;
228
+ if (acceptedTruncTokens << numTokenTruncBits != acceptedTokens) {
229
+ throw new Error(`acceptedTokens must have the last ${numTokenTruncBits} bits ` +
230
+ 'set to zero, use prepareAcceptedTokens to get a valid amount');
231
+ }
232
+ // Divide rounding up
233
+ const askedTruncSats = (acceptedTruncTokens * this.tokenScaleFactor +
234
+ this.scaledTruncTokensPerTruncSat -
235
+ 1n) /
236
+ this.scaledTruncTokensPerTruncSat;
237
+ // Un-truncate sats
238
+ return askedTruncSats << numSatsTruncBits;
239
+ }
240
+ /**
241
+ * Prepare the given acceptedTokens amount for the Script; `acceptedTokens`
242
+ * must have the lowest numTokenTruncBytes bytes set to 0 and this function
243
+ * does this for us.
244
+ **/
245
+ prepareAcceptedTokens(acceptedTokens) {
246
+ const numTokenTruncBits = BigInt(8 * this.numTokenTruncBytes);
247
+ return (acceptedTokens >> numTokenTruncBits) << numTokenTruncBits;
248
+ }
249
+ /**
250
+ * Calculate the actual priceNanoSatsPerToken of this offer, factoring in
251
+ * all approximation inacurracies.
252
+ * Due to the rounding, the price can change based on the accepted token
253
+ * amount. By default it calculates the price per token for accepting the
254
+ * entire offer.
255
+ **/
256
+ priceNanoSatsPerToken(acceptedTokens) {
257
+ acceptedTokens ?? (acceptedTokens = this.offeredTokens());
258
+ const prepared = this.prepareAcceptedTokens(acceptedTokens);
259
+ const sats = this.askedSats(prepared);
260
+ return (sats * 1000000000n) / prepared;
261
+ }
262
+ adPushdata() {
263
+ const serAdPushdata = (writer) => {
264
+ if (this.tokenProtocol == 'ALP') {
265
+ // On ALP, we signal AGR0 in the pushdata
266
+ writer.putBytes(AGORA_LOKAD_ID);
267
+ writer.putU8(AgoraPartial.COVENANT_VARIANT.length);
268
+ writer.putBytes(strToBytes(AgoraPartial.COVENANT_VARIANT));
269
+ }
270
+ writer.putU8(this.numTokenTruncBytes);
271
+ writer.putU8(this.numSatsTruncBytes);
272
+ writer.putU64(this.tokenScaleFactor);
273
+ writer.putU64(this.scaledTruncTokensPerTruncSat);
274
+ writer.putU64(this.minAcceptedScaledTruncTokens);
275
+ writer.putBytes(this.makerPk);
276
+ };
277
+ const lengthWriter = new WriterLength();
278
+ serAdPushdata(lengthWriter);
279
+ const bytesWriter = new WriterBytes(lengthWriter.length);
280
+ serAdPushdata(bytesWriter);
281
+ return bytesWriter.data;
282
+ }
283
+ covenantConsts() {
284
+ const adPushdata = this.adPushdata();
285
+ // "Consts" is serialized data with the terms of the offer + the token
286
+ // protocol intros.
287
+ if (this.tokenProtocol == 'SLP') {
288
+ const slpSendIntro = slpSend(this.tokenId, this.tokenType, [
289
+ 0,
290
+ ]).bytecode;
291
+ const covenantConstsWriter = new WriterBytes(slpSendIntro.length + adPushdata.length);
292
+ covenantConstsWriter.putBytes(slpSendIntro);
293
+ covenantConstsWriter.putBytes(adPushdata);
294
+ return [covenantConstsWriter.data, slpSendIntro.length];
295
+ }
296
+ else if (this.tokenProtocol == 'ALP') {
297
+ const alpSendTemplate = alpSend(this.tokenId, this.tokenType, []);
298
+ // ALP SEND section, but without the num amounts
299
+ const alpSendIntro = alpSendTemplate.slice(0, alpSendTemplate.length - 1);
300
+ // eMPP script with Agora ad, but without the ALP section
301
+ const emppIntro = emppScript([adPushdata]);
302
+ const covenantConstsWriter = new WriterBytes(alpSendIntro.length + emppIntro.bytecode.length);
303
+ covenantConstsWriter.putBytes(alpSendIntro);
304
+ covenantConstsWriter.putBytes(emppIntro.bytecode);
305
+ return [covenantConstsWriter.data, alpSendIntro.length];
306
+ }
307
+ else {
308
+ throw new Error('Not implemented');
309
+ }
310
+ }
311
+ script() {
312
+ const [covenantConsts, tokenIntroLen] = this.covenantConsts();
313
+ // Serialize scaled tokens as 8-byte little endian.
314
+ // Even though Script currently doesn't support 64-bit integers,
315
+ // this allows us to eventually upgrade to 64-bit without changing this
316
+ // Script at all.
317
+ const scaledTruncTokens8LeWriter = new WriterBytes(8);
318
+ scaledTruncTokens8LeWriter.putU64(this.truncTokens * this.tokenScaleFactor);
319
+ const scaledTruncTokens8Le = scaledTruncTokens8LeWriter.data;
320
+ return Script.fromOps([
321
+ // # Push consts
322
+ pushBytesOp(covenantConsts),
323
+ // # Push offered token amount as scaled trunc tokens, as u64 LE
324
+ pushBytesOp(scaledTruncTokens8Le),
325
+ // # Use OP_CODESEPERATOR to remove the above two (large) pushops
326
+ // # from the sighash preimage (tx size optimization)
327
+ OP_CODESEPARATOR,
328
+ // OP_ROT(isPurchase, _, _)
329
+ OP_ROT,
330
+ // OP_IF(isPurchase)
331
+ OP_IF,
332
+ // scaledTruncTokens = OP_BIN2NUM(scaledTruncTokens8Le)
333
+ OP_BIN2NUM,
334
+ // OP_ROT(acceptedScaledTruncTokens, _, _)
335
+ OP_ROT,
336
+ // # Verify accepted amount doesn't exceed available amount
337
+ // OP_2DUP(scaledTruncTokens, acceptedScaledTruncTokens)
338
+ OP_2DUP,
339
+ // isNotExcessive = OP_GREATERTHANOREQUAL(scaledTruncTokens,
340
+ // acceptedScaledTruncTokens)
341
+ OP_GREATERTHANOREQUAL,
342
+ // OP_VERIFY(isNotExcessive)
343
+ OP_VERIFY,
344
+ // # Verify accepted amount is above a required minimum
345
+ // OP_DUP(acceptedScaledTruncTokens)
346
+ OP_DUP,
347
+ // # Ensure minimum accepted amount is not violated
348
+ pushNumberOp(this.minAcceptedScaledTruncTokens),
349
+ // isEnough = OP_GREATERTHANOREQUAL(acceptedScaledTruncTokens,
350
+ // minAcceptedScaledTruncTokens)
351
+ OP_GREATERTHANOREQUAL,
352
+ // OP_VERIFY(isEnough)
353
+ OP_VERIFY,
354
+ // # Verify accepted amount is scaled correctly, must be a
355
+ // # multiple of tokenScaleFactor.
356
+ // OP_DUP(acceptedScaledTruncTokens)
357
+ OP_DUP,
358
+ pushNumberOp(this.tokenScaleFactor),
359
+ // scaleRemainder = OP_MOD(acceptedScaledTruncTokens,
360
+ // tokenScaleFactor)
361
+ OP_MOD,
362
+ OP_0,
363
+ // OP_EQUALVERIFY(scaleRemainder, 0)
364
+ OP_EQUALVERIFY,
365
+ // OP_TUCK(_, acceptedScaledTruncTokens);
366
+ OP_TUCK,
367
+ // # Calculate tokens left over after purchase
368
+ // leftoverScaledTruncTokens = OP_SUB(scaledTruncTokens,
369
+ // acceptedScaledTruncTokens)
370
+ OP_SUB,
371
+ // # Get token intro from consts
372
+ // depthConsts = depth_of(consts)
373
+ pushNumberOp(2),
374
+ // consts = OP_PICK(depthConsts);
375
+ OP_PICK,
376
+ // # Size of the token protocol intro
377
+ pushNumberOp(tokenIntroLen),
378
+ // tokenIntro, agoraIntro = OP_SPLIT(consts, introSize)
379
+ OP_SPLIT,
380
+ // OP_DROP(agoraIntro)
381
+ OP_DROP,
382
+ // OP_OVER(leftoverScaledTruncTokens, _)
383
+ OP_OVER,
384
+ // hasLeftover = OP_0NOTEQUAL(leftoverScaledTruncTokens)
385
+ // # (SCRIPT_VERIFY_MINIMALIF is not on eCash, but better be safe)
386
+ OP_0NOTEQUAL,
387
+ // Insert (sub)script that builds the OP_RETURN for SLP/ALP
388
+ ...this._scriptBuildOpReturn(tokenIntroLen),
389
+ // # Add trunc padding for sats to un-truncate sats
390
+ pushBytesOp(new Uint8Array(this.numSatsTruncBytes)),
391
+ // outputsOpreturnPad = OP_CAT(opreturnOutput, truncPaddingSats)
392
+ OP_CAT,
393
+ // OP_ROT(acceptedScaledTruncTokens, _, _)
394
+ OP_ROT,
395
+ // # We divide rounding up when we calc sats, so add divisor - 1
396
+ pushNumberOp(this.scaledTruncTokensPerTruncSat - 1n),
397
+ OP_ADD,
398
+ // # Price (scaled + truncated)
399
+ pushNumberOp(this.scaledTruncTokensPerTruncSat),
400
+ // # Calculate how many (truncated) sats the user has to pay
401
+ // requiredTruncSats = OP_DIV(acceptedScaledTruncTokens,
402
+ // scaledTruncTokensPerTruncSat)
403
+ OP_DIV,
404
+ // # Build the required sats with the correct byte length
405
+ // truncLen = 8 - numSatsTruncBytes
406
+ pushNumberOp(8 - this.numSatsTruncBytes),
407
+ // requiredTruncSatsLe = OP_NUM2BIN(requiredTruncSats, truncLen)
408
+ OP_NUM2BIN,
409
+ // # Build OP_RETURN output + satoshi amount (8 bytes LE).
410
+ // # We already added the padding to un-truncate sats in the
411
+ // # previous OP_CAT to the output.
412
+ // outputsOpreturnSats =
413
+ // OP_CAT(outputsOpreturnPad, requiredTruncSatsLe)
414
+ OP_CAT,
415
+ // # Build maker's P2PKH script
416
+ // p2pkhIntro = [25, OP_DUP, OP_HASH160, 20]
417
+ pushBytesOp(new Uint8Array([25, OP_DUP, OP_HASH160, 20])),
418
+ // OP_2OVER(consts, leftoverScaledTruncTokens, _, _);
419
+ OP_2OVER,
420
+ // OP_DROP(leftoverScaledTruncTokens);
421
+ OP_DROP,
422
+ // # Slice out pubkey from the consts (always the last 33 bytes)
423
+ // pubkeyIdx = consts.length - 33
424
+ pushNumberOp(covenantConsts.length - 33),
425
+ // rest, makerPk = OP_SPLIT(consts, pubkeyIdx)
426
+ OP_SPLIT,
427
+ // OP_NIP(rest, _)
428
+ OP_NIP,
429
+ // makerPkh = OP_HASH160(makerPk)
430
+ OP_HASH160,
431
+ // makerP2pkh1 = OP_CAT(p2pkhIntro, makerPkh)
432
+ OP_CAT,
433
+ // p2pkhOutro = [OP_EQUALVERIFY, OP_CHECKSIG]
434
+ pushBytesOp(new Uint8Array([OP_EQUALVERIFY, OP_CHECKSIG])),
435
+ // makerScript = OP_CAT(makerP2pkh1, p2pkhOutro)
436
+ OP_CAT,
437
+ // # Now we have the first 2 outputs: OP_RETURN + maker P2PKH
438
+ // outputsOpreturnMaker = OP_CAT(outputsOpreturnSats, makerScript)
439
+ OP_CAT,
440
+ // # Move to altstack, we need it when calculating hashOutputs
441
+ // OP_TOALTSTACK(outputsOpreturnMaker)
442
+ OP_TOALTSTACK,
443
+ // # Build loopback P2SH, will receive the leftover tokens with
444
+ // # a Script with the same terms.
445
+ // OP_TUCK(_, leftoverScaledTruncTokens);
446
+ OP_TUCK,
447
+ // P2SH has dust sats
448
+ pushNumberOp(this.dustAmount),
449
+ OP_8,
450
+ // dustAmount8le = OP_NUM2BIN(dustAmount, 8)
451
+ OP_NUM2BIN,
452
+ // p2shIntro = [23, OP_HASH160, 20]
453
+ pushBytesOp(new Uint8Array([23, OP_HASH160, 20])),
454
+ // loopbackOutputIntro = OP_CAT(dustAmount8le, p2shIntro);
455
+ OP_CAT,
456
+ // # Build the new redeem script; same terms but different
457
+ // # scaledTruncTokens8Le.
458
+ // # Build opcode to push consts. Sometimes they get long and we
459
+ // # need OP_PUSHDATA1.
460
+ // pushConstsOpcode = if consts.length >= OP_PUSHDATA1 {
461
+ // [OP_PUSHDATA1, consts.length]
462
+ // } else {
463
+ // [consts.length]
464
+ // }
465
+ pushBytesOp(new Uint8Array(covenantConsts.length >= OP_PUSHDATA1
466
+ ? [OP_PUSHDATA1, covenantConsts.length]
467
+ : [covenantConsts.length])),
468
+ // OP_2SWAP(consts, leftoverScaledTruncTokens, _, _)
469
+ OP_2SWAP,
470
+ OP_8,
471
+ // OP_TUCK(_, 8)
472
+ OP_TUCK,
473
+ // leftoverScaledTruncTokens8le =
474
+ // OP_NUM2BIN(leftoverScaledTruncTokens, 8)
475
+ OP_NUM2BIN,
476
+ // pushLeftoverScaledTruncTokens8le =
477
+ // OP_CAT(8, leftoverScaledTruncTokens8le)
478
+ OP_CAT,
479
+ // constsPushLeftover =
480
+ // OP_CAT(consts, pushLeftoverScaledTruncTokens8le)
481
+ OP_CAT,
482
+ // # The two ops that push consts plus amount
483
+ // pushState = OP_CAT(pushConstsOpcode, constsPushLeftover)
484
+ OP_CAT,
485
+ // opcodesep = [OP_CODESEPARATOR]
486
+ pushBytesOp(new Uint8Array([OP_CODESEPARATOR])),
487
+ // loopbackScriptIntro = OP_CAT(pushState, opcodesep)
488
+ OP_CAT,
489
+ // depthPreimage4_10 = depth_of(preimage4_10);
490
+ pushNumberOp(3),
491
+ // preimage4_10 = OP_PICK(depthPreimage4_10);
492
+ OP_PICK,
493
+ // scriptCodeIdx = 36 + if scriptLen < 0xfd { 1 } else { 3 }
494
+ pushNumberOp(36 + (this.scriptLen < 0xfd ? 1 : 3)),
495
+ // outpoint, preimage5_10 = OP_SPLIT(preimage4_10, scriptCodeIdx)
496
+ OP_SPLIT,
497
+ // OP_NIP(outpoint, __)
498
+ OP_NIP,
499
+ // # Split out scriptCode
500
+ pushNumberOp(this.scriptLen),
501
+ // script_code, preimage6_10 = OP_SPLIT(preimage5_10, scriptLen)
502
+ OP_SPLIT,
503
+ // # Extract hashOutputs
504
+ OP_12,
505
+ // (preimage6_7, preimage8_10) = OP_SPLIT(preimage6_10, 12)
506
+ OP_SPLIT,
507
+ // OP_NIP(preimage6_7, _)
508
+ OP_NIP,
509
+ // # Split out hashOutputs
510
+ pushNumberOp(32),
511
+ // actualHashOutputs, preimage9_10 = OP_SPLIT(preimage8_10, 32)
512
+ OP_SPLIT,
513
+ // OP_DROP(preimage9_10)
514
+ OP_DROP,
515
+ // # Move to altstack, will be needed later
516
+ // OP_TOALTSTACK(actualHashOutputs)
517
+ OP_TOALTSTACK,
518
+ // # Build redeemScript of loopback P2SH output
519
+ // loopbackScript = OP_CAT(loopbackScriptIntro, scriptCode)
520
+ OP_CAT,
521
+ // # Calculate script hash for P2SH script
522
+ // loopbackScriptHash = OP_HASH160(loopbackScript)
523
+ OP_HASH160,
524
+ // loopbackOutputIntroSh =
525
+ // OP_CAT(loopbackOutputIntro, loopbackScriptHash)
526
+ OP_CAT,
527
+ // p2shEnd = [OP_EQUAL]
528
+ pushBytesOp(new Uint8Array([OP_EQUAL])),
529
+ // # Build loopback P2SH output
530
+ // loopbackOutput = OP_CAT(loopbackOutputIntroSh, p2shEnd)
531
+ OP_CAT,
532
+ // # Check if we have tokens left over and send them back
533
+ // # It is cheaper (in bytes) to build the loopback output and then
534
+ // # throw it away if needed than to not build it at all.
535
+ // OP_SWAP(leftoverScaledTruncTokens, _)
536
+ OP_SWAP,
537
+ // hasLeftover = OP_0NOTEQUAL(leftoverScaledTruncTokens)
538
+ OP_0NOTEQUAL,
539
+ // OP_NOTIF(hasLeftover)
540
+ OP_NOTIF,
541
+ // OP_DROP(loopbackOutput)
542
+ OP_DROP,
543
+ // loopbackOutput = []
544
+ pushBytesOp(new Uint8Array()),
545
+ OP_ENDIF,
546
+ // OP_ROT(buyerOutputs, _, _)
547
+ OP_ROT,
548
+ // # Verify user specified output, otherwise total burn on ALP
549
+ // buyerOutputs, buyerOutputsSize = OP_SIZE(buyerOutputs)
550
+ OP_SIZE,
551
+ // isNotEmpty = OP_0NOTEQUAL(buyerOutputsSize)
552
+ OP_0NOTEQUAL,
553
+ // OP_VERIFY(isNotEmpty)
554
+ OP_VERIFY,
555
+ // # Loopback + taker outputs
556
+ // outputsLoopbackTaker = OP_CAT(loopbackOutput, buyerOutputs)
557
+ OP_CAT,
558
+ // OP_FROMALTSTACK(actualHashOutputs)
559
+ OP_FROMALTSTACK,
560
+ // OP_FROMALTSTACK(outputsOpreturnMaker)
561
+ OP_FROMALTSTACK,
562
+ // OP_ROT(outputsLoopbackTaker, _, _)
563
+ OP_ROT,
564
+ // # Outputs expected by this Script
565
+ // expectedOutputs = OP_CAT(outputsOpreturnMaker,
566
+ // outputsLoopbackTaker)
567
+ OP_CAT,
568
+ // expectedHashOutputs = OP_HASH256(expectedOutputs)
569
+ OP_HASH256,
570
+ // # Verify tx has the expected outputs
571
+ // OP_EQUALVERIFY(actualHashOutputs, expectedHashOutputs)
572
+ OP_EQUALVERIFY,
573
+ // # Build sighash preimage parts 1 to 3 via OP_NUM2BIN
574
+ // txVersion = 2
575
+ OP_2,
576
+ // preimage1_3Len = 4 + 32 + 32
577
+ pushNumberOp(4 + 32 + 32),
578
+ // preimage1_3 = OP_NUM2BIN(txVersion, preimage1_3Len)
579
+ OP_NUM2BIN,
580
+ // # Build full sighash preimage
581
+ // OP_SWAP(preimage4_10, preimage1_3)
582
+ OP_SWAP,
583
+ // preimage = OP_CAT(preimage1_3, preimage4_10)
584
+ OP_CAT,
585
+ // # Sighash for this covenant
586
+ // preimageSha256 = OP_SHA256(preimage)
587
+ OP_SHA256,
588
+ // # Verify our sighash actually matches that of the transaction
589
+ // OP_3DUP(covenantPk, covenantSig, preimageSha256)
590
+ OP_3DUP,
591
+ // OP_ROT(covenantPk, covenantSig, preimageSha256)
592
+ OP_ROT,
593
+ // OP_CHECKDATASIGVERIFY(covenantSig, preimageSha256, covenantPk)
594
+ OP_CHECKDATASIGVERIFY,
595
+ // OP_DROP(preimageSha256)
596
+ OP_DROP,
597
+ // sigHashFlags = [ALL_ANYONECANPAY_BIP143]
598
+ pushBytesOp(new Uint8Array([ALL_ANYONECANPAY_BIP143.toInt()])),
599
+ // covenantSigFlagged = OP_CAT(covenantSig, sigHashFlags)
600
+ OP_CAT,
601
+ // covenantSig, pk = OP_SWAP(covenantPk, covenantSigFlagged)
602
+ OP_SWAP,
603
+ OP_ELSE,
604
+ // # "Cancel" branch, split out the maker pubkey and verify sig
605
+ // # is for the maker pubkey.
606
+ // OP_DROP(scaledTruncTokens8le);
607
+ OP_DROP,
608
+ // pubkeyIdx = consts.length - 33
609
+ pushNumberOp(covenantConsts.length - 33),
610
+ // rest, pk = OP_SPLIT(consts, pubkeyIdx)
611
+ OP_SPLIT,
612
+ // OP_NIP(rest, __)
613
+ OP_NIP,
614
+ OP_ENDIF,
615
+ // # SLP and ALP differ at the end of the Script
616
+ ...this._scriptOutro(),
617
+ ]);
618
+ }
619
+ _scriptBuildOpReturn(tokenIntroLen) {
620
+ // Script takes in the token amounts and builds the OP_RETURN for the
621
+ // corresponding protocol
622
+ if (this.tokenProtocol == 'SLP') {
623
+ return this._scriptBuildSlpOpReturn();
624
+ }
625
+ else if (this.tokenProtocol == 'ALP') {
626
+ return this._scriptBuildAlpOpReturn(tokenIntroLen);
627
+ }
628
+ else {
629
+ throw new Error('Only SLP implemented');
630
+ }
631
+ }
632
+ _scriptBuildSlpOpReturn() {
633
+ return [
634
+ // # If there's a leftover, append it to the token amounts
635
+ // OP_IF(hasLeftover)
636
+ OP_IF,
637
+ // # Size of an SLP amount
638
+ OP_8,
639
+ // tokenIntro8 = OP_CAT(tokenIntro, 8);
640
+ OP_CAT,
641
+ // OP_OVER(leftoverScaledTruncTokens, _)
642
+ OP_OVER,
643
+ // # Scale down the scaled leftover amount
644
+ pushNumberOp(this.tokenScaleFactor),
645
+ // leftoverTokensTrunc = OP_DIV(leftoverScaledTruncTokens,
646
+ // tokenScaleFactor)
647
+ OP_DIV,
648
+ // # Serialize the leftover trunc tokens (overflow-safe)
649
+ ...this._scriptSerTruncTokens(8),
650
+ // # SLP uses big-endian, so we have to use OP_REVERSEBYTES
651
+ // leftoverTokenTruncBe = OP_REVERSEBYTES(leftoverTokenTruncLe)
652
+ OP_REVERSEBYTES,
653
+ // # Bytes to un-truncate the leftover tokens
654
+ pushBytesOp(new Uint8Array(this.numTokenTruncBytes)),
655
+ // # Build the actual 8 byte big-endian leftover
656
+ // leftoverToken8be = OP_CAT(leftoverTokenTruncBe, untruncatePad);
657
+ OP_CAT,
658
+ // # Append the leftover to the token intro
659
+ // tokenScript = OP_CAT(tokenIntro8, leftoverToken8be);
660
+ OP_CAT,
661
+ OP_ENDIF,
662
+ // # Append accepted token amount going to the taker
663
+ // # Size of an SLP amount
664
+ OP_8,
665
+ // tokenScript = OP_CAT(tokenScript, 8)
666
+ OP_CAT,
667
+ // # Get the accepted token amount
668
+ // depthAcceptedScaledTruncTokens =
669
+ // depth_of(acceptedScaledTruncTokens)
670
+ pushNumberOp(2),
671
+ // acceptedScaledTruncTokens =
672
+ // OP_PICK(depthAcceptedScaledTruncTokens)
673
+ OP_PICK,
674
+ // # Scale down the accepted token amount
675
+ pushNumberOp(this.tokenScaleFactor),
676
+ // acceptedTokensTrunc = OP_DIV(acceptedScaledTruncTokens,
677
+ // tokenScaleFactor)
678
+ OP_DIV,
679
+ // # Serialize the accepted token amount (overflow-safe)
680
+ ...this._scriptSerTruncTokens(8),
681
+ // # SLP uses big-endian, so we have to use OP_REVERSEBYTES
682
+ // acceptedTokensTruncBe = OP_REVERSEBYTES(acceptedTokensTruncLe);
683
+ OP_REVERSEBYTES,
684
+ // # Bytes to un-truncate the leftover tokens
685
+ pushBytesOp(new Uint8Array(this.numTokenTruncBytes)),
686
+ // acceptedTokens8be = OP_CAT(acceptedTokensTruncBe, untruncatePad);
687
+ OP_CAT,
688
+ // # Finished SLP token script
689
+ // tokenScript = OP_CAT(tokenScript, acceptedTokens8be);
690
+ OP_CAT,
691
+ // # Build OP_RETURN script with 0u64 and size prepended
692
+ // # tokenScript, tokenScriptSize = OP_SIZE(tokenScript)
693
+ OP_SIZE,
694
+ // # Build output value (0u64) + tokenScriptSize.
695
+ // # In case the tokenScriptSize > 127, it will be represented as
696
+ // # 0xXX00 in Script, but it should be just 0xXX.
697
+ // # We could serialize to 2 bytes and then cut one off, but here we
698
+ // # use a neat optimization: We serialize to 9 bytes (resulting in
699
+ // # 0xXX0000000000000000) and then call OP_REVERSEBYTES, which
700
+ // # will result in 0x0000000000000000XX, which is exactly what the
701
+ // # first 9 bytes of the OP_RETURN output should look like.
702
+ OP_9,
703
+ // tokenScriptSize9Le = OP_NUM2BIN(tokenScriptSize, 9)
704
+ OP_NUM2BIN,
705
+ // opreturnValueSize = OP_REVERSEBYTES(tokenScriptSize9Le);
706
+ OP_REVERSEBYTES,
707
+ // OP_SWAP(tokenScript, opreturnValueSize);
708
+ OP_SWAP,
709
+ // opreturnOutput = OP_CAT(opreturnValueSize, tokenScript);
710
+ OP_CAT,
711
+ ];
712
+ }
713
+ _scriptBuildAlpOpReturn(tokenIntroLen) {
714
+ // Script takes in the token amounts and builds the OP_RETURN for the
715
+ // ALP token protocol
716
+ return [
717
+ // # If there's a leftover, add it to the token amounts
718
+ // OP_IF(hasLeftover)
719
+ OP_IF,
720
+ // numTokenAmounts = 3
721
+ OP_3,
722
+ // # Append the number of token amounts + the first 0 amount +
723
+ // # un-truncate padding for the 2nd output.
724
+ // # We meld these three ops into one by using OP_NUM2BIN using
725
+ // # 7 + numTokenTruncBytes bytes, which gives us the number of
726
+ // # amounts in the first byte, followed by 6 zero bytes for the
727
+ // # first output, and then numTokenTruncBytes bytes for the
728
+ // # un-truncate padding.
729
+ pushNumberOp(7 + this.numTokenTruncBytes),
730
+ // tokenAmounts1 = OP_NUM2BIN(numTokenAmounts, size)
731
+ OP_NUM2BIN,
732
+ // tokenIntro = OP_CAT(tokenIntro, tokenAmounts1)
733
+ OP_CAT,
734
+ // OP_OVER(leftoverScaledTruncTokens, __)
735
+ OP_OVER,
736
+ // # Scale down the scaled leftover amount
737
+ pushNumberOp(this.tokenScaleFactor),
738
+ // nextSerValue = OP_DIV(leftoverScaledTruncTokens,
739
+ // tokenScaleFactor)
740
+ OP_DIV,
741
+ // # Serialize size for leftoverTokensTrunc, and also already add the un-truncate padding for the 3rd amount
742
+ // # Combining these two ops also doesn't require us to serialize overflow-aware
743
+ pushNumberOp(6 /*- this.numTokenTruncBytes + this.numTokenTruncBytes*/),
744
+ OP_ELSE,
745
+ // # Append the number of token amounts + the first 0 amount +
746
+ // # un-truncate padding for the 3rd output.
747
+ // nextSerValue = 2
748
+ OP_2,
749
+ // serializeSize = 7 + numTokenTruncBytes
750
+ pushNumberOp(7 + this.numTokenTruncBytes),
751
+ OP_ENDIF,
752
+ // tokenAmounts2 = OP_NUM2BIN(numTokenAmounts, serializeSize)
753
+ // # Serialize 1st/2nd output + padding for 2nd/3rd output
754
+ OP_NUM2BIN,
755
+ // # Build the part of the token section that has all the amounts
756
+ // # for the maker (i.e. 0) and covenant loopback, and the
757
+ // # un-truncate padding for the accepted token amount.
758
+ // tokenSection1Pad = OP_CAT(tokenIntro, tokenAmounts2)
759
+ OP_CAT,
760
+ // depthAcceptedScaledTruncTokens =
761
+ // depth_of(acceptedScaledTruncTokens)
762
+ pushNumberOp(2),
763
+ // acceptedScaledTruncTokens =
764
+ // OP_PICK(depthAcceptedScaledTruncTokens)
765
+ OP_PICK,
766
+ // # Scale down the accepted token amount
767
+ pushNumberOp(this.tokenScaleFactor),
768
+ // acceptedTokensTrunc = OP_DIV(acceptedScaledTruncTokens,
769
+ // tokenScaleFactor)
770
+ OP_DIV,
771
+ // # Serialize accepted token amount (overflow-safe)
772
+ ...this._scriptSerTruncTokens(6),
773
+ // # Finished token section
774
+ // tokenSection = OP_CAT(tokenSection1Pad, acceptedTokensTruncLe);
775
+ OP_CAT,
776
+ // Turn token section into a pushdata op
777
+ // tokenSection, tokenSectionSize = OP_SIZE(tokenSection)
778
+ OP_SIZE,
779
+ // OP_SWAP(tokenSection, tokenSectionSize)
780
+ OP_SWAP,
781
+ // let pushTokenSection = OP_CAT(tokenSectionSize, tokenSection);
782
+ OP_CAT,
783
+ // Get empp intro from consts
784
+ // depthConsts = depth_of(consts)
785
+ pushNumberOp(3),
786
+ // consts = OP_PICK(depthConsts)
787
+ OP_PICK,
788
+ // # Split out the emppAgoraIntro to prepend it to the OP_RETURN
789
+ pushNumberOp(tokenIntroLen),
790
+ // tokenIntro, emppAgoraIntro = OP_SPLIT(consts, alpIntroSize)
791
+ OP_SPLIT,
792
+ // # We don't need the tokenIntro
793
+ // OP_NIP(tokenIntro, _)
794
+ OP_NIP,
795
+ // Build OP_RETURN script with 0u64 and size prepended
796
+ // OP_SWAP(pushTokenSection, _)
797
+ OP_SWAP,
798
+ // emppScript = OP_CAT(emppIntro, pushTokenSection)
799
+ OP_CAT,
800
+ // emppScript, emppScriptSize = OP_SIZE(emppScript)
801
+ OP_SIZE,
802
+ // # Build output value (0u64) + tokenScriptSize.
803
+ // # See _scriptBuildSlpOpReturn for an explanation
804
+ OP_9,
805
+ // emppScriptSizeZero8 = OP_NUM2BIN(emppScriptSize, _9)
806
+ OP_NUM2BIN,
807
+ // zero8EmppScriptSize = OP_REVERSEBYTES(emppScriptSizeZero8)
808
+ OP_REVERSEBYTES,
809
+ // OP_SWAP(emppScript, zero8EmppScriptSize)
810
+ OP_SWAP,
811
+ // let opreturnOutput = OP_CAT(zero8EmppScriptSize, emppScript)
812
+ OP_CAT,
813
+ ];
814
+ }
815
+ _scriptSerTruncTokens(numSerBytes) {
816
+ // Serialize the number on the stack using the configured truncation
817
+ if (this.numTokenTruncBytes == numSerBytes - 3) {
818
+ // Edge case where we only have 3 bytes space to serialize the
819
+ // number, but if the MSB of the number is set, OP_NUM2BIN will
820
+ // serialize using 4 bytes (with the last byte being just 0x00),
821
+ // so we always serialize using 4 bytes and then cut the last
822
+ // byte (that's always 0x00) off.
823
+ return [
824
+ pushNumberOp(4),
825
+ OP_NUM2BIN,
826
+ pushNumberOp(3),
827
+ OP_SPLIT,
828
+ OP_DROP,
829
+ ];
830
+ }
831
+ else {
832
+ // If we have 4 or more bytes space, we can always serialize
833
+ // just using normal OP_NUM2BIN.
834
+ return [
835
+ pushNumberOp(numSerBytes - this.numTokenTruncBytes),
836
+ OP_NUM2BIN,
837
+ ];
838
+ }
839
+ }
840
+ _scriptOutro() {
841
+ if (this.tokenProtocol == 'SLP') {
842
+ // Verify the sig, and also ensure the first two push ops of the
843
+ // scriptSig are "AGR0" "PARTIAL", which will always have to be the
844
+ // first two ops because of the cleanstack rule.
845
+ return [
846
+ OP_CHECKSIGVERIFY,
847
+ pushBytesOp(strToBytes(AgoraPartial.COVENANT_VARIANT)),
848
+ OP_EQUALVERIFY,
849
+ pushBytesOp(AGORA_LOKAD_ID),
850
+ OP_EQUAL,
851
+ ];
852
+ }
853
+ else if (this.tokenProtocol == 'ALP') {
854
+ return [OP_CHECKSIG];
855
+ }
856
+ else {
857
+ throw new Error('Only SLP implemented');
858
+ }
859
+ }
860
+ /**
861
+ * redeemScript of the Script advertizing this offer.
862
+ * It requires a setup tx followed by the actual offer, which reveals
863
+ * the covenantConsts.
864
+ * The reason we have an OP_CHECKSIGVERIFY (as opposed to just leaving it
865
+ * as "anyone can spend with this pushdata") is so that others on the
866
+ * network can't spend this UTXO (and potentially take the tokens in it),
867
+ * and only the maker can spend it.
868
+ **/
869
+ adScript() {
870
+ const [covenantConsts, _] = this.covenantConsts();
871
+ return Script.fromOps([
872
+ pushBytesOp(covenantConsts),
873
+ pushNumberOp(covenantConsts.length - 33),
874
+ OP_SPLIT,
875
+ OP_NIP,
876
+ OP_CHECKSIGVERIFY,
877
+ pushBytesOp(strToBytes(AgoraPartial.COVENANT_VARIANT)),
878
+ OP_EQUALVERIFY,
879
+ pushBytesOp(AGORA_LOKAD_ID),
880
+ OP_EQUAL,
881
+ ]);
882
+ }
883
+ }
884
+ AgoraPartial.COVENANT_VARIANT = 'PARTIAL';
885
+ function makeScriptSigIntro(tokenProtocol) {
886
+ switch (tokenProtocol) {
887
+ case 'SLP':
888
+ // For SLP, we need to add "AGR0" "PARTIAL" at the beginning of the
889
+ // scriptSig, to advertize it via the LOKAD ID. ALP uses the
890
+ // OP_RETURN, so there this is not needed.
891
+ return [
892
+ pushBytesOp(AGORA_LOKAD_ID),
893
+ pushBytesOp(strToBytes(AgoraPartial.COVENANT_VARIANT)),
894
+ ];
895
+ default:
896
+ return [];
897
+ }
898
+ }
899
+ export const AgoraPartialSignatory = (params, acceptedTruncTokens, covenantSk, covenantPk) => {
900
+ return (ecc, input) => {
901
+ const preimage = input.sigHashPreimage(ALL_ANYONECANPAY_BIP143, 0);
902
+ const sighash = sha256d(preimage.bytes);
903
+ const covenantSig = ecc.schnorrSign(covenantSk, sighash);
904
+ const hasLeftover = params.truncTokens > acceptedTruncTokens;
905
+ const buyerOutputIdx = hasLeftover ? 3 : 2;
906
+ const buyerOutputs = input.unsignedTx.tx.outputs.slice(buyerOutputIdx);
907
+ const serTakerOutputs = (writer) => {
908
+ for (const output of buyerOutputs) {
909
+ writeTxOutput(output, writer);
910
+ }
911
+ };
912
+ const writerLength = new WriterLength();
913
+ serTakerOutputs(writerLength);
914
+ const writer = new WriterBytes(writerLength.length);
915
+ serTakerOutputs(writer);
916
+ const buyerOutputsSer = writer.data;
917
+ return Script.fromOps([
918
+ ...makeScriptSigIntro(params.tokenProtocol),
919
+ pushBytesOp(covenantPk),
920
+ pushBytesOp(covenantSig),
921
+ pushBytesOp(buyerOutputsSer),
922
+ pushBytesOp(preimage.bytes.slice(4 + 32 + 32)), // preimage_4_10
923
+ pushNumberOp(acceptedTruncTokens * params.tokenScaleFactor),
924
+ OP_1, // is_purchase = true
925
+ pushBytesOp(preimage.redeemScript.bytecode),
926
+ ]);
927
+ };
928
+ };
929
+ export const AgoraPartialCancelSignatory = (makerSk, tokenProtocol) => {
930
+ return (ecc, input) => {
931
+ const preimage = input.sigHashPreimage(ALL_BIP143, 0);
932
+ const sighash = sha256d(preimage.bytes);
933
+ const cancelSig = flagSignature(ecc.schnorrSign(makerSk, sighash), ALL_BIP143);
934
+ return Script.fromOps([
935
+ ...makeScriptSigIntro(tokenProtocol),
936
+ pushBytesOp(cancelSig),
937
+ OP_0, // is_purchase = false
938
+ pushBytesOp(preimage.redeemScript.bytecode),
939
+ ]);
940
+ };
941
+ };
942
+ export const AgoraPartialAdSignatory = (makerSk) => {
943
+ return (ecc, input) => {
944
+ const preimage = input.sigHashPreimage(ALL_BIP143);
945
+ const sighash = sha256d(preimage.bytes);
946
+ const makerSig = flagSignature(ecc.schnorrSign(makerSk, sighash), ALL_BIP143);
947
+ return Script.fromOps([
948
+ pushBytesOp(AGORA_LOKAD_ID),
949
+ pushBytesOp(strToBytes(AgoraPartial.COVENANT_VARIANT)),
950
+ pushBytesOp(makerSig),
951
+ pushBytesOp(preimage.redeemScript.bytecode),
952
+ ]);
953
+ };
954
+ };
955
+ //# sourceMappingURL=partial.js.map