Skip to content

Commit 4df0adb

Browse files
authored
Merge pull request #96 from kilnfi/fetch-ai
[SOF-3594] add FET support
2 parents 01a4dc0 + 5989c14 commit 4df0adb

File tree

5 files changed

+274
-12
lines changed

5 files changed

+274
-12
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@kilnfi/sdk",
3-
"version": "2.12.3",
3+
"version": "2.13.0",
44
"autor": "Kiln <[email protected]> (https://kiln.fi)",
55
"license": "BUSL-1.1",
66
"description": "JavaScript sdk for Kiln API",
@@ -57,4 +57,4 @@
5757
"prettier": "^3.2.5",
5858
"typescript": "^5.4.3"
5959
}
60-
}
60+
}

src/integrations/fb_signer.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
CreateTransactionResponse,
33
FireblocksSDK,
44
PeerType,
5+
SigningAlgorithm,
56
TransactionArguments,
67
TransactionOperation,
78
TransactionResponse,
@@ -55,10 +56,13 @@ export class FbSigner {
5556
if (
5657
tx.status == TransactionStatus.BLOCKED ||
5758
tx.status == TransactionStatus.FAILED ||
58-
tx.status == TransactionStatus.REJECTED ||
5959
tx.status == TransactionStatus.CANCELLED
6060
) {
6161
throw Error(`Fireblocks signer: the transaction has been ${tx.status}`);
62+
} else if (tx.status == TransactionStatus.REJECTED) {
63+
throw Error(
64+
`Fireblocks signer: the transaction has been rejected, make sure that the TAP security policy is not blocking the transaction`,
65+
);
6266
}
6367
tx = await this.fireblocks.getTransactionById(fbTx.id);
6468
}
@@ -95,6 +99,43 @@ export class FbSigner {
9599
}
96100
}
97101

102+
/**
103+
* Sign a generic transaction with fireblocks using Fireblocks raw message signing feature.
104+
* @param payloadToSign: transaction data in hexadecimal
105+
* @param derivationPath: derivation path of the token to sign
106+
* @param algorithm: algorithm of the token to sign
107+
* @param note: optional fireblocks custom note
108+
*/
109+
public async signGenericWithFB(
110+
payloadContent: string,
111+
derivationPath: number[],
112+
algorithm: SigningAlgorithm,
113+
note?: string,
114+
): Promise<TransactionResponse> {
115+
try {
116+
const payloadToSign = {
117+
operation: TransactionOperation.RAW,
118+
note,
119+
extraParameters: {
120+
rawMessageData: {
121+
messages: [
122+
{
123+
content: payloadContent,
124+
derivationPath,
125+
},
126+
],
127+
algorithm,
128+
},
129+
},
130+
};
131+
const fbTx = await this.fireblocks.createTransaction(payloadToSign);
132+
return await this.waitForTxCompletion(fbTx);
133+
} catch (err: any) {
134+
console.log(err);
135+
throw new Error("Fireblocks signer (signGenericWithFB): " + err);
136+
}
137+
}
138+
98139
/**
99140
* Sign and broadcast a transaction with fireblocks using Fireblocks contract call feature
100141
* @param payloadToSign: transaction data in hexadecimal

src/kiln.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
import api from "./api";
2-
import { KILN_VALIDATORS as v } from "./validators";
3-
import { EthService } from "./services/eth";
4-
import { SolService } from "./services/sol";
5-
import { AtomService } from "./services/atom";
62
import { AccountService } from "./services/accounts";
73
import { AdaService } from "./services/ada";
8-
import { NearService } from "./services/near";
4+
import { AtomService } from "./services/atom";
95
import { DotService } from "./services/dot";
10-
import { XtzService } from "./services/xtz";
6+
import { DydxService } from "./services/dydx";
7+
import { EthService } from "./services/eth";
8+
import { FetService } from "./services/fet";
9+
import { FireblocksService } from "./services/fireblocks";
1110
import { MaticService } from "./services/matic";
11+
import { NearService } from "./services/near";
12+
import { NobleService } from "./services/noble";
1213
import { OsmoService } from "./services/osmo";
13-
import { FireblocksService } from "./services/fireblocks";
14-
import { DydxService } from "./services/dydx";
14+
import { SolService } from "./services/sol";
1515
import { TiaService } from "./services/tia";
16-
import { NobleService } from "./services/noble";
16+
import { XtzService } from "./services/xtz";
17+
import { KILN_VALIDATORS as v } from "./validators";
1718

1819
type Config = {
1920
apiToken: string;
@@ -38,6 +39,7 @@ export class Kiln {
3839
dydx: DydxService;
3940
tia: TiaService;
4041
noble: NobleService;
42+
fet: FetService;
4143

4244
constructor({ testnet, apiToken, baseUrl }: Config) {
4345
api.defaults.headers.common.Authorization = `Bearer ${apiToken}`;
@@ -58,5 +60,6 @@ export class Kiln {
5860
this.dydx = new DydxService({ testnet });
5961
this.tia = new TiaService({ testnet });
6062
this.noble = new NobleService({ testnet });
63+
this.fet = new FetService({ testnet });
6164
}
6265
}

src/services/fet.ts

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
import { Service } from "./service";
2+
3+
import { ServiceProps } from "../types/service";
4+
import { Integration } from "../types/integrations";
5+
import api from "../api";
6+
import { DecodedTxRaw } from "@cosmjs/proto-signing";
7+
import {
8+
CosmosSignedTx,
9+
CosmosTx,
10+
CosmosTxHash,
11+
CosmosTxStatus,
12+
} from "../types/cosmos";
13+
import { SigningAlgorithm } from "fireblocks-sdk";
14+
15+
export class FetService extends Service {
16+
constructor({ testnet }: ServiceProps) {
17+
super({ testnet });
18+
}
19+
20+
/**
21+
* Convert FET to aFET
22+
* @param amountFet
23+
*/
24+
fetToAfet(amountFet: string): string {
25+
return (parseFloat(amountFet) * 10 ** 18).toFixed();
26+
}
27+
28+
/**
29+
* Craft fetch.ai staking transaction
30+
* @param accountId id of the kiln account to use for the stake transaction
31+
* @param pubkey wallet pubkey, this is different from the wallet address
32+
* @param validatorAddress validator address to delegate to
33+
* @param amountFet how many tokens to stake in FET
34+
* @param restakeRewards If enabled, the rewards will be automatically restaked
35+
*/
36+
async craftStakeTx(
37+
accountId: string,
38+
pubkey: string,
39+
validatorAddress: string,
40+
amountFet: number,
41+
restakeRewards: boolean = false
42+
): Promise<CosmosTx> {
43+
const { data } = await api.post<CosmosTx>(`/v1/fet/transaction/stake`, {
44+
account_id: accountId,
45+
pubkey: pubkey,
46+
validator: validatorAddress,
47+
amount_afet: this.fetToAfet(amountFet.toString()),
48+
restake_rewards: restakeRewards,
49+
});
50+
return data;
51+
}
52+
53+
/**
54+
* Craft fetch.ai withdraw rewards transaction
55+
* @param pubkey wallet pubkey, this is different from the wallet address
56+
* @param validatorAddress validator address to which the delegation has been made
57+
*/
58+
async craftWithdrawRewardsTx(
59+
pubkey: string,
60+
validatorAddress: string
61+
): Promise<CosmosTx> {
62+
const { data } = await api.post<CosmosTx>(
63+
`/v1/fet/transaction/withdraw-rewards`,
64+
{
65+
pubkey: pubkey,
66+
validator: validatorAddress,
67+
}
68+
);
69+
return data;
70+
}
71+
72+
/**
73+
* Craft fetch.ai restake rewards transaction
74+
* @param pubkey wallet pubkey, this is different from the wallet address
75+
* @param validatorAccount validator account address (wallet controlling the validator)
76+
* @param validatorAddress validator address to which the delegation has been made
77+
*/
78+
async craftRestakeRewardsTx(
79+
pubkey: string,
80+
validatorAddress: string
81+
): Promise<CosmosTx> {
82+
const { data } = await api.post<CosmosTx>(
83+
`/v1/fet/transaction/restake-rewards`,
84+
{
85+
pubkey: pubkey,
86+
validator_address: validatorAddress,
87+
}
88+
);
89+
return data;
90+
}
91+
92+
/**
93+
* Craft fetch.ai unstaking transaction
94+
* @param pubkey wallet pubkey, this is different from the wallet address
95+
* @param validatorAddress validator address to which the delegation has been made
96+
* @param amountFet how many tokens to undelegate in FET
97+
*/
98+
async craftUnstakeTx(
99+
pubkey: string,
100+
validatorAddress: string,
101+
amountFet?: number
102+
): Promise<CosmosTx> {
103+
const { data } = await api.post<CosmosTx>(`/v1/fet/transaction/unstake`, {
104+
pubkey: pubkey,
105+
validator: validatorAddress,
106+
amount_afet: amountFet ? this.fetToAfet(amountFet.toString()) : undefined,
107+
});
108+
return data;
109+
}
110+
111+
/**
112+
* Craft fetch.ai redelegate transaction
113+
* @param accountId id of the kiln account to use for the new stake
114+
* @param pubkey wallet pubkey, this is different from the wallet address
115+
* @param validatorSourceAddress validator address of the current delegation
116+
* @param validatorDestinationAddress validator address to which the delegation will be moved
117+
* @param amountFet how many tokens to redelegate in FET
118+
*/
119+
async craftRedelegateTx(
120+
accountId: string,
121+
pubkey: string,
122+
validatorSourceAddress: string,
123+
validatorDestinationAddress: string,
124+
amountFet?: number
125+
): Promise<CosmosTx> {
126+
const { data } = await api.post<CosmosTx>(
127+
`/v1/fet/transaction/redelegate`,
128+
{
129+
account_id: accountId,
130+
pubkey: pubkey,
131+
validator_source: validatorSourceAddress,
132+
validator_destination: validatorDestinationAddress,
133+
amount_afet: amountFet
134+
? this.fetToAfet(amountFet.toString())
135+
: undefined,
136+
}
137+
);
138+
return data;
139+
}
140+
141+
/**
142+
* Sign transaction with given integration
143+
* @param integration custody solution to sign with
144+
* @param tx raw transaction
145+
* @param note note to identify the transaction in your custody solution
146+
*/
147+
async sign(
148+
integration: Integration,
149+
tx: CosmosTx,
150+
note?: string
151+
): Promise<CosmosSignedTx> {
152+
const payloadContent = tx.data.unsigned_tx_hash;
153+
const derivationPath = [44, 118, integration.vaultId, 0, 0];
154+
const signingAlgorithm = SigningAlgorithm.MPC_ECDSA_SECP256K1;
155+
const fbNote = note ? note : "FET tx from @kilnfi/sdk";
156+
157+
const signer = this.getFbSigner(integration);
158+
const fbTx = await signer.signGenericWithFB(
159+
payloadContent,
160+
derivationPath,
161+
signingAlgorithm,
162+
fbNote
163+
);
164+
const signature: string = fbTx.signedMessages![0].signature.fullSig;
165+
const { data } = await api.post<CosmosSignedTx>(
166+
`/v1/fet/transaction/prepare`,
167+
{
168+
pubkey: tx.data.pubkey,
169+
tx_body: tx.data.tx_body,
170+
tx_auth_info: tx.data.tx_auth_info,
171+
signature: signature,
172+
}
173+
);
174+
data.data.fireblocks_tx = fbTx;
175+
return data;
176+
}
177+
178+
/**
179+
* Broadcast transaction to the network
180+
* @param signedTx
181+
*/
182+
async broadcast(signedTx: CosmosSignedTx): Promise<CosmosTxHash> {
183+
const { data } = await api.post<CosmosTxHash>(
184+
`/v1/fet/transaction/broadcast`,
185+
{
186+
tx_serialized: signedTx.data.signed_tx_serialized,
187+
}
188+
);
189+
return data;
190+
}
191+
192+
/**
193+
* Get transaction status
194+
* @param txHash
195+
*/
196+
async getTxStatus(txHash: string): Promise<CosmosTxStatus> {
197+
const { data } = await api.get<CosmosTxStatus>(
198+
`/v1/fet/transaction/status?tx_hash=${txHash}`
199+
);
200+
return data;
201+
}
202+
203+
/**
204+
* Decode transaction
205+
* @param txSerialized transaction serialized
206+
*/
207+
async decodeTx(txSerialized: string): Promise<DecodedTxRaw> {
208+
const { data } = await api.get<DecodedTxRaw>(
209+
`/v1/fet/transaction/decode?tx_serialized=${txSerialized}`
210+
);
211+
return data;
212+
}
213+
}

src/validators.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,9 @@ export const KILN_VALIDATORS = {
5252
KILN: "tz3btDQsDkqq2G7eBdrrLqetaAfLVw6BnPez",
5353
},
5454
},
55+
FET: {
56+
mainnet: {
57+
KILN: "fetchvaloper146mj09yzu3mvz7pmy4dvs4z9wr2mst7ryjvncp",
58+
},
59+
},
5560
};

0 commit comments

Comments
 (0)