Skip to content

Commit ffd4daf

Browse files
authored
add signNobleTx and noble example (#155)
* add signNobleTx and noble example * format
1 parent 5479cbc commit ffd4daf

File tree

5 files changed

+150
-1
lines changed

5 files changed

+150
-1
lines changed

bun.lockb

32 Bytes
Binary file not shown.

examples/noble.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { Kiln, usdcToUusdc } from "../src/kiln";
2+
import fs from "node:fs";
3+
import 'dotenv/config'
4+
import type { FireblocksIntegration } from "../src/fireblocks.ts";
5+
6+
7+
const apiSecret = fs.readFileSync(`${__dirname}/fireblocks_secret_prod.key`, 'utf8');
8+
9+
const k = new Kiln({
10+
baseUrl: process.env.KILN_API_URL as string,
11+
apiToken: process.env.KILN_API_KEY as string,
12+
});
13+
14+
const vault: FireblocksIntegration = {
15+
provider: 'fireblocks',
16+
fireblocksApiKey: process.env.FIREBLOCKS_API_KEY as string,
17+
fireblocksSecretKey: apiSecret,
18+
vaultId: 37
19+
};
20+
21+
try {
22+
console.log('crafting...');
23+
// const s = await k.fireblocks.getSdk(vault);
24+
// const p = await s.getPublicKeyInfoForVaultAccount({
25+
// assetId: "DYDX_DYDX",
26+
// compressed: true,
27+
// vaultAccountId: 37,
28+
// change: 0,
29+
// addressIndex: 0,
30+
// });
31+
// console.log(getCosmosAddress('02d92b48d3e9ef34f2016eac7857a02768c88e30aea7a2366bc5ba032a22eceb8b', 'noble'));
32+
const tx = await k.client.POST(
33+
'/v1/noble/transaction/burn-usdc',
34+
{
35+
body: {
36+
pubkey: '02d92b48d3e9ef34f2016eac7857a02768c88e30aea7a2366bc5ba032a22eceb8b',
37+
recipient: '0xBC86717BaD3F8CcF86d2882a6bC351C94580A994',
38+
amount_uusdc: usdcToUusdc('0.01').toString(),
39+
}
40+
}
41+
);
42+
console.log('signing...');
43+
if(!tx.data?.data) throw new Error('No data in response');
44+
const signResponse = await k.fireblocks.signNobleTx(vault, tx.data.data);
45+
console.log('broadcasting...');
46+
if(!signResponse.signed_tx?.data?.signed_tx_serialized) throw new Error('No signed_tx in response');
47+
const broadcastedTx = await k.client.POST("/v1/noble/transaction/broadcast", {
48+
body: {
49+
tx_serialized: signResponse.signed_tx.data.signed_tx_serialized,
50+
}
51+
});
52+
console.log(broadcastedTx);
53+
54+
} catch (err) {
55+
console.log(err);
56+
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"homepage": "https://github.com/kilnfi/sdk-js#readme",
3636
"dependencies": {
3737
"@types/bun": "^1.1.11",
38+
"bech32": "^2.0.0",
3839
"fireblocks-sdk": "^5.32.0",
3940
"openapi-fetch": "^0.12.0",
4041
"viem": "^2.21.29"

src/fireblocks.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,61 @@ export class FireblocksService {
453453
};
454454
}
455455

456+
/**
457+
* Sign a NOBLE transaction on Fireblocks
458+
*/
459+
async signNobleTx(
460+
integration: FireblocksIntegration,
461+
tx: components['schemas']['DYDXUnsignedTx'],
462+
note?: string,
463+
): Promise<{
464+
signed_tx: { data: components['schemas']['DYDXSignedTx'] };
465+
fireblocks_tx: TransactionResponse;
466+
}> {
467+
const payload = {
468+
rawMessageData: {
469+
messages: [
470+
{
471+
content: tx.unsigned_tx_hash,
472+
derivationPath: [44, 118, integration.vaultId, 0, 0],
473+
preHash: {
474+
content: tx.unsigned_tx_serialized,
475+
hashAlgorithm: 'SHA256',
476+
},
477+
},
478+
],
479+
algorithm: SigningAlgorithm.MPC_ECDSA_SECP256K1,
480+
},
481+
};
482+
483+
const fbSigner = this.getSigner(integration);
484+
const fbNote = note ? note : 'NOBLE tx from @kilnfi/sdk';
485+
const fbTx = await fbSigner.sign(payload, undefined, fbNote);
486+
const signature = fbTx.signedMessages?.[0]?.signature.fullSig;
487+
488+
if (!signature) {
489+
throw new Error('Fireblocks signature is missing');
490+
}
491+
492+
const preparedTx = await this.client.POST('/v1/noble/transaction/prepare', {
493+
body: {
494+
pubkey: tx.pubkey,
495+
tx_body: tx.tx_body,
496+
tx_auth_info: tx.tx_auth_info,
497+
signature: signature,
498+
},
499+
});
500+
501+
if (preparedTx.error) {
502+
throw new Error('Failed to prepare transaction');
503+
}
504+
505+
return {
506+
signed_tx: preparedTx.data,
507+
fireblocks_tx: fbTx,
508+
};
509+
}
510+
456511
/**
457512
* Sign a OSMO transaction on Fireblocks
458513
*/

src/utils.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { formatUnits, parseUnits } from 'viem';
1+
import { bech32 } from 'bech32';
2+
import { formatUnits, parseUnits, ripemd160, sha256 } from 'viem';
23

34
/**
45
* Convert wei to ETH
@@ -161,9 +162,45 @@ export const uusdcToUsdc = (uusdc: bigint): string => {
161162
return formatUnits(uusdc, 6);
162163
};
163164

165+
/**
166+
* Convert USDC to uUSDC
167+
*/
168+
export const usdcToUusdc = (usdc: string): bigint => {
169+
return parseUnits(usdc, 6);
170+
};
171+
164172
/**
165173
* Convert uKAVA to KAVA
166174
*/
167175
export const ukavaToKava = (ukava: bigint): string => {
168176
return formatUnits(ukava, 6);
169177
};
178+
179+
/**
180+
* Get a cosmos address from its public key and prefix
181+
* @param pubkey
182+
* @param prefix
183+
*/
184+
export const getCosmosAddress = (pubkey: string, prefix: string): string => {
185+
const compressed_pubkey = compressPublicKey(pubkey);
186+
const hash = sha256(Uint8Array.from(Buffer.from(compressed_pubkey, 'hex')));
187+
const raw_addr = ripemd160(hash, 'bytes');
188+
return bech32.encode(prefix, bech32.toWords(raw_addr));
189+
};
190+
191+
/**
192+
* Compress a cosmos public key
193+
* @param pubkey
194+
*/
195+
export const compressPublicKey = (pubkey: string): string => {
196+
const pub_key_buffer = new Uint8Array(Buffer.from(pubkey, 'hex'));
197+
if (pub_key_buffer.length !== 65) return pubkey;
198+
const x = pub_key_buffer.slice(1, 33);
199+
const y = pub_key_buffer.slice(33);
200+
// We will add 0x02 if the last bit isn't set, otherwise we will add 0x03
201+
// @ts-ignore
202+
const prefix = y[y.length - 1] & 1 ? '03' : '02';
203+
// Concatenate the prefix and the x value to get the compressed key
204+
const compressed_key = Buffer.concat([new Uint8Array(Buffer.from(prefix, 'hex')), x]);
205+
return compressed_key.toString('hex');
206+
};

0 commit comments

Comments
 (0)