import { CHAIN_NAMESPACES, SUPPORTED_BITCOIN_P2WPKH_CHAINS, sdkLogger, } from '@portal-hq/utils' import type { IPortalApi } from '@portal-hq/utils' import type { RawSignOptions, SendAssetParams } from '@portal-hq/utils' import type { BroadcastBitcoinP2wpkhTransactionResponse, BroadcastParam, BuildBitcoinP2wpkhTransactionResponse, SendAssetResponse, } from '../../../types' import type { ChainHandler, SendOptions } from './ChainHandler' export class BitcoinP2wpkhChainHandler implements ChainHandler { private static readonly SUPPORTED_CHAINS = SUPPORTED_BITCOIN_P2WPKH_CHAINS as readonly string[] readonly namespace = CHAIN_NAMESPACES.BIP122 constructor( private readonly api: IPortalApi, private readonly rawSign: ( message: string, chainId?: string, options?: RawSignOptions, ) => Promise, ) {} public async send( params: SendAssetParams, chainId: string, options: SendOptions, ): Promise { const { to, token, amount } = params const { sponsorGas, signatureApprovalMemo, traceId } = options if (sponsorGas !== undefined) { sdkLogger.warn( '[Portal] sponsorGas is not supported for Bitcoin (bip122) transactions and will be ignored.', ) } const buildResponse = await this.buildTransaction( { to, token, amount }, chainId, ) if (buildResponse?.error) { throw new Error(`[Portal] ${buildResponse.error}`) } const signatureHashes = buildResponse.transaction?.signatureHashes if (!Array.isArray(signatureHashes)) { throw new Error( '[Portal] Invalid transaction signatureHashes. Expected an array of strings.', ) } const signatures = await Promise.all( signatureHashes.map(async (signatureHash) => { const signResponse = await this.rawSign(signatureHash, chainId, { signatureApprovalMemo, traceId, }) if (typeof signResponse !== 'string') { throw new Error( '[Portal] Invalid signature response from rawSign. Expected a string.', ) } return signResponse }), ) const rawTxHex = buildResponse.transaction?.rawTxHex if (!rawTxHex) { throw new Error('[Portal] Invalid build response: missing rawTxHex') } const broadcastResponse = await this.broadcastTransaction( { signatures, rawTxHex }, chainId, ) return { txHash: broadcastResponse.data.txHash } as SendAssetResponse } public async buildTransaction( params: { to: string; token: string; amount: string }, chainId: string, ): Promise { if (!params.to || !params.token || !params.amount) { throw new Error( '[Portal] Missing required parameters: to, token, or amount', ) } this.assertSupportedChain(chainId) return this.api.buildBitcoinP2wpkhTransaction(chainId, params) } public async broadcastTransaction( params: BroadcastParam, chainId: string, ): Promise { if (!params.signatures || !params.rawTxHex) { throw new Error( '[Portal] Missing required parameters: signatures or rawTxHex', ) } if ( !Array.isArray(params.signatures) || !params.signatures.every((sig) => typeof sig === 'string') ) { throw new Error( '[Portal] Invalid signatures parameter: must be an array of strings', ) } if (typeof params.rawTxHex !== 'string') { throw new Error('[Portal] Invalid rawTxHex parameter: must be a string') } this.assertSupportedChain(chainId) return this.api.broadcastBitcoinP2wpkhTransaction(chainId, params) } private assertSupportedChain(chainId: string): void { if (!BitcoinP2wpkhChainHandler.SUPPORTED_CHAINS.includes(chainId)) { throw new Error( `[Portal] Unsupported Bitcoin chainId: "${chainId}". ` + `Only P2WPKH chains are supported: ` + BitcoinP2wpkhChainHandler.SUPPORTED_CHAINS.join(', '), ) } } }