@getomnichain/omnichain 0.6.1 → 0.6.2
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 +24 -0
- package/dist/cjs/utxo/bytes.js +11 -0
- package/dist/cjs/utxo/bytes.js.map +1 -0
- package/dist/cjs/utxo/index.js +3 -0
- package/dist/cjs/utxo/index.js.map +1 -1
- package/dist/cjs/utxo/keys.js +56 -0
- package/dist/cjs/utxo/keys.js.map +1 -0
- package/dist/cjs/utxo/raw_transaction.js +39 -0
- package/dist/cjs/utxo/raw_transaction.js.map +1 -0
- package/dist/cjs/utxo/script.js.map +1 -1
- package/dist/cjs/utxo/unsigned_utxo_transaction.js +135 -0
- package/dist/cjs/utxo/unsigned_utxo_transaction.js.map +1 -1
- package/dist/cjs/utxo/utxo_chain.js +137 -0
- package/dist/cjs/utxo/utxo_chain.js.map +1 -1
- package/dist/esm/utxo/bytes.d.ts +3 -0
- package/dist/esm/utxo/bytes.d.ts.map +1 -0
- package/dist/esm/utxo/bytes.js +7 -0
- package/dist/esm/utxo/bytes.js.map +1 -0
- package/dist/esm/utxo/index.d.ts +1 -0
- package/dist/esm/utxo/index.d.ts.map +1 -1
- package/dist/esm/utxo/index.js +1 -0
- package/dist/esm/utxo/index.js.map +1 -1
- package/dist/esm/utxo/keys.d.ts +8 -0
- package/dist/esm/utxo/keys.d.ts.map +1 -0
- package/dist/esm/utxo/keys.js +17 -0
- package/dist/esm/utxo/keys.js.map +1 -0
- package/dist/esm/utxo/raw_transaction.d.ts +5 -0
- package/dist/esm/utxo/raw_transaction.d.ts.map +1 -0
- package/dist/esm/utxo/raw_transaction.js +35 -0
- package/dist/esm/utxo/raw_transaction.js.map +1 -0
- package/dist/esm/utxo/script.d.ts +1 -0
- package/dist/esm/utxo/script.d.ts.map +1 -1
- package/dist/esm/utxo/script.js.map +1 -1
- package/dist/esm/utxo/unsigned_utxo_transaction.d.ts +7 -1
- package/dist/esm/utxo/unsigned_utxo_transaction.d.ts.map +1 -1
- package/dist/esm/utxo/unsigned_utxo_transaction.js +136 -1
- package/dist/esm/utxo/unsigned_utxo_transaction.js.map +1 -1
- package/dist/esm/utxo/utxo.d.ts +26 -0
- package/dist/esm/utxo/utxo.d.ts.map +1 -1
- package/dist/esm/utxo/utxo_chain.d.ts +8 -1
- package/dist/esm/utxo/utxo_chain.d.ts.map +1 -1
- package/dist/esm/utxo/utxo_chain.js +138 -1
- package/dist/esm/utxo/utxo_chain.js.map +1 -1
- package/dist/esm/utxo/utxo_network_params.d.ts +2 -1
- package/dist/esm/utxo/utxo_network_params.d.ts.map +1 -1
- package/docs/CONNECTIONS.md +6 -0
- package/docs/utxo.md +80 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,29 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.6.2] — 2026-10-04
|
|
12
|
+
|
|
13
|
+
Adds multi-signer UTXO transactions (RIN-267). A consumer that spends pre-selected inputs owned by several keys can now assemble, sign per key, finalize and broadcast without importing `bitcoinjs-lib`. Keys stay with the consumer; the SDK receives signer objects.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **`UtxoChain.assembleTransaction({ inputs, outputs, rbfEnabled? }): UnsignedUtxoTransaction`**: builds from explicit inputs (`{ utxo, parentTxHex }`) and outputs (`{ kind: 'address', address, valueSats }` or `{ kind: 'opReturn', data }`), in the given order, with no coin selection and no automatic change.
|
|
18
|
+
- Inputs are added the way `createTransferUnsignedTransaction` adds them (`nonWitnessUtxo` always, `witnessUtxo` on segwit and P2SH, RBF or final sequence).
|
|
19
|
+
- `feeSats` is inputs minus outputs, `feeRateSatsPerVByte` is `feeSats / estimatedVBytes`, `changeAddress` is `null`, and `inputsToSign` groups input indices by `ownerAddress`.
|
|
20
|
+
- Every input is checked against its parent transaction (txid, output value, output script) and against its `ownerAddress`, so the reported fee is what the chain will charge.
|
|
21
|
+
- Address outputs must be at or above the chain's dust value; at most one OP_RETURN output of up to 80 bytes; outputs may not exceed inputs.
|
|
22
|
+
- **`UnsignedUtxoTransaction.signWith(signer): UnsignedUtxoTransaction`**: signs the P2WPKH and P2PKH inputs whose script belongs to `signer.publicKey` and returns a new transaction, so calls chain. Repeating a key is a no-op; a key that owns no input throws. Taproot, P2WSH and P2SH inputs are never signed. Works on transactions from `createTransferUnsignedTransaction` too.
|
|
23
|
+
- **Parent checks before signing and finalizing**: `signWith` and `finalize` require every input's parent transaction, refuse a declared amount or script that differs from the parent output, and refuse a transaction whose real fee differs from `feeSats`. The signer is never called for such a transaction, so a provider that misreports an amount cannot lead to a signed, fee-burning transaction.
|
|
24
|
+
- **`UnsignedUtxoTransaction.finalize(): { hex, txid, vsize }`**: requires a signature on every input (naming the first unsigned index), verifies every signature in strict low-S mode, refuses a fee rate of 5000 sat/vB or more, and returns broadcast-ready hex with its txid and real vsize.
|
|
25
|
+
- **`UtxoChain.addressForPublicKey(publicKey, scriptType = 'p2wpkh')`**: P2WPKH or P2PKH address of a 33-byte compressed public key on the chain's network. P2WPKH on a network without segwit (Dogecoin) throws `FeatureNotSupported`.
|
|
26
|
+
- **`utxoFromRawTransaction(rawTxHex, vout, ownerAddress)`**: reads one output of a raw transaction into an `UnspentTransactionOutput`, without a network call; the txid is computed from the hex.
|
|
27
|
+
- **Types**: `UtxoPsbtInput`, `UtxoPsbtOutput`, `AssembleUtxoTransactionRequest`, `UtxoSigner`, `FinalizedUtxoTransaction`, `UtxoSingleKeyScriptType`, and `UtxoNetworkInfo`, which is now the declared type of `UtxoNetworkParams.networkInfo` (same structure as before).
|
|
28
|
+
|
|
29
|
+
### Note
|
|
30
|
+
|
|
31
|
+
- Additive only; no existing method changes behaviour. All errors are `ChainError` (`InvalidArgument`, `InvalidAddress`, or `FeatureNotSupported`).
|
|
32
|
+
- TypeScript-only; omnichain-py has no counterpart.
|
|
33
|
+
|
|
11
34
|
## [0.6.1] — 2026-10-04
|
|
12
35
|
|
|
13
36
|
Adds a raw storage-slot read to `EvmChain` (RIN-320). Consumers can read contract state, including an EIP-7702 delegate's state in the EOA's own storage, without reaching through `getProvider()`.
|
|
@@ -516,6 +539,7 @@ Initial npm release of `@getomnichain/omnichain`. Replaces prior vendored-submod
|
|
|
516
539
|
|
|
517
540
|
---
|
|
518
541
|
|
|
542
|
+
[0.6.2]: https://github.com/getomnichain/omnichain/releases/tag/v0.6.2
|
|
519
543
|
[0.6.1]: https://github.com/getomnichain/omnichain/releases/tag/v0.6.1
|
|
520
544
|
[0.5.1]: https://github.com/getomnichain/omnichain/releases/tag/v0.5.1
|
|
521
545
|
[0.5.0]: https://github.com/getomnichain/omnichain/releases/tag/v0.5.0
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isByteArray = isByteArray;
|
|
4
|
+
exports.sameBytes = sameBytes;
|
|
5
|
+
function isByteArray(value) {
|
|
6
|
+
return ArrayBuffer.isView(value) && value.BYTES_PER_ELEMENT === 1;
|
|
7
|
+
}
|
|
8
|
+
function sameBytes(a, b) {
|
|
9
|
+
return Buffer.from(a.buffer, a.byteOffset, a.byteLength).equals(Buffer.from(b.buffer, b.byteOffset, b.byteLength));
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=bytes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bytes.js","sourceRoot":"","sources":["../../../utxo/bytes.ts"],"names":[],"mappings":";;AAAA,kCAEC;AAED,8BAEC;AAND,SAAgB,WAAW,CAAC,KAAc;IACxC,OAAO,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,IAAK,KAAoB,CAAC,iBAAiB,KAAK,CAAC,CAAC;AACpF,CAAC;AAED,SAAgB,SAAS,CAAC,CAAa,EAAE,CAAa;IACpD,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;AACrH,CAAC"}
|
package/dist/cjs/utxo/index.js
CHANGED
|
@@ -14,9 +14,12 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.utxoFromRawTransaction = void 0;
|
|
17
18
|
__exportStar(require("./coin_selection.js"), exports);
|
|
18
19
|
__exportStar(require("./ecc.js"), exports);
|
|
19
20
|
__exportStar(require("./fee.js"), exports);
|
|
21
|
+
var raw_transaction_ts_1 = require("./raw_transaction.js");
|
|
22
|
+
Object.defineProperty(exports, "utxoFromRawTransaction", { enumerable: true, get: function () { return raw_transaction_ts_1.utxoFromRawTransaction; } });
|
|
20
23
|
__exportStar(require("./script.js"), exports);
|
|
21
24
|
__exportStar(require("./tools/broadcaster.js"), exports);
|
|
22
25
|
__exportStar(require("./tools/chain_tip_provider.js"), exports);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../utxo/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../utxo/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,sDAAoC;AACpC,2CAAyB;AACzB,2CAAyB;AACzB,2DAA8D;AAArD,4HAAA,sBAAsB,OAAA;AAC/B,8CAA4B;AAC5B,yDAAuC;AACvC,gEAA8C;AAC9C,2DAAyC;AACzC,sEAAoD;AACpD,2DAAyC;AACzC,iEAA+C;AAC/C,4CAA0B;AAC1B,kDAAgC;AAChC,mDAAiC;AACjC,wDAAsC;AACtC,2DAAyC;AACzC,+DAA6C;AAC7C,iDAA+B;AAC/B,iDAA+B;AAC/B,kDAAgC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.COMPACT_SIGNATURE_BYTES = void 0;
|
|
37
|
+
exports.isCompressedPublicKey = isCompressedPublicKey;
|
|
38
|
+
exports.singleKeyPayment = singleKeyPayment;
|
|
39
|
+
exports.verifyStrictSignature = verifyStrictSignature;
|
|
40
|
+
const bitcoinjs_lib_1 = require("bitcoinjs-lib");
|
|
41
|
+
const ecc = __importStar(require("tiny-secp256k1"));
|
|
42
|
+
const bytes_ts_1 = require("./bytes.js");
|
|
43
|
+
const script_ts_1 = require("./script.js");
|
|
44
|
+
exports.COMPACT_SIGNATURE_BYTES = 64;
|
|
45
|
+
function isCompressedPublicKey(publicKey) {
|
|
46
|
+
return (0, bytes_ts_1.isByteArray)(publicKey) && ecc.isPointCompressed(publicKey);
|
|
47
|
+
}
|
|
48
|
+
function singleKeyPayment(publicKey, scriptType, network) {
|
|
49
|
+
return scriptType === script_ts_1.UtxoScriptTypes.P2WPKH
|
|
50
|
+
? bitcoinjs_lib_1.payments.p2wpkh({ pubkey: publicKey, network })
|
|
51
|
+
: bitcoinjs_lib_1.payments.p2pkh({ pubkey: publicKey, network });
|
|
52
|
+
}
|
|
53
|
+
function verifyStrictSignature(publicKey, hash, signature) {
|
|
54
|
+
return ecc.verify(hash, publicKey, signature, true);
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../../../utxo/keys.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AASA,sDAEC;AAED,4CAQC;AAED,sDAMC;AA7BD,iDAAyC;AACzC,oDAAsC;AAEtC,yCAAyC;AACzC,2CAAuE;AAG1D,QAAA,uBAAuB,GAAG,EAAE,CAAC;AAE1C,SAAgB,qBAAqB,CAAC,SAAkB;IACtD,OAAO,IAAA,sBAAW,EAAC,SAAS,CAAC,IAAI,GAAG,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC;AACpE,CAAC;AAED,SAAgB,gBAAgB,CAC9B,SAAqB,EACrB,UAAmC,EACnC,OAAwB;IAExB,OAAO,UAAU,KAAK,2BAAe,CAAC,MAAM;QAC1C,CAAC,CAAC,wBAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;QACjD,CAAC,CAAC,wBAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;AACrD,CAAC;AAED,SAAgB,qBAAqB,CACnC,SAAqB,EACrB,IAAgB,EAChB,SAAqB;IAErB,OAAO,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;AACtD,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.decodeRawTransaction = decodeRawTransaction;
|
|
4
|
+
exports.utxoFromRawTransaction = utxoFromRawTransaction;
|
|
5
|
+
const bitcoinjs_lib_1 = require("bitcoinjs-lib");
|
|
6
|
+
const errors_ts_1 = require("../errors.js");
|
|
7
|
+
const script_ts_1 = require("./script.js");
|
|
8
|
+
const HEX_BYTES = /^(?:[0-9a-fA-F]{2})+$/;
|
|
9
|
+
function decodeRawTransaction(rawTxHex) {
|
|
10
|
+
if (typeof rawTxHex !== 'string' || !HEX_BYTES.test(rawTxHex))
|
|
11
|
+
return null;
|
|
12
|
+
try {
|
|
13
|
+
return bitcoinjs_lib_1.Transaction.fromHex(rawTxHex);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function utxoFromRawTransaction(rawTxHex, vout, ownerAddress) {
|
|
20
|
+
const tx = decodeRawTransaction(rawTxHex);
|
|
21
|
+
if (tx === null) {
|
|
22
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, 'utxoFromRawTransaction: rawTxHex is not a valid transaction');
|
|
23
|
+
}
|
|
24
|
+
const txid = tx.getId();
|
|
25
|
+
const output = Number.isInteger(vout) && vout >= 0 ? tx.outs[vout] : undefined;
|
|
26
|
+
if (output === undefined) {
|
|
27
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, `utxoFromRawTransaction: transaction ${txid} has no output ${vout} (it has ${tx.outs.length})`, { txHash: txid });
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
txid,
|
|
31
|
+
vout,
|
|
32
|
+
valueSats: Number(output.value),
|
|
33
|
+
scriptPubKeyHex: Buffer.from(output.script).toString('hex'),
|
|
34
|
+
scriptType: (0, script_ts_1.detectScriptType)(output.script),
|
|
35
|
+
confirmations: 0,
|
|
36
|
+
ownerAddress,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=raw_transaction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"raw_transaction.js","sourceRoot":"","sources":["../../../utxo/raw_transaction.ts"],"names":[],"mappings":";;AASA,oDAOC;AAED,wDA8BC;AAhDD,iDAA4C;AAE5C,4CAA2D;AAE3D,2CAA+C;AAG/C,MAAM,SAAS,GAAG,uBAAuB,CAAC;AAE1C,SAAgB,oBAAoB,CAAC,QAAgB;IACnD,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3E,IAAI,CAAC;QACH,OAAO,2BAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAgB,sBAAsB,CACpC,QAAgB,EAChB,IAAY,EACZ,YAAoB;IAEpB,MAAM,EAAE,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAC1C,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;QAChB,MAAM,IAAI,sBAAU,CAClB,2BAAe,CAAC,eAAe,EAC/B,6DAA6D,CAC9D,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC;IACxB,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/E,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,IAAI,sBAAU,CAClB,2BAAe,CAAC,eAAe,EAC/B,uCAAuC,IAAI,kBAAkB,IAAI,YAAY,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,EAC9F,EAAE,MAAM,EAAE,IAAI,EAAE,CACjB,CAAC;IACJ,CAAC;IACD,OAAO;QACL,IAAI;QACJ,IAAI;QACJ,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;QAC/B,eAAe,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;QAC3D,UAAU,EAAE,IAAA,4BAAgB,EAAC,MAAM,CAAC,MAAM,CAAC;QAC3C,aAAa,EAAE,CAAC;QAChB,YAAY;KACb,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"script.js","sourceRoot":"","sources":["../../../utxo/script.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"script.js","sourceRoot":"","sources":["../../../utxo/script.ts"],"names":[],"mappings":";;;AAkBA,4CA4BC;AAED,sDAEC;AAED,oDAGC;AAED,kDAOC;AAhED,iDAA8F;AAE9F,qEAA+D;AAElD,QAAA,eAAe,GAAG;IAC7B,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,MAAM;IACZ,MAAM,EAAE,QAAQ;IAChB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,MAAM;IACZ,QAAQ,EAAE,WAAW;IACrB,WAAW,EAAE,cAAc;CACnB,CAAC;AAMX,SAAgB,gBAAgB,CAAC,MAAkB;IACjD,MAAM,CAAC,GAAG,MAAM,CAAC;IACjB,IACE,CAAC,CAAC,MAAM,KAAK,EAAE;QACf,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI;QACb,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI;QACb,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI;QACb,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI;QACd,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI,EACd,CAAC;QACD,OAAO,uBAAe,CAAC,KAAK,CAAC;IAC/B,CAAC;IACD,IAAI,CAAC,CAAC,MAAM,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC;QACxE,OAAO,uBAAe,CAAC,IAAI,CAAC;IAC9B,CAAC;IACD,IAAI,CAAC,CAAC,MAAM,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACtD,OAAO,uBAAe,CAAC,MAAM,CAAC;IAChC,CAAC;IACD,IAAI,CAAC,CAAC,MAAM,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACtD,OAAO,uBAAe,CAAC,KAAK,CAAC;IAC/B,CAAC;IACD,IAAI,CAAC,CAAC,MAAM,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACtD,OAAO,uBAAe,CAAC,IAAI,CAAC;IAC9B,CAAC;IACD,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAClC,OAAO,uBAAe,CAAC,QAAQ,CAAC;IAClC,CAAC;IACD,OAAO,uBAAe,CAAC,WAAW,CAAC;AACrC,CAAC;AAED,SAAgB,qBAAqB,CAAC,UAAkB,EAAE,OAAyB;IACjF,OAAO,uBAAU,CAAC,cAAc,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;AACxD,CAAC;AAED,SAAgB,oBAAoB,CAAC,UAAkB,EAAE,OAAyB;IAChF,MAAM,MAAM,GAAG,qBAAqB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAC1D,OAAO,gBAAgB,CAAC,MAAM,CAAC,CAAC;AAClC,CAAC;AAED,SAAgB,mBAAmB,CAAC,IAAgB;IAClD,IAAI,IAAI,CAAC,MAAM,GAAG,4CAAmB,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CACb,yBAAyB,IAAI,CAAC,MAAM,gBAAgB,4CAAmB,QAAQ,CAChF,CAAC;IACJ,CAAC;IACD,OAAO,sBAAS,CAAC,OAAO,CAAC,CAAC,uBAAO,CAAC,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACnE,CAAC"}
|
|
@@ -2,8 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.UnsignedUtxoTransaction = void 0;
|
|
4
4
|
const bitcoinjs_lib_1 = require("bitcoinjs-lib");
|
|
5
|
+
const errors_ts_1 = require("../errors.js");
|
|
5
6
|
const network_type_ts_1 = require("../network_type.js");
|
|
6
7
|
const unsigned_transaction_ts_1 = require("../unsigned_transaction.js");
|
|
8
|
+
const bytes_ts_1 = require("./bytes.js");
|
|
9
|
+
const keys_ts_1 = require("./keys.js");
|
|
10
|
+
const script_ts_1 = require("./script.js");
|
|
7
11
|
class UnsignedUtxoTransaction extends unsigned_transaction_ts_1.UnsignedTransaction {
|
|
8
12
|
params;
|
|
9
13
|
psbtBase64;
|
|
@@ -31,6 +35,137 @@ class UnsignedUtxoTransaction extends unsigned_transaction_ts_1.UnsignedTransact
|
|
|
31
35
|
toPsbt() {
|
|
32
36
|
return bitcoinjs_lib_1.Psbt.fromBase64(this.psbtBase64, { network: this.params.networkInfo });
|
|
33
37
|
}
|
|
38
|
+
signWith(signer) {
|
|
39
|
+
if (!(0, keys_ts_1.isCompressedPublicKey)(signer?.publicKey)) {
|
|
40
|
+
throw this.invalid('signWith: signer.publicKey must be a 33-byte compressed secp256k1 public key');
|
|
41
|
+
}
|
|
42
|
+
const psbt = this.toPsbt();
|
|
43
|
+
const prevouts = this.verifiedPrevouts(psbt, 'signWith');
|
|
44
|
+
const owned = this.inputIndicesOwnedBy(prevouts, signer.publicKey);
|
|
45
|
+
if (owned.length === 0) {
|
|
46
|
+
throw this.invalid('signWith: the signer owns none of the inputs');
|
|
47
|
+
}
|
|
48
|
+
const checkedSigner = {
|
|
49
|
+
publicKey: signer.publicKey,
|
|
50
|
+
sign: (hash) => {
|
|
51
|
+
const signature = signer.sign(hash);
|
|
52
|
+
if (!(0, bytes_ts_1.isByteArray)(signature) || signature.length !== keys_ts_1.COMPACT_SIGNATURE_BYTES) {
|
|
53
|
+
throw this.invalid(`signWith: signer.sign must return a ${keys_ts_1.COMPACT_SIGNATURE_BYTES}-byte compact ECDSA signature`);
|
|
54
|
+
}
|
|
55
|
+
return signature;
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
for (const index of owned) {
|
|
59
|
+
if (hasSignatureFrom(psbt, index, signer.publicKey))
|
|
60
|
+
continue;
|
|
61
|
+
try {
|
|
62
|
+
psbt.signInput(index, checkedSigner);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
if ((0, errors_ts_1.isChainError)(err))
|
|
66
|
+
throw err;
|
|
67
|
+
throw this.invalid(`signWith: signing input ${index} failed: ${errorMessage(err)}`, err);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return this.withPsbt(psbt);
|
|
71
|
+
}
|
|
72
|
+
finalize() {
|
|
73
|
+
const psbt = this.toPsbt();
|
|
74
|
+
this.verifiedPrevouts(psbt, 'finalize');
|
|
75
|
+
psbt.data.inputs.forEach((input, index) => {
|
|
76
|
+
if (!input.partialSig || input.partialSig.length === 0) {
|
|
77
|
+
throw this.invalid(`finalize: input ${index} is not signed`);
|
|
78
|
+
}
|
|
79
|
+
if (!signaturesAreValid(psbt, index)) {
|
|
80
|
+
throw this.invalid(`finalize: input ${index} has an invalid signature`);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
let tx;
|
|
84
|
+
try {
|
|
85
|
+
psbt.finalizeAllInputs();
|
|
86
|
+
tx = psbt.extractTransaction();
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
throw this.invalid(`finalize: ${errorMessage(err)}`, err);
|
|
90
|
+
}
|
|
91
|
+
return { hex: tx.toHex(), txid: tx.getId(), vsize: tx.virtualSize() };
|
|
92
|
+
}
|
|
93
|
+
verifiedPrevouts(psbt, method) {
|
|
94
|
+
const prevouts = psbt.data.inputs.map((input, index) => {
|
|
95
|
+
if (!input.nonWitnessUtxo) {
|
|
96
|
+
throw this.invalid(`${method}: input ${index} carries no parent transaction (nonWitnessUtxo)`);
|
|
97
|
+
}
|
|
98
|
+
const parent = parseParent(input.nonWitnessUtxo);
|
|
99
|
+
if (parent === null) {
|
|
100
|
+
throw this.invalid(`${method}: input ${index} has a parent transaction that does not parse`);
|
|
101
|
+
}
|
|
102
|
+
const spent = psbt.txInputs[index];
|
|
103
|
+
if (!(0, bytes_ts_1.sameBytes)(parent.getHash(), spent.hash)) {
|
|
104
|
+
throw this.invalid(`${method}: input ${index} has a parent transaction that is not the one it spends`);
|
|
105
|
+
}
|
|
106
|
+
const prevout = parent.outs[spent.index];
|
|
107
|
+
if (prevout === undefined) {
|
|
108
|
+
throw this.invalid(`${method}: input ${index} spends output ${spent.index}, which its parent transaction does not have`);
|
|
109
|
+
}
|
|
110
|
+
if (input.witnessUtxo && input.witnessUtxo.value !== prevout.value) {
|
|
111
|
+
throw this.invalid(`${method}: input ${index} declares ${input.witnessUtxo.value} sats, but its parent output holds ${prevout.value} sats`);
|
|
112
|
+
}
|
|
113
|
+
if (input.witnessUtxo && !(0, bytes_ts_1.sameBytes)(input.witnessUtxo.script, prevout.script)) {
|
|
114
|
+
throw this.invalid(`${method}: input ${index} declares a script that differs from its parent output`);
|
|
115
|
+
}
|
|
116
|
+
return prevout;
|
|
117
|
+
});
|
|
118
|
+
const realFeeSats = prevouts.reduce((sum, prevout) => sum + prevout.value, 0n) -
|
|
119
|
+
psbt.txOutputs.reduce((sum, output) => sum + output.value, 0n);
|
|
120
|
+
if (Number(realFeeSats) !== this.feeSats) {
|
|
121
|
+
throw this.invalid(`${method}: the transaction would pay ${realFeeSats} sats in fees, not the reported ${this.feeSats} sats`);
|
|
122
|
+
}
|
|
123
|
+
return prevouts;
|
|
124
|
+
}
|
|
125
|
+
inputIndicesOwnedBy(prevouts, publicKey) {
|
|
126
|
+
const ownedScripts = [script_ts_1.UtxoScriptTypes.P2WPKH, script_ts_1.UtxoScriptTypes.P2PKH].map((scriptType) => (0, keys_ts_1.singleKeyPayment)(publicKey, scriptType, this.params.networkInfo).output);
|
|
127
|
+
return prevouts.flatMap((prevout, index) => ownedScripts.some((owned) => (0, bytes_ts_1.sameBytes)(owned, prevout.script)) ? [index] : []);
|
|
128
|
+
}
|
|
129
|
+
withPsbt(psbt) {
|
|
130
|
+
return new UnsignedUtxoTransaction({
|
|
131
|
+
chainId: this.chainId,
|
|
132
|
+
params: this.params,
|
|
133
|
+
psbtBase64: psbt.toBase64(),
|
|
134
|
+
selectedInputs: this.selectedInputs,
|
|
135
|
+
feeSats: this.feeSats,
|
|
136
|
+
feeRateSatsPerVByte: this.feeRateSatsPerVByte,
|
|
137
|
+
estimatedVBytes: this.estimatedVBytes,
|
|
138
|
+
totalInputSats: this.totalInputSats,
|
|
139
|
+
totalOutputSats: this.totalOutputSats,
|
|
140
|
+
changeAddress: this.changeAddress,
|
|
141
|
+
inputsToSign: this.inputsToSign,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
invalid(message, cause) {
|
|
145
|
+
return new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, `UnsignedUtxoTransaction.${message}`, { chainId: this.chainId }, cause);
|
|
146
|
+
}
|
|
34
147
|
}
|
|
35
148
|
exports.UnsignedUtxoTransaction = UnsignedUtxoTransaction;
|
|
149
|
+
function parseParent(nonWitnessUtxo) {
|
|
150
|
+
try {
|
|
151
|
+
return bitcoinjs_lib_1.Transaction.fromBuffer(nonWitnessUtxo);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function hasSignatureFrom(psbt, index, publicKey) {
|
|
158
|
+
return psbt.data.inputs[index].partialSig?.some((sig) => (0, bytes_ts_1.sameBytes)(sig.pubkey, publicKey)) ?? false;
|
|
159
|
+
}
|
|
160
|
+
function signaturesAreValid(psbt, index) {
|
|
161
|
+
try {
|
|
162
|
+
return psbt.validateSignaturesOfInput(index, keys_ts_1.verifyStrictSignature);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function errorMessage(err) {
|
|
169
|
+
return err instanceof Error ? err.message : String(err);
|
|
170
|
+
}
|
|
36
171
|
//# sourceMappingURL=unsigned_utxo_transaction.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unsigned_utxo_transaction.js","sourceRoot":"","sources":["../../../utxo/unsigned_utxo_transaction.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"unsigned_utxo_transaction.js","sourceRoot":"","sources":["../../../utxo/unsigned_utxo_transaction.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,6 +19,9 @@ const utxo_transaction_status_ts_1 = require("./utxo_transaction_status.js");
|
|
|
19
19
|
require("./ecc.js");
|
|
20
20
|
const coin_selection_ts_1 = require("./coin_selection.js");
|
|
21
21
|
const fee_ts_1 = require("./fee.js");
|
|
22
|
+
const bytes_ts_1 = require("./bytes.js");
|
|
23
|
+
const keys_ts_1 = require("./keys.js");
|
|
24
|
+
const raw_transaction_ts_1 = require("./raw_transaction.js");
|
|
22
25
|
const script_ts_1 = require("./script.js");
|
|
23
26
|
const utxo_network_params_ts_1 = require("./utxo_network_params.js");
|
|
24
27
|
const unsigned_utxo_transaction_ts_1 = require("./unsigned_utxo_transaction.js");
|
|
@@ -268,6 +271,72 @@ class UtxoChain extends chain_base_ts_1.Chain {
|
|
|
268
271
|
async createTransferUnsignedTransaction(req) {
|
|
269
272
|
return this.buildTransfer(req, undefined);
|
|
270
273
|
}
|
|
274
|
+
assembleTransaction(request) {
|
|
275
|
+
const { inputs, outputs } = request;
|
|
276
|
+
if (inputs.length === 0) {
|
|
277
|
+
throw this.invalidArgument('assembleTransaction: at least one input is required');
|
|
278
|
+
}
|
|
279
|
+
if (outputs.length === 0) {
|
|
280
|
+
throw this.invalidArgument('assembleTransaction: at least one output is required');
|
|
281
|
+
}
|
|
282
|
+
const spentOutpoints = new Set();
|
|
283
|
+
const inputScriptTypes = inputs.map((input, index) => {
|
|
284
|
+
const scriptType = this.checkPsbtInput(input, index);
|
|
285
|
+
const outpoint = `${input.utxo.txid.toLowerCase()}:${input.utxo.vout}`;
|
|
286
|
+
if (spentOutpoints.has(outpoint)) {
|
|
287
|
+
throw this.invalidArgument(`assembleTransaction: inputs[${index}] spends ${outpoint} a second time`);
|
|
288
|
+
}
|
|
289
|
+
spentOutpoints.add(outpoint);
|
|
290
|
+
return scriptType;
|
|
291
|
+
});
|
|
292
|
+
const resolvedOutputs = outputs.map((output, index) => this.resolvePsbtOutput(output, index));
|
|
293
|
+
const opReturnDataLengths = resolvedOutputs.flatMap((o) => o.opReturnDataLength === null ? [] : [o.opReturnDataLength]);
|
|
294
|
+
if (opReturnDataLengths.length > 1) {
|
|
295
|
+
throw this.invalidArgument('assembleTransaction: at most one opReturn output is allowed');
|
|
296
|
+
}
|
|
297
|
+
const totalInputSats = inputs.reduce((sum, { utxo }) => sum + utxo.valueSats, 0);
|
|
298
|
+
const totalOutputSats = resolvedOutputs.reduce((sum, o) => sum + o.valueSats, 0);
|
|
299
|
+
if (totalOutputSats > totalInputSats) {
|
|
300
|
+
throw this.invalidArgument(`assembleTransaction: outputs total ${totalOutputSats} sats but inputs only ${totalInputSats} sats`);
|
|
301
|
+
}
|
|
302
|
+
const sequence = (request.rbfEnabled ?? this.rbfEnabled) ? utxo_network_params_ts_1.RBF_SEQUENCE : utxo_network_params_ts_1.FINAL_SEQUENCE;
|
|
303
|
+
const psbt = new bitcoinjs_lib_1.Psbt({ network: this.params.networkInfo });
|
|
304
|
+
const inputsToSign = {};
|
|
305
|
+
inputs.forEach(({ utxo, parentTxHex }, index) => {
|
|
306
|
+
this.addInputToPsbt({ psbt, utxo, sequence, parentTxHex });
|
|
307
|
+
(inputsToSign[utxo.ownerAddress] ??= []).push(index);
|
|
308
|
+
});
|
|
309
|
+
for (const output of resolvedOutputs) {
|
|
310
|
+
psbt.addOutput({ script: output.script, value: BigInt(output.valueSats) });
|
|
311
|
+
}
|
|
312
|
+
const estimatedVBytes = (0, fee_ts_1.estimateTxVBytes)(inputScriptTypes, resolvedOutputs.flatMap((o) => (o.opReturnDataLength === null ? [(0, script_ts_1.detectScriptType)(o.script)] : [])), opReturnDataLengths);
|
|
313
|
+
const feeSats = totalInputSats - totalOutputSats;
|
|
314
|
+
return new unsigned_utxo_transaction_ts_1.UnsignedUtxoTransaction({
|
|
315
|
+
chainId: this.chainId,
|
|
316
|
+
params: this.params,
|
|
317
|
+
psbtBase64: psbt.toBase64(),
|
|
318
|
+
selectedInputs: inputs.map(({ utxo }) => utxo),
|
|
319
|
+
feeSats,
|
|
320
|
+
feeRateSatsPerVByte: feeSats / estimatedVBytes,
|
|
321
|
+
estimatedVBytes,
|
|
322
|
+
totalInputSats,
|
|
323
|
+
totalOutputSats,
|
|
324
|
+
changeAddress: null,
|
|
325
|
+
inputsToSign,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
addressForPublicKey(publicKey, scriptType = script_ts_1.UtxoScriptTypes.P2WPKH) {
|
|
329
|
+
if (!(0, keys_ts_1.isCompressedPublicKey)(publicKey)) {
|
|
330
|
+
throw this.invalidArgument('addressForPublicKey: publicKey must be a 33-byte compressed secp256k1 public key');
|
|
331
|
+
}
|
|
332
|
+
if (scriptType !== script_ts_1.UtxoScriptTypes.P2WPKH && scriptType !== script_ts_1.UtxoScriptTypes.P2PKH) {
|
|
333
|
+
throw this.invalidArgument(`addressForPublicKey: scriptType must be '${script_ts_1.UtxoScriptTypes.P2WPKH}' or '${script_ts_1.UtxoScriptTypes.P2PKH}', got ${String(scriptType)}`);
|
|
334
|
+
}
|
|
335
|
+
if (scriptType === script_ts_1.UtxoScriptTypes.P2WPKH && !this.params.networkInfo.bech32) {
|
|
336
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.FeatureNotSupported, `${this.name}: the network has no segwit addresses; use scriptType '${script_ts_1.UtxoScriptTypes.P2PKH}'`, { chainId: this.chainId });
|
|
337
|
+
}
|
|
338
|
+
return (0, keys_ts_1.singleKeyPayment)(publicKey, scriptType, this.params.networkInfo).address;
|
|
339
|
+
}
|
|
271
340
|
async broadcast(signed, opts) {
|
|
272
341
|
if (opts && opts.signal !== undefined) {
|
|
273
342
|
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.FeatureNotSupported, `UTXO broadcast: signal is not honored in 0.3.0 (silently ignoring would let a caller conclude 'not sent' while the tx still lands). Cancellation returns in 0.3.1.`, { chainId: this.chainId });
|
|
@@ -465,6 +534,66 @@ class UtxoChain extends chain_base_ts_1.Chain {
|
|
|
465
534
|
}
|
|
466
535
|
psbt.addInput(input);
|
|
467
536
|
}
|
|
537
|
+
checkPsbtInput({ utxo, parentTxHex }, index) {
|
|
538
|
+
const field = `assembleTransaction: inputs[${index}]`;
|
|
539
|
+
const parent = (0, raw_transaction_ts_1.decodeRawTransaction)(parentTxHex);
|
|
540
|
+
if (parent === null) {
|
|
541
|
+
throw this.invalidArgument(`${field}.parentTxHex is not a valid transaction`);
|
|
542
|
+
}
|
|
543
|
+
const parentTxid = parent.getId();
|
|
544
|
+
if (typeof utxo.txid !== 'string' || utxo.txid.toLowerCase() !== parentTxid) {
|
|
545
|
+
throw this.invalidArgument(`${field}: parentTxHex is transaction ${parentTxid}, not utxo.txid ${String(utxo.txid)}`, { txHash: parentTxid });
|
|
546
|
+
}
|
|
547
|
+
const parentOutput = Number.isInteger(utxo.vout) && utxo.vout >= 0 ? parent.outs[utxo.vout] : undefined;
|
|
548
|
+
if (parentOutput === undefined) {
|
|
549
|
+
throw this.invalidArgument(`${field}: transaction ${parentTxid} has no output ${utxo.vout} (it has ${parent.outs.length})`, { txHash: parentTxid });
|
|
550
|
+
}
|
|
551
|
+
if (Number(parentOutput.value) !== utxo.valueSats) {
|
|
552
|
+
throw this.invalidArgument(`${field}: utxo.valueSats ${utxo.valueSats} does not match the parent output value ${parentOutput.value}`, { txHash: parentTxid });
|
|
553
|
+
}
|
|
554
|
+
const script = Buffer.from(parentOutput.script);
|
|
555
|
+
if (typeof utxo.scriptPubKeyHex !== 'string' || utxo.scriptPubKeyHex.toLowerCase() !== script.toString('hex')) {
|
|
556
|
+
throw this.invalidArgument(`${field}: utxo.scriptPubKeyHex does not match the parent output script`, { txHash: parentTxid });
|
|
557
|
+
}
|
|
558
|
+
const scriptType = (0, script_ts_1.detectScriptType)(script);
|
|
559
|
+
if (scriptType === script_ts_1.UtxoScriptTypes.OpReturn || scriptType === script_ts_1.UtxoScriptTypes.NonStandard) {
|
|
560
|
+
throw this.invalidArgument(`${field}: cannot spend an output of script type ${scriptType}`, { txHash: parentTxid });
|
|
561
|
+
}
|
|
562
|
+
if (!(0, bytes_ts_1.sameBytes)(this.outputScriptFor(utxo.ownerAddress, `${field}.utxo.ownerAddress`), script)) {
|
|
563
|
+
throw this.invalidArgument(`${field}: utxo.ownerAddress ${utxo.ownerAddress} does not own the output it spends`, { txHash: parentTxid, address: utxo.ownerAddress });
|
|
564
|
+
}
|
|
565
|
+
return scriptType;
|
|
566
|
+
}
|
|
567
|
+
resolvePsbtOutput(output, index) {
|
|
568
|
+
const field = `assembleTransaction: outputs[${index}]`;
|
|
569
|
+
if (output.kind === 'address') {
|
|
570
|
+
const script = this.outputScriptFor(output.address, `${field}.address`);
|
|
571
|
+
if (!Number.isSafeInteger(output.valueSats) || output.valueSats < this.params.dustValueSats) {
|
|
572
|
+
throw this.invalidArgument(`${field}.valueSats must be an integer of at least ${this.params.dustValueSats} sats (dust), got ${output.valueSats}`);
|
|
573
|
+
}
|
|
574
|
+
return { script, valueSats: output.valueSats, opReturnDataLength: null };
|
|
575
|
+
}
|
|
576
|
+
if (output.kind === 'opReturn') {
|
|
577
|
+
if (!(0, bytes_ts_1.isByteArray)(output.data) || output.data.length > utxo_network_params_ts_1.OP_RETURN_MAX_BYTES) {
|
|
578
|
+
throw this.invalidArgument(`${field}.data must be a byte array of at most ${utxo_network_params_ts_1.OP_RETURN_MAX_BYTES} bytes`);
|
|
579
|
+
}
|
|
580
|
+
return { script: (0, script_ts_1.buildOpReturnScript)(output.data), valueSats: 0, opReturnDataLength: output.data.length };
|
|
581
|
+
}
|
|
582
|
+
throw this.invalidArgument(`${field}.kind must be 'address' or 'opReturn', got ${String(output.kind)}`);
|
|
583
|
+
}
|
|
584
|
+
outputScriptFor(address, field) {
|
|
585
|
+
const script = this.validateAddress(address) ? tryOutputScript(address, this.params.networkInfo) : null;
|
|
586
|
+
if (script === null) {
|
|
587
|
+
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidAddress, `${this.name}: ${field} is not a valid address: ${String(address)}`, { chainId: this.chainId, address });
|
|
588
|
+
}
|
|
589
|
+
return script;
|
|
590
|
+
}
|
|
591
|
+
invalidArgument(message, meta = {}) {
|
|
592
|
+
return new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, `${this.name}: ${message}`, {
|
|
593
|
+
chainId: this.chainId,
|
|
594
|
+
...meta,
|
|
595
|
+
});
|
|
596
|
+
}
|
|
468
597
|
async resolveFeeRate(overrideRate, targetBlocks) {
|
|
469
598
|
if (overrideRate !== undefined) {
|
|
470
599
|
if (overrideRate < 1) {
|
|
@@ -502,6 +631,14 @@ class UtxoChain extends chain_base_ts_1.Chain {
|
|
|
502
631
|
}
|
|
503
632
|
}
|
|
504
633
|
exports.UtxoChain = UtxoChain;
|
|
634
|
+
function tryOutputScript(address, network) {
|
|
635
|
+
try {
|
|
636
|
+
return (0, script_ts_1.addressToScriptPubKey)(address, network);
|
|
637
|
+
}
|
|
638
|
+
catch {
|
|
639
|
+
return null;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
505
642
|
function bigintToNumber(value) {
|
|
506
643
|
if (value > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
507
644
|
throw new errors_ts_1.ChainError(errors_ts_1.ChainErrorKinds.InvalidArgument, `UTXO amount ${value} exceeds Number.MAX_SAFE_INTEGER`);
|