import { generateTemplateIdentifier } from "@xo-cash/engine"; import { hexToBin, lockingBytecodeToCashAddress } from "@bitauth/libauth"; import { bold, dim } from "../utils.js"; import type { CommandDependencies, CommandIO } from "./types.js"; import { CommandError } from "./types.js"; import { resolveTemplate } from "../utils.js"; /** * Prints the help message for the receive command */ export const printReceiveHelp = (io: CommandIO): void => { io.out( ` ${bold("Usage:")} xo-cli receive [role-identifier] ${bold("Description:")} Generate a single-use receiving address from a template. ${bold("Arguments:")} ${dim("Path to the template JSON file")} ${dim("The output identifier within the template (e.g. 'receiveOutput')")} [role-identifier] ${dim("The role identifier (e.g. 'receiver'). Auto-selects the first role if omitted.")} ${bold("Options:")} -h --help ${dim("Show this help message")} `, ); }; /** * Command which creates a single-use address/lockingScript for a given template and role. * Throws CommandError on failure, returns address data on success. * * @param deps - The command dependencies. * @param args - Positional args after the command name, e.g. ["template.json", "receiveOutput", "receiver"]. * @param options - Parsed option flags. * @returns The address data. * @throws CommandError if the command fails. */ export const handleReceiveCommand = async ( deps: CommandDependencies, args: string[], _options: Record, ): Promise<{ address: string }> => { // Get the template query, output identifier, and role identifier from the arguments const templateQuery = args[0]; const outputIdentifier = args[1]; const roleIdentifier = args[2]; // Log the receive args deps.io.verbose( `Receive args - template: ${templateQuery}, output: ${outputIdentifier}, role: ${roleIdentifier}`, ); // If no template query or output identifier is provided, print the help message and throw an error if (!templateQuery || !outputIdentifier) { deps.io.verbose("Missing required arguments"); printReceiveHelp(deps.io); throw new CommandError( "receive.arguments.missing", "Missing required arguments", ); } // Resolve and read the template file const template = await resolveTemplate(deps, templateQuery); const templateIdentifier = generateTemplateIdentifier(template); deps.io.verbose(`Template identifier: ${templateIdentifier}`); // Generate the locking bytecode (returned as a hex string) const lockingBytecodeHex = await deps.app.engine.generateLockingBytecode( templateIdentifier, outputIdentifier, roleIdentifier, ); deps.io.verbose(`Locking bytecode hex: ${lockingBytecodeHex}`); // Convert the locking bytecode to a BCH cash address const result = lockingBytecodeToCashAddress({ bytecode: hexToBin(lockingBytecodeHex), prefix: "bitcoincash", }); if (typeof result === "string") { deps.io.err(`Failed to encode address: ${result}`); throw new CommandError( "receive.address.encode_failed", `Failed to encode address: ${result}`, ); } deps.io.out(result.address); return { address: result.address }; };