Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add KSM (Kusama) support #109

Merged
merged 5 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Check out the [full documentation](https://docs.kiln.fi/v1/connect/overview).
- ADA
- ATOM
- DOT
- KSM
- DYDX
- ETH
- FET
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@kilnfi/sdk",
"version": "2.17.3",
"version": "2.18.0",
"autor": "Kiln <[email protected]> (https://kiln.fi)",
"license": "BUSL-1.1",
"description": "JavaScript sdk for Kiln API",
Expand Down
4 changes: 2 additions & 2 deletions src/integrations/fb_signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { utils } from "ethers";
import { EthTx } from "../types/eth";
import { MaticTx } from "../types/matic";

type AssetId =
export type AssetId =
| "SOL_TEST"
| "SOL"
| "ETH_TEST3"
Expand All @@ -28,8 +28,8 @@ type AssetId =
| "NEAR"
| "XTZ_TEST"
| "XTZ"
| "WND"
| "DOT"
| "KSM"
| "DV4TNT_TEST"
| "DYDX_DYDX"
| "CELESTIA"
Expand Down
3 changes: 3 additions & 0 deletions src/kiln.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { EthService } from "./services/eth";
import { FetService } from "./services/fet";
import { FireblocksService } from "./services/fireblocks";
import { InjService } from "./services/inj";
import { KsmService } from "./services/ksm";
import { MaticService } from "./services/matic";
import { NearService } from "./services/near";
import { NobleService } from "./services/noble";
Expand Down Expand Up @@ -36,6 +37,7 @@ export class Kiln {
ada: AdaService;
near: NearService;
dot: DotService;
ksm: KsmService;
xtz: XtzService;
matic: MaticService;
osmo: OsmoService;
Expand All @@ -60,6 +62,7 @@ export class Kiln {
this.ada = new AdaService({ testnet });
this.near = new NearService({ testnet });
this.dot = new DotService({ testnet });
this.ksm = new KsmService({ testnet });
this.xtz = new XtzService({ testnet });
this.matic = new MaticService({ testnet });
this.osmo = new OsmoService({ testnet });
Expand Down
302 changes: 9 additions & 293 deletions src/services/dot.ts
Original file line number Diff line number Diff line change
@@ -1,309 +1,25 @@
import { Service } from "./service";
import { DotRewardDestination, DotSignedTx, DotTx, DotTxHash, DotTxStatus } from "../types/dot";
import { ServiceProps } from "../types/service";
import { Integration } from "../types/integrations";
import api from "../api";
import { UnsignedTransaction } from "@substrate/txwrapper-polkadot";
import { parseUnits } from "viem";
import { ServiceProps } from "../types/service";
import { SubstrateService } from "./substrate";

/**
* Staking docs: https://polkadot.js.org/docs/substrate/extrinsics#staking
* Nomination pools docs: https://polkadot.js.org/docs/substrate/extrinsics#nominationpools
*/
export class DotService extends Service {
constructor({ testnet }: ServiceProps) {
super({ testnet });
export class DotService extends SubstrateService {
constructor(props: ServiceProps) {
super(props, "DOT");
}

/**
* Convert WND (testnet token) to PLANCK
* To be used in testnet (Westend)
* @param amountWnd
*/
wndToPlanck(amountWnd: string): string {
return parseUnits(amountWnd, 12).toString();
mainToPlanck(amount: string): string {
return this.dotToPlanck(amount);
}

/**
* Convert DOT to PLANCK
* To be used in mainnet
* @param amountDot
* Convert DOT to planck
* @param amountDot amount in planck
*/
dotToPlanck(amountDot: string): string {
return parseUnits(amountDot, 10).toString();
}

/**
* Craft dot bonding transaction
* @param accountId id of the kiln account to use for the stake transaction
* @param stashAccount stash account address (your most secure cold wallet)
* @param amountDot amount to bond in DOT
* @param rewardDestination
*/
async craftBondTx(
accountId: string,
stashAccount: string,
amountDot: number,
rewardDestination: DotRewardDestination,
): Promise<DotTx> {
const amountPlanck = this.testnet ? this.wndToPlanck(amountDot.toString()) : this.dotToPlanck(amountDot.toString());

const { data } = await api.post<DotTx>(`/v1/dot/transaction/bond`, {
account_id: accountId,
stash_account: stashAccount,
amount_planck: amountPlanck,
reward_destination: rewardDestination,
});
return data;
}

/**
* Craft dot bonding extra token transaction (to be used if you already bonded tokens)
* @param stashAccount stash account address
* @param amountDot amount to bond extra in DOT
*/
async craftBondExtraTx(stashAccount: string, amountDot: number): Promise<DotTx> {
const amountPlanck = this.testnet ? this.wndToPlanck(amountDot.toString()) : this.dotToPlanck(amountDot.toString());

const { data } = await api.post<DotTx>(`/v1/dot/transaction/bond-extra`, {
stash_account: stashAccount,
amount_planck: amountPlanck,
});
return data;
}

/**
* Craft dot rebond transaction (to be used to rebond unbonding token)
* @param stashAccount stash account address
* @param amountDot amount to rebond in DOT
*/
async craftRebondTx(stashAccount: string, amountDot: number): Promise<DotTx> {
const amountPlanck = this.testnet ? this.wndToPlanck(amountDot.toString()) : this.dotToPlanck(amountDot.toString());

const { data } = await api.post<DotTx>(`/v1/dot/transaction/rebond`, {
stash_account: stashAccount,
amount_planck: amountPlanck,
});
return data;
}

/**
* Craft dot nominate transaction
* @param stashAccount stash account address
* @param validatorAddresses validator addresses to nominate to
*/
async craftNominateTx(stashAccount: string, validatorAddresses: string[]): Promise<DotTx> {
const { data } = await api.post<DotTx>(`/v1/dot/transaction/nominate`, {
stash_account: stashAccount,
validator_addresses: validatorAddresses,
});
return data;
}

/**
* Craft dot unbonding transaction, there is an unbonding period before your tokens can be withdrawn
* @param stashAccount stash account address
* @param amountDot amount to unrebond in DOT
*/
async craftUnbondTx(stashAccount: string, amountDot: number): Promise<DotTx> {
const amountPlanck = this.testnet ? this.wndToPlanck(amountDot.toString()) : this.dotToPlanck(amountDot.toString());

const { data } = await api.post<DotTx>(`/v1/dot/transaction/unbond`, {
stash_account: stashAccount,
amount_planck: amountPlanck,
});
return data;
}

/**
* Craft dot withdraw unbonded token transaction
* @param stashAccount stash account address
*/
async craftWithdrawUnbondedTx(stashAccount: string): Promise<DotTx> {
const { data } = await api.post<DotTx>(`/v1/dot/transaction/withdraw-unbonded`, {
stash_account: stashAccount,
});
return data;
}

/**
* Craft dot chill transaction that chills the stash account,
* meaning that given account will not nominate
* any validator anymore, so you will stop earning rewards at the beginning
* of the next era.
* @param stashAccount stash account address
*/
async craftChillTx(stashAccount: string): Promise<DotTx> {
const { data } = await api.post<DotTx>(`/v1/dot/transaction/chill`, {
stash_account: stashAccount,
});
return data;
}

/**
* Craft dot set reward destination transaction that updates the destination rewards address for the given stash account
* @param stashAccount stash account address
* @param rewardsDestination:
* 'Staked': rewards are paid into the stash account, increasing the amount at stake accordingly.
* 'Stash': rewards are paid into the stash account, not increasing the amount at stake.
* 'Controller': rewards are paid into the controller account
* Custom account address: rewards are paid into the custom account address
*/
async craftSetPayeeTx(stashAccount: string, rewardsDestination: DotRewardDestination): Promise<DotTx> {
const { data } = await api.post<DotTx>(`/v1/dot/transaction/set-payee`, {
stash_account: stashAccount,
reward_destination: rewardsDestination,
});
return data;
}

/**
* Craft dot join pool transaction
* The amount to bond is transferred from the member to the pools account and immediately increases the pools bond.
* @param accountId
* @param memberAccount
* @param amountDot
* @param poolId
*/
async craftJoinPoolTx(accountId: string, memberAccount: string, amountDot: number, poolId: string): Promise<DotTx> {
const amountPlanck = this.testnet ? this.wndToPlanck(amountDot.toString()) : this.dotToPlanck(amountDot.toString());
const { data } = await api.post<DotTx>(`/v1/dot/transaction/join-pool`, {
account_id: accountId,
member_account: memberAccount,
amount_planck: amountPlanck,
pool_id: poolId,
});
return data;
}

/**
* Craft a pool bond extra transaction
* Bond extra more funds from origin into the pool to which they already belong.
* Bonding extra funds implies an automatic payout of all pending rewards as well.
* @param memberAccount
* @param amountDot
*/
async craftBondExtraToPoolTx(memberAccount: string, amountDot: number): Promise<DotTx> {
const amountPlanck = this.testnet ? this.wndToPlanck(amountDot.toString()) : this.dotToPlanck(amountDot.toString());
const { data } = await api.post<DotTx>(`/v1/dot/transaction/bond-extra-pool`, {
member_account: memberAccount,
amount_planck: amountPlanck,
});
return data;
}

/**
* Craft a pool bond extra transaction to bond available rewards into the pool to which they already belong.
* @param memberAccount
*/
async craftBondRewardsToPoolTx(memberAccount: string): Promise<DotTx> {
const { data } = await api.post<DotTx>(`/v1/dot/transaction/bond-rewards-pool`, {
member_account: memberAccount,
});
return data;
}

/**
* Craft a pool claim payout transaction
* A bonded member can use this to claim their payout based on the rewards that
* the pool has accumulated since their last claimed payout (OR since joining
* if this is their first time claiming rewards).
* The payout will be transferred to the member's account.
* The member will earn rewards pro rata based on the members stake vs the sum of the members in the pools stake. Rewards do not "expire".
* @param memberAccount
*/
async craftClaimPayoutFromPoolTx(memberAccount: string): Promise<DotTx> {
const { data } = await api.post<DotTx>(`/v1/dot/transaction/claim-payout-pool`, {
member_account: memberAccount,
});
return data;
}

/**
* Craft a pool unbond transaction
* Unbond amount funds from the pool.
* It implicitly collects the rewards one last time, since not doing so would mean some rewards would be forfeited.
* Warning: you cannot rebond during the unbonding period with a nomination pool. If you change your mind, you must wait for the unbonding period to end before you can join a nomination pool again.
* @param memberAccount
* @param amountDot
*/
async craftUnbondFromPoolTx(memberAccount: string, amountDot: number): Promise<DotTx> {
const amountPlanck = this.testnet ? this.wndToPlanck(amountDot.toString()) : this.dotToPlanck(amountDot.toString());
const { data } = await api.post<DotTx>(`/v1/dot/transaction/unbond-pool`, {
member_account: memberAccount,
amount_planck: amountPlanck,
});
return data;
}

/**
* Craft a pool withdraw unbonded transaction
* Withdraw unbonded funds from member_account.
* @param memberAccount
*/
async craftWithdrawUnbondedFromPoolTx(memberAccount: string): Promise<DotTx> {
const { data } = await api.post<DotTx>(`/v1/dot/transaction/withdraw-unbonded-pool`, {
member_account: memberAccount,
});
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: DotTx, note?: string): Promise<DotSignedTx> {
const payload = {
rawMessageData: {
messages: [
{
content: tx.data.unsigned_tx_payload.substring(2),
},
],
},
};

const fbSigner = this.getFbSigner(integration);
const fbNote = note ? note : "DOT tx from @kilnfi/sdk";
const fbTx = await fbSigner.sign(payload, this.testnet ? "WND" : "DOT", fbNote);
const signature = `0x00${fbTx.signedMessages![0].signature.fullSig}`;

const { data } = await api.post<DotSignedTx>(`/v1/dot/transaction/prepare`, {
unsigned_tx_serialized: tx.data.unsigned_tx_serialized,
signature: signature,
});
data.data.fireblocks_tx = fbTx;
return data;
}

/**
* Broadcast signed transaction
* @param signedTx
*/
async broadcast(signedTx: DotSignedTx): Promise<DotTxHash> {
const { data } = await api.post<DotTxHash>(`/v1/dot/transaction/broadcast`, {
tx_serialized: signedTx.data.signed_tx_serialized,
});
return data;
}

/**
* Get transaction status
* @param txHash transaction hash
*/
async getTxStatus(txHash: string): Promise<DotTxStatus> {
const { data } = await api.get<DotTxStatus>(`/v1/dot/transaction/status?tx_hash=${txHash}`);
return data;
}

/**
* Decode transaction
* @param txSerialized transaction serialized
*/
async decodeTx(txSerialized: string): Promise<UnsignedTransaction> {
const { data } = await api.get<UnsignedTransaction>(`/v1/dot/transaction/decode?tx_serialized=${txSerialized}`);
return data;
}
}
Loading