UNPKG

@fireblocks/psbt-sdk

Version:

SDK for signing Partially Signed Bitcoin Transactions (PSBTs) using Fireblocks

168 lines 6.99 kB
"use strict"; 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