Skip to content

Commit de219be

Browse files
committed
Add SUI support for Fireblocks integration
- Introduced a new example script for SUI transactions. - Added SUI to Fireblocks asset types. - Implemented SUI transaction signing and broadcasting in FireblocksService. - Added utility functions for converting SUI to mist and vice versa. - Updated KILN_VALIDATORS to include SUI mainnet validator address.
1 parent 099ff4f commit de219be

File tree

5 files changed

+165
-1
lines changed

5 files changed

+165
-1
lines changed

examples/sui.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import type { FireblocksIntegration } from '../src/fireblocks.ts';
2+
import { KILN_VALIDATORS, Kiln, suiToMist } from '../src/kiln.ts';
3+
import { loadEnv } from './env.ts';
4+
5+
const { kilnApiKey, kilnAccountId, kilnApiUrl, fireblocksApiKey, fireblocksApiSecret, fireblocksVaultId } =
6+
await loadEnv();
7+
8+
const k = new Kiln({
9+
baseUrl: kilnApiUrl,
10+
apiToken: kilnApiKey,
11+
});
12+
13+
const vault: FireblocksIntegration = {
14+
config: {
15+
apiKey: fireblocksApiKey,
16+
secretKey: fireblocksApiSecret,
17+
basePath: 'https://api.fireblocks.io/v1',
18+
},
19+
vaultId: fireblocksVaultId,
20+
};
21+
22+
//
23+
// Get the pubkey from Fireblocks
24+
//
25+
const fireblocksWallet = (
26+
await k.fireblocks
27+
.getSdk(vault)
28+
.vaults.getVaultAccountAssetAddressesPaginated({ assetId: 'SUI', vaultAccountId: vault.vaultId, limit: 1 })
29+
).data.addresses?.[0].address;
30+
if (!fireblocksWallet) {
31+
console.log('Failed to get pubkey');
32+
process.exit(0);
33+
}
34+
35+
console.log(fireblocksWallet);
36+
37+
//
38+
// Craft the transaction
39+
//
40+
console.log('Crafting transaction...');
41+
console.log('params:', {
42+
account_id: kilnAccountId,
43+
sender: fireblocksWallet,
44+
validator_address: KILN_VALIDATORS.SUI.mainnet.KILN,
45+
amount_mist: suiToMist('1.1').toString(),
46+
});
47+
const txRequest = await k.client.POST('/sui/transaction/stake', {
48+
body: {
49+
account_id: kilnAccountId,
50+
sender: fireblocksWallet,
51+
validator_address: KILN_VALIDATORS.SUI.mainnet.KILN,
52+
amount_mist: suiToMist('1.1').toString(),
53+
},
54+
});
55+
if (txRequest.error) {
56+
console.log('Failed to craft transaction:', txRequest);
57+
process.exit(1);
58+
} else {
59+
console.log('Crafted transaction:', txRequest.data);
60+
}
61+
console.log('\n\n\n');
62+
63+
//
64+
// Sign the transaction
65+
//
66+
console.log('Signing transaction...');
67+
const signRequest = await (async () => {
68+
try {
69+
return await k.fireblocks.signSuiTx(vault, txRequest.data.data);
70+
} catch (err) {
71+
console.log('Failed to sign transaction:', err);
72+
process.exit(1);
73+
}
74+
})();
75+
console.log('Signed transaction:', signRequest);
76+
console.log('\n\n\n');
77+
78+
//
79+
// Broadcast the transaction
80+
//
81+
console.log('Broadcasting transaction...');
82+
const broadcastedRequest = await k.client.POST('/sui/transaction/broadcast', {
83+
body: {
84+
tx_serialized: signRequest.signed_tx.data.tx_serialized,
85+
serialized_signature: signRequest.signed_tx.data.serialized_signature,
86+
},
87+
});
88+
if (broadcastedRequest.error) {
89+
console.log('Failed to broadcast transaction:', broadcastedRequest);
90+
process.exit(1);
91+
} else {
92+
console.log('Broadcasted transaction:', broadcastedRequest.data);
93+
}

src/fireblocks.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export type FireblocksIntegration = (
2121
const ERRORS = {
2222
MISSING_SIGNATURE: 'An error occurred while attempting to retrieve the signature from Fireblocks.',
2323
FAILED_TO_PREPARE: 'An error occurred while attempting to add the signature to the transaction.',
24+
MISSING_PUBLIC_KEY: 'An error occurred while attempting to retrieve the public key from Fireblocks.',
2425
};
2526

2627
export class FireblocksService {
@@ -1271,4 +1272,54 @@ export class FireblocksService {
12711272
fireblocks_tx: fbTx,
12721273
};
12731274
}
1275+
1276+
async signSuiTx(
1277+
integration: FireblocksIntegration,
1278+
tx: components['schemas']['SUITx'],
1279+
note?: string,
1280+
): Promise<{
1281+
signed_tx: { data: components['schemas']['SUIBroadcastTxPayload'] };
1282+
fireblocks_tx: TransactionResponse;
1283+
}> {
1284+
const payload = {
1285+
rawMessageData: {
1286+
messages: [
1287+
{
1288+
content: tx.unsigned_tx_hash.substring(2),
1289+
},
1290+
],
1291+
},
1292+
};
1293+
1294+
const fbSigner = this.getSigner(integration);
1295+
const fbNote = note ? note : 'SUI tx from @kilnfi/sdk';
1296+
const fbTx = await fbSigner.sign(payload, 'SUI', fbNote);
1297+
const signature = fbTx.signedMessages?.[0]?.signature?.fullSig;
1298+
const fbPubkey = fbTx.signedMessages?.[0]?.publicKey;
1299+
1300+
if (!signature) {
1301+
throw new Error(ERRORS.MISSING_SIGNATURE);
1302+
}
1303+
1304+
if (!fbPubkey) {
1305+
throw new Error(ERRORS.MISSING_PUBLIC_KEY);
1306+
}
1307+
1308+
const preparedTx = await this.client.POST('/sui/transaction/prepare', {
1309+
body: {
1310+
pubkey: fbPubkey,
1311+
signature: signature,
1312+
tx_serialized: tx.unsigned_tx_serialized,
1313+
},
1314+
});
1315+
1316+
if (preparedTx.error) {
1317+
throw new Error(ERRORS.FAILED_TO_PREPARE);
1318+
}
1319+
1320+
return {
1321+
signed_tx: preparedTx.data,
1322+
fireblocks_tx: fbTx,
1323+
};
1324+
}
12741325
}

src/fireblocks_signer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ export type FireblocksAssetId =
3636
| 'KAVA_KAVA'
3737
| 'TRX'
3838
| 'BTC'
39-
| 'SEI';
39+
| 'SEI'
40+
| 'SUI';
4041

4142
export class FireblocksSigner {
4243
constructor(

src/utils.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,3 +336,17 @@ export const seiToUsei = (sei: string): bigint => {
336336
export const useiToSei = (usei: bigint): string => {
337337
return formatUnits(usei, 6);
338338
};
339+
340+
/**
341+
* Convert SUI to mist
342+
*/
343+
export const suiToMist = (sui: string): bigint => {
344+
return parseUnits(sui, 9);
345+
};
346+
347+
/**
348+
* Convert mist to SUI
349+
*/
350+
export const mistToSui = (mist: bigint): string => {
351+
return formatUnits(mist, 9);
352+
};

src/validators.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,9 @@ export const KILN_VALIDATORS = {
105105
KILN: 'seivaloper1u9xeaqdjz3kky2ymdhdsn0ra5uy9tc3eqkjc8w',
106106
},
107107
},
108+
SUI: {
109+
mainnet: {
110+
KILN: '0x92c7bf9914897e8878e559c19a6cffd22e6a569a6dd4d26f8e82e0f2ad1873d6',
111+
},
112+
},
108113
};

0 commit comments

Comments
 (0)