import type { BitcoinConnection } from '@did-btcr2/bitcoin'; import { canonicalize, hash } from '@did-btcr2/common'; import type { SignedBTCR2Update } from '../btcr2-update.js'; import type { Signer } from '@did-btcr2/keypair'; import type { BeaconProcessResult, DataNeed } from '../resolver.js'; import type { SidecarData } from '../types.js'; import type { BroadcastOptions, BroadcastResult } from './beacon.js'; import { SinglePartyBeacon } from './beacon.js'; import type { BeaconService, BeaconSignal, BlockMetadata } from './interfaces.js'; /** * Implements {@link https://dcdpr.github.io/did-btcr2/terminology.html#singleton-beacon | Singleton Beacon}. * @class SingletonBeacon * @type {SingletonBeacon} * @extends {SinglePartyBeacon} */ export class SingletonBeacon extends SinglePartyBeacon { /** * Creates an instance of SingletonBeacon. * @param {BeaconService} service The BeaconService object representing the funded beacon to announce the update to. */ constructor(service: BeaconService, did: string) { super({ ...service, type: 'SingletonBeacon' }, did); } /** * Processes an array of Beacon Signals associated with a Singleton Beacon Service. * @param {Array} signals The beacon signals discovered on-chain. * @param {SidecarData} sidecar The processed sidecar data. * @returns {BeaconProcessResult} Successfully resolved updates and any data needs. */ processSignals( signals: Array, sidecar: SidecarData ): BeaconProcessResult { const updates = new Array<[SignedBTCR2Update, BlockMetadata]>(); const needs = new Array(); for(const signal of signals) { // Signal bytes are hex, matches hex-keyed sidecar maps directly const updateHash = signal.signalBytes; // Look up the signed update in sidecar updateMap const signedUpdate = sidecar.updateMap.get(updateHash); if(!signedUpdate) { // Data not available, emit a need instead of throwing needs.push({ kind : 'NeedSignedUpdate', updateHash, beaconServiceId : this.service.id }); continue; } updates.push([signedUpdate, signal.blockMetadata]); } return { updates, needs }; } /** * Broadcasts a SingletonBeacon signal to the Bitcoin network. * * The signal bytes embedded in OP_RETURN are the SHA-256 canonical hash of the signed update. * UTXO selection, PSBT construction, fee estimation, signing, and broadcast are delegated to * {@link SinglePartyBeacon.buildSignAndBroadcast}. * * @param {SignedBTCR2Update} signedUpdate The signed BTCR2 update to broadcast. * @param {Signer} signer Signer that produces the ECDSA signature for the Bitcoin transaction. * @param {BitcoinConnection} bitcoin The Bitcoin network connection. * @param {BroadcastOptions} [options] Optional broadcast configuration (e.g. fee estimator). * @returns {Promise} The signed update and the signal txid. * @throws {BeaconError} if the bitcoin address is invalid, unfunded, or UTXO cannot cover the fee. */ async broadcastSignal( signedUpdate: SignedBTCR2Update, signer: Signer, bitcoin: BitcoinConnection, options?: BroadcastOptions ): Promise { const signalBytes = hash(canonicalize(signedUpdate)); const txid = await this.buildSignAndBroadcast(signalBytes, signer, bitcoin, options); return { signedUpdate, txid }; } }