@chainvue/verus-sdk 0.7.0 → 0.9.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/dist/VerusSDK.d.ts +4 -1
- package/dist/VerusSDK.d.ts.map +1 -1
- package/dist/VerusSDK.js +6 -2
- package/dist/VerusSDK.js.map +1 -1
- package/dist/bundle.js +495 -273
- package/dist/core/brands.d.ts +31 -0
- package/dist/core/brands.d.ts.map +1 -0
- package/dist/core/brands.js +82 -0
- package/dist/core/brands.js.map +1 -0
- package/dist/currency/index.d.ts.map +1 -1
- package/dist/currency/index.js +6 -1
- package/dist/currency/index.js.map +1 -1
- package/dist/identity/index.d.ts +46 -28
- package/dist/identity/index.d.ts.map +1 -1
- package/dist/identity/index.js +253 -78
- package/dist/identity/index.js.map +1 -1
- package/dist/keys/index.d.ts.map +1 -1
- package/dist/keys/index.js +9 -0
- package/dist/keys/index.js.map +1 -1
- package/dist/signing/index.d.ts +12 -3
- package/dist/signing/index.d.ts.map +1 -1
- package/dist/signing/index.js +17 -4
- package/dist/signing/index.js.map +1 -1
- package/dist/transfer/index.d.ts.map +1 -1
- package/dist/transfer/index.js +9 -2
- package/dist/transfer/index.js.map +1 -1
- package/dist/types/index.d.ts +26 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utxo/index.d.ts +14 -0
- package/dist/utxo/index.d.ts.map +1 -1
- package/dist/utxo/index.js +49 -0
- package/dist/utxo/index.js.map +1 -1
- package/package.json +1 -1
package/dist/identity/index.js
CHANGED
|
@@ -47,6 +47,7 @@ const index_js_1 = require("../constants/index.js");
|
|
|
47
47
|
const index_js_2 = require("../utils/index.js");
|
|
48
48
|
const index_js_3 = require("../signing/index.js");
|
|
49
49
|
const index_js_4 = require("../utxo/index.js");
|
|
50
|
+
const brands_js_1 = require("../core/brands.js");
|
|
50
51
|
const errors_js_1 = require("../errors.js");
|
|
51
52
|
const index_js_5 = require("../keys/index.js");
|
|
52
53
|
const { createUnfundedIdentityUpdate, completeFundedIdentityUpdate } = utxo_lib_1.smarttxs;
|
|
@@ -56,6 +57,10 @@ const HASH256_BYTE_LENGTH = 32;
|
|
|
56
57
|
const NULL_ID_HASH = Buffer.alloc(HASH160_BYTE_LENGTH, 0);
|
|
57
58
|
/** Eval code for CAdvancedNameReservation (sub-ID) */
|
|
58
59
|
const EVAL_IDENTITY_ADVANCEDRESERVATION = 10;
|
|
60
|
+
/** Consensus maximum identity unlock delay (blocks) — CIdentity::MAX_UNLOCK_DELAY, ~22 years. */
|
|
61
|
+
const MAX_UNLOCK_DELAY = 11563200;
|
|
62
|
+
/** ~1 year of blocks (1 block/min); a lock delay above this needs an explicit opt-in. */
|
|
63
|
+
const LOCK_DELAY_SANITY_BLOCKS = 525600;
|
|
59
64
|
// ─── Script / Hash Builders ────────────────────────────────────────
|
|
60
65
|
/**
|
|
61
66
|
* Assert a base58check address has the expected version byte.
|
|
@@ -214,6 +219,14 @@ function buildIdentityScript(identity) {
|
|
|
214
219
|
* Derive the identity i-address from a name and optional parent
|
|
215
220
|
*/
|
|
216
221
|
function deriveIdentityAddress(name, parentIAddress) {
|
|
222
|
+
// nameAndParentAddrToIAddr hashes fromBase58Check(parent).hash regardless of
|
|
223
|
+
// version, so an R-address parent is laundered into an identity-versioned hash
|
|
224
|
+
// that names no real currency. The derived i-address then looks valid but can
|
|
225
|
+
// never be registered — funds paid to it (pay-to-unregistered-identity) burn,
|
|
226
|
+
// and a name commitment built with it wastes its fee. Require an i-address.
|
|
227
|
+
if (parentIAddress) {
|
|
228
|
+
assertAddressVersion(parentIAddress, index_js_1.I_ADDR_VERSION, 'parent');
|
|
229
|
+
}
|
|
217
230
|
return (0, verus_typescript_primitives_3.nameAndParentAddrToIAddr)(name, parentIAddress || undefined);
|
|
218
231
|
}
|
|
219
232
|
/**
|
|
@@ -228,14 +241,29 @@ function isVRSCParent(parentIAddress, network = 'mainnet') {
|
|
|
228
241
|
/**
|
|
229
242
|
* Build the full commitment data needed for Step 1 of identity creation
|
|
230
243
|
*/
|
|
231
|
-
function prepareNameCommitment(name, controlAddress, referralIAddress, parentIAddress, network = 'mainnet'
|
|
232
|
-
|
|
244
|
+
function prepareNameCommitment(name, controlAddress, referralIAddress, parentIAddress, network = 'mainnet',
|
|
245
|
+
/**
|
|
246
|
+
* Explicit 32-byte salt. Omit for a fresh random salt (the normal path); pass
|
|
247
|
+
* a fixed value only to make the commitment deterministic for golden/diff
|
|
248
|
+
* tests. A reused salt on a real registration is a privacy leak, so callers
|
|
249
|
+
* outside tests must not set it.
|
|
250
|
+
*/
|
|
251
|
+
salt = generateSalt()) {
|
|
233
252
|
const systemId = index_js_1.NETWORK_CONFIG[network].chainId;
|
|
234
253
|
// iAddressToHash discards the version byte, so an R-address referral would be
|
|
235
254
|
// laundered into a hash that names no identity (the daemon rejects the
|
|
236
255
|
// registration and the commitment fee is wasted). Require an i-address.
|
|
237
256
|
if (referralIAddress) {
|
|
238
257
|
assertAddressVersion(referralIAddress, index_js_1.I_ADDR_VERSION, 'referral');
|
|
258
|
+
// A sub-ID commitment (non-VRSC parent) would bake the referral into the
|
|
259
|
+
// advanced reservation hash, but _buildSubIdRegistration emits NO referral
|
|
260
|
+
// payout — the registration would then mismatch its own committed hash and
|
|
261
|
+
// the daemon rejects it, wasting the commitment fee. Fail closed here rather
|
|
262
|
+
// than committing a referral this SDK cannot honor.
|
|
263
|
+
if (!isVRSCParent(parentIAddress, network)) {
|
|
264
|
+
throw new errors_js_1.TransactionBuildError('referrals are not supported for sub-ID (non-VRSC-parent) registrations in this SDK; ' +
|
|
265
|
+
'omit the referral for a sub-ID name commitment.');
|
|
266
|
+
}
|
|
239
267
|
}
|
|
240
268
|
const referralHash = referralIAddress
|
|
241
269
|
? (0, index_js_2.iAddressToHash)(referralIAddress)
|
|
@@ -256,7 +284,7 @@ function prepareNameCommitment(name, controlAddress, referralIAddress, parentIAd
|
|
|
256
284
|
}
|
|
257
285
|
const commitmentHash = calculateCommitmentHash(serializedReservation);
|
|
258
286
|
const serializedCommitmentHash = serializeCommitmentHash(commitmentHash);
|
|
259
|
-
const commitmentScript = buildCommitmentScript(commitmentHash, controlAddress);
|
|
287
|
+
const commitmentScript = buildCommitmentScript(commitmentHash, (0, brands_js_1.parseRAddress)(controlAddress, 'controlAddress'));
|
|
260
288
|
return {
|
|
261
289
|
salt,
|
|
262
290
|
serializedReservation,
|
|
@@ -267,18 +295,17 @@ function prepareNameCommitment(name, controlAddress, referralIAddress, parentIAd
|
|
|
267
295
|
};
|
|
268
296
|
}
|
|
269
297
|
/**
|
|
270
|
-
* Build a
|
|
298
|
+
* Build a pay-to-identity output script for an i-address.
|
|
299
|
+
*
|
|
300
|
+
* Alias of {@link identityPaymentScript}. This previously emitted the bare
|
|
301
|
+
* 26-byte template `OP_DUP OP_HASH160 <idhash> OP_EQUALVERIFY OP_CHECKSIG
|
|
302
|
+
* OP_CHECKCRYPTOCONDITION`, which is NOT a valid Verus scriptPubKey — a Verus
|
|
303
|
+
* P2ID is a CryptoCondition (OptCCParams) output. Funds sent to the old
|
|
304
|
+
* template would have gone to a non-standard script the daemon does not treat
|
|
305
|
+
* as identity-spendable. It now delegates to the chain-verified CC builder.
|
|
271
306
|
*/
|
|
272
307
|
function buildP2IDScript(iAddress) {
|
|
273
|
-
|
|
274
|
-
return utxo_lib_1.script.compile([
|
|
275
|
-
utxo_lib_1.opcodes.OP_DUP,
|
|
276
|
-
utxo_lib_1.opcodes.OP_HASH160,
|
|
277
|
-
idHash,
|
|
278
|
-
utxo_lib_1.opcodes.OP_EQUALVERIFY,
|
|
279
|
-
utxo_lib_1.opcodes.OP_CHECKSIG,
|
|
280
|
-
utxo_lib_1.opcodes.OP_CHECKCRYPTOCONDITION,
|
|
281
|
-
]);
|
|
308
|
+
return identityPaymentScript((0, brands_js_1.parseIAddress)(iAddress, 'iAddress'));
|
|
282
309
|
}
|
|
283
310
|
/**
|
|
284
311
|
* The standard pay-to-identity output script (CC EVAL_NONE 1-of-1 to an
|
|
@@ -290,7 +317,9 @@ function identityPaymentScript(iAddress) {
|
|
|
290
317
|
return buildReferralPaymentScript(iAddress);
|
|
291
318
|
}
|
|
292
319
|
/**
|
|
293
|
-
* Build a CC referral payment output script
|
|
320
|
+
* Build a CC referral payment output script. Requires an already-parsed
|
|
321
|
+
* i-address: IdentityID.fromAddress launders the version byte, so accepting a
|
|
322
|
+
* raw string here is exactly the hole that let a wrong-kind address through.
|
|
294
323
|
*/
|
|
295
324
|
function buildReferralPaymentScript(iAddress) {
|
|
296
325
|
const identityDest = new verus_typescript_primitives_1.TxDestination(verus_typescript_primitives_1.IdentityID.fromAddress(iAddress));
|
|
@@ -331,9 +360,9 @@ function calculateRegistrationFees(hasReferral, totalFee = index_js_1.DEFAULT_RE
|
|
|
331
360
|
* Build a new Identity object for registration
|
|
332
361
|
*/
|
|
333
362
|
function createIdentityObject(params) {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
363
|
+
// No assertAddressVersion here: the brands guarantee the version bytes. An
|
|
364
|
+
// R-address primary or an i-address authority is now a compile error at the
|
|
365
|
+
// call site, where the raw string is parsed.
|
|
337
366
|
validateMinSigs(params.minSigs ?? 1, params.primaryAddresses.length);
|
|
338
367
|
const primaryKeys = params.primaryAddresses.map(addr => verus_typescript_primitives_1.KeyID.fromAddress(addr));
|
|
339
368
|
const identity = new verus_typescript_primitives_1.Identity({
|
|
@@ -354,45 +383,79 @@ function createIdentityObject(params) {
|
|
|
354
383
|
/**
|
|
355
384
|
* Build the parent-currency registration-fee output for a sub-ID.
|
|
356
385
|
*
|
|
357
|
-
* The
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
386
|
+
* The fee-output STRUCTURE depends on the parent currency's proofprotocol,
|
|
387
|
+
* verified against accepted on-chain registrations on VRSCTEST:
|
|
388
|
+
* - proofprotocol 2 (centralized / token, e.g. `ownora-nft`): a plain reserve
|
|
389
|
+
* output (EVAL_RESERVE_OUTPUT) holding `feeAmount` of the parent AT the
|
|
390
|
+
* parent's i-address, ZERO native.
|
|
391
|
+
* - otherwise (e.g. proofprotocol 1 PBaaS/fractional, `fum`): a CReserveTransfer
|
|
392
|
+
* (EVAL_RESERVE_TRANSFER) of `feeAmount` to the parent, carrying
|
|
393
|
+
* RESERVE_TRANSFER_FEE (0.0002) native.
|
|
364
394
|
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
*
|
|
368
|
-
*
|
|
395
|
+
* An offline SDK cannot read the parent's proofprotocol, so the caller passes it
|
|
396
|
+
* (`parentProofProtocol`). In both cases the native the transaction actually
|
|
397
|
+
* burns is the parent's idimportfees (`nativeImportFee`); the fee output's own
|
|
398
|
+
* native value is funded and cancels out in the conservation accounting.
|
|
369
399
|
*
|
|
370
|
-
* `
|
|
371
|
-
|
|
400
|
+
* `_controlAddress` is retained for signature compatibility and is unused.
|
|
401
|
+
*/
|
|
402
|
+
function buildRegistrationFeeOutput(parentCurrencyId, feeAmount, systemId, _controlAddress, parentProofProtocol = 2) {
|
|
403
|
+
if (parentProofProtocol === 2) {
|
|
404
|
+
return buildTokenChangeOutput((0, brands_js_1.parseIAddress)(parentCurrencyId, 'parentCurrencyId'), new Map([[parentCurrencyId, feeAmount]]));
|
|
405
|
+
}
|
|
406
|
+
return buildRegistrationFeeReserveTransfer(parentCurrencyId, feeAmount, systemId);
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* CReserveTransfer fee output for a sub-ID under a non-centralized (proofprotocol
|
|
410
|
+
* != 2) parent — the daemon's form for PBaaS/fractional currencies. Carries
|
|
411
|
+
* RESERVE_TRANSFER_FEE native.
|
|
372
412
|
*/
|
|
373
|
-
function
|
|
374
|
-
|
|
375
|
-
|
|
413
|
+
function buildRegistrationFeeReserveTransfer(parentCurrencyId, feeAmount, systemId) {
|
|
414
|
+
const destination = new verus_typescript_primitives_1.TxDestination(verus_typescript_primitives_1.KeyID.fromAddress(index_js_1.RESERVE_TRANSFER_EVAL_PKH));
|
|
415
|
+
const values = new verus_typescript_primitives_1.CurrencyValueMap({
|
|
416
|
+
value_map: new Map([[parentCurrencyId, new bn_js_1.default(feeAmount.toString(10))]]),
|
|
417
|
+
multivalue: false,
|
|
418
|
+
});
|
|
419
|
+
const parentHash = (0, verus_typescript_primitives_3.fromBase58Check)(parentCurrencyId).hash;
|
|
420
|
+
const transferDest = new verus_typescript_primitives_1.TransferDestination({ type: verus_typescript_primitives_1.DEST_ID, destination_bytes: Buffer.from(parentHash) });
|
|
421
|
+
const flags = verus_typescript_primitives_1.RESERVE_TRANSFER_VALID.or(verus_typescript_primitives_1.RESERVE_TRANSFER_BURN_CHANGE_PRICE);
|
|
422
|
+
const resTransfer = new verus_typescript_primitives_1.ReserveTransfer({
|
|
423
|
+
values,
|
|
424
|
+
version: new bn_js_1.default(1),
|
|
425
|
+
flags,
|
|
426
|
+
fee_currency_id: systemId,
|
|
427
|
+
fee_amount: new bn_js_1.default(index_js_1.RESERVE_TRANSFER_FEE.toString(10)),
|
|
428
|
+
transfer_destination: transferDest,
|
|
429
|
+
dest_currency_id: parentCurrencyId,
|
|
430
|
+
});
|
|
431
|
+
const master = new verus_typescript_primitives_1.OptCCParams({
|
|
432
|
+
version: new bn_js_1.default(3), eval_code: new bn_js_1.default(verus_typescript_primitives_2.EVALS.EVAL_NONE), m: new bn_js_1.default(1), n: new bn_js_1.default(1),
|
|
433
|
+
destinations: [destination], vdata: [],
|
|
434
|
+
});
|
|
435
|
+
const params = new verus_typescript_primitives_1.OptCCParams({
|
|
436
|
+
version: new bn_js_1.default(3), eval_code: new bn_js_1.default(verus_typescript_primitives_2.EVALS.EVAL_RESERVE_TRANSFER), m: new bn_js_1.default(1), n: new bn_js_1.default(1),
|
|
437
|
+
destinations: [destination], vdata: [resTransfer.toBuffer()],
|
|
438
|
+
});
|
|
439
|
+
const script = new verus_typescript_primitives_1.SmartTransactionScript(master, params);
|
|
440
|
+
return { script: script.toBuffer(), nativeValue: index_js_1.RESERVE_TRANSFER_FEE };
|
|
376
441
|
}
|
|
377
442
|
/**
|
|
378
443
|
* Build a token change output (EVAL_RESERVE_OUTPUT)
|
|
379
444
|
*/
|
|
380
445
|
function buildTokenChangeOutput(changeAddress, currencyChanges) {
|
|
381
|
-
//
|
|
382
|
-
//
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
// neither an R- nor an i-address rather than silently mis-routing it.
|
|
386
|
-
const version = (0, verus_typescript_primitives_3.fromBase58Check)(changeAddress).version;
|
|
446
|
+
// The brand guarantees the version; narrow it to pick the destination form.
|
|
447
|
+
// A pay-to-identity destination for i-addresses (KeyID.fromAddress would
|
|
448
|
+
// launder an i-address to a transparent script nobody controls), a KeyID for
|
|
449
|
+
// R-addresses, and reject a P2SH address (not a valid reserve destination).
|
|
387
450
|
let destination;
|
|
388
|
-
if (
|
|
451
|
+
if ((0, brands_js_1.isIAddress)(changeAddress)) {
|
|
389
452
|
destination = new verus_typescript_primitives_1.TxDestination(verus_typescript_primitives_1.IdentityID.fromAddress(changeAddress));
|
|
390
453
|
}
|
|
391
|
-
else if (
|
|
454
|
+
else if ((0, brands_js_1.isRAddress)(changeAddress)) {
|
|
392
455
|
destination = new verus_typescript_primitives_1.TxDestination(verus_typescript_primitives_1.KeyID.fromAddress(changeAddress));
|
|
393
456
|
}
|
|
394
457
|
else {
|
|
395
|
-
throw new errors_js_1.TransactionBuildError(`token change address must be an R-address or identity i-address, got
|
|
458
|
+
throw new errors_js_1.TransactionBuildError(`token change address must be an R-address or identity i-address, got: ${changeAddress}`);
|
|
396
459
|
}
|
|
397
460
|
const valueMap = new Map();
|
|
398
461
|
for (const [currency, amount] of currencyChanges) {
|
|
@@ -462,7 +525,7 @@ function buildAndSignCommitment(params, network) {
|
|
|
462
525
|
// (KeyID.fromAddress laundered it to an uncontrollable key — permanently
|
|
463
526
|
// unspendable, wasting the commitment fee). Derive it from the WIF instead.
|
|
464
527
|
const controlAddress = utxo_lib_1.ECPair.fromWIF(params.wif, verusNetwork).getAddress();
|
|
465
|
-
const commitment = prepareNameCommitment(params.name, controlAddress, params.referral, params.parent, network);
|
|
528
|
+
const commitment = prepareNameCommitment(params.name, controlAddress, params.referral, params.parent, network, params.salt);
|
|
466
529
|
const utxos = params.utxos;
|
|
467
530
|
const selection = (0, index_js_4.selectUtxos)(utxos, 0n, new Map(), 1, networkConfig.chainId, undefined, true);
|
|
468
531
|
const txb = new utxo_lib_1.TransactionBuilder(verusNetwork);
|
|
@@ -473,18 +536,35 @@ function buildAndSignCommitment(params, network) {
|
|
|
473
536
|
txb.addInput(Buffer.from(utxo.txid, 'hex').reverse(), utxo.outputIndex, 0xffffffff, Buffer.from(utxo.script, 'hex'));
|
|
474
537
|
}
|
|
475
538
|
txb.addOutput(commitment.commitmentScript, 0);
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
539
|
+
// If native-only UTXOs can't cover the fee, selectUtxos falls back to a
|
|
540
|
+
// token-bearing UTXO and returns its token value as currencyChanges. Emit it
|
|
541
|
+
// as a reserve-output change (bundled with the native change) — otherwise
|
|
542
|
+
// that token value is silently forfeited to the miner.
|
|
543
|
+
const hasTokenChange = selection.currencyChanges.size > 0;
|
|
544
|
+
if (hasTokenChange || selection.nativeChange > 0n) {
|
|
545
|
+
if (hasTokenChange) {
|
|
546
|
+
const tokenChangeScript = buildTokenChangeOutput((0, brands_js_1.parseAddress)(params.changeAddress, 'changeAddress'), selection.currencyChanges);
|
|
547
|
+
txb.addOutput(tokenChangeScript.script, (0, index_js_2.toSafeNumber)(selection.nativeChange));
|
|
548
|
+
}
|
|
549
|
+
else if (params.changeAddress.startsWith('i')) {
|
|
550
|
+
// utxo-lib's addOutput only resolves base58 R-addresses; an i-address
|
|
551
|
+
// changeAddress needs the explicit P2ID script (matching sendCurrency), or
|
|
552
|
+
// it throws an untyped "no matching Script".
|
|
553
|
+
txb.addOutput(identityPaymentScript((0, brands_js_1.parseIAddress)(params.changeAddress, 'changeAddress')), (0, index_js_2.toSafeNumber)(selection.nativeChange));
|
|
482
554
|
}
|
|
483
555
|
else {
|
|
484
556
|
txb.addOutput(params.changeAddress, (0, index_js_2.toSafeNumber)(selection.nativeChange));
|
|
485
557
|
}
|
|
486
558
|
}
|
|
559
|
+
// No token is paid out here (the commitment output carries none), so every
|
|
560
|
+
// token in the selected inputs must return as change.
|
|
561
|
+
(0, index_js_4.assertTokenConservation)(selection.selected, new Map(), selection.currencyChanges, networkConfig.chainId, 'name commitment');
|
|
487
562
|
const unsignedTx = txb.buildIncomplete();
|
|
563
|
+
// Native value conservation: the commitment output is 0 and change is native,
|
|
564
|
+
// so the assembled native fee must equal selection.fee. The fork's absurd-fee
|
|
565
|
+
// guard is blind for inputs > 2^32 sats (it truncates input value mod 2^32),
|
|
566
|
+
// so this bigint check is the real backstop against a change-accounting slip.
|
|
567
|
+
(0, index_js_3.assertNativeConservation)(selection.selected, unsignedTx.outs, selection.fee, 'name commitment');
|
|
488
568
|
const { signedTx, txid } = (0, index_js_3.signTransactionSmart)(unsignedTx.toHex(), params.wif, selection.selected, verusNetwork);
|
|
489
569
|
return {
|
|
490
570
|
signedTx,
|
|
@@ -515,6 +595,17 @@ function buildAndSignRegistration(params, network) {
|
|
|
515
595
|
const verusNetwork = (0, index_js_3.getNetwork)(network === 'testnet');
|
|
516
596
|
const networkConfig = index_js_1.NETWORK_CONFIG[network];
|
|
517
597
|
const systemId = networkConfig.chainId;
|
|
598
|
+
// Step 2 spends the name-commitment output, which is controlled by the key that
|
|
599
|
+
// created it in step 1. The fork signs the commitment input with whatever WIF it
|
|
600
|
+
// is handed, so a mismatched WIF produces a tx the daemon rejects at broadcast
|
|
601
|
+
// (and the commitment fee is wasted). Verify the WIF's hash is the commitment's
|
|
602
|
+
// control hash up front. (The 20-byte control hash is embedded in the CC script.)
|
|
603
|
+
const signerAddr = utxo_lib_1.ECPair.fromWIF(params.wif, verusNetwork).getAddress();
|
|
604
|
+
const signerHash = (0, index_js_2.addressToScriptPubKey)(signerAddr).subarray(3, 23).toString('hex');
|
|
605
|
+
if (!params.commitmentUtxo.script.toLowerCase().includes(signerHash)) {
|
|
606
|
+
throw new errors_js_1.TransactionBuildError(`the provided WIF (${signerAddr}) does not control the name-commitment output; step 2 must be ` +
|
|
607
|
+
`signed by the same key that created the commitment in step 1.`);
|
|
608
|
+
}
|
|
518
609
|
const commitData = params.commitmentData;
|
|
519
610
|
const parentIAddress = commitData.parent || systemId;
|
|
520
611
|
const isSubId = !isVRSCParent(commitData.parent || undefined, network);
|
|
@@ -522,15 +613,15 @@ function buildAndSignRegistration(params, network) {
|
|
|
522
613
|
const identityAddress = deriveIdentityAddress(commitData.name, effectiveParent);
|
|
523
614
|
const identity = createIdentityObject({
|
|
524
615
|
name: commitData.name,
|
|
525
|
-
primaryAddresses: params.primaryAddresses,
|
|
616
|
+
primaryAddresses: params.primaryAddresses.map((a, i) => (0, brands_js_1.parseRAddress)(a, `primaryAddresses[${i}]`)),
|
|
526
617
|
...(params.minSigs !== undefined ? { minSigs: params.minSigs } : {}),
|
|
527
|
-
revocationAuthority: params.revocationAuthority || identityAddress,
|
|
528
|
-
recoveryAuthority: params.recoveryAuthority || identityAddress,
|
|
529
|
-
parentIAddress,
|
|
530
|
-
systemId,
|
|
618
|
+
revocationAuthority: (0, brands_js_1.parseIAddress)(params.revocationAuthority || identityAddress, 'revocationAuthority'),
|
|
619
|
+
recoveryAuthority: (0, brands_js_1.parseIAddress)(params.recoveryAuthority || identityAddress, 'recoveryAuthority'),
|
|
620
|
+
parentIAddress: (0, brands_js_1.parseIAddress)(parentIAddress, 'parentIAddress'),
|
|
621
|
+
systemId: (0, brands_js_1.parseIAddress)(systemId, 'systemId'),
|
|
531
622
|
});
|
|
532
623
|
const identityScript = buildIdentityScript(identity);
|
|
533
|
-
const reservationScript = buildReservationScript(identityAddress, Buffer.from(commitData.namereservationHex, 'hex'), isSubId);
|
|
624
|
+
const reservationScript = buildReservationScript((0, brands_js_1.parseIAddress)(identityAddress, 'identityAddress'), Buffer.from(commitData.namereservationHex, 'hex'), isSubId);
|
|
534
625
|
if (isSubId) {
|
|
535
626
|
return _buildSubIdRegistration(params, identity, identityScript, reservationScript, identityAddress, parentIAddress, systemId, verusNetwork);
|
|
536
627
|
}
|
|
@@ -557,7 +648,7 @@ function _buildVrscRegistration(params, identityScript, reservationScript, ident
|
|
|
557
648
|
}
|
|
558
649
|
for (const referrerAddr of chain) {
|
|
559
650
|
referralOutputs.push({
|
|
560
|
-
script: buildReferralPaymentScript(referrerAddr),
|
|
651
|
+
script: buildReferralPaymentScript((0, brands_js_1.parseIAddress)(referrerAddr, 'referralChain entry')),
|
|
561
652
|
value: fees.referralAmount,
|
|
562
653
|
});
|
|
563
654
|
}
|
|
@@ -587,17 +678,29 @@ function _buildVrscRegistration(params, identityScript, reservationScript, ident
|
|
|
587
678
|
txb.addOutput(referralOut.script, (0, index_js_2.toSafeNumber)(referralOut.value));
|
|
588
679
|
}
|
|
589
680
|
txb.addOutput(reservationScript, 0);
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
681
|
+
// Registration needs ~80-100 native, so Phase-2 selection is especially
|
|
682
|
+
// likely to exhaust pure-native UTXOs and pull a token-bearing one; return
|
|
683
|
+
// its token value as reserve-output change instead of forfeiting it to the
|
|
684
|
+
// miner. (The commitment input carries no currency.)
|
|
685
|
+
const hasTokenChange = selection.currencyChanges.size > 0;
|
|
686
|
+
if (hasTokenChange || selection.nativeChange > 0n) {
|
|
687
|
+
if (hasTokenChange) {
|
|
688
|
+
const tokenChangeScript = buildTokenChangeOutput((0, brands_js_1.parseAddress)(params.changeAddress, 'changeAddress'), selection.currencyChanges);
|
|
689
|
+
txb.addOutput(tokenChangeScript.script, (0, index_js_2.toSafeNumber)(selection.nativeChange));
|
|
690
|
+
}
|
|
691
|
+
else if (params.changeAddress.startsWith('i')) {
|
|
692
|
+
// utxo-lib's addOutput only resolves base58 R-addresses; an i-address
|
|
693
|
+
// changeAddress needs the explicit P2ID script (matching sendCurrency), or
|
|
694
|
+
// it throws an untyped "no matching Script".
|
|
695
|
+
txb.addOutput(identityPaymentScript((0, brands_js_1.parseIAddress)(params.changeAddress, 'changeAddress')), (0, index_js_2.toSafeNumber)(selection.nativeChange));
|
|
596
696
|
}
|
|
597
697
|
else {
|
|
598
698
|
txb.addOutput(params.changeAddress, (0, index_js_2.toSafeNumber)(selection.nativeChange));
|
|
599
699
|
}
|
|
600
700
|
}
|
|
701
|
+
// Referral payouts are native; no token is paid out, so all token value in the
|
|
702
|
+
// selected inputs must return as change.
|
|
703
|
+
(0, index_js_4.assertTokenConservation)(selection.selected, new Map(), selection.currencyChanges, systemId, 'identity registration');
|
|
601
704
|
const unsignedTx = txb.buildIncomplete();
|
|
602
705
|
const allUtxos = [commitUtxo, ...selection.selected];
|
|
603
706
|
// The registration fee is burned as an IMPLICIT miner fee (identity and
|
|
@@ -641,14 +744,38 @@ function _buildSubIdRegistration(params, identity, identityScript, reservationSc
|
|
|
641
744
|
throw new errors_js_1.TransactionBuildError('registrationFeeAmount is required for sub-ID registration. ' +
|
|
642
745
|
'Specify the fee in parent currency satoshis.');
|
|
643
746
|
}
|
|
644
|
-
|
|
747
|
+
// The fee-output structure differs by the parent's proofprotocol (2 =
|
|
748
|
+
// reserve output, else reserve transfer), which the offline SDK cannot read —
|
|
749
|
+
// the caller must supply it (from `getcurrency <parent>.proofprotocol`).
|
|
750
|
+
if (params.parentProofProtocol === undefined) {
|
|
751
|
+
throw new errors_js_1.TransactionBuildError("parentProofProtocol is required for sub-ID registration: pass the parent currency's proofprotocol " +
|
|
752
|
+
'(from `getcurrency <parent>`) — 2 for a centralized/token currency, 1 for a PBaaS/fractional one.');
|
|
753
|
+
}
|
|
754
|
+
const feeOutput = buildRegistrationFeeOutput(parentIAddress, registrationFeeAmount, systemId, params.changeAddress, params.parentProofProtocol);
|
|
645
755
|
const requiredCurrencies = new Map([
|
|
646
756
|
[parentIAddress, registrationFeeAmount],
|
|
647
757
|
]);
|
|
648
|
-
|
|
758
|
+
// The parent currency's idimportfees must leave the transaction as native fee;
|
|
759
|
+
// this SDK is offline and cannot read it, so the caller must pass it explicitly.
|
|
760
|
+
// Silently defaulting to 0 underfunds a currency that charges an import fee, and
|
|
761
|
+
// the daemon rejects the registration.
|
|
762
|
+
if (params.nativeImportFee === undefined) {
|
|
763
|
+
throw new errors_js_1.TransactionBuildError("nativeImportFee is required for sub-ID registration: pass the parent currency's idimportfees " +
|
|
764
|
+
'(from `getcurrency <parent>`), or 0n if it charges none.');
|
|
765
|
+
}
|
|
766
|
+
const nativeImportFee = params.nativeImportFee;
|
|
649
767
|
const nativeTarget = feeOutput.nativeValue + nativeImportFee;
|
|
650
768
|
const numOutputs = 4;
|
|
651
|
-
const selection = (0, index_js_4.selectUtxos)(params.utxos, nativeTarget, requiredCurrencies, numOutputs, systemId, undefined, true
|
|
769
|
+
const selection = (0, index_js_4.selectUtxos)(params.utxos, nativeTarget, requiredCurrencies, numOutputs, systemId, undefined, true,
|
|
770
|
+
// The identity and reservation outputs embed the full serialized identity /
|
|
771
|
+
// advanced name reservation (a large contentMultimap can make them
|
|
772
|
+
// multi-KB); size the fee from their real bytes, matching the VRSC
|
|
773
|
+
// registration / update / define paths, so a big sub-ID isn't fee-estimated
|
|
774
|
+
// below the relay minimum and rejected.
|
|
775
|
+
identityScript.length + reservationScript.length + feeOutput.script.length);
|
|
776
|
+
// The parent-currency fee (requiredCurrencies) is paid to the fee output and
|
|
777
|
+
// any excess returns as token change; guard that no token value is dropped.
|
|
778
|
+
(0, index_js_4.assertTokenConservation)(selection.selected, requiredCurrencies, selection.currencyChanges, systemId, 'sub-ID registration');
|
|
652
779
|
const txb = new utxo_lib_1.TransactionBuilder(network);
|
|
653
780
|
txb.setVersion(4);
|
|
654
781
|
txb.setExpiryHeight((0, index_js_3.resolveExpiryHeight)(params.expiryHeight));
|
|
@@ -664,7 +791,7 @@ function _buildSubIdRegistration(params, identity, identityScript, reservationSc
|
|
|
664
791
|
const hasTokenChange = selection.currencyChanges.size > 0;
|
|
665
792
|
if (hasTokenChange || selection.nativeChange > 0n) {
|
|
666
793
|
if (hasTokenChange) {
|
|
667
|
-
const tokenChangeScript = buildTokenChangeOutput(params.changeAddress, selection.currencyChanges);
|
|
794
|
+
const tokenChangeScript = buildTokenChangeOutput((0, brands_js_1.parseAddress)(params.changeAddress, 'changeAddress'), selection.currencyChanges);
|
|
668
795
|
txb.addOutput(tokenChangeScript.script, (0, index_js_2.toSafeNumber)(selection.nativeChange));
|
|
669
796
|
}
|
|
670
797
|
else {
|
|
@@ -672,7 +799,7 @@ function _buildSubIdRegistration(params, identity, identityScript, reservationSc
|
|
|
672
799
|
// changeAddress needs the explicit P2ID script (matching sendCurrency), or
|
|
673
800
|
// it throws an untyped "no matching Script".
|
|
674
801
|
if (params.changeAddress.startsWith('i')) {
|
|
675
|
-
txb.addOutput(identityPaymentScript(params.changeAddress), (0, index_js_2.toSafeNumber)(selection.nativeChange));
|
|
802
|
+
txb.addOutput(identityPaymentScript((0, brands_js_1.parseIAddress)(params.changeAddress, 'changeAddress')), (0, index_js_2.toSafeNumber)(selection.nativeChange));
|
|
676
803
|
}
|
|
677
804
|
else {
|
|
678
805
|
txb.addOutput(params.changeAddress, (0, index_js_2.toSafeNumber)(selection.nativeChange));
|
|
@@ -742,6 +869,17 @@ function buildAndSignIdentityUpdate(params, network, operation = 'update', lockU
|
|
|
742
869
|
throw new errors_js_1.TransactionBuildError(`the provided WIF (${signerAddress}) is not among the identity's primary addresses ` +
|
|
743
870
|
`[${currentPrimaries.join(', ')}]; it cannot authorize a ${operation}.`);
|
|
744
871
|
}
|
|
872
|
+
// Spending the identity input under primary authority needs min_sigs
|
|
873
|
+
// signatures, but this SDK signs with a single WIF and the bundled fork
|
|
874
|
+
// cannot attach a second signature to a CryptoCondition input. Emitting a
|
|
875
|
+
// 1-of-N-signed tx for a min_sigs>1 identity yields a hex the daemon rejects
|
|
876
|
+
// at broadcast; fail closed instead.
|
|
877
|
+
const currentMinSigs = identity.min_sigs?.toNumber?.() ?? 1;
|
|
878
|
+
if (currentMinSigs > 1) {
|
|
879
|
+
throw new errors_js_1.TransactionBuildError(`this identity requires ${currentMinSigs} signatures (min_sigs > 1); the SDK signs with a single ` +
|
|
880
|
+
`WIF and the bundled fork cannot multi-sign a CryptoCondition input, so a valid ${operation} ` +
|
|
881
|
+
`transaction cannot be produced. Use the daemon for multisig identities.`);
|
|
882
|
+
}
|
|
745
883
|
}
|
|
746
884
|
switch (operation) {
|
|
747
885
|
case 'update': {
|
|
@@ -760,13 +898,21 @@ function buildAndSignIdentityUpdate(params, network, operation = 'update', lockU
|
|
|
760
898
|
identity.setRecovery(params.recoveryAuthority);
|
|
761
899
|
}
|
|
762
900
|
if (params.contentMap) {
|
|
901
|
+
// The daemon REPLACES the whole contentmap when the field is provided
|
|
902
|
+
// (and the contentMultimap branch below already replaces via fromJson).
|
|
903
|
+
// Merging into the parsed on-chain map instead re-attested stale keys and
|
|
904
|
+
// gave no way to delete one. Clear first so the provided map is authoritative.
|
|
905
|
+
identity.content_map.clear();
|
|
763
906
|
for (const [key, value] of Object.entries(params.contentMap)) {
|
|
764
|
-
//
|
|
765
|
-
//
|
|
766
|
-
|
|
767
|
-
//
|
|
768
|
-
|
|
769
|
-
|
|
907
|
+
// Keys are vdxf i-addresses; the primitives run fromBase58Check(key),
|
|
908
|
+
// which discards the version byte — guard it like the multimap branch.
|
|
909
|
+
assertAddressVersion(key, index_js_1.I_ADDR_VERSION, `contentMap key "${key}"`);
|
|
910
|
+
// Values serialize as a fixed uint256 (32 bytes). Buffer.from(_, 'hex')
|
|
911
|
+
// silently drops non-hex and truncates, so a wrong-length value would be
|
|
912
|
+
// committed on-chain as wrong bytes (or build a payload the daemon can't
|
|
913
|
+
// deserialize). Require exactly 64 hex chars.
|
|
914
|
+
if (!/^[0-9a-fA-F]{64}$/.test(value)) {
|
|
915
|
+
throw new errors_js_1.TransactionBuildError(`contentMap["${key}"] must be a 32-byte (64-hex-char) value (got ${JSON.stringify(value)})`);
|
|
770
916
|
}
|
|
771
917
|
identity.content_map.set(key, Buffer.from(value, 'hex'));
|
|
772
918
|
}
|
|
@@ -804,6 +950,13 @@ function buildAndSignIdentityUpdate(params, network, operation = 'update', lockU
|
|
|
804
950
|
if (params.primaryAddresses) {
|
|
805
951
|
identity.setPrimaryAddresses(params.primaryAddresses);
|
|
806
952
|
}
|
|
953
|
+
// Recovery commonly replaces the primary set; allow lowering min_sigs so a
|
|
954
|
+
// 2-of-2 identity can be recovered to a single fresh key (otherwise the
|
|
955
|
+
// stale min_sigs would exceed the new primary count — see the guard below).
|
|
956
|
+
if (params.minSigs !== undefined) {
|
|
957
|
+
validateMinSigs(params.minSigs, identity.primary_addresses?.length ?? 0);
|
|
958
|
+
identity.min_sigs = new bn_js_1.default(params.minSigs);
|
|
959
|
+
}
|
|
807
960
|
if (params.revocationAuthority) {
|
|
808
961
|
identity.setRevocation(params.revocationAuthority);
|
|
809
962
|
}
|
|
@@ -813,11 +966,20 @@ function buildAndSignIdentityUpdate(params, network, operation = 'update', lockU
|
|
|
813
966
|
break;
|
|
814
967
|
}
|
|
815
968
|
case 'lock': {
|
|
816
|
-
const
|
|
817
|
-
if (!
|
|
818
|
-
throw new errors_js_1.TransactionBuildError('
|
|
969
|
+
const delay = lockUnlockParams?.unlockDelayBlocks;
|
|
970
|
+
if (delay === undefined || !Number.isInteger(delay) || delay <= 0) {
|
|
971
|
+
throw new errors_js_1.TransactionBuildError('unlockDelayBlocks (a positive integer RELATIVE delay in blocks, not a block height) is required for lock');
|
|
819
972
|
}
|
|
820
|
-
|
|
973
|
+
if (delay > MAX_UNLOCK_DELAY) {
|
|
974
|
+
throw new errors_js_1.TransactionBuildError(`unlockDelayBlocks ${delay} exceeds the consensus maximum ${MAX_UNLOCK_DELAY} (~22 years)`);
|
|
975
|
+
}
|
|
976
|
+
// A block height (millions) passed as a delay locks the identity for years.
|
|
977
|
+
// Require an explicit opt-in above ~1 year to catch that mistake.
|
|
978
|
+
if (delay > LOCK_DELAY_SANITY_BLOCKS && !lockUnlockParams?.sanityOverride) {
|
|
979
|
+
throw new errors_js_1.TransactionBuildError(`unlockDelayBlocks ${delay} is over ~1 year (${LOCK_DELAY_SANITY_BLOCKS} blocks) — this is a RELATIVE ` +
|
|
980
|
+
`delay, not a block height; pass sanityOverride: true if that long a lock is intended.`);
|
|
981
|
+
}
|
|
982
|
+
identity.lock(new bn_js_1.default(delay));
|
|
821
983
|
break;
|
|
822
984
|
}
|
|
823
985
|
case 'unlock': {
|
|
@@ -834,6 +996,15 @@ function buildAndSignIdentityUpdate(params, network, operation = 'update', lockU
|
|
|
834
996
|
break;
|
|
835
997
|
}
|
|
836
998
|
}
|
|
999
|
+
// A valid identity always has min_sigs <= number of primary addresses. Shrinking
|
|
1000
|
+
// the primary set (update/recover) without lowering minSigs would leave a stale
|
|
1001
|
+
// min_sigs the daemon rejects — or, worse, a permanently unspendable identity.
|
|
1002
|
+
const finalPrimaries = identity.primary_addresses?.length ?? 0;
|
|
1003
|
+
const finalMinSigs = identity.min_sigs?.toNumber?.() ?? 1;
|
|
1004
|
+
if (finalMinSigs > finalPrimaries) {
|
|
1005
|
+
throw new errors_js_1.TransactionBuildError(`resulting identity has min_sigs ${finalMinSigs} but only ${finalPrimaries} primary address(es); ` +
|
|
1006
|
+
`pass minSigs to lower it when reducing the primary set.`);
|
|
1007
|
+
}
|
|
837
1008
|
const identityBuf = identity.toBuffer();
|
|
838
1009
|
const unfundedHex = createUnfundedIdentityUpdate(identityBuf.toString('hex'), verusNetwork, (0, index_js_3.resolveExpiryHeight)(params.expiryHeight));
|
|
839
1010
|
const selection = (0, index_js_4.selectUtxos)(params.utxos, 0n, new Map(), 1, systemId, undefined, true,
|
|
@@ -841,6 +1012,10 @@ function buildAndSignIdentityUpdate(params, network, operation = 'update', lockU
|
|
|
841
1012
|
// contentMultimap can make it multi-KB); size the fee from its real bytes
|
|
842
1013
|
// so a big update isn't fee-estimated below the relay minimum.
|
|
843
1014
|
unfundedHex.length / 2);
|
|
1015
|
+
// Update spends only native fees and emits no token-change output, so a
|
|
1016
|
+
// token-bearing funding UTXO would be silently dropped. Fail closed if one
|
|
1017
|
+
// was selected (both maps empty ⇒ assert no token enters).
|
|
1018
|
+
(0, index_js_4.assertTokenConservation)(selection.selected, new Map(), new Map(), systemId, 'identity update');
|
|
844
1019
|
const txb = new utxo_lib_1.TransactionBuilder(verusNetwork);
|
|
845
1020
|
txb.setVersion(4);
|
|
846
1021
|
txb.setExpiryHeight((0, index_js_3.resolveExpiryHeight)(params.expiryHeight));
|
|
@@ -857,7 +1032,7 @@ function buildAndSignIdentityUpdate(params, network, operation = 'update', lockU
|
|
|
857
1032
|
// changeAddress needs the explicit P2ID script (matching sendCurrency), or
|
|
858
1033
|
// it throws an untyped "no matching Script".
|
|
859
1034
|
if (params.changeAddress.startsWith('i')) {
|
|
860
|
-
txb.addOutput(identityPaymentScript(params.changeAddress), (0, index_js_2.toSafeNumber)(selection.nativeChange));
|
|
1035
|
+
txb.addOutput(identityPaymentScript((0, brands_js_1.parseIAddress)(params.changeAddress, 'changeAddress')), (0, index_js_2.toSafeNumber)(selection.nativeChange));
|
|
861
1036
|
}
|
|
862
1037
|
else {
|
|
863
1038
|
txb.addOutput(params.changeAddress, (0, index_js_2.toSafeNumber)(selection.nativeChange));
|