From 05457e63607a603c5e28bfed4bd1933c71b151b0 Mon Sep 17 00:00:00 2001 From: Arno Simon Date: Fri, 1 Dec 2023 17:05:35 +0100 Subject: [PATCH 1/2] add dydx and fireblocks service --- src/integrations/fb_signer.ts | 5 +- src/kiln.ts | 5 +- src/services/dydx.ts | 206 ++++++++++++++++++++++++++++++++++ src/services/fireblocks.ts | 23 ++++ src/types/dydx.ts | 35 ++++++ 5 files changed, 272 insertions(+), 2 deletions(-) create mode 100644 src/services/dydx.ts create mode 100644 src/services/fireblocks.ts create mode 100644 src/types/dydx.ts diff --git a/src/integrations/fb_signer.ts b/src/integrations/fb_signer.ts index 895e02b..09334e4 100644 --- a/src/integrations/fb_signer.ts +++ b/src/integrations/fb_signer.ts @@ -29,7 +29,10 @@ type AssetId = | 'XTZ_TEST' | 'XTZ' | 'WND' - | 'DOT'; + | 'DOT' + | 'DV4TNT_TEST' + | 'DYDX_DYDX' + ; export class FbSigner { protected fireblocks: FireblocksSDK; diff --git a/src/kiln.ts b/src/kiln.ts index dc2ee82..8139e45 100644 --- a/src/kiln.ts +++ b/src/kiln.ts @@ -10,6 +10,7 @@ import { DotService } from './services/dot'; import { XtzService } from './services/xtz'; import { MaticService } from './services/matic'; import { OsmoService } from './services/osmo'; +import { FireblocksService } from "./services/fireblocks"; type Config = { apiToken: string; @@ -20,9 +21,10 @@ type Config = { export const KILN_VALIDATORS = v; export class Kiln { + fireblocks: FireblocksService; + accounts: AccountService; eth: EthService; sol: SolService; - accounts: AccountService; atom: AtomService; ada: AdaService; near: NearService; @@ -39,6 +41,7 @@ export class Kiln { ? 'https://api.testnet.kiln.fi' : 'https://api.kiln.fi'; + this.fireblocks = new FireblocksService({ testnet }); this.accounts = new AccountService({ testnet }); this.eth = new EthService({ testnet }); this.sol = new SolService({ testnet }); diff --git a/src/services/dydx.ts b/src/services/dydx.ts new file mode 100644 index 0000000..8209c99 --- /dev/null +++ b/src/services/dydx.ts @@ -0,0 +1,206 @@ +import { Service } from './service'; + +import { ServiceProps } from '../types/service'; +import { Integration } from '../types/integrations'; +import api from '../api'; +import { DecodedTxRaw } from "@cosmjs/proto-signing"; +import { DydxSignedTx, DydxTxHash, DydxTxStatus, DydxTx } from "../types/dydx"; + +export class DydxService extends Service { + + constructor({ testnet }: ServiceProps) { + super({ testnet }); + } + + /** + * Convert DYDX to ADYDX + * @param amountDydx + */ + dydxToAdydx(amountDydx: string): string { + return (parseFloat(amountDydx) * 10 ** 18).toFixed(); + } + + /** + * Craft dydx staking transaction + * @param accountId id of the kiln account to use for the stake transaction + * @param pubkey wallet pubkey, this is different from the wallet address + * @param validatorAddress validator address to delegate to + * @param amountDydx how many tokens to stake in DYDX + */ + async craftStakeTx( + accountId: string, + pubkey: string, + validatorAddress: string, + amountDydx: number, + ): Promise { + try { + const { data } = await api.post( + `/v1/dydx/transaction/stake`, + { + account_id: accountId, + pubkey: pubkey, + validator: validatorAddress, + amount_adydx: this.dydxToAdydx(amountDydx.toString()), + }); + return data; + } catch (err: any) { + throw new Error(err); + } + } + + /** + * Craft dydx withdraw rewards transaction + * @param pubkey wallet pubkey, this is different from the wallet address + * @param validatorAddress validator address to which the delegation has been made + */ + async craftWithdrawRewardsTx( + pubkey: string, + validatorAddress: string, + ): Promise { + try { + const { data } = await api.post( + `/v1/dydx/transaction/withdraw-rewards`, + { + pubkey: pubkey, + validator: validatorAddress, + }); + return data; + } catch (err: any) { + throw new Error(err); + } + } + + /** + * Craft dydx unstaking transaction + * @param pubkey wallet pubkey, this is different from the wallet address + * @param validatorAddress validator address to which the delegation has been made + * @param amountDydx how many tokens to undelegate in DYDX + */ + async craftUnstakeTx( + pubkey: string, + validatorAddress: string, + amountDydx?: number, + ): Promise { + try { + const { data } = await api.post( + `/v1/dydx/transaction/unstake`, + { + pubkey: pubkey, + validator: validatorAddress, + amount_adydx: amountDydx ? this.dydxToAdydx(amountDydx.toString()) : undefined, + }); + return data; + } catch (err: any) { + throw new Error(err); + } + } + + /** + * Craft dydx redelegate transaction + * @param accountId id of the kiln account to use for the new stake + * @param pubkey wallet pubkey, this is different from the wallet address + * @param validatorSourceAddress validator address of the current delegation + * @param validatorDestinationAddress validator address to which the delegation will be moved + * @param amountDydx how many tokens to redelegate in DYDX + */ + async craftRedelegateTx( + accountId: string, + pubkey: string, + validatorSourceAddress: string, + validatorDestinationAddress: string, + amountDydx?: number, + ): Promise { + try { + const { data } = await api.post( + `/v1/dydx/transaction/redelegate`, + { + account_id: accountId, + pubkey: pubkey, + validator_source: validatorSourceAddress, + validator_destination: validatorDestinationAddress, + amount_adydx: amountDydx ? this.dydxToAdydx(amountDydx.toString()) : undefined, + }); + return data; + } catch (err: any) { + throw new Error(err); + } + } + + /** + * 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: DydxTx, note?: string): Promise { + const payload = { + rawMessageData: { + messages: [ + { + 'content': tx.data.unsigned_tx_hash, + }, + ], + }, + }; + const fbNote = note ? note : 'DYDX tx from @kilnfi/sdk'; + const signer = this.getFbSigner(integration); + const fbTx = await signer.signWithFB(payload, this.testnet ? 'DV4TNT_TEST' : 'DYDX_DYDX', fbNote); + const signature: string = fbTx.signedMessages![0].signature.fullSig; + const { data } = await api.post( + `/v1/dydx/transaction/prepare`, + { + pubkey: tx.data.pubkey, + tx_body: tx.data.tx_body, + tx_auth_info: tx.data.tx_auth_info, + signature: signature, + }); + data.data.fireblocks_tx = fbTx; + return data; + } + + + /** + * Broadcast transaction to the network + * @param signedTx + */ + async broadcast(signedTx: DydxSignedTx): Promise { + try { + const { data } = await api.post( + `/v1/dydx/transaction/broadcast`, + { + tx_serialized: signedTx.data.signed_tx_serialized, + }); + return data; + } catch (e: any) { + throw new Error(e); + } + } + + /** + * Get transaction status + * @param txHash + */ + async getTxStatus(txHash: string): Promise { + try { + const { data } = await api.get( + `/v1/dydx/transaction/status?tx_hash=${txHash}`); + return data; + } catch (e: any) { + throw new Error(e); + } + } + + /** + * Decode transaction + * @param txSerialized transaction serialized + */ + async decodeTx(txSerialized: string): Promise { + try { + const { data } = await api.get( + `/v1/dydx/transaction/decode?tx_serialized=${txSerialized}`); + return data; + } catch (error: any) { + throw new Error(error); + } + } +} diff --git a/src/services/fireblocks.ts b/src/services/fireblocks.ts new file mode 100644 index 0000000..c3a9a3b --- /dev/null +++ b/src/services/fireblocks.ts @@ -0,0 +1,23 @@ +import { Integration } from "../types/integrations"; +import { Service } from "./service"; +import { PublicKeyResponse } from "fireblocks-sdk"; + +export class FireblocksService extends Service { + + /** + * Get fireblocks wallet pubkey compressed + * @param integration + * @param assetId + */ + async getPubkey(integration: Integration, assetId: string): Promise { + const fbSdk = this.getFbSdk(integration); + const data = await fbSdk.getPublicKeyInfoForVaultAccount({ + assetId: assetId, + vaultAccountId: integration.vaultId, + change: 0, + addressIndex: 0, + compressed: true, + }); + return data; + } +} \ No newline at end of file diff --git a/src/types/dydx.ts b/src/types/dydx.ts new file mode 100644 index 0000000..356958f --- /dev/null +++ b/src/types/dydx.ts @@ -0,0 +1,35 @@ +import { IndexedTx, StdFee } from "@cosmjs/stargate"; +import { EncodeObject } from "@cosmjs/proto-signing"; +import { TransactionResponse } from "fireblocks-sdk"; + +export type DydxTx = { + data: { + unsigned_tx_hash: string; + unsigned_tx_serialized: string; + tx_body: string; + tx_auth_info: string; + pubkey: string; + message: EncodeObject; + fee: StdFee; + } +} + +export type DydxSignedTx = { + data: { + fireblocks_tx: TransactionResponse; + signed_tx_serialized: string; + }; +}; + +export type DydxTxHash = { + data: { + tx_hash: string; + }; +}; + +export type DydxTxStatus = { + data: { + status: 'success' | 'error', + receipt: IndexedTx | null, + } +} \ No newline at end of file From 0d604dec00cd27c197c3aa9a03984faa40eea847 Mon Sep 17 00:00:00 2001 From: Arno Simon Date: Fri, 1 Dec 2023 17:06:51 +0100 Subject: [PATCH 2/2] add dydx service --- README.md | 1 + src/kiln.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/README.md b/README.md index ffa9501..d110129 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Check out the [full documentation](https://docs.kiln.fi/v1/connect/overview). - SOL - XTZ - OSMO +- DYDX - More protocol to come, don't hesitate to contact us (support@kiln.fi) ## Installation diff --git a/src/kiln.ts b/src/kiln.ts index 8139e45..5d93936 100644 --- a/src/kiln.ts +++ b/src/kiln.ts @@ -11,6 +11,7 @@ import { XtzService } from './services/xtz'; import { MaticService } from './services/matic'; import { OsmoService } from './services/osmo'; import { FireblocksService } from "./services/fireblocks"; +import { DydxService } from "./services/dydx"; type Config = { apiToken: string; @@ -32,6 +33,7 @@ export class Kiln { xtz: XtzService; matic: MaticService; osmo: OsmoService; + dydx: DydxService; constructor({ testnet, apiToken, baseUrl }: Config) { api.defaults.headers.common.Authorization = `Bearer ${apiToken}`; @@ -52,5 +54,6 @@ export class Kiln { this.xtz = new XtzService({ testnet }); this.matic = new MaticService({ testnet }); this.osmo = new OsmoService({ testnet }); + this.dydx = new DydxService({ testnet }); } } \ No newline at end of file