diff --git a/examples/mantra.ts b/examples/mantra.ts new file mode 100644 index 0000000..3c3d6c6 --- /dev/null +++ b/examples/mantra.ts @@ -0,0 +1,45 @@ +import { omToUom, Kiln } from '../src/kiln.ts'; +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('/mantra/transaction/stake', { + body: { + account_id: process.env.KILN_ACCOUNT_ID as string, + pubkey: '028dfa6f41c655e38a0f8f2e3f3aa3e1246907a9bb299933f11996e2a345a21e10', + validator: 'mantravaloper146mj09yzu3mvz7pmy4dvs4z9wr2mst7ram37xw', + amount_uom: omToUom('0.01').toString(), + }, + }); + + console.log(tx); + console.log('signing...'); + if (!tx.data?.data) throw new Error('No data in response'); + const signResponse = await k.fireblocks.signMantraTx(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('/mantra/transaction/broadcast', { + body: { + tx_serialized: signResponse.signed_tx.data.signed_tx_serialized, + }, + }); + console.log(broadcastedTx); +} catch (err) { + console.log(err); +} diff --git a/package.json b/package.json index 10ef056..de2ba4a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kilnfi/sdk", - "version": "4.0.4", + "version": "4.0.5", "autor": "Kiln (https://kiln.fi)", "license": "BUSL-1.1", "description": "JavaScript sdk for Kiln API", diff --git a/src/fireblocks.ts b/src/fireblocks.ts index f4e2223..3230948 100644 --- a/src/fireblocks.ts +++ b/src/fireblocks.ts @@ -345,6 +345,61 @@ export class FireblocksService { }; } + /** + * Sign a MANTRA transaction on Fireblocks + */ + async signMantraTx( + integration: FireblocksIntegration, + tx: components['schemas']['MANTRAUnsignedTx'] | components['schemas']['MANTRAStakeUnsignedTx'], + note?: string, + ): Promise<{ + signed_tx: { data: components['schemas']['MANTRASignedTx'] }; + fireblocks_tx: TransactionResponse; + }> { + const payload = { + rawMessageData: { + messages: [ + { + content: tx.unsigned_tx_hash, + derivationPath: [44, 118, integration.vaultId, 0, 0], + preHash: { + content: tx.unsigned_tx_serialized, + hashAlgorithm: 'SHA256', + }, + }, + ], + algorithm: SigningAlgorithm.MPC_ECDSA_SECP256K1, + }, + }; + + const fbSigner = this.getSigner(integration); + const fbNote = note ? note : 'MANTRA tx from @kilnfi/sdk'; + const fbTx = await fbSigner.sign(payload, undefined, fbNote); + const signature = fbTx.signedMessages?.[0]?.signature.fullSig; + + if (!signature) { + throw new Error('Fireblocks signature is missing'); + } + + const preparedTx = await this.client.POST('/mantra/transaction/prepare', { + body: { + pubkey: tx.pubkey, + tx_body: tx.tx_body, + tx_auth_info: tx.tx_auth_info, + signature: signature, + }, + }); + + if (preparedTx.error) { + throw new Error('Failed to prepare transaction'); + } + + return { + signed_tx: preparedTx.data, + fireblocks_tx: fbTx, + }; + } + /** * Sign a INJ transaction on Fireblocks */ diff --git a/src/utils.ts b/src/utils.ts index d4383d5..5c4ed91 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -71,6 +71,13 @@ export const kavaToUkava = (kava: string): bigint => { return parseUnits(kava, 6); }; +/** + * Convert OM to uOM + */ +export const omToUom = (om: string): bigint => { + return parseUnits(om, 6); +}; + /** * Convert uZETA to ZETA */ @@ -176,6 +183,13 @@ export const ukavaToKava = (ukava: bigint): string => { return formatUnits(ukava, 6); }; +/** + * Convert uOM to OM + */ +export const uomToOm = (uom: bigint): string => { + return formatUnits(uom, 6); +}; + /** * Get a cosmos address from its public key and prefix * @param pubkey