Skip to content

Commit

Permalink
chore: add alby support to send bulk
Browse files Browse the repository at this point in the history
  • Loading branch information
ruge0326 authored and topether21 committed May 14, 2024
1 parent e586d27 commit 2034902
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 16 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nosft-core",
"version": "2.5.0",
"version": "2.5.2",
"private": false,
"main": "./dist/index.js",
"module": "./dist/index.mjs",
Expand Down
184 changes: 170 additions & 14 deletions src/app/psbt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Crypto } from './crypto';
import { Address } from './address';
import { NETWORK, NETWORK_NAME, BOOST_UTXO_VALUE } from '../config/constants';
import { isMetamaskProvider } from './wallet';
import { Utxo } from './utxo';

bitcoin.initEccLib(ecc);

Expand All @@ -28,11 +29,11 @@ function isHexadecimal(str) {
const getPsbt = (psbtContent) => {
const psbt = isHexadecimal(psbtContent)
? bitcoin.Psbt.fromHex(psbtContent, {
network: NETWORK,
})
network: NETWORK,
})
: bitcoin.Psbt.fromBase64(psbtContent, {
network: NETWORK,
});
network: NETWORK,
});

return psbt;
};
Expand All @@ -45,6 +46,7 @@ const getPsbtBase64 = (psbtContent) => {
const Psbt = function (config) {
const addressModule = Address(config);
const cryptoModule = Crypto(config);
const utxoModule = Utxo(config);

const psbtModule = {
getPsbt,
Expand Down Expand Up @@ -154,8 +156,8 @@ const Psbt = function (config) {

signPsbtForBoostByXverse: async ({ psbt, address }) => {
const signPsbtOptions: SignTransactionOptions = {
onFinish: () => {},
onCancel: () => {},
onFinish: () => { },
onCancel: () => { },
payload: {
network: {
type: NETWORK_NAME,
Expand Down Expand Up @@ -253,8 +255,8 @@ const Psbt = function (config) {
});

const signPsbtOptions: SignTransactionOptions = {
onFinish: () => {},
onCancel: () => {},
onFinish: () => { },
onCancel: () => { },
payload: {
network: {
type: NETWORK_NAME,
Expand Down Expand Up @@ -331,6 +333,160 @@ const Psbt = function (config) {
return psbtModule.broadcastPsbt(psbt);
},

signAndBroadcastMultipleUtxos: async ({
address, pubKey, selectedUtxos,
ownedUtxos, destinationBtcAddress, sendFeeRate
}) => {
const utxosWithInscription = selectedUtxos.filter(utxo => utxo.inscriptionId);
const utxosWithoutInscription = selectedUtxos.filter(utxo => !utxo.inscriptionId);
if (utxosWithInscription.length === 0 && utxosWithoutInscription.length === 0) {
throw new Error('At least one ordinal or utxo is required.');
}
const provider = SessionStorage.get(SessionsStorageKeys.DOMAIN);
if (provider !== 'alby') {
throw new Error('Signing not supported.');
}

const spendableUtxos = ownedUtxos.filter(utxo => !selectedUtxos.some(ownedUtxo => ownedUtxo.txid === utxo.txid))
.filter((x) => x.status.confirmed)
.filter(utxo => !utxo.inscriptionId)
.sort((a, b) => b.value - a.value);

const selectedAmountWithoutInscription = utxosWithoutInscription.reduce((acc, utxo) => acc + utxo.value, 0);
let cardinalAmount = selectedAmountWithoutInscription;
let calculatedFee = 0;

const inputs = [...utxosWithInscription, ...utxosWithoutInscription];
let isCardinalAdded = false;

do {
let shouldAddChangeOutput = utxosWithInscription.length > 0 || isCardinalAdded;
calculatedFee = cryptoModule.calculateFee({
vins: inputs.length,
vouts: utxosWithInscription.length + 1, // 1 for destination output amount
recommendedFeeRate: sendFeeRate,
includeChangeOutput: shouldAddChangeOutput ? 1 : 0,
});

if (cardinalAmount < calculatedFee) {
const utxo = spendableUtxos.shift();
if (!utxo) {
throw new Error(`Not enough cardinal spendable funds added.`);
}
inputs.push(utxo);
cardinalAmount += utxo.value;
isCardinalAdded = true;
}
} while (cardinalAmount < calculatedFee);

const inputAddressInfo = await addressModule.getAddressInfo(pubKey);
const psbt = new bitcoin.Psbt({ network: config.NETWORK });

for (const utxo of inputs) {
const inputParams = psbtModule.getInputParams({ utxo, inputAddressInfo });
psbt.addInput(inputParams);
}

const ordinals = inputs.filter(utxo => utxo.inscriptionId);
const nonOrdinals = inputs.filter(utxo => !utxo.inscriptionId);
const spendableAmount = nonOrdinals.reduce((acc, utxo) => acc + utxo.value, 0);

for (const utxo of ordinals) {
psbt.addOutput({
address: destinationBtcAddress,
value: utxo.value,
});
}

let changeAmount = 0;
// Add change output
if (isCardinalAdded) {
changeAmount = cardinalAmount - calculatedFee;
psbt.addOutput({
address,
value: changeAmount,
});
}

// Add destination output
if (changeAmount > 0) {
psbt.addOutput({
address: destinationBtcAddress,
value: spendableAmount - changeAmount,
});
} else {
psbt.addOutput({
address: destinationBtcAddress,
value: spendableAmount - calculatedFee,
});
}

const witnessScripts = [];
const witnessValues = [];

psbt.data.inputs.forEach((input, i) => {
if (!input.finalScriptWitness && !input.witnessUtxo) {
// @ts-ignore
const tx = bitcoin.Transaction.fromBuffer(psbt.data.inputs[i].nonWitnessUtxo);
const output = tx.outs[psbt.txInputs[i].index];
psbt.updateInput(i, {
witnessUtxo: output,
});
// @ts-ignore
witnessScripts.push(output.script);
// @ts-ignore
witnessValues.push(output.value);
} else {
// @ts-ignore
witnessScripts.push(psbt.data.inputs[i].witnessUtxo.script);
// @ts-ignore
witnessValues.push(psbt.data.inputs[i].witnessUtxo.value);
}
});

debugger;
const psbtOutputs = psbt.data.inputs.map((_, index) => {
// @ts-ignore
const sigHash = psbt.__CACHE.__TX.hashForWitnessV1(
index,
witnessScripts,
witnessValues,
bitcoin.Transaction.SIGHASH_DEFAULT
);

return {
index,
sigHash: sigHash.toString('hex')
};
});

await Promise.all(psbtOutputs.map(async output => {
const signature = await window.nostr.signSchnorr(output.sigHash);
psbt.updateInput(output.index, {
tapKeySig: serializeTaprootSignature(Buffer.from(signature, 'hex')),
});
return signature;
}));

const final_signed_psbt = psbt.finalizeAllInputs();
const final_fee = final_signed_psbt.getFee();
const final_tx = final_signed_psbt.extractTransaction();
const final_vbytes = final_tx.virtualSize();
const final_hex = final_tx.toHex();
const final_fee_rate = (final_fee / final_vbytes).toFixed(1);
console.log(`Final fee rate of signed psbt is ~${final_fee_rate} sat/vbyte`);
console.log('final_signed_psbt', final_signed_psbt.toHex());

debugger;

return {
final_hex,
final_fee_rate,
final_fee,
final_signed_psbt,
};
},

createAndSignPsbtForBoost: async ({ pubKey, utxo, destinationBtcAddress, sighashType }) => {
const inputAddressInfo = await addressModule.getAddressInfo(pubKey);
const psbt = psbtModule.createPsbt({
Expand Down Expand Up @@ -372,8 +528,8 @@ const Psbt = function (config) {

signPsbtListingXverse: async ({ psbt, address }) => {
const signPsbtOptions: SignTransactionOptions = {
onFinish: () => {},
onCancel: () => {},
onFinish: () => { },
onCancel: () => { },
payload: {
network: {
type: NETWORK_NAME,
Expand Down Expand Up @@ -578,8 +734,8 @@ const Psbt = function (config) {
}

const signPsbtOptions: SignTransactionOptions = {
onFinish: () => {},
onCancel: () => {},
onFinish: () => { },
onCancel: () => { },
payload: {
network: {
type: NETWORK_NAME,
Expand Down Expand Up @@ -677,8 +833,8 @@ const Psbt = function (config) {
}

const signPsbtOptions: SignTransactionOptions = {
onFinish: () => {},
onCancel: () => {},
onFinish: () => { },
onCancel: () => { },
payload: {
network: {
type: NETWORK_NAME,
Expand Down
1 change: 0 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"outDir": "./dist",
"declaration": true,
"rootDir": "./src",
Expand Down

0 comments on commit 2034902

Please sign in to comment.