@fireblocks/psbt-sdk
Version:
SDK for signing Partially Signed Bitcoin Transactions (PSBTs) using Fireblocks
168 lines • 6.99 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.rawSign = rawSign;
exports.batchRawSign = batchRawSign;
exports.createFireblocksClient = createFireblocksClient;
const package_json_1 = require("../package.json");
const ts_sdk_1 = require("@fireblocks/ts-sdk");
const ts_sdk_2 = require("@fireblocks/ts-sdk");
const debug_1 = __importDefault(require("debug"));
const constants_1 = require("./constants");
const bitcoinjs_lib_1 = require("bitcoinjs-lib");
const debugLog = (0, debug_1.default)("fireblocks_psbt_sdk:status");
async function getTxResponse(fireblocks, txId) {
try {
let response = await fireblocks.transactions.getTransaction({ txId });
let tx = response.data;
while (tx.status !== ts_sdk_1.TransactionStateEnum.Completed) {
let messageToConsole = `Transaction ${tx.id} is currently at status - ${tx.status}`;
debugLog(messageToConsole);
await new Promise((resolve) => setTimeout(resolve, 1000));
response = await fireblocks.transactions.getTransaction({ txId });
tx = response.data;
switch (tx.status) {
case ts_sdk_1.TransactionStateEnum.Blocked:
case ts_sdk_1.TransactionStateEnum.Cancelled:
case ts_sdk_1.TransactionStateEnum.Failed:
case ts_sdk_1.TransactionStateEnum.Rejected:
throw new Error(`Signing request failed/blocked/cancelled: Transaction: ${tx.id} status is ${tx.status}${tx.subStatus ? ` (${tx.subStatus})` : ""}`);
default:
break;
}
}
while (tx.status !== ts_sdk_1.TransactionStateEnum.Completed)
;
return tx;
}
catch (error) {
throw error;
}
}
async function rawSign({ fireblocks, content, vaultId, addressIndex, assetId, note, }) {
var _a, _b, _c;
const transactionPayload = {
assetId: assetId,
operation: ts_sdk_2.TransactionOperation.Raw,
source: {
type: ts_sdk_2.TransferPeerPathType.VaultAccount,
id: vaultId,
},
note: trimNote(note !== null && note !== void 0 ? note : constants_1.FALLBACK_NOTE),
extraParameters: {
psbt: extractPsbt(note),
rawMessageData: {
messages: [
{
content,
bip44addressIndex: addressIndex,
},
],
},
},
};
const tx = await createTransactionAndGetResponse(fireblocks, transactionPayload);
const signature = (_c = (_b = (_a = tx === null || tx === void 0 ? void 0 : tx.signedMessages) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.signature) === null || _c === void 0 ? void 0 : _c.fullSig;
if (!signature) {
throw new Error("Signature is undefined.");
}
return Buffer.from(signature, "hex");
}
async function batchRawSign({ fireblocks, signatureRequests, note, }) {
// Sanity checks
if (signatureRequests.length === 0) {
throw new Error("No signature requests provided.");
}
const firstSigner = signatureRequests[0].signer;
const allSameAssetAndVault = signatureRequests.every((req) => req.signer.assetId === firstSigner.assetId &&
req.signer.vaultId === firstSigner.vaultId);
if (!allSameAssetAndVault) {
throw new Error("All signature requests in a batchRawSign must have the same assetId and vaultId.");
}
const { assetId, vaultId } = firstSigner;
const transactionPayload = {
assetId: assetId,
operation: ts_sdk_2.TransactionOperation.Raw,
source: {
type: ts_sdk_2.TransferPeerPathType.VaultAccount,
id: vaultId,
},
note: trimNote(note !== null && note !== void 0 ? note : constants_1.FALLBACK_NOTE),
extraParameters: {
psbt: extractPsbt(note),
rawMessageData: {
messages: signatureRequests.map((signatureRequest) => ({
content: signatureRequest.hash.toString("hex"),
bip44addressIndex: signatureRequest.signer.addressIndex,
})),
},
},
};
const tx = await createTransactionAndGetResponse(fireblocks, transactionPayload);
if (!tx.signedMessages ||
tx.signedMessages.length !== signatureRequests.length) {
throw new Error("Unexpected number of signed messages.");
}
return tx.signedMessages.map((signedMessage) => {
var _a, _b;
return ({
signer: (_a = signatureRequests.find((req) => {
var _a;
return req.signer.addressIndex ===
((_a = signedMessage.derivationPath) === null || _a === void 0 ? void 0 : _a[signedMessage.derivationPath.length - 1]);
})) === null || _a === void 0 ? void 0 : _a.signer,
hash: Buffer.from(signedMessage.content, "hex"),
signature: Buffer.from((_b = signedMessage.signature) === null || _b === void 0 ? void 0 : _b.fullSig, "hex"),
});
});
}
function createFireblocksClient(fireblocks) {
var _a;
return new ts_sdk_1.Fireblocks(Object.assign(Object.assign({}, fireblocks), { additionalOptions: Object.assign(Object.assign({}, fireblocks === null || fireblocks === void 0 ? void 0 : fireblocks.additionalOptions), { userAgent: getUserAgent((_a = fireblocks.additionalOptions) === null || _a === void 0 ? void 0 : _a.userAgent) }) }));
}
function getUserAgent(userProvidedUserAgent) {
let userAgent = `psbt-sdk/${package_json_1.version}`;
if (userProvidedUserAgent) {
userAgent = `${userProvidedUserAgent} ${userAgent}`;
}
return userAgent;
}
async function createTransactionAndGetResponse(fireblocks, transactionPayload) {
const transactionResponse = await fireblocks.transactions.createTransaction({
transactionRequest: transactionPayload,
});
const txId = transactionResponse.data.id;
if (!txId) {
throw new Error("Transaction ID is undefined.");
}
return await getTxResponse(fireblocks, txId);
}
function trimNote(note) {
if (!note || note.length <= constants_1.MAX_NOTE_LENGTH) {
return note;
}
return note.slice(0, constants_1.MAX_NOTE_LENGTH - 3) + "...";
}
function extractPsbt(note) {
if (!note)
return undefined;
const words = note.split(/\s+/);
for (const word of words) {
if (isValidPsbt(word)) {
return word;
}
}
return undefined;
}
function isValidPsbt(psbtString) {
try {
bitcoinjs_lib_1.Psbt.fromBase64(psbtString);
return true;
}
catch (error) {
return false;
}
}
//# sourceMappingURL=fireblocksUtils.js.map