From 9889abfdaabed15a7898677b55ad295bba811882 Mon Sep 17 00:00:00 2001 From: Martin Saldinger <51637671+LeTamanoir@users.noreply.github.com> Date: Wed, 8 Jan 2025 15:21:24 +0100 Subject: [PATCH] simplify error messages (#186) * cleanup example * atom example * tia example * dydx example * fet example * inj example * om example * osmo example * zeta example * ada example * improve error messages * fix lint --- examples/.env.example | 5 +- examples/ada.ts | 83 ++++++++++++++++++++ examples/atom.ts | 81 ++++++++++++++++++++ examples/dydx.ts | 81 ++++++++++++++++++++ examples/env.ts | 37 +++++++++ examples/eth.ts | 52 ------------- examples/fet.ts | 87 +++++++++++++++++++++ examples/inj.ts | 81 ++++++++++++++++++++ examples/kava.ts | 116 ++++++++++++++++++---------- examples/near.ts | 46 ----------- examples/noble.ts | 56 -------------- examples/om.ts | 111 ++++++++++++++++++--------- examples/osmo.ts | 81 ++++++++++++++++++++ examples/sol.ts | 48 ------------ examples/tia.ts | 81 ++++++++++++++++++++ examples/ton.ts | 60 --------------- examples/trx.ts | 45 ----------- examples/xtz.ts | 47 ------------ examples/zeta.ts | 87 +++++++++++++++++++++ src/fireblocks.ts | 92 +++++++++++----------- src/fireblocks_signer.ts | 159 ++++++++++++++++++--------------------- src/validators.ts | 30 ++++++++ 22 files changed, 1009 insertions(+), 557 deletions(-) create mode 100644 examples/ada.ts create mode 100644 examples/atom.ts create mode 100644 examples/dydx.ts create mode 100644 examples/env.ts delete mode 100644 examples/eth.ts create mode 100644 examples/fet.ts create mode 100644 examples/inj.ts delete mode 100644 examples/near.ts delete mode 100644 examples/noble.ts create mode 100644 examples/osmo.ts delete mode 100644 examples/sol.ts create mode 100644 examples/tia.ts delete mode 100644 examples/ton.ts delete mode 100644 examples/trx.ts delete mode 100644 examples/xtz.ts create mode 100644 examples/zeta.ts diff --git a/examples/.env.example b/examples/.env.example index ea4bdea..d208346 100644 --- a/examples/.env.example +++ b/examples/.env.example @@ -1,4 +1,5 @@ KILN_API_URL="" -KILN_ACCOUNT_ID="" KILN_API_KEY="" -FIREBLOCKS_API_KEY="" \ No newline at end of file +KILN_ACCOUNT_ID="" +FIREBLOCKS_API_KEY="" +FIREBLOCKS_VAULT_ID="" diff --git a/examples/ada.ts b/examples/ada.ts new file mode 100644 index 0000000..96bf1c0 --- /dev/null +++ b/examples/ada.ts @@ -0,0 +1,83 @@ +import { Kiln, KILN_VALIDATORS } from '../src/kiln.ts'; +import type { FireblocksIntegration } from '../src/fireblocks.ts'; +import { loadEnv } from './env.ts'; + +const { kilnApiKey, kilnAccountId, kilnApiUrl, fireblocksApiKey, fireblocksApiSecret, fireblocksVaultId } = + await loadEnv(); + +const k = new Kiln({ + baseUrl: kilnApiUrl, + apiToken: kilnApiKey, +}); + +const vault: FireblocksIntegration = { + config: { + apiKey: fireblocksApiKey, + secretKey: fireblocksApiSecret, + basePath: 'https://api.fireblocks.io/v1', + }, + vaultId: fireblocksVaultId, +}; + +// +// Get the pubkey from Fireblocks +// +const fireblocksWallet = ( + await k.fireblocks + .getSdk(vault) + .vaults.getVaultAccountAssetAddressesPaginated({ assetId: 'ADA', vaultAccountId: vault.vaultId, limit: 1 }) +).data.addresses?.[0].address; +if (!fireblocksWallet) { + console.log('Failed to get pubkey'); + process.exit(0); +} + +// +// Craft the transaction +// +console.log('Crafting transaction...'); +const txRequest = await k.client.POST('/ada/transaction/stake', { + body: { + account_id: kilnAccountId, + pool_id: KILN_VALIDATORS.ADA.mainnet.KILN0, + wallet: fireblocksWallet, + }, +}); +if (txRequest.error) { + console.log('Failed to craft transaction:', txRequest); + process.exit(1); +} else { + console.log('Crafted transaction:', txRequest.data); +} +console.log('\n\n\n'); + +// +// Sign the transaction +// +console.log('Signing transaction...'); +const signRequest = await (async () => { + try { + return await k.fireblocks.signAdaTx(vault, txRequest.data.data); + } catch (err) { + console.log('Failed to sign transaction:', err); + process.exit(1); + } +})(); +console.log('Signed transaction:', signRequest); +console.log('\n\n\n'); + +// +// Broadcast the transaction +// +console.log('Broadcasting transaction...'); +const broadcastedRequest = await k.client.POST('/ada/transaction/broadcast', { + body: { + tx_serialized: signRequest.signed_tx.data.signed_tx_serialized, + }, +}); +if (broadcastedRequest.error) { + console.log('Failed to broadcast transaction:', broadcastedRequest); + process.exit(1); +} else { + console.log('Broadcasted transaction:', broadcastedRequest.data); +} diff --git a/examples/atom.ts b/examples/atom.ts new file mode 100644 index 0000000..4e4bf5e --- /dev/null +++ b/examples/atom.ts @@ -0,0 +1,81 @@ +import { atomToUatom, Kiln, KILN_VALIDATORS } from '../src/kiln.ts'; +import type { FireblocksIntegration } from '../src/fireblocks.ts'; +import { loadEnv } from './env.ts'; + +const { kilnApiKey, kilnAccountId, kilnApiUrl, fireblocksApiKey, fireblocksApiSecret, fireblocksVaultId } = + await loadEnv(); + +const k = new Kiln({ + baseUrl: kilnApiUrl, + apiToken: kilnApiKey, +}); + +const vault: FireblocksIntegration = { + config: { + apiKey: fireblocksApiKey, + secretKey: fireblocksApiSecret, + basePath: 'https://api.fireblocks.io/v1', + }, + vaultId: fireblocksVaultId, +}; + +// +// Get the pubkey from Fireblocks +// +const fireblocksPubkey = (await k.fireblocks.getPubkey(vault, 'ATOM_COS')).publicKey; +if (!fireblocksPubkey) { + console.log('Failed to get pubkey'); + process.exit(0); +} + +// +// Craft the transaction +// +console.log('Crafting transaction...'); +const txRequest = await k.client.POST('/atom/transaction/stake', { + body: { + account_id: kilnAccountId, + pubkey: fireblocksPubkey, + validator: KILN_VALIDATORS.ATOM.mainnet.KILN, + amount_uatom: atomToUatom('0.01').toString(), + restake_rewards: false, + }, +}); +if (txRequest.error) { + console.log('Failed to craft transaction:', txRequest); + process.exit(1); +} else { + console.log('Crafted transaction:', txRequest.data); +} +console.log('\n\n\n'); + +// +// Sign the transaction +// +console.log('Signing transaction...'); +const signRequest = await (async () => { + try { + return await k.fireblocks.signAtomTx(vault, txRequest.data.data); + } catch (err) { + console.log('Failed to sign transaction:', err); + process.exit(1); + } +})(); +console.log('Signed transaction:', signRequest); +console.log('\n\n\n'); + +// +// Broadcast the transaction +// +console.log('Broadcasting transaction...'); +const broadcastedRequest = await k.client.POST('/atom/transaction/broadcast', { + body: { + tx_serialized: signRequest.signed_tx.data.signed_tx_serialized, + }, +}); +if (broadcastedRequest.error) { + console.log('Failed to broadcast transaction:', broadcastedRequest); + process.exit(1); +} else { + console.log('Broadcasted transaction:', broadcastedRequest.data); +} diff --git a/examples/dydx.ts b/examples/dydx.ts new file mode 100644 index 0000000..a3adf0b --- /dev/null +++ b/examples/dydx.ts @@ -0,0 +1,81 @@ +import { dydxToAdydx, Kiln, KILN_VALIDATORS } from '../src/kiln.ts'; +import type { FireblocksIntegration } from '../src/fireblocks.ts'; +import { loadEnv } from './env.ts'; + +const { kilnApiKey, kilnAccountId, kilnApiUrl, fireblocksApiKey, fireblocksApiSecret, fireblocksVaultId } = + await loadEnv(); + +const k = new Kiln({ + baseUrl: kilnApiUrl, + apiToken: kilnApiKey, +}); + +const vault: FireblocksIntegration = { + config: { + apiKey: fireblocksApiKey, + secretKey: fireblocksApiSecret, + basePath: 'https://api.fireblocks.io/v1', + }, + vaultId: fireblocksVaultId, +}; + +// +// Get the pubkey from Fireblocks +// +const fireblocksPubkey = (await k.fireblocks.getPubkey(vault, 'DYDX_DYDX')).publicKey; +if (!fireblocksPubkey) { + console.log('Failed to get pubkey'); + process.exit(0); +} + +// +// Craft the transaction +// +console.log('Crafting transaction...'); +const txRequest = await k.client.POST('/dydx/transaction/stake', { + body: { + account_id: kilnAccountId, + pubkey: fireblocksPubkey, + validator: KILN_VALIDATORS.DYDX.mainnet.KILN, + amount_adydx: dydxToAdydx('0.01').toString(), + restake_rewards: false, + }, +}); +if (txRequest.error) { + console.log('Failed to craft transaction:', txRequest); + process.exit(1); +} else { + console.log('Crafted transaction:', txRequest.data); +} +console.log('\n\n\n'); + +// +// Sign the transaction +// +console.log('Signing transaction...'); +const signRequest = await (async () => { + try { + return await k.fireblocks.signDydxTx(vault, txRequest.data.data); + } catch (err) { + console.log('Failed to sign transaction:', err); + process.exit(1); + } +})(); +console.log('Signed transaction:', signRequest); +console.log('\n\n\n'); + +// +// Broadcast the transaction +// +console.log('Broadcasting transaction...'); +const broadcastedRequest = await k.client.POST('/dydx/transaction/broadcast', { + body: { + tx_serialized: signRequest.signed_tx.data.signed_tx_serialized, + }, +}); +if (broadcastedRequest.error) { + console.log('Failed to broadcast transaction:', broadcastedRequest); + process.exit(1); +} else { + console.log('Broadcasted transaction:', broadcastedRequest.data); +} diff --git a/examples/env.ts b/examples/env.ts new file mode 100644 index 0000000..d7381b8 --- /dev/null +++ b/examples/env.ts @@ -0,0 +1,37 @@ +export const loadEnv = async () => { + if (!Bun.env.KILN_API_URL) { + console.log('KILN_API_URL is required'); + process.exit(1); + } + + if (!Bun.env.KILN_ACCOUNT_ID) { + console.log('KILN_ACCOUNT_ID is required'); + process.exit(1); + } + + if (!Bun.env.KILN_API_KEY) { + console.log('KILN_API_KEY is required'); + process.exit(1); + } + + if (!Bun.env.FIREBLOCKS_API_KEY) { + console.log('FIREBLOCKS_API_KEY is required'); + process.exit(1); + } + + if (!Bun.env.FIREBLOCKS_VAULT_ID) { + console.log('FIREBLOCKS_VAULT_ID is required'); + process.exit(1); + } + + const fireblocksApiSecret = await Bun.file('fireblocks_secret_prod.key').text(); + + return { + kilnApiUrl: Bun.env.KILN_API_URL, + kilnApiKey: Bun.env.KILN_API_KEY, + kilnAccountId: Bun.env.KILN_ACCOUNT_ID, + fireblocksApiKey: Bun.env.FIREBLOCKS_API_KEY, + fireblocksVaultId: Bun.env.FIREBLOCKS_VAULT_ID as `${number}`, + fireblocksApiSecret, + }; +}; diff --git a/examples/eth.ts b/examples/eth.ts deleted file mode 100644 index 4571f49..0000000 --- a/examples/eth.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Kiln } from "../src/kiln"; -import type { Integration } from "../lib/types/integrations"; -import fs from "node:fs"; -import 'dotenv/config' - - -const apiSecret = fs.readFileSync(`${__dirname}/fireblocks_secret.key`, 'utf8'); - -const k = new Kiln({ - baseUrl: process.env.KILN_API_URL, - apiToken: process.env.KILN_API_KEY, -}); - -const vault: Integration = { - provider: 'fireblocks', - fireblocksApiKey: process.env.FIREBLOCKS_API_KEY, - fireblocksSecretKey: apiSecret, - vaultId: 14, - fireblocksDestinationId: '07df91b4-7788-4833-a8f4-428facef68cc', -}; - -try { - console.log('crafting...'); - const tx = await k.client.POST( - '/eth/transaction/stake', - { - body: { - account_id: process.env.KILN_ACCOUNT_ID, - wallet: '0x91CcA1b774350232391d337213C0dE544DF1Ed75', - amount_wei: '32000000000000000000' - } - } - ); - // Sign and broadcast using Fireblocks contract calls - // console.log('signing and broadcasting...'); - // const res = await k.fireblocks.signAndBroadcastEthTx(vault, tx.data.data, 'ETH_TEST6'); - // console.log(res); - - // Sign and broadcast using Fireblocks raw signing and Kiln Connect to broadcast - console.log('signing...'); - const signResponse = await k.fireblocks.signEthTx(vault, tx.data.data, "ETH_TEST6"); - console.log('broadcasting...'); - const broadcastedTx = await k.client.POST("/eth/transaction/broadcast", { - body: { - tx_serialized: signResponse.signed_tx.data.signed_tx_serialized, - } - }); - console.log(broadcastedTx); - -} catch (err) { - console.log(err); -} \ No newline at end of file diff --git a/examples/fet.ts b/examples/fet.ts new file mode 100644 index 0000000..3bc923c --- /dev/null +++ b/examples/fet.ts @@ -0,0 +1,87 @@ +import { fetToAfet, Kiln, KILN_VALIDATORS } from '../src/kiln.ts'; +import type { FireblocksIntegration } from '../src/fireblocks.ts'; +import { loadEnv } from './env.ts'; + +const { kilnApiKey, kilnAccountId, kilnApiUrl, fireblocksApiKey, fireblocksApiSecret, fireblocksVaultId } = + await loadEnv(); + +const k = new Kiln({ + baseUrl: kilnApiUrl, + apiToken: kilnApiKey, +}); + +const vault: FireblocksIntegration = { + config: { + apiKey: fireblocksApiKey, + secretKey: fireblocksApiSecret, + basePath: 'https://api.fireblocks.io/v1', + }, + vaultId: fireblocksVaultId, +}; + +// +// Get the pubkey from Fireblocks +// +const fireblocksPubkey = ( + await k.fireblocks.getSdk(vault).vaults.getPublicKeyInfo({ + algorithm: 'MPC_ECDSA_SECP256K1', + derivationPath: JSON.stringify([44, 118, Number(vault.vaultId), 0, 0]), + compressed: true, + }) +).data.publicKey; +if (!fireblocksPubkey) { + console.log('Failed to get pubkey'); + process.exit(0); +} + +// +// Craft the transaction +// +console.log('Crafting transaction...'); +const txRequest = await k.client.POST('/fet/transaction/stake', { + body: { + account_id: kilnAccountId, + pubkey: fireblocksPubkey, + validator: KILN_VALIDATORS.FET.mainnet.KILN, + amount_afet: fetToAfet('0.01').toString(), + restake_rewards: false, + }, +}); +if (txRequest.error) { + console.log('Failed to craft transaction:', txRequest); + process.exit(1); +} else { + console.log('Crafted transaction:', txRequest.data); +} +console.log('\n\n\n'); + +// +// Sign the transaction +// +console.log('Signing transaction...'); +const signRequest = await (async () => { + try { + return await k.fireblocks.signFetTx(vault, txRequest.data.data); + } catch (err) { + console.log('Failed to sign transaction:', err); + process.exit(1); + } +})(); +console.log('Signed transaction:', signRequest); +console.log('\n\n\n'); + +// +// Broadcast the transaction +// +console.log('Broadcasting transaction...'); +const broadcastedRequest = await k.client.POST('/fet/transaction/broadcast', { + body: { + tx_serialized: signRequest.signed_tx.data.signed_tx_serialized, + }, +}); +if (broadcastedRequest.error) { + console.log('Failed to broadcast transaction:', broadcastedRequest); + process.exit(1); +} else { + console.log('Broadcasted transaction:', broadcastedRequest.data); +} diff --git a/examples/inj.ts b/examples/inj.ts new file mode 100644 index 0000000..0e26fea --- /dev/null +++ b/examples/inj.ts @@ -0,0 +1,81 @@ +import { injToInj, Kiln, KILN_VALIDATORS } from '../src/kiln.ts'; +import type { FireblocksIntegration } from '../src/fireblocks.ts'; +import { loadEnv } from './env.ts'; + +const { kilnApiKey, kilnAccountId, kilnApiUrl, fireblocksApiKey, fireblocksApiSecret, fireblocksVaultId } = + await loadEnv(); + +const k = new Kiln({ + baseUrl: kilnApiUrl, + apiToken: kilnApiKey, +}); + +const vault: FireblocksIntegration = { + config: { + apiKey: fireblocksApiKey, + secretKey: fireblocksApiSecret, + basePath: 'https://api.fireblocks.io/v1', + }, + vaultId: fireblocksVaultId, +}; + +// +// Get the pubkey from Fireblocks +// +const fireblocksPubkey = (await k.fireblocks.getPubkey(vault, 'INJ_INJ')).publicKey; +if (!fireblocksPubkey) { + console.log('Failed to get pubkey'); + process.exit(0); +} + +// +// Craft the transaction +// +console.log('Crafting transaction...'); +const txRequest = await k.client.POST('/inj/transaction/stake', { + body: { + account_id: kilnAccountId, + pubkey: fireblocksPubkey, + validator: KILN_VALIDATORS.INJ.mainnet.KILN, + amount_inj: injToInj('0.01').toString(), + restake_rewards: false, + }, +}); +if (txRequest.error) { + console.log('Failed to craft transaction:', txRequest); + process.exit(1); +} else { + console.log('Crafted transaction:', txRequest.data); +} +console.log('\n\n\n'); + +// +// Sign the transaction +// +console.log('Signing transaction...'); +const signRequest = await (async () => { + try { + return await k.fireblocks.signInjTx(vault, txRequest.data.data); + } catch (err) { + console.log('Failed to sign transaction:', err); + process.exit(1); + } +})(); +console.log('Signed transaction:', signRequest); +console.log('\n\n\n'); + +// +// Broadcast the transaction +// +console.log('Broadcasting transaction...'); +const broadcastedRequest = await k.client.POST('/inj/transaction/broadcast', { + body: { + tx_serialized: signRequest.signed_tx.data.signed_tx_serialized, + }, +}); +if (broadcastedRequest.error) { + console.log('Failed to broadcast transaction:', broadcastedRequest); + process.exit(1); +} else { + console.log('Broadcasted transaction:', broadcastedRequest.data); +} diff --git a/examples/kava.ts b/examples/kava.ts index 7681767..9bd69a4 100644 --- a/examples/kava.ts +++ b/examples/kava.ts @@ -1,48 +1,86 @@ -import { kavaToUkava, Kiln } from "../src/kiln"; -import fs from "node:fs"; -import 'dotenv/config' -import type { FireblocksIntegration } from "../src/fireblocks.ts"; +import { kavaToUkava, Kiln, KILN_VALIDATORS } from '../src/kiln.ts'; +import type { FireblocksIntegration } from '../src/fireblocks.ts'; +import { loadEnv } from './env.ts'; - -const apiSecret = fs.readFileSync(`${__dirname}/fireblocks_secret_prod.key`, 'utf8'); +const { kilnApiKey, kilnAccountId, kilnApiUrl, fireblocksApiKey, fireblocksApiSecret, fireblocksVaultId } = + await loadEnv(); const k = new Kiln({ - baseUrl: process.env.KILN_API_URL as string, - apiToken: process.env.KILN_API_KEY as string, + baseUrl: kilnApiUrl, + apiToken: kilnApiKey, }); const vault: FireblocksIntegration = { - provider: 'fireblocks', - fireblocksApiKey: process.env.FIREBLOCKS_API_KEY as string, - fireblocksSecretKey: apiSecret, - vaultId: 37 + config: { + apiKey: fireblocksApiKey, + secretKey: fireblocksApiSecret, + basePath: 'https://api.fireblocks.io/v1', + }, + vaultId: fireblocksVaultId, }; -try { - console.log('crafting...'); - const tx = await k.client.POST( - '/kava/transaction/stake', - { - body: { - account_id: process.env.KILN_ACCOUNT_ID as string, - pubkey: '0233335b6c68a85e01b85055d0e8c2fcef42fed977898422ef3a5f6baf9a9a413e', - validator: 'kavavaloper1djqecw6nn5tydxq0shan7srv8j65clsf79myt8', - amount_ukava: kavaToUkava('0.01').toString(), - } - } - ); - console.log('signing...'); - if(!tx.data?.data) throw new Error('No data in response'); - const signResponse = await k.fireblocks.signKavaTx(vault, tx.data.data); - console.log('broadcasting...'); - if(!signResponse.signed_tx?.data?.signed_tx_serialized) throw new Error('No signed_tx in response'); - const broadcastedTx = await k.client.POST("/kava/transaction/broadcast", { - body: { - tx_serialized: signResponse.signed_tx.data.signed_tx_serialized, - } - }); - console.log(broadcastedTx); +// +// Get the pubkey from Fireblocks +// +const fireblocksPubkey = ( + await k.fireblocks.getSdk(vault).vaults.getPublicKeyInfo({ + algorithm: 'MPC_ECDSA_SECP256K1', + derivationPath: JSON.stringify([44, 459, Number(vault.vaultId), 0, 0]), + compressed: true, + }) +).data.publicKey; +if (!fireblocksPubkey) { + console.log('Failed to get pubkey'); + process.exit(0); +} + +// +// Craft the transaction +// +console.log('Crafting transaction...'); +const txRequest = await k.client.POST('/kava/transaction/stake', { + body: { + account_id: kilnAccountId, + pubkey: fireblocksPubkey, + validator: KILN_VALIDATORS.KAVA.mainnet.KILN, + amount_ukava: kavaToUkava('0.01').toString(), + }, +}); +if (txRequest.error) { + console.log('Failed to craft transaction:', txRequest); + process.exit(1); +} else { + console.log('Crafted transaction:', txRequest.data); +} +console.log('\n\n\n'); -} catch (err) { - console.log(err); -} \ No newline at end of file +// +// Sign the transaction +// +console.log('Signing transaction...'); +const signRequest = await (async () => { + try { + return await k.fireblocks.signKavaTx(vault, txRequest.data.data); + } catch (err) { + console.log('Failed to sign transaction:', err); + process.exit(1); + } +})(); +console.log('Signed transaction:', signRequest); +console.log('\n\n\n'); + +// +// Broadcast the transaction +// +console.log('Broadcasting transaction...'); +const broadcastedRequest = await k.client.POST('/kava/transaction/broadcast', { + body: { + tx_serialized: signRequest.signed_tx.data.signed_tx_serialized, + }, +}); +if (broadcastedRequest.error) { + console.log('Failed to broadcast transaction:', broadcastedRequest); + process.exit(1); +} else { + console.log('Broadcasted transaction:', broadcastedRequest.data); +} diff --git a/examples/near.ts b/examples/near.ts deleted file mode 100644 index c97af55..0000000 --- a/examples/near.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Kiln } from "../src/kiln"; -import type { FireblocksIntegration } from "../src/fireblocks"; -import fs from "node:fs"; -import 'dotenv/config' - - -const apiSecret = fs.readFileSync(`${__dirname}/fireblocks_secret.key`, 'utf8'); - -const k = new Kiln({ - baseUrl: 'https://api.testnet.kiln.fi', - apiToken: process.env.KILN_API_KEY, -}); - -const vault: FireblocksIntegration = { - provider: 'fireblocks', - fireblocksApiKey: process.env.FIREBLOCKS_API_KEY, - fireblocksSecretKey: apiSecret, - vaultId: 14 -}; - -try { - console.log('crafting...'); - const tx = await k.client.POST( - '/near/transaction/stake', - { - body: { - account_id: 'd3f1b917-72b1-4982-a4dd-93fce579a708', - wallet: 'c36b1a5da2e60d1fd5d3a6b46f7399eb26571457f3272f3c978bc9527ad2335f', - pool_id: 'kiln.pool.f863973.m0', - amount_yocto: '1000000000000000000000000', - } - } - ); - console.log('signing...'); - const signResponse = await k.fireblocks.signNearTx(vault, tx.data.data, "NEAR_TEST"); - console.log('broadcasting...'); - const broadcastedTx = await k.client.POST("/near/transaction/broadcast", { - body: { - signed_tx_serialized: signResponse.signed_tx.data.signed_tx_serialized, - } - }); - console.log(broadcastedTx); - -} catch (err) { - console.log(err); -} \ No newline at end of file diff --git a/examples/noble.ts b/examples/noble.ts deleted file mode 100644 index 3b66f2e..0000000 --- a/examples/noble.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Kiln, usdcToUusdc } from "../src/kiln"; -import fs from "node:fs"; -import 'dotenv/config' -import type { FireblocksIntegration } from "../src/fireblocks.ts"; - - -const apiSecret = fs.readFileSync(`${__dirname}/fireblocks_secret_prod.key`, 'utf8'); - -const k = new Kiln({ - baseUrl: process.env.KILN_API_URL as string, - apiToken: process.env.KILN_API_KEY as string, -}); - -const vault: FireblocksIntegration = { - provider: 'fireblocks', - fireblocksApiKey: process.env.FIREBLOCKS_API_KEY as string, - fireblocksSecretKey: apiSecret, - vaultId: 37 -}; - -try { - console.log('crafting...'); - // const s = await k.fireblocks.getSdk(vault); - // const p = await s.getPublicKeyInfoForVaultAccount({ - // assetId: "DYDX_DYDX", - // compressed: true, - // vaultAccountId: 37, - // change: 0, - // addressIndex: 0, - // }); - // console.log(getCosmosAddress('02d92b48d3e9ef34f2016eac7857a02768c88e30aea7a2366bc5ba032a22eceb8b', 'noble')); - const tx = await k.client.POST( - '/noble/transaction/burn-usdc', - { - body: { - pubkey: '02d92b48d3e9ef34f2016eac7857a02768c88e30aea7a2366bc5ba032a22eceb8b', - recipient: '0xBC86717BaD3F8CcF86d2882a6bC351C94580A994', - amount_uusdc: usdcToUusdc('0.01').toString(), - } - } - ); - console.log('signing...'); - if(!tx.data?.data) throw new Error('No data in response'); - const signResponse = await k.fireblocks.signNobleTx(vault, tx.data.data); - console.log('broadcasting...'); - if(!signResponse.signed_tx?.data?.signed_tx_serialized) throw new Error('No signed_tx in response'); - const broadcastedTx = await k.client.POST("/noble/transaction/broadcast", { - body: { - tx_serialized: signResponse.signed_tx.data.signed_tx_serialized, - } - }); - console.log(broadcastedTx); - -} catch (err) { - console.log(err); -} \ No newline at end of file diff --git a/examples/om.ts b/examples/om.ts index d8d94f0..4cf6b4a 100644 --- a/examples/om.ts +++ b/examples/om.ts @@ -1,46 +1,87 @@ -import { omToUom, Kiln } from '../src/kiln.ts'; -import fs from 'node:fs'; -import 'dotenv/config'; +import { Kiln, KILN_VALIDATORS, omToUom } from '../src/kiln.ts'; import type { FireblocksIntegration } from '../src/fireblocks.ts'; +import { loadEnv } from './env.ts'; -const apiSecret = fs.readFileSync(`${__dirname}/fireblocks_secret_prod.key`, 'utf8'); +const { kilnApiKey, kilnAccountId, kilnApiUrl, fireblocksApiKey, fireblocksApiSecret, fireblocksVaultId } = + await loadEnv(); const k = new Kiln({ - baseUrl: process.env.KILN_API_URL as string, - apiToken: process.env.KILN_API_KEY as string, + baseUrl: kilnApiUrl, + apiToken: kilnApiKey, }); const vault: FireblocksIntegration = { - provider: 'fireblocks', - fireblocksApiKey: process.env.FIREBLOCKS_API_KEY as string, - fireblocksSecretKey: apiSecret, - vaultId: 17, + config: { + apiKey: fireblocksApiKey, + secretKey: fireblocksApiSecret, + basePath: 'https://api.fireblocks.io/v1', + }, + vaultId: fireblocksVaultId, }; -try { - console.log('crafting...'); - const tx = await k.client.POST('/om/transaction/stake', { - body: { - account_id: process.env.KILN_ACCOUNT_ID as string, - pubkey: '028dfa6f41c655e38a0f8f2e3f3aa3e1246907a9bb299933f11996e2a345a21e10', - validator: 'mantravaloper146mj09yzu3mvz7pmy4dvs4z9wr2mst7ram37xw', - amount_uom: omToUom('0.01').toString(), - restake_rewards: false, - }, - }); +// +// Get the pubkey from Fireblocks +// +const fireblocksPubkey = ( + await k.fireblocks.getSdk(vault).vaults.getPublicKeyInfo({ + algorithm: 'MPC_ECDSA_SECP256K1', + derivationPath: JSON.stringify([44, 118, Number(vault.vaultId), 0, 0]), + compressed: true, + }) +).data.publicKey; +if (!fireblocksPubkey) { + console.log('Failed to get pubkey'); + process.exit(0); +} + +// +// Craft the transaction +// +console.log('Crafting transaction...'); +const txRequest = await k.client.POST('/om/transaction/stake', { + body: { + account_id: kilnAccountId, + pubkey: fireblocksPubkey, + validator: KILN_VALIDATORS.OM.mainnet.KILN, + amount_uom: omToUom('0.01').toString(), + restake_rewards: false, + }, +}); +if (txRequest.error) { + console.log('Failed to craft transaction:', txRequest); + process.exit(1); +} else { + console.log('Crafted transaction:', txRequest.data); +} +console.log('\n\n\n'); + +// +// Sign the transaction +// +console.log('Signing transaction...'); +const signRequest = await (async () => { + try { + return await k.fireblocks.signOmTx(vault, txRequest.data.data); + } catch (err) { + console.log('Failed to sign transaction:', err); + process.exit(1); + } +})(); +console.log('Signed transaction:', signRequest); +console.log('\n\n\n'); - console.log(tx); - console.log('signing...'); - if (!tx.data?.data) throw new Error('No data in response'); - const signResponse = await k.fireblocks.signOmTx(vault, tx.data.data); - console.log('broadcasting...'); - if (!signResponse.signed_tx?.data?.signed_tx_serialized) throw new Error('No signed_tx in response'); - const broadcastedTx = await k.client.POST('/om/transaction/broadcast', { - body: { - tx_serialized: signResponse.signed_tx.data.signed_tx_serialized, - }, - }); - console.log(broadcastedTx); -} catch (err) { - console.log(err); +// +// Broadcast the transaction +// +console.log('Broadcasting transaction...'); +const broadcastedRequest = await k.client.POST('/om/transaction/broadcast', { + body: { + tx_serialized: signRequest.signed_tx.data.signed_tx_serialized, + }, +}); +if (broadcastedRequest.error) { + console.log('Failed to broadcast transaction:', broadcastedRequest); + process.exit(1); +} else { + console.log('Broadcasted transaction:', broadcastedRequest.data); } diff --git a/examples/osmo.ts b/examples/osmo.ts new file mode 100644 index 0000000..dc3eca1 --- /dev/null +++ b/examples/osmo.ts @@ -0,0 +1,81 @@ +import { Kiln, KILN_VALIDATORS, osmoToUosmo } from '../src/kiln.ts'; +import type { FireblocksIntegration } from '../src/fireblocks.ts'; +import { loadEnv } from './env.ts'; + +const { kilnApiKey, kilnAccountId, kilnApiUrl, fireblocksApiKey, fireblocksApiSecret, fireblocksVaultId } = + await loadEnv(); + +const k = new Kiln({ + baseUrl: kilnApiUrl, + apiToken: kilnApiKey, +}); + +const vault: FireblocksIntegration = { + config: { + apiKey: fireblocksApiKey, + secretKey: fireblocksApiSecret, + basePath: 'https://api.fireblocks.io/v1', + }, + vaultId: fireblocksVaultId, +}; + +// +// Get the pubkey from Fireblocks +// +const fireblocksPubkey = (await k.fireblocks.getPubkey(vault, 'OSMO')).publicKey; +if (!fireblocksPubkey) { + console.log('Failed to get pubkey'); + process.exit(0); +} + +// +// Craft the transaction +// +console.log('Crafting transaction...'); +const txRequest = await k.client.POST('/osmo/transaction/stake', { + body: { + account_id: kilnAccountId, + pubkey: fireblocksPubkey, + validator: KILN_VALIDATORS.OSMO.mainnet.INTEROP, + amount_uosmo: osmoToUosmo('0.01').toString(), + restake_rewards: false, + }, +}); +if (txRequest.error) { + console.log('Failed to craft transaction:', txRequest); + process.exit(1); +} else { + console.log('Crafted transaction:', txRequest.data); +} +console.log('\n\n\n'); + +// +// Sign the transaction +// +console.log('Signing transaction...'); +const signRequest = await (async () => { + try { + return await k.fireblocks.signOsmoTx(vault, txRequest.data.data); + } catch (err) { + console.log('Failed to sign transaction:', err); + process.exit(1); + } +})(); +console.log('Signed transaction:', signRequest); +console.log('\n\n\n'); + +// +// Broadcast the transaction +// +console.log('Broadcasting transaction...'); +const broadcastedRequest = await k.client.POST('/osmo/transaction/broadcast', { + body: { + tx_serialized: signRequest.signed_tx.data.signed_tx_serialized, + }, +}); +if (broadcastedRequest.error) { + console.log('Failed to broadcast transaction:', broadcastedRequest); + process.exit(1); +} else { + console.log('Broadcasted transaction:', broadcastedRequest.data); +} diff --git a/examples/sol.ts b/examples/sol.ts deleted file mode 100644 index 6fe69ab..0000000 --- a/examples/sol.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Kiln, solToLamports } from "../src/kiln"; -import fs from "node:fs"; -import 'dotenv/config' -import type { FireblocksIntegration } from "../src/fireblocks.ts"; - - -const apiSecret = fs.readFileSync(`${__dirname}/fireblocks_secret.key`, 'utf8'); - -const k = new Kiln({ - baseUrl: process.env.KILN_API_URL as string, - apiToken: process.env.KILN_API_KEY as string, -}); - -const vault: FireblocksIntegration = { - provider: 'fireblocks', - fireblocksApiKey: process.env.FIREBLOCKS_API_KEY as string, - fireblocksSecretKey: apiSecret, - vaultId: 14 -}; - -try { - console.log('crafting...'); - const tx = await k.client.POST( - '/sol/transaction/stake', - { - body: { - account_id: 'd3f1b917-72b1-4982-a4dd-93fce579a708', - wallet: 'E9qDxpwuPFeFB7vDdDibdCbWwHy867eYz3rV29bAevuC', - amount_lamports: solToLamports('0.01').toString(), - vote_account_address: 'FwR3PbjS5iyqzLiLugrBqKSa5EKZ4vK9SKs7eQXtT59f' - } - } - ); - console.log('signing...'); - if(!tx.data) throw new Error('No data in response'); - const signResponse = await k.fireblocks.signSolTx(vault, tx.data.data, "SOL_TEST"); - if(!signResponse.signed_tx?.data.signed_tx_serialized) throw new Error('No signed_tx in response'); - console.log('broadcasting...'); - const broadcastedTx = await k.client.POST("/sol/transaction/broadcast", { - body: { - tx_serialized: signResponse.signed_tx.data.signed_tx_serialized - } - }); - console.log(broadcastedTx); - -} catch (err) { - console.log(err); -} \ No newline at end of file diff --git a/examples/tia.ts b/examples/tia.ts new file mode 100644 index 0000000..717cb13 --- /dev/null +++ b/examples/tia.ts @@ -0,0 +1,81 @@ +import { Kiln, KILN_VALIDATORS, tiaToUtia } from '../src/kiln.ts'; +import type { FireblocksIntegration } from '../src/fireblocks.ts'; +import { loadEnv } from './env.ts'; + +const { kilnApiKey, kilnAccountId, kilnApiUrl, fireblocksApiKey, fireblocksApiSecret, fireblocksVaultId } = + await loadEnv(); + +const k = new Kiln({ + baseUrl: kilnApiUrl, + apiToken: kilnApiKey, +}); + +const vault: FireblocksIntegration = { + config: { + apiKey: fireblocksApiKey, + secretKey: fireblocksApiSecret, + basePath: 'https://api.fireblocks.io/v1', + }, + vaultId: fireblocksVaultId, +}; + +// +// Get the pubkey from Fireblocks +// +const fireblocksPubkey = (await k.fireblocks.getPubkey(vault, 'CELESTIA')).publicKey; +if (!fireblocksPubkey) { + console.log('Failed to get pubkey'); + process.exit(0); +} + +// +// Craft the transaction +// +console.log('Crafting transaction...'); +const txRequest = await k.client.POST('/tia/transaction/stake', { + body: { + account_id: kilnAccountId, + pubkey: fireblocksPubkey, + validator: KILN_VALIDATORS.TIA.mainnet.KILN, + amount_utia: tiaToUtia('0.01').toString(), + restake_rewards: false, + }, +}); +if (txRequest.error) { + console.log('Failed to craft transaction:', txRequest); + process.exit(1); +} else { + console.log('Crafted transaction:', txRequest.data); +} +console.log('\n\n\n'); + +// +// Sign the transaction +// +console.log('Signing transaction...'); +const signRequest = await (async () => { + try { + return await k.fireblocks.signTiaTx(vault, txRequest.data.data); + } catch (err) { + console.log('Failed to sign transaction:', err); + process.exit(1); + } +})(); +console.log('Signed transaction:', signRequest); +console.log('\n\n\n'); + +// +// Broadcast the transaction +// +console.log('Broadcasting transaction...'); +const broadcastedRequest = await k.client.POST('/tia/transaction/broadcast', { + body: { + tx_serialized: signRequest.signed_tx.data.signed_tx_serialized, + }, +}); +if (broadcastedRequest.error) { + console.log('Failed to broadcast transaction:', broadcastedRequest); + process.exit(1); +} else { + console.log('Broadcasted transaction:', broadcastedRequest.data); +} diff --git a/examples/ton.ts b/examples/ton.ts deleted file mode 100644 index d7b2667..0000000 --- a/examples/ton.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Kiln, tonToNanoton } from "../src/kiln"; -import fs from "node:fs"; -import 'dotenv/config' -import type { FireblocksIntegration } from "../src/fireblocks.ts"; - - -const apiSecret = fs.readFileSync(`${__dirname}/fireblocks_secret_prod.key`, 'utf8'); - -const k = new Kiln({ - baseUrl: process.env.KILN_API_URL as string, - apiToken: process.env.KILN_API_KEY as string, -}); - -const vault: FireblocksIntegration = { - provider: 'fireblocks', - fireblocksApiKey: process.env.FIREBLOCKS_API_KEY as string, - fireblocksSecretKey: apiSecret, - vaultId: 17 -}; - -try { - console.log('crafting...'); - const tx = await k.client.POST( - '/ton/transaction/stake-ton-whales-pool', - { - body: { - account_id: process.env.KILN_ACCOUNT_ID as string, - wallet: 'UQAd57R6nYTCpgo1OxSmpbFRsIO6HIIfO2SW6WcfCe5qIo08', - pool_address: 'EQBXDSbE9s03Waq62YuGdtqe-bcjsN6K9fi64eUy9M8H_Yhf', - vesting_contract_address: 'EQBdL-upJbGStg4MF8acfEfilqd34cfoHe_k2E-yecki3yS6', - amount_nanoton: tonToNanoton('2').toString(), - } - } - ); - // const tx = await k.client.POST( - // '/v1/ton/transaction/unstake-ton-whales-pool', - // { - // body: { - // wallet: 'UQAd57R6nYTCpgo1OxSmpbFRsIO6HIIfO2SW6WcfCe5qIo08', - // pool_address: 'EQBXDSbE9s03Waq62YuGdtqe-bcjsN6K9fi64eUy9M8H_Yhf', - // vesting_contract_address: 'EQBdL-upJbGStg4MF8acfEfilqd34cfoHe_k2E-yecki3yS6', - // amount_nanoton: tonToNanoton('0.5').toString(), - // } - // } - // ); - console.log('signing...'); - if(!tx.data?.data) throw new Error('No data in response'); - const signResponse = await k.fireblocks.signTonTx(vault, tx.data.data, "TON"); - console.log('broadcasting...'); - if(!signResponse.signed_tx?.data?.signed_tx_serialized) throw new Error('No signed_tx in response'); - const broadcastedTx = await k.client.POST("/ton/transaction/broadcast", { - body: { - tx_serialized: signResponse.signed_tx.data.signed_tx_serialized, - } - }); - console.log(broadcastedTx); - -} catch (err) { - console.log(err); -} \ No newline at end of file diff --git a/examples/trx.ts b/examples/trx.ts deleted file mode 100644 index b41cfe5..0000000 --- a/examples/trx.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Kiln, trxToSun } from '../src/kiln'; -import type { FireblocksIntegration } from '../src/fireblocks'; -import fs from 'node:fs'; -import 'dotenv/config'; - -const apiSecret = fs.readFileSync(`${__dirname}/fireblocks_secret_prod.key`, 'utf8'); - -const k = new Kiln({ - baseUrl: process.env.KILN_API_URL as string, - apiToken: process.env.KILN_API_KEY, -}); - -const vault: FireblocksIntegration = { - provider: 'fireblocks', - fireblocksApiKey: process.env.FIREBLOCKS_API_KEY as string, - fireblocksSecretKey: apiSecret, - vaultId: 17, -}; - -try { - console.log('crafting...'); - const tx = await k.client.POST('/trx/transaction/stake', { - body: { - account_id: '9d7b0e32-203d-4b1d-9ff1-99ccae420c7f', - owner_address: 'TAERHY5gyzDRmAaeqqa6C4Fuyc9HLnnHx7', - amount_sun: Number(trxToSun('1')), - resource: 'BANDWIDTH', - }, - }); - - if (!tx.data) throw new Error('No data in response'); - - console.log('signing...'); - const signResponse = await k.fireblocks.signTrxTx(vault, tx.data.data); - - console.log('broadcasting...'); - const broadcastedTx = await k.client.POST('/trx/transaction/broadcast', { - body: { - tx_serialized: signResponse.signed_tx.data.signed_tx_serialized, - }, - }); - console.log(broadcastedTx); -} catch (err) { - console.log(err); -} diff --git a/examples/xtz.ts b/examples/xtz.ts deleted file mode 100644 index 1b84e08..0000000 --- a/examples/xtz.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Kiln } from "../src/kiln"; -import fs from "node:fs"; -import 'dotenv/config' -import type { FireblocksIntegration } from "../src/fireblocks.ts"; - - -const apiSecret = fs.readFileSync(`${__dirname}/fireblocks_secret.key`, 'utf8'); - -const k = new Kiln({ - baseUrl: process.env.KILN_API_URL as string, - apiToken: process.env.KILN_API_KEY as string, -}); - -const vault: FireblocksIntegration = { - provider: 'fireblocks', - fireblocksApiKey: process.env.FIREBLOCKS_API_KEY as string, - fireblocksSecretKey: apiSecret, - vaultId: 14 -}; - -try { - console.log('crafting...'); - const tx = await k.client.POST( - '/xtz/transaction/delegate', - { - body: { - account_id: 'd3f1b917-72b1-4982-a4dd-93fce579a708', - wallet: 'tz1WmYmabzcmguEiJzZyL4rKDhBdgfa1bLod', - baker_address: 'tz1YgDUQV2eXm8pUWNz3S5aWP86iFzNp4jnD' - } - } - ); - console.log('signing...'); - if(!tx.data) throw new Error('No data in response'); - const signResponse = await k.fireblocks.signXtzTx(vault, tx.data.data, "XTZ_TEST"); - if(!signResponse.signed_tx?.data.signed_tx_serialized) throw new Error('No signed_tx in response'); - console.log('broadcasting...'); - const broadcastedTx = await k.client.POST("/xtz/transaction/broadcast", { - body: { - tx_serialized: signResponse.signed_tx.data.signed_tx_serialized - } - }); - console.log(broadcastedTx); - -} catch (err) { - console.log(err); -} \ No newline at end of file diff --git a/examples/zeta.ts b/examples/zeta.ts new file mode 100644 index 0000000..fe3a0d8 --- /dev/null +++ b/examples/zeta.ts @@ -0,0 +1,87 @@ +import { Kiln, KILN_VALIDATORS, zetaToAzeta } from '../src/kiln.ts'; +import type { FireblocksIntegration } from '../src/fireblocks.ts'; +import { loadEnv } from './env.ts'; + +const { kilnApiKey, kilnAccountId, kilnApiUrl, fireblocksApiKey, fireblocksApiSecret, fireblocksVaultId } = + await loadEnv(); + +const k = new Kiln({ + baseUrl: kilnApiUrl, + apiToken: kilnApiKey, +}); + +const vault: FireblocksIntegration = { + config: { + apiKey: fireblocksApiKey, + secretKey: fireblocksApiSecret, + basePath: 'https://api.fireblocks.io/v1', + }, + vaultId: fireblocksVaultId, +}; + +// +// Get the pubkey from Fireblocks +// +const fireblocksPubkey = ( + await k.fireblocks.getSdk(vault).vaults.getPublicKeyInfo({ + algorithm: 'MPC_ECDSA_SECP256K1', + derivationPath: JSON.stringify([44, 118, Number(vault.vaultId), 0, 0]), + compressed: true, + }) +).data.publicKey; +if (!fireblocksPubkey) { + console.log('Failed to get pubkey'); + process.exit(0); +} + +// +// Craft the transaction +// +console.log('Crafting transaction...'); +const txRequest = await k.client.POST('/zeta/transaction/stake', { + body: { + account_id: kilnAccountId, + pubkey: fireblocksPubkey, + validator: KILN_VALIDATORS.ZETA.mainnet.KILN, + amount_azeta: zetaToAzeta('0.01').toString(), + restake_rewards: false, + }, +}); +if (txRequest.error) { + console.log('Failed to craft transaction:', txRequest); + process.exit(1); +} else { + console.log('Crafted transaction:', txRequest.data); +} +console.log('\n\n\n'); + +// +// Sign the transaction +// +console.log('Signing transaction...'); +const signRequest = await (async () => { + try { + return await k.fireblocks.signZetaTx(vault, txRequest.data.data); + } catch (err) { + console.log('Failed to sign transaction:', err); + process.exit(1); + } +})(); +console.log('Signed transaction:', signRequest); +console.log('\n\n\n'); + +// +// Broadcast the transaction +// +console.log('Broadcasting transaction...'); +const broadcastedRequest = await k.client.POST('/zeta/transaction/broadcast', { + body: { + tx_serialized: signRequest.signed_tx.data.signed_tx_serialized, + }, +}); +if (broadcastedRequest.error) { + console.log('Failed to broadcast transaction:', broadcastedRequest); + process.exit(1); +} else { + console.log('Broadcasted transaction:', broadcastedRequest.data); +} diff --git a/src/fireblocks.ts b/src/fireblocks.ts index c41a555..a43c04f 100644 --- a/src/fireblocks.ts +++ b/src/fireblocks.ts @@ -8,7 +8,7 @@ import { } from '@fireblocks/ts-sdk'; import type { Client } from 'openapi-fetch'; -import { FireblocksSigner } from './fireblocks_signer.js'; +import { type FireblocksAssetId, FireblocksSigner } from './fireblocks_signer.js'; import type { components, paths } from './openapi/schema.js'; export type FireblocksIntegration = ( @@ -18,6 +18,11 @@ export type FireblocksIntegration = ( 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.', +}; + export class FireblocksService { client: Client; @@ -46,7 +51,10 @@ export class FireblocksService { /** * Get fireblocks wallet pubkey compressed */ - async getPubkey(integration: FireblocksIntegration, assetId: string): Promise { + async getPubkey( + integration: FireblocksIntegration, + assetId: (string & {}) | FireblocksAssetId, + ): Promise { const fbSdk = this.getSdk(integration); const data = await fbSdk.vaults.getPublicKeyInfoForAddress({ assetId: assetId, @@ -97,7 +105,7 @@ export class FireblocksService { .map((signedMessage) => signedMessage.signature?.fullSig) .filter((s) => s !== undefined); if (!signatures) { - throw new Error('Fireblocks signature is missing'); + throw new Error(ERRORS.MISSING_SIGNATURE); } const preparedTx = await this.client.POST('/sol/transaction/prepare', { @@ -108,7 +116,7 @@ export class FireblocksService { }); if (preparedTx.error) { - throw new Error('Failed to prepare transaction'); + throw new Error(ERRORS.FAILED_TO_PREPARE); } return { @@ -154,7 +162,7 @@ export class FireblocksService { signature: message.signature?.fullSig as string, })); if (!signedMessages) { - throw new Error('Fireblocks signature is missing'); + throw new Error(ERRORS.MISSING_SIGNATURE); } const preparedTx = await this.client.POST('/ada/transaction/prepare', { @@ -165,7 +173,7 @@ export class FireblocksService { }); if (preparedTx.error) { - throw new Error('Failed to prepare transaction'); + throw new Error(ERRORS.FAILED_TO_PREPARE); } return { @@ -205,7 +213,7 @@ export class FireblocksService { const signature = fbTx.signedMessages?.[0]?.signature?.fullSig; if (!signature) { - throw new Error('Fireblocks signature is missing'); + throw new Error(ERRORS.MISSING_SIGNATURE); } const preparedTx = await this.client.POST('/atom/transaction/prepare', { @@ -218,7 +226,7 @@ export class FireblocksService { }); if (preparedTx.error) { - throw new Error('Failed to prepare transaction'); + throw new Error(ERRORS.FAILED_TO_PREPARE); } return { @@ -258,7 +266,7 @@ export class FireblocksService { const signature = fbTx.signedMessages?.[0]?.signature?.fullSig; if (!signature) { - throw new Error('Fireblocks signature is missing'); + throw new Error(ERRORS.MISSING_SIGNATURE); } const preparedTx = await this.client.POST('/dydx/transaction/prepare', { @@ -271,7 +279,7 @@ export class FireblocksService { }); if (preparedTx.error) { - throw new Error('Failed to prepare transaction'); + throw new Error(ERRORS.FAILED_TO_PREPARE); } return { @@ -313,7 +321,7 @@ export class FireblocksService { const signature = fbTx.signedMessages?.[0]?.signature?.fullSig; if (!signature) { - throw new Error('Fireblocks signature is missing'); + throw new Error(ERRORS.MISSING_SIGNATURE); } const preparedTx = await this.client.POST('/fet/transaction/prepare', { @@ -326,7 +334,7 @@ export class FireblocksService { }); if (preparedTx.error) { - throw new Error('Failed to prepare transaction'); + throw new Error(ERRORS.FAILED_TO_PREPARE); } return { @@ -368,7 +376,7 @@ export class FireblocksService { const signature = fbTx.signedMessages?.[0]?.signature?.fullSig; if (!signature) { - throw new Error('Fireblocks signature is missing'); + throw new Error(ERRORS.MISSING_SIGNATURE); } const preparedTx = await this.client.POST('/om/transaction/prepare', { @@ -381,7 +389,7 @@ export class FireblocksService { }); if (preparedTx.error) { - throw new Error('Failed to prepare transaction'); + throw new Error(ERRORS.FAILED_TO_PREPARE); } return { @@ -421,7 +429,7 @@ export class FireblocksService { const signature = fbTx.signedMessages?.[0]?.signature?.fullSig; if (!signature) { - throw new Error('Fireblocks signature is missing'); + throw new Error(ERRORS.MISSING_SIGNATURE); } const preparedTx = await this.client.POST('/inj/transaction/prepare', { @@ -434,7 +442,7 @@ export class FireblocksService { }); if (preparedTx.error) { - throw new Error('Failed to prepare transaction'); + throw new Error(ERRORS.FAILED_TO_PREPARE); } return { @@ -476,7 +484,7 @@ export class FireblocksService { const signature = fbTx.signedMessages?.[0]?.signature?.fullSig; if (!signature) { - throw new Error('Fireblocks signature is missing'); + throw new Error(ERRORS.MISSING_SIGNATURE); } const preparedTx = await this.client.POST('/kava/transaction/prepare', { @@ -489,7 +497,7 @@ export class FireblocksService { }); if (preparedTx.error) { - throw new Error('Failed to prepare transaction'); + throw new Error(ERRORS.FAILED_TO_PREPARE); } return { @@ -531,7 +539,7 @@ export class FireblocksService { const signature = fbTx.signedMessages?.[0]?.signature?.fullSig; if (!signature) { - throw new Error('Fireblocks signature is missing'); + throw new Error(ERRORS.MISSING_SIGNATURE); } const preparedTx = await this.client.POST('/noble/transaction/prepare', { @@ -544,7 +552,7 @@ export class FireblocksService { }); if (preparedTx.error) { - throw new Error('Failed to prepare transaction'); + throw new Error(ERRORS.FAILED_TO_PREPARE); } return { @@ -584,7 +592,7 @@ export class FireblocksService { const signature = fbTx.signedMessages?.[0]?.signature?.fullSig; if (!signature) { - throw new Error('Fireblocks signature is missing'); + throw new Error(ERRORS.MISSING_SIGNATURE); } const preparedTx = await this.client.POST('/osmo/transaction/prepare', { @@ -597,7 +605,7 @@ export class FireblocksService { }); if (preparedTx.error) { - throw new Error('Failed to prepare transaction'); + throw new Error(ERRORS.FAILED_TO_PREPARE); } return { @@ -637,7 +645,7 @@ export class FireblocksService { const signature = fbTx.signedMessages?.[0]?.signature?.fullSig; if (!signature) { - throw new Error('Fireblocks signature is missing'); + throw new Error(ERRORS.MISSING_SIGNATURE); } const preparedTx = await this.client.POST('/tia/transaction/prepare', { @@ -650,7 +658,7 @@ export class FireblocksService { }); if (preparedTx.error) { - throw new Error('Failed to prepare transaction'); + throw new Error(ERRORS.FAILED_TO_PREPARE); } return { @@ -692,7 +700,7 @@ export class FireblocksService { const signature = fbTx.signedMessages?.[0]?.signature?.fullSig; if (!signature) { - throw new Error('Fireblocks signature is missing'); + throw new Error(ERRORS.MISSING_SIGNATURE); } const preparedTx = await this.client.POST('/zeta/transaction/prepare', { @@ -705,7 +713,7 @@ export class FireblocksService { }); if (preparedTx.error) { - throw new Error('Failed to prepare transaction'); + throw new Error(ERRORS.FAILED_TO_PREPARE); } return { @@ -740,7 +748,7 @@ export class FireblocksService { const fbTx = await fbSigner.sign(payload, 'DOT', fbNote); if (!fbTx.signedMessages?.[0]?.signature?.fullSig) { - throw new Error('Fireblocks signature is missing'); + throw new Error(ERRORS.MISSING_SIGNATURE); } const signature = `0x00${fbTx.signedMessages?.[0]?.signature.fullSig}`; @@ -753,7 +761,7 @@ export class FireblocksService { }); if (preparedTx.error) { - throw new Error('Failed to prepare transaction'); + throw new Error(ERRORS.FAILED_TO_PREPARE); } return { @@ -788,7 +796,7 @@ export class FireblocksService { const fbTx = await fbSigner.sign(payload, 'KSM', fbNote); if (!fbTx.signedMessages?.[0]?.signature?.fullSig) { - throw new Error('Fireblocks signature is missing'); + throw new Error(ERRORS.MISSING_SIGNATURE); } const signature = `0x00${fbTx.signedMessages?.[0]?.signature.fullSig}`; @@ -801,7 +809,7 @@ export class FireblocksService { }); if (preparedTx.error) { - throw new Error('Failed to prepare transaction'); + throw new Error(ERRORS.FAILED_TO_PREPARE); } return { @@ -843,7 +851,7 @@ export class FireblocksService { const signature = fbTx?.signedMessages?.[0]?.signature; if (!signature) { - throw new Error('Fireblocks signature is missing'); + throw new Error(ERRORS.MISSING_SIGNATURE); } const preparedTx = await this.client.POST('/eth/transaction/prepare', { @@ -856,7 +864,7 @@ export class FireblocksService { }); if (preparedTx.error) { - throw new Error('Failed to prepare transaction'); + throw new Error(ERRORS.FAILED_TO_PREPARE); } return { @@ -917,7 +925,7 @@ export class FireblocksService { const signature = fbTx?.signedMessages?.[0]?.signature; if (!signature) { - throw new Error('Fireblocks signature is missing'); + throw new Error(ERRORS.MISSING_SIGNATURE); } const preparedTx = await this.client.POST('/pol/transaction/prepare', { @@ -930,7 +938,7 @@ export class FireblocksService { }); if (preparedTx.error) { - throw new Error('Failed to prepare transaction'); + throw new Error(ERRORS.FAILED_TO_PREPARE); } return { @@ -986,7 +994,7 @@ export class FireblocksService { const signature = fbTx.signedMessages?.[0]?.signature?.fullSig; if (!signature) { - throw new Error('Fireblocks signature is missing'); + throw new Error(ERRORS.MISSING_SIGNATURE); } const preparedTx = await this.client.POST('/ton/transaction/prepare', { @@ -998,7 +1006,7 @@ export class FireblocksService { }); if (preparedTx.error) { - throw new Error('Failed to prepare transaction'); + throw new Error(ERRORS.FAILED_TO_PREPARE); } return { @@ -1035,7 +1043,7 @@ export class FireblocksService { const signature = fbTx.signedMessages?.[0]?.signature?.fullSig; if (!signature) { - throw new Error('Fireblocks signature is missing'); + throw new Error(ERRORS.MISSING_SIGNATURE); } const preparedTx = await this.client.POST('/xtz/transaction/prepare', { @@ -1046,7 +1054,7 @@ export class FireblocksService { }); if (preparedTx.error) { - throw new Error('Failed to prepare transaction'); + throw new Error(ERRORS.FAILED_TO_PREPARE); } return { @@ -1083,7 +1091,7 @@ export class FireblocksService { const signature = fbTx.signedMessages?.[0]?.signature?.fullSig; if (!signature) { - throw new Error('Fireblocks signature is missing'); + throw new Error(ERRORS.MISSING_SIGNATURE); } const preparedTx = await this.client.POST('/near/transaction/prepare', { @@ -1094,7 +1102,7 @@ export class FireblocksService { }); if (preparedTx.error) { - throw new Error('Failed to prepare transaction'); + throw new Error(ERRORS.FAILED_TO_PREPARE); } return { @@ -1133,7 +1141,7 @@ export class FireblocksService { const fbTx = await fbSigner.sign(payload, 'TRX', fbNote); if (!fbTx.signedMessages?.[0]?.signature) { - throw new Error('Fireblocks signature is missing'); + throw new Error(ERRORS.MISSING_SIGNATURE); } const signature = `${fbTx.signedMessages[0].signature.fullSig}0${fbTx.signedMessages[0].signature.v}`; @@ -1146,7 +1154,7 @@ export class FireblocksService { }); if (preparedTx.error) { - throw new Error('Failed to prepare transaction'); + throw new Error(ERRORS.FAILED_TO_PREPARE); } return { diff --git a/src/fireblocks_signer.ts b/src/fireblocks_signer.ts index ff3179c..3c17efb 100644 --- a/src/fireblocks_signer.ts +++ b/src/fireblocks_signer.ts @@ -49,24 +49,25 @@ export class FireblocksSigner { * @param fbTx fireblocks transaction */ protected async waitForTxCompletion(fbTx: CreateTransactionResponse): Promise { - try { - let tx = fbTx; - while (tx.status !== 'COMPLETED') { - if (tx.status === 'BLOCKED' || tx.status === 'FAILED' || tx.status === 'CANCELLED') { - throw Error(`Fireblocks signer: the transaction has been ${tx.status}`); - } - if (tx.status === 'REJECTED') { - throw Error( - 'Fireblocks signer: the transaction has been rejected, make sure that the TAP security policy is not blocking the transaction', - ); - } - tx = (await this.fireblocks.transactions.getTransaction({ txId: fbTx.id as string })).data; + let tx = fbTx; + while (tx.status !== 'COMPLETED') { + // see https://developers.fireblocks.com/reference/transaction-substatuses#failed-substatuses + const ERRORS: Record = { + BLOCKED: 'The transaction has been blocked by the TAP security policy.', + FAILED: 'The transaction has failed.', + CANCELLED: 'The transaction has been cancelled.', + REJECTED: + 'The transaction has been rejected, make sure that the TAP security policy is not blocking the transaction.', + }; + if (tx.status && tx.status in ERRORS) { + throw Error(ERRORS[tx.status]); } - - return tx; - } catch (err) { - throw new Error(`Fireblocks signer (waitForTxCompletion): ${err}`); + // wait a bit before polling again + await new Promise((r) => setTimeout(r, 500)); + tx = (await this.fireblocks.transactions.getTransaction({ txId: fbTx.id as string })).data; } + + return tx; } /** @@ -76,28 +77,24 @@ export class FireblocksSigner { * @param note optional fireblocks custom note */ public async sign(payloadToSign: object, assetId?: FireblocksAssetId, note = ''): Promise { - try { - const assetArgs = assetId - ? ({ - assetId, - source: { - type: 'VAULT_ACCOUNT', - id: this.vaultId, - }, - } satisfies Partial) - : undefined; + const assetArgs = assetId + ? ({ + assetId, + source: { + type: 'VAULT_ACCOUNT', + id: this.vaultId, + }, + } satisfies Partial) + : undefined; - const tx: TransactionRequest = { - ...assetArgs, - operation: 'RAW', - note, - extraParameters: payloadToSign, - }; - const fbTx = (await this.fireblocks.transactions.createTransaction({ transactionRequest: tx })).data; - return await this.waitForTxCompletion(fbTx); - } catch (err) { - throw new Error(`Fireblocks signer (signWithFB): ${err}`); - } + const tx: TransactionRequest = { + ...assetArgs, + operation: 'RAW', + note, + extraParameters: payloadToSign, + }; + const fbTx = (await this.fireblocks.transactions.createTransaction({ transactionRequest: tx })).data; + return await this.waitForTxCompletion(fbTx); } /** @@ -111,31 +108,27 @@ export class FireblocksSigner { assetId: 'ETH' | 'ETH_TEST5' | 'ETH_TEST6', note = '', ): Promise { - try { - const tx: TransactionRequest = { - assetId: assetId, - operation: 'TYPED_MESSAGE', - source: { - type: 'VAULT_ACCOUNT', - id: this.vaultId, - }, - note, - extraParameters: { - rawMessageData: { - messages: [ - { - content: eip712message, - type: 'EIP712', - }, - ], - }, + const tx: TransactionRequest = { + assetId: assetId, + operation: 'TYPED_MESSAGE', + source: { + type: 'VAULT_ACCOUNT', + id: this.vaultId, + }, + note, + extraParameters: { + rawMessageData: { + messages: [ + { + content: eip712message, + type: 'EIP712', + }, + ], }, - }; - const fbTx = (await this.fireblocks.transactions.createTransaction({ transactionRequest: tx })).data; - return await this.waitForTxCompletion(fbTx); - } catch (err) { - throw new Error(`Fireblocks signer (signWithFB): ${err}`); - } + }, + }; + const fbTx = (await this.fireblocks.transactions.createTransaction({ transactionRequest: tx })).data; + return await this.waitForTxCompletion(fbTx); } /** @@ -155,29 +148,25 @@ export class FireblocksSigner { sendAmount = true, note = '', ): Promise { - try { - const txArgs: TransactionRequest = { - assetId: assetId, - operation: 'CONTRACT_CALL', - source: { - type: 'VAULT_ACCOUNT', - id: this.vaultId, - }, - destination: { - type: 'EXTERNAL_WALLET', - id: destinationId, - }, - amount: tx.amount_wei && sendAmount ? formatEther(BigInt(tx.amount_wei), 'wei') : '0', - note, - extraParameters: payloadToSign, - gasLimit: tx.gas_limit, - priorityFee: formatUnits(BigInt(tx.max_priority_fee_per_gas_wei), 9), - maxFee: formatUnits(BigInt(tx.max_fee_per_gas_wei), 9), - }; - const fbTx = (await this.fireblocks.transactions.createTransaction({ transactionRequest: txArgs })).data; - return await this.waitForTxCompletion(fbTx); - } catch (err) { - throw new Error(`Fireblocks signer (signAndBroadcastWithFB): ${err}`); - } + const txArgs: TransactionRequest = { + assetId: assetId, + operation: 'CONTRACT_CALL', + source: { + type: 'VAULT_ACCOUNT', + id: this.vaultId, + }, + destination: { + type: 'EXTERNAL_WALLET', + id: destinationId, + }, + amount: tx.amount_wei && sendAmount ? formatEther(BigInt(tx.amount_wei), 'wei') : '0', + note, + extraParameters: payloadToSign, + gasLimit: tx.gas_limit, + priorityFee: formatUnits(BigInt(tx.max_priority_fee_per_gas_wei), 9), + maxFee: formatUnits(BigInt(tx.max_fee_per_gas_wei), 9), + }; + const fbTx = (await this.fireblocks.transactions.createTransaction({ transactionRequest: txArgs })).data; + return await this.waitForTxCompletion(fbTx); } } diff --git a/src/validators.ts b/src/validators.ts index 1d36753..2eb5bcc 100644 --- a/src/validators.ts +++ b/src/validators.ts @@ -65,4 +65,34 @@ export const KILN_VALIDATORS = { KILN: 'zetavaloper1u9xeaqdjz3kky2ymdhdsn0ra5uy9tc3ep3yfhe', }, }, + KAVA: { + mainnet: { + KILN: 'kavavaloper1djqecw6nn5tydxq0shan7srv8j65clsf79myt8', + }, + }, + TIA: { + mainnet: { + KILN: 'celestiavaloper1djqecw6nn5tydxq0shan7srv8j65clsfmnxcfu', + }, + }, + DYDX: { + mainnet: { + KILN: 'dydxvaloper1u9xeaqdjz3kky2ymdhdsn0ra5uy9tc3elj2jte', + }, + }, + INJ: { + mainnet: { + KILN: 'injvaloper1rf0fczrw2gnhuju86kmjtku4dvf9dcc2mpe7pe', + }, + }, + OM: { + mainnet: { + KILN: 'mantravaloper146mj09yzu3mvz7pmy4dvs4z9wr2mst7ram37xw', + }, + }, + OSMO: { + mainnet: { + INTEROP: 'osmovaloper146mj09yzu3mvz7pmy4dvs4z9wr2mst7rq8p8gy', + }, + }, };