From ba6ad5066993b0bc8ff215655a3c890f88b782e5 Mon Sep 17 00:00:00 2001 From: Arno Simon Date: Thu, 23 May 2024 16:05:27 +0200 Subject: [PATCH 1/2] ton support --- src/integrations/fb_signer.ts | 4 +- src/kiln.ts | 3 + src/services/ton.ts | 148 ++++++++++++++++++++++++++++++++++ src/types/ton.ts | 28 +++++++ 4 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 src/services/ton.ts create mode 100644 src/types/ton.ts diff --git a/src/integrations/fb_signer.ts b/src/integrations/fb_signer.ts index 4e73123..574b987 100644 --- a/src/integrations/fb_signer.ts +++ b/src/integrations/fb_signer.ts @@ -33,7 +33,9 @@ type AssetId = | "DV4TNT_TEST" | "DYDX_DYDX" | "CELESTIA" - | "INJ_INJ"; + | "INJ_INJ" + | "TON_TEST" + | "TON"; export class FbSigner { protected fireblocks: FireblocksSDK; diff --git a/src/kiln.ts b/src/kiln.ts index 693ccb4..5fe16ad 100644 --- a/src/kiln.ts +++ b/src/kiln.ts @@ -16,6 +16,7 @@ import { TiaService } from "./services/tia"; import { XtzService } from "./services/xtz"; import { KILN_VALIDATORS as v } from "./validators"; import { InjService } from "./services/inj"; +import { TonService } from "./services/ton"; type Config = { apiToken: string; @@ -42,6 +43,7 @@ export class Kiln { noble: NobleService; fet: FetService; inj: InjService; + ton: TonService; constructor({ testnet, apiToken, baseUrl }: Config) { api.defaults.headers.common.Authorization = `Bearer ${apiToken}`; @@ -64,5 +66,6 @@ export class Kiln { this.noble = new NobleService({ testnet }); this.fet = new FetService({ testnet }); this.inj = new InjService({ testnet }); + this.ton = new TonService({ testnet }); } } diff --git a/src/services/ton.ts b/src/services/ton.ts new file mode 100644 index 0000000..9d007f5 --- /dev/null +++ b/src/services/ton.ts @@ -0,0 +1,148 @@ +import api from "../api"; +import { Service } from "./service"; +import { ServiceProps } from "../types/service"; +import { Integration } from "../types/integrations"; +import { parseUnits } from "viem"; +import { TonSignedTx, TonTx, TonTxHash, TonTxStatus } from "../types/ton"; + +export class TonService extends Service { + constructor({ testnet }: ServiceProps) { + super({ testnet }); + } + + /** + * Craft TON staking transaction to a single nomination pool + * @param accountId id of the kiln account to use for the stake transaction + * @param walletAddress used to create the stake account and retrieve rewards in the future + * @param poolAddress vote account address of the validator that you wish to delegate to + * @param amountTon how much to stake in TON + */ + async craftStakeSingleNominationPoolTx( + accountId: string, + walletAddress: string, + poolAddress: string, + amountTon: number, + ): Promise { + const { data } = await api.post(`/v1/ton/transaction/stake-single-nomination-pool`, { + account_id: accountId, + wallet: walletAddress, + amount_nanoton: this.tonToNanoTon(amountTon.toString()), + pool_address: poolAddress, + }); + return data; + } + + /** + * Craft TON staking transaction to a nomination pool + * @param accountId id of the kiln account to use for the stake transaction + * @param walletAddress used to create the stake account and retrieve rewards in the future + * @param poolAddress vote account address of the validator that you wish to delegate to + * @param amountTon how much to stake in TON + */ + async craftStakeNominationPoolTx( + accountId: string, + walletAddress: string, + poolAddress: string, + amountTon: number, + ): Promise { + const { data } = await api.post(`/v1/ton/transaction/stake-nomination-pool`, { + account_id: accountId, + wallet: walletAddress, + amount_nanoton: this.tonToNanoTon(amountTon.toString()), + pool_address: poolAddress, + }); + return data; + } + + /** + * Craft TON unstake transaction from a single nomination pool + * @param walletAddress used to create the stake account and retrieve rewards in the future + * @param poolAddress vote account address of the validator that you wish to delegate to + * @param amountTon how much to stake in TON + */ + async craftUnstakeSingleNominationPoolTx( + walletAddress: string, + poolAddress: string, + amountTon?: number, + ): Promise { + const { data } = await api.post(`/v1/ton/transaction/unstake-single-nomination-pool`, { + wallet: walletAddress, + amount_nanoton: amountTon ? this.tonToNanoTon(amountTon.toString()) : undefined, + pool_address: poolAddress, + }); + return data; + } + + /** + * Craft TON unstake transaction from a nomination pool + * @param walletAddress used to create the stake account and retrieve rewards in the future + * @param poolAddress vote account address of the validator that you wish to delegate to + */ + async craftUnstakeNominationPoolTx(walletAddress: string, poolAddress: string): Promise { + const { data } = await api.post(`/v1/ton/transaction/unstake-nomination-pool`, { + wallet: walletAddress, + pool_address: poolAddress, + }); + return data; + } + + /** + * Sign transaction with given integration + * @param integration custody solution to sign with + * @param tx raw transaction + * @param note note to identify the transaction in your custody solution + */ + async sign(integration: Integration, tx: TonTx, note?: string): Promise { + const payload = { + rawMessageData: { + messages: [ + { + content: tx.data.unsigned_tx_hash, + }, + ], + }, + }; + + const fbSigner = this.getFbSigner(integration); + const fbNote = note ? note : "TON tx from @kilnfi/sdk"; + const fbTx = await fbSigner.sign(payload, this.testnet ? "TON_TEST" : "TON", fbNote); + const signature = + fbTx.signedMessages && fbTx.signedMessages.length > 0 ? fbTx.signedMessages[0].signature.fullSig : undefined; + + const { data } = await api.post(`/v1/ton/transaction/prepare`, { + unsigned_tx_serialized: tx.data.unsigned_tx_serialized, + signature: signature, + from: tx.data.from, + }); + data.data.fireblocks_tx = fbTx; + return data; + } + + /** + * Broadcast transaction to the network + * @param signedTx serialized signed tx + */ + async broadcast(signedTx: TonSignedTx): Promise { + const { data } = await api.post(`/v1/ton/transaction/broadcast`, { + tx_serialized: signedTx.data.signed_tx_serialized, + }); + return data; + } + + /** + * Get transaction status by message hash + * @param msgHash transaction hash + */ + async getTxStatus(msgHash: string): Promise { + const { data } = await api.get(`/v1/ton/transaction/status?msg_hash=${msgHash}`); + return data; + } + + /** + * Convert TON to nanoTON + * @param ton + */ + tonToNanoTon(ton: string): string { + return parseUnits(ton, 9).toString(); + } +} diff --git a/src/types/ton.ts b/src/types/ton.ts new file mode 100644 index 0000000..2b6d61a --- /dev/null +++ b/src/types/ton.ts @@ -0,0 +1,28 @@ +import { TransactionResponse } from "fireblocks-sdk"; + +export type TonTx = { + data: { + unsigned_tx_hash: string; + unsigned_tx_serialized: string; + from: string; + }; +}; + +export type TonSignedTx = { + data: { + fireblocks_tx: TransactionResponse; + signed_tx_serialized: string; + }; +}; + +export type TonTxHash = { + data: { + tx_hash: string; + }; +}; +export type TonTxStatus = { + data: { + status: "success" | "error"; + receipt: TransactionResponse | null; + }; +}; From a4b0c694c00246ca1757c742c622eede9263b2b2 Mon Sep 17 00:00:00 2001 From: Arno Simon Date: Thu, 23 May 2024 16:19:11 +0200 Subject: [PATCH 2/2] update readme + version --- README.md | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 032592b..5f0c6ba 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Check out the [full documentation](https://docs.kiln.fi/v1/connect/overview). - OSMO - SOL - TIA +- TON - XTZ - More protocol to come, don't hesitate to contact us (support@kiln.fi) diff --git a/package.json b/package.json index 2370cb8..9903235 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kilnfi/sdk", - "version": "2.14.2", + "version": "2.15.0", "autor": "Kiln (https://kiln.fi)", "license": "BUSL-1.1", "description": "JavaScript sdk for Kiln API",