import { FC, useState } from 'react';
import { useDynamicContext } from '@dynamic-labs/sdk-react-core';
import { isBitcoinWallet } from '@dynamic-labs/bitcoin';
import { Psbt } from 'bitcoinjs-lib';
import { networks } from 'bitcoinjs-lib';
export const SignPsbtExample: FC = () => {
const { primaryWallet } = useDynamicContext();
const [unsignedPsbt, setUnsignedPsbt] = useState<string>('');
const [signedPsbt, setSignedPsbt] = useState<string>('');
const [error, setError] = useState<string>('');
const [isLoading, setIsLoading] = useState<boolean>(false);
const handleSignPsbt = async () => {
if (!unsignedPsbt.trim()) {
setError('Please enter an unsigned PSBT');
return;
}
if (!primaryWallet || !isBitcoinWallet(primaryWallet)) {
setError('Bitcoin wallet not found');
return;
}
setIsLoading(true);
setError('');
setSignedPsbt('');
try {
// Sign the PSBT (does not finalize)
// For embedded wallets, only unsignedPsbtBase64 is required
const { signedPsbt: result } = await primaryWallet.signPsbt({
unsignedPsbtBase64: unsignedPsbt.trim(),
});
setSignedPsbt(result);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to sign PSBT');
} finally {
setIsLoading(false);
}
};
const handleReviewPsbt = () => {
if (!signedPsbt) {
setError('No signed PSBT to review');
return;
}
try {
// Parse the signed PSBT for review
const psbt = Psbt.fromBase64(signedPsbt, { network: networks.bitcoin });
// Extract transaction information
const inputs = psbt.txInputs.map((input, index) => ({
index,
hash: input.hash.reverse().toString('hex'),
index: input.index,
}));
const outputs = psbt.txOutputs.map((output, index) => ({
index,
address: output.address,
value: output.value,
}));
console.log('PSBT Review:', {
inputs,
outputs,
isFinalized: psbt.finalized,
});
// Show review UI to user
alert(
`PSBT Review:\nInputs: ${inputs.length}\nOutputs: ${outputs.length}\nFinalized: ${psbt.finalized}`,
);
} catch (err) {
setError('Failed to parse PSBT for review');
}
};
const handleFinalizePsbt = () => {
if (!signedPsbt) {
setError('No signed PSBT to finalize');
return;
}
try {
// Parse the signed PSBT
const psbt = Psbt.fromBase64(signedPsbt, { network: networks.bitcoin });
// Finalize all inputs
psbt.finalizeAllInputs();
// Extract the finalized transaction
const finalizedTx = psbt.extractTransaction();
// Get the transaction hex for broadcasting
const txHex = finalizedTx.toHex();
console.log('Finalized transaction hex:', txHex);
alert(
'PSBT finalized! Transaction hex: ' + txHex.substring(0, 50) + '...',
);
// The transaction can now be broadcast to the Bitcoin network
return txHex;
} catch (err) {
setError(
'Failed to finalize PSBT: ' +
(err instanceof Error ? err.message : String(err)),
);
}
};
return (
<div className='sign-psbt-example'>
<h2>Sign PSBT</h2>
<div>
<label htmlFor='unsigned-psbt'>Unsigned PSBT (Base64):</label>
<textarea
id='unsigned-psbt'
value={unsignedPsbt}
onChange={(e) => setUnsignedPsbt(e.target.value)}
placeholder='Enter unsigned PSBT in Base64 format'
rows={5}
style={{ width: '100%', margin: '10px 0' }}
/>
</div>
<button
onClick={handleSignPsbt}
disabled={isLoading || !unsignedPsbt.trim()}
style={{ margin: '10px 0' }}
>
{isLoading ? 'Signing...' : 'Sign PSBT'}
</button>
{signedPsbt && (
<div>
<h3>Signed PSBT (Not Finalized)</h3>
<textarea
value={signedPsbt}
readOnly
rows={5}
style={{ width: '100%', margin: '10px 0' }}
/>
<div>
<button onClick={handleReviewPsbt} style={{ margin: '10px 5px' }}>
Review PSBT
</button>
<button onClick={handleFinalizePsbt} style={{ margin: '10px 5px' }}>
Finalize PSBT
</button>
</div>
</div>
)}
{error && <div style={{ color: 'red', margin: '10px 0' }}>{error}</div>}
</div>
);
};