Skip to content

Commit a9edb7e

Browse files
authored
Merge pull request #51 from near-examples/reconstruct_bitcoin_signed_tx_after_refresh
fix: Reconstruct Bitcoin signed transaction after the page refresh
2 parents 5a6cf05 + 269c1ba commit a9edb7e

File tree

2 files changed

+97
-7
lines changed

2 files changed

+97
-7
lines changed

src/components/Bitcoin.jsx

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,19 @@ export function BitcoinView({ props: { setStatus } }) {
2727
const derivationPath = useDebounce(derivation, 500);
2828

2929
const getSignedTx = async () => {
30-
const signedTx = await wallet.getTransactionResult(transactions[0]);
31-
console.log("signedTx", signedTx);
32-
setSignedTransaction(signedTx);
30+
const signature = await wallet.getTransactionResult(transactions[0]);
31+
32+
const signedTransaction = await BTC.reconstructSignedTransactionFromSessionStorage(signature);
33+
34+
setSignedTransaction(signedTransaction);
35+
removeUrlParams();
3336
};
3437

3538
useEffect(() => {
36-
if (transactions.length) getSignedTx();
37-
}, [transactions]);
39+
if (transactions.length === 0) return;
40+
41+
getSignedTx();
42+
}, []);
3843

3944
useEffect(() => {
4045
setSenderAddress("Waiting for you to stop typing...");
@@ -72,6 +77,17 @@ export function BitcoinView({ props: { setStatus } }) {
7277
wallet,
7378
});
7479

80+
sessionStorage.setItem(
81+
"btc_transaction",
82+
JSON.stringify({
83+
from: senderAddress,
84+
to: receiver,
85+
amount,
86+
utxos,
87+
publicKey: senderPK
88+
})
89+
);
90+
7591
setStatus(
7692
"🕒 Asking MPC to sign the transaction, this might take a while..."
7793
);
@@ -84,7 +100,6 @@ export function BitcoinView({ props: { setStatus } }) {
84100
path: derivationPath,
85101
wallet,
86102
});
87-
console.log('signedTransaction', signedTransaction);
88103
setStatus("✅ Signed payload ready to be relayed to the Bitcoin network");
89104
setSignedTransaction(signedTransaction);
90105
setStep("relay");
@@ -128,6 +143,12 @@ export function BitcoinView({ props: { setStatus } }) {
128143
setLoading(false);
129144
};
130145

146+
function removeUrlParams() {
147+
const url = new URL(window.location.href);
148+
url.searchParams.delete("transactionHashes");
149+
window.history.replaceState({}, document.title, url);
150+
}
151+
131152
return (
132153
<>
133154
<div className="row my-3">

src/services/bitcoin.js

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,79 @@ export class Bitcoin {
117117
return psbt; // Return the generated signature
118118
}
119119

120+
reconstructSignedTransaction = async ({
121+
psbt,
122+
utxos,
123+
publicKey,
124+
signature,
125+
}) => {
126+
const keyPair = {
127+
publicKey: Buffer.from(publicKey, "hex"),
128+
sign: async (transactionHash) => {
129+
const utxo = utxos[0]; // The UTXO being spent
130+
const value = utxo.value; // The value in satoshis of the UTXO being spent
131+
132+
if (isNaN(value)) {
133+
throw new Error(
134+
`Invalid value for UTXO at index ${transactionHash}: ${utxo.value}`
135+
);
136+
}
137+
138+
const { big_r, s } = signature;
139+
140+
// Reconstruct the signature
141+
const rHex = big_r.affine_point.slice(2); // Remove the "03" prefix
142+
let sHex = s.scalar;
143+
144+
// Pad s if necessary
145+
if (sHex.length < 64) {
146+
sHex = sHex.padStart(64, "0");
147+
}
148+
149+
const rBuf = Buffer.from(rHex, "hex");
150+
const sBuf = Buffer.from(sHex, "hex");
151+
152+
// Combine r and s
153+
return Buffer.concat([rBuf, sBuf]);
154+
},
155+
};
156+
157+
// Sign each input manually
158+
await Promise.all(
159+
utxos.map(async (_, index) => {
160+
try {
161+
await psbt.signInputAsync(index, keyPair);
162+
console.log(`Input ${index} signed successfully`);
163+
} catch (e) {
164+
console.warn(`Error signing input ${index}:`, e);
165+
}
166+
})
167+
);
168+
169+
psbt.finalizeAllInputs(); // Finalize the PSBT
170+
171+
return psbt; // Return the generated signature
172+
};
173+
174+
reconstructSignedTransactionFromSessionStorage = async (signature) => {
175+
const { from, to, amount, utxos, publicKey } = JSON.parse(
176+
sessionStorage.getItem("btc_transaction")
177+
);
178+
179+
const psbt = await constructPsbt(from, utxos, to, amount, this.networkId);
180+
181+
return this.reconstructSignedTransaction({
182+
psbt,
183+
utxos,
184+
signature,
185+
publicKey,
186+
});
187+
};
188+
120189
broadcastTX = async (signedTransaction) => {
121190
// broadcast tx
122191
const bitcoinRpc = `https://blockstream.info/${this.networkId === 'testnet' ? 'testnet' : ''}/api`;
123-
const res = await fetch(`https://corsproxy.io/?${bitcoinRpc}/tx`, {
192+
const res = await fetch(`${bitcoinRpc}/tx`, {
124193
method: 'POST',
125194
body: signedTransaction.extractTransaction().toHex(),
126195
});

0 commit comments

Comments
 (0)