Skip to content

Commit

Permalink
STREAM-1597: account for transfer fees when creating Airdrops (#177)
Browse files Browse the repository at this point in the history
* STREAM-1597: account for transfer fees when creating Airdrops

* bump

* move calculateAmountWithTransferFees to a function
  • Loading branch information
Yolley authored May 24, 2024
1 parent c769b8b commit 9e892b3
Show file tree
Hide file tree
Showing 8 changed files with 79 additions and 17 deletions.
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"packages": [
"packages/*"
],
"version": "6.3.7",
"version": "6.3.8",
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
}
2 changes: 1 addition & 1 deletion packages/common/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@streamflow/common",
"version": "6.3.7",
"version": "6.3.8",
"description": "Common utilities and types used by streamflow packages.",
"homepage": "https://github.com/streamflow-finance/js-sdk/",
"main": "dist/index.js",
Expand Down
2 changes: 1 addition & 1 deletion packages/distributor/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@streamflow/distributor",
"version": "6.3.7",
"version": "6.3.8",
"description": "JavaScript SDK to interact with Streamflow Airdrop protocol.",
"homepage": "https://github.com/streamflow-finance/js-sdk/",
"main": "dist/index.js",
Expand Down
55 changes: 43 additions & 12 deletions packages/distributor/solana/client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import BN from "bn.js";
import PQueue from "p-queue";
import { ASSOCIATED_TOKEN_PROGRAM_ID, NATIVE_MINT, createTransferCheckedInstruction } from "@solana/spl-token";
import {
ASSOCIATED_TOKEN_PROGRAM_ID,
NATIVE_MINT,
createTransferCheckedInstruction,
createTransferCheckedWithFeeInstruction,
getTransferFeeConfig,
} from "@solana/spl-token";
import {
Connection,
PublicKey,
Expand Down Expand Up @@ -45,6 +51,7 @@ import {
} from "./generated/instructions";
import { ClaimStatus, MerkleDistributor } from "./generated/accounts";
import {
calculateAmountWithTransferFees,
getClaimantStatusPda,
getDistributorPda,
getEventAuthorityPda,
Expand Down Expand Up @@ -123,6 +130,7 @@ export default class SolanaDistributorClient {
const ixs: TransactionInstruction[] = prepareBaseInstructions(this.connection, extParams);
const mint = extParams.isNative ? NATIVE_MINT : new PublicKey(data.mint);
const { mint: mintAccount, tokenProgramId } = await getMintAndProgram(this.connection, mint);
const transferFeeConfig = getTransferFeeConfig(mintAccount);
const distributorPublicKey = getDistributorPda(this.programId, mint, data.version);
const tokenVault = await ata(mint, distributorPublicKey, tokenProgramId);
const senderTokens = await ata(mint, extParams.invoker.publicKey, tokenProgramId);
Expand Down Expand Up @@ -161,18 +169,41 @@ export default class SolanaDistributorClient {
}

ixs.push(newDistributor(args, accounts, this.programId));
ixs.push(
createTransferCheckedInstruction(
senderTokens,
mint,
tokenVault,
extParams.invoker.publicKey,

if (transferFeeConfig) {
const { transferAmount, feeCharged } = await calculateAmountWithTransferFees(
this.connection,
transferFeeConfig,
BigInt(data.maxTotalClaim.toString()),
mintAccount.decimals,
undefined,
tokenProgramId,
),
);
);

ixs.push(
createTransferCheckedWithFeeInstruction(
senderTokens,
mint,
tokenVault,
extParams.invoker.publicKey,
transferAmount,
mintAccount.decimals,
feeCharged,
undefined,
tokenProgramId,
),
);
} else {
ixs.push(
createTransferCheckedInstruction(
senderTokens,
mint,
tokenVault,
extParams.invoker.publicKey,
BigInt(data.maxTotalClaim.toString()),
mintAccount.decimals,
undefined,
tokenProgramId,
),
);
}

return { distributorPublicKey, ixs };
}
Expand Down
2 changes: 2 additions & 0 deletions packages/distributor/solana/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { ICluster } from "@streamflow/common";

export const ONE_IN_BASIS_POINTS = BigInt(10_000);

export const DISTRIBUTOR_PROGRAM_ID = {
[ICluster.Devnet]: "MErKy6nZVoVAkryxAejJz2juifQ4ArgLgHmaJCQkU7N",
[ICluster.Mainnet]: "MErKy6nZVoVAkryxAejJz2juifQ4ArgLgHmaJCQkU7N",
Expand Down
29 changes: 29 additions & 0 deletions packages/distributor/solana/utils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { TransferFeeConfig } from "@solana/spl-token";
import { SignerWalletAdapter } from "@solana/wallet-adapter-base";
import { Connection, Keypair, PublicKey, Transaction, VersionedTransaction } from "@solana/web3.js";
import { ContractError } from "@streamflow/common";
import { ConfirmationParams, signAndExecuteTransaction, ThrottleParams } from "@streamflow/common/solana";

import { fromTxError } from "./generated/errors";
import { ONE_IN_BASIS_POINTS } from "./constants";

export const divCeilN = (n: bigint, d: bigint): bigint => n / d + (n % d ? BigInt(1) : BigInt(0));

export function getDistributorPda(programId: PublicKey, mint: PublicKey, version: number): PublicKey {
// Constructing the seed for the PDA
Expand Down Expand Up @@ -52,3 +56,28 @@ export async function wrappedSignAndExecuteTransaction(
throw err;
}
}

export async function calculateAmountWithTransferFees(
connection: Connection,
transferFeeConfig: TransferFeeConfig,
transferAmount: bigint,
): Promise<{ transferAmount: bigint; feeCharged: bigint }> {
const epoch = await connection.getEpochInfo();
const transferFee =
epoch.epoch >= transferFeeConfig.newerTransferFee.epoch
? transferFeeConfig.newerTransferFee
: transferFeeConfig.olderTransferFee;
const transferFeeBasisPoints = BigInt(transferFee.transferFeeBasisPoints);
let feeCharged = BigInt(0);

if (transferFeeBasisPoints !== BigInt(0)) {
const numerator = transferAmount * ONE_IN_BASIS_POINTS;
const denominator = ONE_IN_BASIS_POINTS - transferFeeBasisPoints;
const rawPreFeeAmount = divCeilN(numerator, denominator);
const fee = rawPreFeeAmount - transferAmount;
transferAmount = rawPreFeeAmount;
feeCharged = fee > transferFee.maximumFee ? transferFee.maximumFee : fee;
}

return { transferAmount, feeCharged };
}
2 changes: 1 addition & 1 deletion packages/eslint-config/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@streamflow/eslint-config",
"version": "6.3.7",
"version": "6.3.8",
"license": "ISC",
"main": "index.js",
"files": [
Expand Down
2 changes: 1 addition & 1 deletion packages/stream/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@streamflow/stream",
"version": "6.3.7",
"version": "6.3.8",
"description": "JavaScript SDK to interact with Streamflow protocol.",
"homepage": "https://github.com/streamflow-finance/js-sdk/",
"main": "dist/index.js",
Expand Down

0 comments on commit 9e892b3

Please sign in to comment.