diff --git a/examples/trx.ts b/examples/trx.ts new file mode 100644 index 0000000..570af00 --- /dev/null +++ b/examples/trx.ts @@ -0,0 +1,45 @@ +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: '', + owner_address: 'TAERHY5gyzDRmAaeqqa6C4Fuyc9HLnnHx7', + amount_sun: trxToSun('1').toString(), + 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: { + signed_tx_serialized: signResponse.signed_tx.data.signed_tx_serialized, + }, + }); + console.log(broadcastedTx); +} catch (err) { + console.log(err); +} diff --git a/src/fireblocks.ts b/src/fireblocks.ts index 821bd8f..f4e2223 100644 --- a/src/fireblocks.ts +++ b/src/fireblocks.ts @@ -1005,7 +1005,7 @@ export class FireblocksService { } /** - * Sign a NEAR transaction on Fireblocks + * Sign a Near transaction on Fireblocks */ async signNearTx( integration: FireblocksIntegration, @@ -1051,4 +1051,56 @@ export class FireblocksService { fireblocks_tx: fbTx, }; } + + /** + * Sign a Trx transaction on Fireblocks + */ + async signTrxTx( + integration: FireblocksIntegration, + tx: components['schemas']['TRXUnsignedTx'], + note?: string, + ): Promise<{ + signed_tx: { data: components['schemas']['TRXSignedTx'] }; + fireblocks_tx: 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'; + const fbTx = await fbSigner.sign(payload, 'TRX', fbNote); + + if (!fbTx.signedMessages?.[0]?.signature) { + throw new Error('Fireblocks signature is missing'); + } + + const signature = `${fbTx.signedMessages[0].signature.fullSig}0${fbTx.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) { + throw new Error('Failed to prepare transaction'); + } + + return { + signed_tx: preparedTx.data, + fireblocks_tx: fbTx, + }; + } } diff --git a/src/fireblocks_signer.ts b/src/fireblocks_signer.ts index 200cfbe..65bf510 100644 --- a/src/fireblocks_signer.ts +++ b/src/fireblocks_signer.ts @@ -39,7 +39,8 @@ export type FireblocksAssetId = | 'INJ_INJ' | 'TON_TEST' | 'TON' - | 'KAVA_KAVA'; + | 'KAVA_KAVA' + | 'TRX'; export class FireblocksSigner { protected fireblocks: FireblocksSDK; diff --git a/src/utils.ts b/src/utils.ts index d640037..d4383d5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -239,3 +239,13 @@ export const tiaToUtia = (tia: string): bigint => { export const fetToAfet = (fet: string): bigint => { return parseUnits(fet, 18); // afet uses 18 decimals }; + +// Convert TRX to sun +export const trxToSun = (trx: string): bigint => { + return parseUnits(trx, 6); +}; + +// Convert sun to TRX +export const sunToTrx = (trx: bigint): string => { + return formatUnits(trx, 6); +};