| | 1 | | using Microsoft.Extensions.Options; |
| | 2 | | using NBitcoin; |
| | 3 | |
|
| | 4 | | namespace NLightning.Infrastructure.Bitcoin.Builders; |
| | 5 | |
|
| | 6 | | using Comparers; |
| | 7 | | using Domain.Bitcoin.Transactions.Constants; |
| | 8 | | using Domain.Bitcoin.Transactions.Models; |
| | 9 | | using Domain.Bitcoin.ValueObjects; |
| | 10 | | using Domain.Node.Options; |
| | 11 | | using Interfaces; |
| | 12 | | using Outputs; |
| | 13 | |
|
| | 14 | | public class CommitmentTransactionBuilder : ICommitmentTransactionBuilder |
| | 15 | | { |
| | 16 | | private readonly Network _network; |
| | 17 | |
|
| 68 | 18 | | public CommitmentTransactionBuilder(IOptions<NodeOptions> nodeOptions) |
| | 19 | | { |
| 68 | 20 | | _network = Network.GetNetwork(nodeOptions.Value.BitcoinNetwork) ?? |
| 68 | 21 | | throw new ArgumentException("Invalid Bitcoin network specified", nameof(nodeOptions)); |
| 68 | 22 | | } |
| | 23 | |
|
| | 24 | | public SignedTransaction Build(CommitmentTransactionModel transaction) |
| | 25 | | { |
| 68 | 26 | | if (transaction.FundingOutput.TransactionId is null || transaction.FundingOutput.Index is null) |
| 0 | 27 | | throw new ArgumentException("Funding output must have a valid transaction Id and index."); |
| | 28 | |
|
| | 29 | | // Create a new Bitcoin transaction |
| 68 | 30 | | var tx = Transaction.Create(_network); |
| | 31 | |
|
| | 32 | | // Set the transaction version as per BOLT spec |
| 68 | 33 | | tx.Version = TransactionConstants.CommitmentTransactionVersion; |
| | 34 | |
|
| | 35 | | // Set lock time derived from the commitment number |
| 68 | 36 | | tx.LockTime = new LockTime(transaction.GetLockTime()); |
| | 37 | |
|
| | 38 | | // Create an out-point for the funding transaction |
| 68 | 39 | | var outpoint = new OutPoint(new uint256(transaction.FundingOutput.TransactionId), |
| 68 | 40 | | transaction.FundingOutput.Index.Value); |
| | 41 | | // Set the sequence number derived from the commitment number |
| 68 | 42 | | tx.Inputs.Add(outpoint, null, null, new Sequence(transaction.GetSequence())); |
| | 43 | |
|
| | 44 | | // Create a list to collect all outputs |
| 68 | 45 | | var outputs = new List<BaseOutput>(); |
| | 46 | |
|
| | 47 | | // Convert and add to_local output if present |
| 68 | 48 | | if (transaction.ToLocalOutput != null) |
| | 49 | | { |
| 60 | 50 | | var toLocalOutput = new ToLocalOutput(transaction.ToLocalOutput.Amount, |
| 60 | 51 | | new PubKey(transaction.ToLocalOutput.LocalDelayedPaymentPubKey), |
| 60 | 52 | | new PubKey(transaction.ToLocalOutput.RevocationPubKey), |
| 60 | 53 | | transaction.ToLocalOutput.ToSelfDelay); |
| | 54 | |
|
| 60 | 55 | | outputs.Add(toLocalOutput); |
| | 56 | | } |
| | 57 | |
|
| | 58 | | // Convert and add to_remote output if present |
| 68 | 59 | | if (transaction.ToRemoteOutput != null) |
| | 60 | | { |
| 68 | 61 | | var hasAnchors = transaction.LocalAnchorOutput != null || transaction.RemoteAnchorOutput != null; |
| 68 | 62 | | var toRemoteOutput = new ToRemoteOutput(transaction.ToRemoteOutput.Amount, hasAnchors, |
| 68 | 63 | | new PubKey(transaction.ToRemoteOutput.RemotePaymentPubKey)); |
| | 64 | |
|
| 68 | 65 | | outputs.Add(toRemoteOutput); |
| | 66 | | } |
| | 67 | |
|
| | 68 | | // Convert and add local anchor output if present |
| 68 | 69 | | if (transaction.LocalAnchorOutput != null) |
| | 70 | | { |
| 0 | 71 | | var localAnchorOutput = new ToAnchorOutput(transaction.LocalAnchorOutput.Amount, |
| 0 | 72 | | new PubKey(transaction.LocalAnchorOutput.FundingPubKey)); |
| | 73 | |
|
| 0 | 74 | | outputs.Add(localAnchorOutput); |
| | 75 | | } |
| | 76 | |
|
| | 77 | | // Convert and add remote anchor output if present |
| 68 | 78 | | if (transaction.RemoteAnchorOutput != null) |
| | 79 | | { |
| 0 | 80 | | var remoteAnchorOutput = new ToAnchorOutput(transaction.RemoteAnchorOutput.Amount, |
| 0 | 81 | | new PubKey(transaction.RemoteAnchorOutput.FundingPubKey)); |
| | 82 | |
|
| 0 | 83 | | outputs.Add(remoteAnchorOutput); |
| | 84 | | } |
| | 85 | |
|
| | 86 | | // Convert and add offered HTLC outputs |
| 264 | 87 | | foreach (var htlcOutput in transaction.OfferedHtlcOutputs) |
| | 88 | | { |
| 64 | 89 | | var hasAnchors = transaction.LocalAnchorOutput != null || transaction.RemoteAnchorOutput != null; |
| 64 | 90 | | var offeredHtlc = new OfferedHtlcOutput(htlcOutput.Amount, htlcOutput.CltvExpiry, hasAnchors, |
| 64 | 91 | | new PubKey(htlcOutput.LocalHtlcPubKey), |
| 64 | 92 | | htlcOutput.PaymentHash, |
| 64 | 93 | | new PubKey(htlcOutput.RemoteHtlcPubKey), |
| 64 | 94 | | new PubKey(htlcOutput.RevocationPubKey)); |
| | 95 | |
|
| 64 | 96 | | outputs.Add(offeredHtlc); |
| | 97 | | } |
| | 98 | |
|
| | 99 | | // Convert and add received HTLC outputs |
| 272 | 100 | | foreach (var htlcOutput in transaction.ReceivedHtlcOutputs) |
| | 101 | | { |
| 68 | 102 | | var hasAnchors = transaction.LocalAnchorOutput != null || transaction.RemoteAnchorOutput != null; |
| 68 | 103 | | var receivedHtlc = new ReceivedHtlcOutput(htlcOutput.Amount, htlcOutput.CltvExpiry, hasAnchors, |
| 68 | 104 | | new PubKey(htlcOutput.LocalHtlcPubKey), htlcOutput.PaymentHash, |
| 68 | 105 | | new PubKey(htlcOutput.RemoteHtlcPubKey), |
| 68 | 106 | | new PubKey(htlcOutput.RevocationPubKey)); |
| | 107 | |
|
| 68 | 108 | | outputs.Add(receivedHtlc); |
| | 109 | | } |
| | 110 | |
|
| | 111 | | // Sort outputs using TransactionOutputComparer |
| 68 | 112 | | outputs.Sort(TransactionOutputComparer.Instance); |
| | 113 | |
|
| | 114 | | // Add sorted outputs to the transaction |
| 656 | 115 | | foreach (var output in outputs) |
| | 116 | | { |
| 260 | 117 | | tx.Outputs.Add(output.ToTxOut()); |
| | 118 | | } |
| | 119 | |
|
| | 120 | | // Return as SignedTransaction |
| 68 | 121 | | return new SignedTransaction(tx.GetHash().ToBytes(), tx.ToBytes()); |
| | 122 | | } |
| | 123 | | } |