@kilnfi/sdk
Version:
JavaScript sdk for Kiln API
1,478 lines (1,324 loc) • 43.3 kB
text/typescript
import {
type AssetTypeResponse,
type ConfigurationOptions,
Fireblocks,
type PublicKeyInformation,
SignedMessageAlgorithmEnum,
type TransactionResponse,
} from '@fireblocks/ts-sdk';
import type { Client } from 'openapi-fetch';
import { type FireblocksAssetId, FireblocksSigner } from './fireblocks_signer.js';
import type { components, paths } from './openapi/schema.js';
export type FireblocksIntegration = (
| { config?: never; instance: Fireblocks }
| { config: ConfigurationOptions; instance?: never }
) & {
vaultId: `${number}`;
};
const ERRORS = {
MISSING_SIGNATURE: 'An error occurred while attempting to retrieve the signature from Fireblocks.',
FAILED_TO_PREPARE: 'An error occurred while attempting to add the signature to the transaction.',
MISSING_PUBLIC_KEY: 'An error occurred while attempting to retrieve the public key from Fireblocks.',
};
export class FireblocksService {
client: Client<paths>;
constructor(client: Client<paths>) {
this.client = client;
}
/**
* Retrieve a fireblocks SDK from a Fireblocks integration
*/
getSdk(integration: FireblocksIntegration): Fireblocks {
if (integration.instance) {
return integration.instance;
}
return new Fireblocks(integration.config);
}
/**
* Retrieve a fireblocks signer from a Fireblocks integration
*/
getSigner(integration: FireblocksIntegration): FireblocksSigner {
const sdk = this.getSdk(integration);
return new FireblocksSigner(sdk, integration.vaultId);
}
/**
* Get fireblocks wallet pubkey compressed
*/
async getPubkey(
integration: FireblocksIntegration,
assetId: (string & {}) | FireblocksAssetId,
): Promise<PublicKeyInformation> {
const fbSdk = this.getSdk(integration);
const data = await fbSdk.vaults.getPublicKeyInfoForAddress({
assetId: assetId,
vaultAccountId: integration.vaultId,
change: 0,
addressIndex: 0,
compressed: true,
});
return data.data;
}
/**
* List Fireblocks supported assets
*/
async getAssets(integration: FireblocksIntegration): Promise<AssetTypeResponse[]> {
const fbSdk = this.getSdk(integration);
return (await fbSdk.blockchainsAssets.getSupportedAssets()).data;
}
/**
* Sign a Solana transaction on Fireblocks
*/
async signSolTx(
integration: FireblocksIntegration,
tx: components['schemas']['SOLStakeTx'],
assetId: 'SOL_TEST' | 'SOL',
note?: string,
): Promise<{
signed_tx: { data: components['schemas']['SOLPreparedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const payload = {
rawMessageData: {
messages: [
{
content: tx.unsigned_tx_hash,
},
],
},
};
const fbSigner = this.getSigner(integration);
const fbNote = note ? note : 'SOL tx from @kilnfi/sdk';
const fbTx = await fbSigner.sign(payload, assetId, fbNote);
const signatures = fbTx.signedMessages
?.filter((signedMessage) => signedMessage.derivationPath?.[3] === 0)
.map((signedMessage) => signedMessage.signature?.fullSig)
.filter((s) => s !== undefined);
if (!signatures) {
throw new Error(ERRORS.MISSING_SIGNATURE);
}
const preparedTx = await this.client.POST('/sol/transaction/prepare', {
body: {
unsigned_tx_serialized: tx.unsigned_tx_serialized,
signatures: signatures,
},
});
if (preparedTx.error || !preparedTx.data) {
throw new Error(ERRORS.FAILED_TO_PREPARE);
}
return {
signed_tx: preparedTx.data,
fireblocks_tx: fbTx,
};
}
/**
* Create a Cardano transaction in Fireblocks without waiting for completion
*
* The payload must not contain an inputsSelection: it is only meaningful for
* Fireblocks-built transfers (not RAW signing, where the tx is already built)
* and the Fireblocks API rejects ADA RAW transactions carrying it with
* HTTP 500 {"message":"Unexpected error","code":1404}.
*/
async createAdaTx(
integration: FireblocksIntegration,
tx: components['schemas']['ADAUnsignedTx'],
note?: string,
): Promise<TransactionResponse> {
const payload = {
rawMessageData: {
messages: [
{
content: tx.unsigned_tx_hash,
preHash: {
content: tx.unsigned_tx_body_serialized,
hashAlgorithm: 'BLAKE2',
},
},
{
content: tx.unsigned_tx_hash,
preHash: {
content: tx.unsigned_tx_body_serialized,
hashAlgorithm: 'BLAKE2',
},
bip44change: 2,
},
],
},
};
const fbSigner = this.getSigner(integration);
const fbNote = note ? note : 'ADA tx from @kilnfi/sdk';
return await fbSigner.createTransaction(payload, 'ADA', fbNote);
}
/**
* Wait for a Cardano transaction to complete and prepare it for broadcast
*/
async waitForAdaTxCompletion(
integration: FireblocksIntegration,
tx: components['schemas']['ADAUnsignedTx'],
fbTx: TransactionResponse,
): Promise<{
signed_tx: { data: components['schemas']['ADASignedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbSigner = this.getSigner(integration);
const completedTx = await fbSigner.waitForTxCompletion(fbTx);
const signedMessages = completedTx.signedMessages?.map((message) => ({
pubkey: message.publicKey as string,
signature: message.signature?.fullSig as string,
}));
if (!signedMessages) {
throw new Error(ERRORS.MISSING_SIGNATURE);
}
const preparedTx = await this.client.POST('/ada/transaction/prepare', {
body: {
unsigned_tx_serialized: tx.unsigned_tx_serialized,
signed_messages: signedMessages,
},
});
if (preparedTx.error || !preparedTx.data) {
throw new Error(ERRORS.FAILED_TO_PREPARE);
}
return {
signed_tx: preparedTx.data,
fireblocks_tx: completedTx,
};
}
/**
* Sign a Cardano transaction on Fireblocks
*/
async signAdaTx(
integration: FireblocksIntegration,
tx: components['schemas']['ADAUnsignedTx'],
note?: string,
): Promise<{
signed_tx: { data: components['schemas']['ADASignedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbTx = await this.createAdaTx(integration, tx, note);
return await this.waitForAdaTxCompletion(integration, tx, fbTx);
}
/**
* Create an ATOM transaction in Fireblocks without waiting for completion
*/
async createAtomTx(
integration: FireblocksIntegration,
tx: components['schemas']['ATOMUnsignedTx'] | components['schemas']['ATOMStakeUnsignedTx'],
note?: string,
): Promise<TransactionResponse> {
const payload = {
rawMessageData: {
messages: [
{
content: tx.unsigned_tx_hash,
preHash: {
content: tx.unsigned_tx_serialized,
hashAlgorithm: 'SHA256',
},
},
],
},
};
const fbSigner = this.getSigner(integration);
const fbNote = note ? note : 'ATOM tx from @kilnfi/sdk';
return await fbSigner.createTransaction(payload, 'ATOM_COS', fbNote);
}
/**
* Wait for an ATOM transaction to complete and prepare it for broadcast
*/
async waitForAtomTxCompletion(
integration: FireblocksIntegration,
tx: components['schemas']['ATOMUnsignedTx'] | components['schemas']['ATOMStakeUnsignedTx'],
fbTx: TransactionResponse,
): Promise<{
signed_tx: { data: components['schemas']['ATOMSignedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbSigner = this.getSigner(integration);
const completedTx = await fbSigner.waitForTxCompletion(fbTx);
const signature = completedTx.signedMessages?.[0]?.signature?.fullSig;
if (!signature) {
throw new Error(ERRORS.MISSING_SIGNATURE);
}
const preparedTx = await this.client.POST('/atom/transaction/prepare', {
body: {
pubkey: tx.pubkey,
tx_body: tx.tx_body,
tx_auth_info: tx.tx_auth_info,
signature: signature,
},
});
if (preparedTx.error || !preparedTx.data) {
throw new Error(ERRORS.FAILED_TO_PREPARE);
}
return {
signed_tx: preparedTx.data,
fireblocks_tx: completedTx,
};
}
/**
* Sign an ATOM transaction on Fireblocks (combines createAtomTx and waitForAtomTxCompletion)
*/
async signAtomTx(
integration: FireblocksIntegration,
tx: components['schemas']['ATOMUnsignedTx'] | components['schemas']['ATOMStakeUnsignedTx'],
note?: string,
): Promise<{
signed_tx: { data: components['schemas']['ATOMSignedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbTx = await this.createAtomTx(integration, tx, note);
return await this.waitForAtomTxCompletion(integration, tx, fbTx);
}
/**
* Sign a DYDX transaction on Fireblocks
*/
/**
* Create a DYDX transaction in Fireblocks without waiting for completion
*/
async createDydxTx(
integration: FireblocksIntegration,
tx: components['schemas']['DYDXUnsignedTx'] | components['schemas']['DYDXStakeUnsignedTx'],
note?: string,
): Promise<TransactionResponse> {
const payload = {
rawMessageData: {
messages: [
{
content: tx.unsigned_tx_hash,
preHash: {
content: tx.unsigned_tx_serialized,
hashAlgorithm: 'SHA256',
},
},
],
},
};
const fbSigner = this.getSigner(integration);
const fbNote = note ? note : 'DYDX tx from @kilnfi/sdk';
return await fbSigner.createTransaction(payload, 'DYDX_DYDX', fbNote);
}
/**
* Wait for a DYDX transaction to complete and prepare it for broadcast
*/
async waitForDydxTxCompletion(
integration: FireblocksIntegration,
tx: components['schemas']['DYDXUnsignedTx'] | components['schemas']['DYDXStakeUnsignedTx'],
fbTx: TransactionResponse,
): Promise<{
signed_tx: { data: components['schemas']['DYDXSignedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbSigner = this.getSigner(integration);
const completedTx = await fbSigner.waitForTxCompletion(fbTx);
const signature = completedTx.signedMessages?.[0]?.signature?.fullSig;
if (!signature) {
throw new Error(ERRORS.MISSING_SIGNATURE);
}
const preparedTx = await this.client.POST('/dydx/transaction/prepare', {
body: {
pubkey: tx.pubkey,
tx_body: tx.tx_body,
tx_auth_info: tx.tx_auth_info,
signature: signature,
},
});
if (preparedTx.error || !preparedTx.data) {
throw new Error(ERRORS.FAILED_TO_PREPARE);
}
return {
signed_tx: preparedTx.data,
fireblocks_tx: completedTx,
};
}
/**
* Sign a DYDX transaction on Fireblocks (combines createDydxTx and waitForDydxTxCompletion)
*/
async signDydxTx(
integration: FireblocksIntegration,
tx: components['schemas']['DYDXUnsignedTx'] | components['schemas']['DYDXStakeUnsignedTx'],
note?: string,
): Promise<{
signed_tx: { data: components['schemas']['DYDXSignedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbTx = await this.createDydxTx(integration, tx, note);
return await this.waitForDydxTxCompletion(integration, tx, fbTx);
}
/**
* Sign a FET transaction on Fireblocks
*/
/**
* Create a FET transaction in Fireblocks without waiting for completion
*/
async createFetTx(
integration: FireblocksIntegration,
tx: components['schemas']['FETUnsignedTx'] | components['schemas']['FETStakeUnsignedTx'],
note?: string,
): Promise<TransactionResponse> {
const payload = {
rawMessageData: {
messages: [
{
content: tx.unsigned_tx_hash,
derivationPath: [44, 118, Number(integration.vaultId), 0, 0],
preHash: {
content: tx.unsigned_tx_serialized,
hashAlgorithm: 'SHA256',
},
},
],
algorithm: SignedMessageAlgorithmEnum.EcdsaSecp256K1,
},
};
const fbSigner = this.getSigner(integration);
const fbNote = note ? note : 'FET tx from @kilnfi/sdk';
return await fbSigner.createTransaction(payload, undefined, fbNote);
}
/**
* Wait for a FET transaction to complete and prepare it for broadcast
*/
async waitForFetTxCompletion(
integration: FireblocksIntegration,
tx: components['schemas']['FETUnsignedTx'] | components['schemas']['FETStakeUnsignedTx'],
fbTx: TransactionResponse,
): Promise<{
signed_tx: { data: components['schemas']['FETSignedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbSigner = this.getSigner(integration);
const completedTx = await fbSigner.waitForTxCompletion(fbTx);
const signature = completedTx.signedMessages?.[0]?.signature?.fullSig;
if (!signature) {
throw new Error(ERRORS.MISSING_SIGNATURE);
}
const preparedTx = await this.client.POST('/fet/transaction/prepare', {
body: {
pubkey: tx.pubkey,
tx_body: tx.tx_body,
tx_auth_info: tx.tx_auth_info,
signature: signature,
},
});
if (preparedTx.error || !preparedTx.data) {
throw new Error(ERRORS.FAILED_TO_PREPARE);
}
return {
signed_tx: preparedTx.data,
fireblocks_tx: completedTx,
};
}
/**
* Sign a FET transaction on Fireblocks (combines createFetTx and waitForFetTxCompletion)
*/
async signFetTx(
integration: FireblocksIntegration,
tx: components['schemas']['FETUnsignedTx'] | components['schemas']['FETStakeUnsignedTx'],
note?: string,
): Promise<{
signed_tx: { data: components['schemas']['FETSignedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbTx = await this.createFetTx(integration, tx, note);
return await this.waitForFetTxCompletion(integration, tx, fbTx);
}
/**
* Sign a INJ transaction on Fireblocks
*/
/**
* Create a INJ transaction in Fireblocks without waiting for completion
*/
async createInjTx(
integration: FireblocksIntegration,
tx: components['schemas']['INJUnsignedTx'] | components['schemas']['INJStakeUnsignedTx'],
note?: string,
): Promise<TransactionResponse> {
const payload = {
rawMessageData: {
messages: [
{
content: tx.unsigned_tx_hash,
preHash: {
content: tx.unsigned_tx_serialized,
hashAlgorithm: 'SHA256',
},
},
],
},
};
const fbSigner = this.getSigner(integration);
const fbNote = note ? note : 'INJ tx from @kilnfi/sdk';
return await fbSigner.createTransaction(payload, 'INJ_INJ', fbNote);
}
/**
* Wait for a INJ transaction to complete and prepare it for broadcast
*/
async waitForInjTxCompletion(
integration: FireblocksIntegration,
tx: components['schemas']['INJUnsignedTx'] | components['schemas']['INJStakeUnsignedTx'],
fbTx: TransactionResponse,
): Promise<{
signed_tx: { data: components['schemas']['INJSignedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbSigner = this.getSigner(integration);
const completedTx = await fbSigner.waitForTxCompletion(fbTx);
const signature = completedTx.signedMessages?.[0]?.signature?.fullSig;
if (!signature) {
throw new Error(ERRORS.MISSING_SIGNATURE);
}
const preparedTx = await this.client.POST('/inj/transaction/prepare', {
body: {
pubkey: tx.pubkey,
tx_body: tx.tx_body,
tx_auth_info: tx.tx_auth_info,
signature: signature,
},
});
if (preparedTx.error || !preparedTx.data) {
throw new Error(ERRORS.FAILED_TO_PREPARE);
}
return {
signed_tx: preparedTx.data,
fireblocks_tx: completedTx,
};
}
/**
* Sign a INJ transaction on Fireblocks (combines createInjTx and waitForInjTxCompletion)
*/
async signInjTx(
integration: FireblocksIntegration,
tx: components['schemas']['INJUnsignedTx'] | components['schemas']['INJStakeUnsignedTx'],
note?: string,
): Promise<{
signed_tx: { data: components['schemas']['INJSignedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbTx = await this.createInjTx(integration, tx, note);
return await this.waitForInjTxCompletion(integration, tx, fbTx);
}
/**
* Sign a CRO transaction on Fireblocks
*/
/**
* Create a CRO transaction in Fireblocks without waiting for completion
*/
async createCroTx(
integration: FireblocksIntegration,
tx: components['schemas']['CROUnsignedTx'] | components['schemas']['CROStakeUnsignedTx'],
note?: string,
): Promise<TransactionResponse> {
const payload = {
rawMessageData: {
messages: [
{
content: tx.unsigned_tx_hash,
derivationPath: [44, 394, Number(integration.vaultId), 0, 0],
preHash: {
content: tx.unsigned_tx_serialized,
hashAlgorithm: 'SHA256',
},
},
],
algorithm: SignedMessageAlgorithmEnum.EcdsaSecp256K1,
},
};
const fbSigner = this.getSigner(integration);
const fbNote = note ? note : 'CRO tx from @kilnfi/sdk';
return await fbSigner.createTransaction(payload, undefined, fbNote);
}
/**
* Wait for a CRO transaction to complete and prepare it for broadcast
*/
async waitForCroTxCompletion(
integration: FireblocksIntegration,
tx: components['schemas']['CROUnsignedTx'] | components['schemas']['CROStakeUnsignedTx'],
fbTx: TransactionResponse,
): Promise<{
signed_tx: { data: components['schemas']['CROSignedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbSigner = this.getSigner(integration);
const completedTx = await fbSigner.waitForTxCompletion(fbTx);
const signature = completedTx.signedMessages?.[0]?.signature?.fullSig;
if (!signature) {
throw new Error(ERRORS.MISSING_SIGNATURE);
}
const preparedTx = await this.client.POST('/cro/transaction/prepare', {
body: {
pubkey: tx.pubkey,
tx_body: tx.tx_body,
tx_auth_info: tx.tx_auth_info,
signature: signature,
},
});
if (preparedTx.error || !preparedTx.data) {
throw new Error(ERRORS.FAILED_TO_PREPARE);
}
return {
signed_tx: preparedTx.data,
fireblocks_tx: completedTx,
};
}
/**
* Sign a CRO transaction on Fireblocks (combines createCroTx and waitForCroTxCompletion)
*/
async signCroTx(
integration: FireblocksIntegration,
tx: components['schemas']['CROUnsignedTx'] | components['schemas']['CROStakeUnsignedTx'],
note?: string,
): Promise<{
signed_tx: { data: components['schemas']['CROSignedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbTx = await this.createCroTx(integration, tx, note);
return await this.waitForCroTxCompletion(integration, tx, fbTx);
}
/**
* Sign a NOBLE transaction on Fireblocks
*/
/**
* Create a NOBLE transaction in Fireblocks without waiting for completion
*/
async createNobleTx(
integration: FireblocksIntegration,
tx: components['schemas']['DYDXUnsignedTx'],
note?: string,
): Promise<TransactionResponse> {
const payload = {
rawMessageData: {
messages: [
{
content: tx.unsigned_tx_hash,
derivationPath: [44, 118, Number(integration.vaultId), 0, 0],
preHash: {
content: tx.unsigned_tx_serialized,
hashAlgorithm: 'SHA256',
},
},
],
algorithm: SignedMessageAlgorithmEnum.EcdsaSecp256K1,
},
};
const fbSigner = this.getSigner(integration);
const fbNote = note ? note : 'NOBLE tx from @kilnfi/sdk';
return await fbSigner.createTransaction(payload, undefined, fbNote);
}
/**
* Wait for a NOBLE transaction to complete and prepare it for broadcast
*/
async waitForNobleTxCompletion(
integration: FireblocksIntegration,
tx: components['schemas']['DYDXUnsignedTx'],
fbTx: TransactionResponse,
): Promise<{
signed_tx: { data: components['schemas']['DYDXSignedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbSigner = this.getSigner(integration);
const completedTx = await fbSigner.waitForTxCompletion(fbTx);
const signature = completedTx.signedMessages?.[0]?.signature?.fullSig;
if (!signature) {
throw new Error(ERRORS.MISSING_SIGNATURE);
}
const preparedTx = await this.client.POST('/noble/transaction/prepare', {
body: {
pubkey: tx.pubkey,
tx_body: tx.tx_body,
tx_auth_info: tx.tx_auth_info,
signature: signature,
},
});
if (preparedTx.error || !preparedTx.data) {
throw new Error(ERRORS.FAILED_TO_PREPARE);
}
return {
signed_tx: preparedTx.data,
fireblocks_tx: completedTx,
};
}
/**
* Sign a NOBLE transaction on Fireblocks (combines createNobleTx and waitForNobleTxCompletion)
*/
async signNobleTx(
integration: FireblocksIntegration,
tx: components['schemas']['DYDXUnsignedTx'],
note?: string,
): Promise<{
signed_tx: { data: components['schemas']['DYDXSignedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbTx = await this.createNobleTx(integration, tx, note);
return await this.waitForNobleTxCompletion(integration, tx, fbTx);
}
/**
* Sign a OSMO transaction on Fireblocks
*/
/**
* Create a OSMO transaction in Fireblocks without waiting for completion
*/
async createOsmoTx(
integration: FireblocksIntegration,
tx: components['schemas']['OSMOUnsignedTx'] | components['schemas']['OSMOStakeUnsignedTx'],
note?: string,
): Promise<TransactionResponse> {
const payload = {
rawMessageData: {
messages: [
{
content: tx.unsigned_tx_hash,
preHash: {
content: tx.unsigned_tx_serialized,
hashAlgorithm: 'SHA256',
},
},
],
},
};
const fbSigner = this.getSigner(integration);
const fbNote = note ? note : 'OSMO tx from @kilnfi/sdk';
return await fbSigner.createTransaction(payload, 'OSMO', fbNote);
}
/**
* Wait for a OSMO transaction to complete and prepare it for broadcast
*/
async waitForOsmoTxCompletion(
integration: FireblocksIntegration,
tx: components['schemas']['OSMOUnsignedTx'] | components['schemas']['OSMOStakeUnsignedTx'],
fbTx: TransactionResponse,
): Promise<{
signed_tx: { data: components['schemas']['OSMOSignedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbSigner = this.getSigner(integration);
const completedTx = await fbSigner.waitForTxCompletion(fbTx);
const signature = completedTx.signedMessages?.[0]?.signature?.fullSig;
if (!signature) {
throw new Error(ERRORS.MISSING_SIGNATURE);
}
const preparedTx = await this.client.POST('/osmo/transaction/prepare', {
body: {
pubkey: tx.pubkey,
tx_body: tx.tx_body,
tx_auth_info: tx.tx_auth_info,
signature: signature,
},
});
if (preparedTx.error || !preparedTx.data) {
throw new Error(ERRORS.FAILED_TO_PREPARE);
}
return {
signed_tx: preparedTx.data,
fireblocks_tx: completedTx,
};
}
/**
* Sign a OSMO transaction on Fireblocks (combines createOsmoTx and waitForOsmoTxCompletion)
*/
async signOsmoTx(
integration: FireblocksIntegration,
tx: components['schemas']['OSMOUnsignedTx'] | components['schemas']['OSMOStakeUnsignedTx'],
note?: string,
): Promise<{
signed_tx: { data: components['schemas']['OSMOSignedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbTx = await this.createOsmoTx(integration, tx, note);
return await this.waitForOsmoTxCompletion(integration, tx, fbTx);
}
/**
* Sign a TIA transaction on Fireblocks
*/
/**
* Create a TIA transaction in Fireblocks without waiting for completion
*/
async createTiaTx(
integration: FireblocksIntegration,
tx: components['schemas']['TIAUnsignedTx'] | components['schemas']['TIAStakeUnsignedTx'],
note?: string,
): Promise<TransactionResponse> {
const payload = {
rawMessageData: {
messages: [
{
content: tx.unsigned_tx_hash,
preHash: {
content: tx.unsigned_tx_serialized,
hashAlgorithm: 'SHA256',
},
},
],
},
};
const fbSigner = this.getSigner(integration);
const fbNote = note ? note : 'TIA tx from @kilnfi/sdk';
return await fbSigner.createTransaction(payload, 'CELESTIA', fbNote);
}
/**
* Wait for a TIA transaction to complete and prepare it for broadcast
*/
async waitForTiaTxCompletion(
integration: FireblocksIntegration,
tx: components['schemas']['TIAUnsignedTx'] | components['schemas']['TIAStakeUnsignedTx'],
fbTx: TransactionResponse,
): Promise<{
signed_tx: { data: components['schemas']['TIASignedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbSigner = this.getSigner(integration);
const completedTx = await fbSigner.waitForTxCompletion(fbTx);
const signature = completedTx.signedMessages?.[0]?.signature?.fullSig;
if (!signature) {
throw new Error(ERRORS.MISSING_SIGNATURE);
}
const preparedTx = await this.client.POST('/tia/transaction/prepare', {
body: {
pubkey: tx.pubkey,
tx_body: tx.tx_body,
tx_auth_info: tx.tx_auth_info,
signature: signature,
},
});
if (preparedTx.error || !preparedTx.data) {
throw new Error(ERRORS.FAILED_TO_PREPARE);
}
return {
signed_tx: preparedTx.data,
fireblocks_tx: completedTx,
};
}
/**
* Sign a TIA transaction on Fireblocks (combines createTiaTx and waitForTiaTxCompletion)
*/
async signTiaTx(
integration: FireblocksIntegration,
tx: components['schemas']['TIAUnsignedTx'] | components['schemas']['TIAStakeUnsignedTx'],
note?: string,
): Promise<{
signed_tx: { data: components['schemas']['TIASignedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbTx = await this.createTiaTx(integration, tx, note);
return await this.waitForTiaTxCompletion(integration, tx, fbTx);
}
/**
* Sign an ETH transaction with given integration using Fireblocks raw signing
*/
async signEthTx(
integration: FireblocksIntegration,
tx: components['schemas']['ETHUnsignedTx'],
assetId: 'ETH_TEST_HOODI' | 'ETH',
note?: string,
): Promise<{
signed_tx: { data: components['schemas']['ETHSignedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const payload = {
rawMessageData: {
messages: [
{
content: tx.unsigned_tx_hash,
preHash: {
content: tx.unsigned_tx_serialized,
hashAlgorithm: 'KECCAK256',
},
},
],
},
};
const fbSigner = this.getSigner(integration);
const fbNote = note ? note : 'ETH tx from @kilnfi/sdk';
const fbTx = await fbSigner.sign(payload, assetId, fbNote);
const signature = fbTx?.signedMessages?.[0]?.signature;
if (!signature) {
throw new Error(ERRORS.MISSING_SIGNATURE);
}
const preparedTx = await this.client.POST('/eth/transaction/prepare', {
body: {
unsigned_tx_serialized: tx.unsigned_tx_serialized,
r: `0x${signature.r}`,
s: `0x${signature.s}`,
v: signature.v ?? 0,
},
});
if (preparedTx.error || !preparedTx.data) {
throw new Error(ERRORS.FAILED_TO_PREPARE);
}
return {
signed_tx: preparedTx.data,
fireblocks_tx: fbTx,
};
}
/**
* Sign and broadcast an ETH transaction with given integration using Fireblocks contract call feature
*/
async signAndBroadcastEthTx(
integration: FireblocksIntegration,
tx: components['schemas']['ETHUnsignedTx'],
assetId: 'ETH_TEST_HOODI' | 'ETH',
fireblocksDestinationId: string,
note?: string,
) {
const payload = {
contractCallData: tx.contract_call_data,
};
const fbSigner = this.getSigner(integration);
const fbNote = note ? note : 'ETH tx from @kilnfi/sdk';
return await fbSigner.signAndBroadcastWith(payload, assetId, tx, fireblocksDestinationId, true, fbNote);
}
/**
* Sign a POL transaction with given integration using Fireblocks raw signing
*/
async signPolTx(
integration: FireblocksIntegration,
tx: components['schemas']['POLUnsignedTx'],
assetId: 'ETH_TEST5' | 'ETH',
note?: string,
): Promise<{
signed_tx: { data: components['schemas']['POLSignedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const payload = {
rawMessageData: {
messages: [
{
content: tx.unsigned_tx_hash,
preHash: {
content: tx.unsigned_tx_serialized,
hashAlgorithm: 'KECCAK256',
},
},
],
},
};
const fbSigner = this.getSigner(integration);
const fbNote = note ? note : 'POL tx from @kilnfi/sdk';
const fbTx = await fbSigner.sign(payload, assetId, fbNote);
const signature = fbTx?.signedMessages?.[0]?.signature;
if (!signature) {
throw new Error(ERRORS.MISSING_SIGNATURE);
}
const preparedTx = await this.client.POST('/pol/transaction/prepare', {
body: {
unsigned_tx_serialized: tx.unsigned_tx_serialized,
r: `0x${signature.r}`,
s: `0x${signature.s}`,
v: signature.v ?? 0,
},
});
if (preparedTx.error || !preparedTx.data) {
throw new Error(ERRORS.FAILED_TO_PREPARE);
}
return {
signed_tx: preparedTx.data,
fireblocks_tx: fbTx,
};
}
/**
* Sign and broadcast a POL transaction with given integration using Fireblocks contract call feature
*/
async signAndBroadcastPolTx(
integration: FireblocksIntegration,
tx: components['schemas']['POLUnsignedTx'],
assetId: 'ETH_TEST5' | 'ETH',
fireblocksDestinationId: string,
note?: string,
) {
const payload = {
contractCallData: tx.contract_call_data,
};
const fbSigner = this.getSigner(integration);
const fbNote = note ? note : 'POL tx from @kilnfi/sdk';
return await fbSigner.signAndBroadcastWith(payload, assetId, tx, fireblocksDestinationId, true, fbNote);
}
/**
* Create a TON transaction in Fireblocks without waiting for completion
*/
async createTonTx(
integration: FireblocksIntegration,
tx: components['schemas']['TONTx'],
assetId: 'TON_TEST' | 'TON',
note?: string,
): Promise<TransactionResponse> {
const payload = {
rawMessageData: {
messages: [
{
content: tx.unsigned_tx_hash,
//TODO : ADD PREHASH LATER IF FB SUPPORT TON HASH ALGORITHM
},
],
},
};
const fbSigner = this.getSigner(integration);
const fbNote = note ? note : 'TON tx from @kilnfi/sdk';
return await fbSigner.createTransaction(payload, assetId, fbNote);
}
/**
* Wait for a TON transaction to complete and prepare it for broadcast
*/
async waitForTonTxCompletion(
integration: FireblocksIntegration,
tx: components['schemas']['TONTx'],
fbTx: TransactionResponse,
): Promise<{
signed_tx: { data: components['schemas']['TONPreparedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbSigner = this.getSigner(integration);
const completedTx = await fbSigner.waitForTxCompletion(fbTx);
const signature = completedTx.signedMessages?.[0]?.signature?.fullSig;
if (!signature) {
throw new Error(ERRORS.MISSING_SIGNATURE);
}
const preparedTx = await this.client.POST('/ton/transaction/prepare', {
body: {
unsigned_tx_serialized: tx.unsigned_tx_serialized,
signature: signature,
from: tx.from,
},
});
if (preparedTx.error || !preparedTx.data) {
throw new Error(ERRORS.FAILED_TO_PREPARE);
}
return {
signed_tx: preparedTx.data,
fireblocks_tx: completedTx,
};
}
/**
* Sign a TON transaction on Fireblocks
*/
async signTonTx(
integration: FireblocksIntegration,
tx: components['schemas']['TONTx'],
assetId: 'TON_TEST' | 'TON',
note?: string,
): Promise<{
signed_tx: { data: components['schemas']['TONPreparedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbTx = await this.createTonTx(integration, tx, assetId, note);
return await this.waitForTonTxCompletion(integration, tx, fbTx);
}
/**
* Create a XTZ transaction in Fireblocks without waiting for completion
*/
async createXtzTx(
integration: FireblocksIntegration,
tx: components['schemas']['XTZUnsignedTx'],
note?: string,
): Promise<TransactionResponse> {
const payload = {
rawMessageData: {
messages: [
{
content: tx.unsigned_tx_hash,
},
],
},
};
const fbSigner = this.getSigner(integration);
const fbNote = note ? note : 'XTZ tx from @kilnfi/sdk';
return await fbSigner.createTransaction(payload, 'XTZ', fbNote);
}
/**
* Wait for a XTZ transaction to complete and prepare it for broadcast
*/
async waitForXtzTxCompletion(
integration: FireblocksIntegration,
tx: components['schemas']['XTZUnsignedTx'],
fbTx: TransactionResponse,
): Promise<{
signed_tx: { data: components['schemas']['XTZSignedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbSigner = this.getSigner(integration);
const completedTx = await fbSigner.waitForTxCompletion(fbTx);
const signature = completedTx.signedMessages?.[0]?.signature?.fullSig;
if (!signature) {
throw new Error(ERRORS.MISSING_SIGNATURE);
}
const preparedTx = await this.client.POST('/xtz/transaction/prepare', {
body: {
unsigned_tx_serialized: tx.unsigned_tx_serialized,
signature: signature,
},
});
if (preparedTx.error || !preparedTx.data) {
throw new Error(ERRORS.FAILED_TO_PREPARE);
}
return {
signed_tx: preparedTx.data,
fireblocks_tx: completedTx,
};
}
/**
* Sign a XTZ transaction on Fireblocks (combines createXtzTx and waitForXtzTxCompletion)
*/
async signXtzTx(
integration: FireblocksIntegration,
tx: components['schemas']['XTZUnsignedTx'],
note?: string,
): Promise<{
signed_tx: { data: components['schemas']['XTZSignedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbTx = await this.createXtzTx(integration, tx, note);
return await this.waitForXtzTxCompletion(integration, tx, fbTx);
}
/**
* Create a NEAR transaction in Fireblocks without waiting for completion
*/
async createNearTx(
integration: FireblocksIntegration,
tx: components['schemas']['NEARTx'],
assetId: 'NEAR_TEST' | 'NEAR',
note?: string,
): Promise<TransactionResponse> {
const payload = {
rawMessageData: {
messages: [
{
content: tx.unsigned_tx_hash,
preHash: {
content: tx.unsigned_tx_serialized,
hashAlgorithm: 'SHA256',
},
},
],
},
};
const fbSigner = this.getSigner(integration);
const fbNote = note ? note : 'NEAR tx from @kilnfi/sdk';
return await fbSigner.createTransaction(payload, assetId, fbNote);
}
/**
* Wait for a NEAR transaction to complete and prepare it for broadcast
*/
async waitForNearTxCompletion(
integration: FireblocksIntegration,
tx: components['schemas']['NEARTx'],
fbTx: TransactionResponse,
): Promise<{
signed_tx: { data: components['schemas']['NEARSignedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbSigner = this.getSigner(integration);
const completedTx = await fbSigner.waitForTxCompletion(fbTx);
const signature = completedTx.signedMessages?.[0]?.signature?.fullSig;
if (!signature) {
throw new Error(ERRORS.MISSING_SIGNATURE);
}
const preparedTx = await this.client.POST('/near/transaction/prepare', {
body: {
unsigned_tx_serialized: tx.unsigned_tx_serialized,
signature: signature,
},
});
if (preparedTx.error || !preparedTx.data) {
throw new Error(ERRORS.FAILED_TO_PREPARE);
}
return {
signed_tx: preparedTx.data,
fireblocks_tx: fbTx,
};
}
/**
* Sign a NEAR transaction on Fireblocks (combines createNearTx and waitForNearTxCompletion)
*/
async signNearTx(
integration: FireblocksIntegration,
tx: components['schemas']['NEARTx'],
assetId: 'NEAR_TEST' | 'NEAR',
note?: string,
): Promise<{
signed_tx: { data: components['schemas']['NEARSignedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbTx = await this.createNearTx(integration, tx, assetId, note);
return await this.waitForNearTxCompletion(integration, tx, fbTx);
}
/**
* Sign a Trx transaction on Fireblocks
*/
async createTrxTx(
integration: FireblocksIntegration,
tx: components['schemas']['TRXUnsignedTx'],
note?: string,
): Promise<TransactionResponse> {
const payload = {
rawMessageData: {
messages: [
{
content: tx.unsigned_tx_id,
preHash: {
content: tx.unsigned_tx_serialized,
hashAlgorithm: 'SHA256',
},
},
],
},
};
const fbSigner = this.getSigner(integration);
const fbNote = note ? note : 'TRX tx from @kilnfi/sdk';
return await fbSigner.createTransaction(payload, 'TRX', fbNote);
}
async waitForTrxTxCompletion(
integration: FireblocksIntegration,
tx: components['schemas']['TRXUnsignedTx'],
fbTx: TransactionResponse,
): Promise<{
signed_tx: { data: components['schemas']['TRXPreparedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbSigner = this.getSigner(integration);
const completedTx = await fbSigner.waitForTxCompletion(fbTx);
if (!completedTx.signedMessages?.[0]?.signature) {
throw new Error(ERRORS.MISSING_SIGNATURE);
}
const signature = `${completedTx.signedMessages[0].signature.fullSig}0${completedTx.signedMessages[0].signature.v}`;
const preparedTx = await this.client.POST('/trx/transaction/prepare', {
body: {
unsigned_tx_serialized: tx.unsigned_tx_serialized,
signature: signature,
},
});
if (preparedTx.error || !preparedTx.data) {
throw new Error(ERRORS.FAILED_TO_PREPARE);
}
return {
signed_tx: preparedTx.data,
fireblocks_tx: completedTx,
};
}
async signTrxTx(
integration: FireblocksIntegration,
tx: components['schemas']['TRXUnsignedTx'],
note?: string,
): Promise<{
signed_tx: { data: components['schemas']['TRXPreparedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbTx = await this.createTrxTx(integration, tx, note);
return await this.waitForTrxTxCompletion(integration, tx, fbTx);
}
/**
* Sign a SEI transaction on Fireblocks
*/
/**
* Create a SEI transaction in Fireblocks without waiting for completion
*/
async createSeiTx(
integration: FireblocksIntegration,
tx: components['schemas']['SEIUnsignedTx'] | components['schemas']['SEIStakeUnsignedTx'],
note?: string,
): Promise<TransactionResponse> {
const payload = {
rawMessageData: {
messages: [
{
content: tx.unsigned_tx_hash,
derivationPath: [44, 60, Number(integration.vaultId), 0, 0],
preHash: {
content: tx.unsigned_tx_serialized,
hashAlgorithm: 'SHA256',
},
},
],
algorithm: SignedMessageAlgorithmEnum.EcdsaSecp256K1,
},
};
const fbSigner = this.getSigner(integration);
const fbNote = note ? note : 'SEI tx from @kilnfi/sdk';
return await fbSigner.createTransaction(payload, undefined, fbNote);
}
/**
* Wait for a SEI transaction to complete and prepare it for broadcast
*/
async waitForSeiTxCompletion(
integration: FireblocksIntegration,
tx: components['schemas']['SEIUnsignedTx'] | components['schemas']['SEIStakeUnsignedTx'],
fbTx: TransactionResponse,
): Promise<{
signed_tx: { data: components['schemas']['SEISignedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbSigner = this.getSigner(integration);
const completedTx = await fbSigner.waitForTxCompletion(fbTx);
const signature = completedTx.signedMessages?.[0]?.signature?.fullSig;
if (!signature) {
throw new Error(ERRORS.MISSING_SIGNATURE);
}
const preparedTx = await this.client.POST('/sei/transaction/prepare', {
body: {
pubkey: tx.pubkey,
tx_body: tx.tx_body,
tx_auth_info: tx.tx_auth_info,
signature: signature,
},
});
if (preparedTx.error || !preparedTx.data) {
throw new Error(ERRORS.FAILED_TO_PREPARE);
}
return {
signed_tx: preparedTx.data,
fireblocks_tx: completedTx,
};
}
/**
* Sign a SEI transaction on Fireblocks (combines createSeiTx and waitForSeiTxCompletion)
*/
async signSeiTx(
integration: FireblocksIntegration,
tx: components['schemas']['SEIUnsignedTx'] | components['schemas']['SEIStakeUnsignedTx'],
note?: string,
): Promise<{
signed_tx: { data: components['schemas']['SEISignedTx'] };
fireblocks_tx: TransactionResponse;
}> {
const fbTx = await this.createSeiTx(integration, tx, note);
return await this.waitForSeiTxCompletion(integration, tx, fbTx);
}
async signSuiTx(
integration: FireblocksIntegration,
tx: components['schemas']['SUITx'],
note?: string,
): Promise<{
signed_tx: { data: components['schemas']['SUIBroadcastTxPayload'] };
fireblocks_tx: TransactionResponse;
}> {
const payload = {
rawMessageData: {
messages: [
{
content: tx.unsigned_tx_hash.substring(2),
},
],
},
};
const fbSigner = this.getSigner(integration);
const fbNote = note ? note : 'SUI tx from @kilnfi/sdk';
const fbTx = await fbSigner.sign(payload, 'SUI', fbNote);
const signature = fbTx.signedMessages?.[0]?.signature?.fullSig;
const fbPubkey = fbTx.signedMessages?.[0]?.publicKey;
if (!signature) {
throw new Error(ERRORS.MISSING_SIGNATURE);
}
if (!fbPubkey) {
throw new Error(ERRORS.MISSING_PUBLIC_KEY);
}
const preparedTx = await this.client.POST('/sui/transaction/prepare', {
body: {
pubkey: fbPubkey,
signature: signature,
tx_serialized: tx.unsigned_tx_serialized,
},
});
if (preparedTx.error || !preparedTx.data) {
throw new Error(ERRORS.FAILED_TO_PREPARE);
}
return {
signed_tx: preparedTx.data,
fireblocks_tx: fbTx,
};
}
}