diff --git a/src/doichain/getNameOPStackScript.d.ts b/src/doichain/getNameOPStackScript.d.ts new file mode 100644 index 000000000..42cffc27a --- /dev/null +++ b/src/doichain/getNameOPStackScript.d.ts @@ -0,0 +1,15 @@ +/// + +/** + * @param nameId - The identifier for the name. + * @param nameValue - The value associated with the name. + * @param recipientAddress - The recipient's Doichain address. + * @param network - The Doichain network (e.g., 'mainent', 'testnet', 'regtest'). + * @returns The compiled script as a Buffer. + */ +export function getNameOPStackScript( + nameId: string, + nameValue: string, + recipientAddress: string, + network: string +): Buffer; diff --git a/src/doichain/getNameOPStackScript.js b/src/doichain/getNameOPStackScript.js new file mode 100644 index 000000000..35ba467fe --- /dev/null +++ b/src/doichain/getNameOPStackScript.js @@ -0,0 +1,97 @@ +import { address, script } from '@doichain/doichainjs-lib'; + +export const NAME_MAX_LENGTH = 255; +export const VALUE_MAX_LENGTH = 520; + +const ERRORS = { + NAME_ID_DEFINED: 'nameId and nameValue must be defined', + NAME_ID_LENGTH: `nameId must be at least 3 characters and not longer than ${NAME_MAX_LENGTH}`, + NAME_VALUE_LENGTH: `nameValue must not be longer than ${VALUE_MAX_LENGTH}`, + INVALID_ADDRESS: 'Invalid recipient address: ', +}; + +/** + * Creates a NameOPStackScript from a nameId, nameValue, and recipientAddress + * Reference implementations: + * - https://github.com/brandonrobertz/bitcore-namecoin/blob/master/lib/names.js + * - https://github.com/doichain/doichain-transaction + * + * @param {string} nameId - The identifier for the name. + * @param {string} nameValue - The value associated with the name. + * @param {string} recipientAddress - The recipient's Doichain address. + * @param {string} network - The Doichain network (e.g., 'mainent', 'testnet', 'regtest'). + * @returns {Buffer} The compiled script as a Buffer. + */ +export const getNameOPStackScript = ( + nameId, + nameValue, + recipientAddress, + network, +) => { + if (!nameId || nameValue === undefined) { + throw new Error(ERRORS.NAME_ID_DEFINED); + } + + if (nameId.length > NAME_MAX_LENGTH || nameId.length < 3) { + throw new Error(ERRORS.NAME_ID_LENGTH); + } + + if (nameValue.length > VALUE_MAX_LENGTH) { + throw new Error(ERRORS.NAME_VALUE_LENGTH); + } + + const op_name = Buffer.from(nameId).toString('hex'); + const op_value = Buffer.from(nameValue).toString('hex'); + + let op_address; + let opCodesStackScript; + let decoded; + try { + decoded = address.fromBase58Check(recipientAddress); + op_address = decoded.hash.toString('hex'); + opCodesStackScript = script.fromASM( + ` + OP_10 + ${op_name} + ${op_value} + OP_2DROP + OP_DROP + OP_DUP + OP_HASH160 + ${op_address} + OP_EQUALVERIFY + OP_CHECKSIG + ` + .trim() + .replace(/\s+/g, ' '), + ); + } catch (legacyError) { + try { + decoded = address.fromBech32(recipientAddress); + op_address = decoded.data.toString('hex'); + + opCodesStackScript = script.fromASM( + ` + OP_10 + ${op_name} + ${op_value} + OP_2DROP + OP_DROP + OP_0 + ${op_address} + ` + .trim() + .replace(/\s+/g, ' '), + ); + } catch (segwitError) { + throw new Error( + ERRORS.INVALID_ADDRESS + + legacyError.message + + ' or ' + + segwitError.message, + ); + } + } + + return opCodesStackScript; +}; diff --git a/src/index.d.ts b/src/index.d.ts index 3655f4560..de7c4629b 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -4,6 +4,7 @@ import * as networks from './networks'; import * as payments from './payments'; import * as script from './script'; export { address, crypto, networks, payments, script }; +export { getNameOPStackScript } from './doichain/getNameOPStackScript'; export { Block } from './block'; /** @hidden */ export { TaggedHashPrefix } from './crypto'; diff --git a/src/index.js b/src/index.js index c742ceef6..166cb8e43 100644 --- a/src/index.js +++ b/src/index.js @@ -21,6 +21,8 @@ const payments = require('./payments'); exports.payments = payments; const script = require('./script'); exports.script = script; +const { getNameOPStackScript } = require('./doichain/getNameOPStackScript'); +exports.getNameOPStackScript = getNameOPStackScript; var block_1 = require('./block'); Object.defineProperty(exports, 'Block', { enumerable: true, diff --git a/src/psbt.js b/src/psbt.js index 4c34a66de..7d7eafc2c 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -343,10 +343,11 @@ class Psbt { tapLeafHashToFinalize, finalScriptsFunc = bip371_1.tapScriptFinalizer, ) { - if (!input.witnessUtxo) - throw new Error( - `Cannot finalize input #${inputIndex}. Missing withness utxo.`, - ); + //TODO + // if (!input.witnessUtxo) + // throw new Error( + // `Cannot finalize input #${inputIndex}. Missing withness utxo.`, + // ); // Check key spend first. Increased privacy and reduced block space. if (input.tapKeySig) { const payment = payments.p2tr({