@send-fun/sdk 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +8 -9
  2. package/dist/index.cjs +526 -98
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +595 -93
  5. package/dist/index.d.cts.map +1 -1
  6. package/dist/index.d.mts +595 -93
  7. package/dist/index.d.mts.map +1 -1
  8. package/dist/index.mjs +526 -98
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +1 -1
  11. package/src/dex/generated/accounts/globalConfig.ts +39 -2
  12. package/src/dex/generated/accounts/pool.ts +41 -2
  13. package/src/dex/generated/accounts/rewardAccrual.ts +39 -2
  14. package/src/dex/generated/index.ts +1 -0
  15. package/src/dex/generated/shared/index.ts +52 -0
  16. package/src/dex/trade.ts +12 -13
  17. package/src/launchpad/create.ts +8 -8
  18. package/src/launchpad/generated/accounts/bondingCurve.ts +39 -2
  19. package/src/launchpad/generated/accounts/globalConfig.ts +39 -2
  20. package/src/launchpad/generated/accounts/rewardAccrual.ts +39 -2
  21. package/src/launchpad/generated/index.ts +1 -0
  22. package/src/launchpad/generated/shared/index.ts +52 -0
  23. package/src/launchpad/migrate.ts +1 -1
  24. package/src/launchpad/trade.ts +15 -15
  25. package/src/math/amm.ts +40 -33
  26. package/src/math/fee-decay.ts +3 -2
  27. package/src/math/fees.ts +4 -2
  28. package/src/math/internal.ts +1 -1
  29. package/src/nexus/fee-helpers.ts +5 -3
  30. package/src/nexus/generated/accounts/altRegistry.ts +41 -2
  31. package/src/nexus/generated/accounts/creatorFeeConfig.ts +39 -2
  32. package/src/nexus/generated/accounts/feePreset.ts +41 -2
  33. package/src/nexus/generated/accounts/globalConfig.ts +39 -2
  34. package/src/nexus/generated/accounts/partnerConfig.ts +39 -2
  35. package/src/nexus/generated/accounts/partnerMetadata.ts +39 -2
  36. package/src/nexus/generated/accounts/rewardState.ts +41 -2
  37. package/src/nexus/generated/accounts/stakingConfig.ts +39 -2
  38. package/src/nexus/generated/accounts/userRewardDebt.ts +39 -2
  39. package/src/nexus/generated/accounts/userStakePosition.ts +39 -2
  40. package/src/nexus/generated/index.ts +1 -0
  41. package/src/nexus/generated/shared/index.ts +52 -0
  42. package/src/nexus/staking.ts +45 -41
  43. package/src/platform.ts +2 -2
  44. package/src/transfer-fee.ts +19 -17
  45. package/src/utils/chunk.ts +1 -1
  46. package/src/utils/creator-hash.ts +6 -3
  47. package/src/utils/index.ts +2 -0
  48. package/src/utils/mint-info.ts +8 -7
  49. package/src/utils/partner.ts +1 -1
  50. package/src/utils/pda.ts +1 -1
@@ -42,6 +42,7 @@ import {
42
42
  type ReadonlyUint8Array,
43
43
  } from '@solana/kit';
44
44
  import { SEND_LAUNCHPAD_PROGRAM_ADDRESS } from '../programs/index.js';
45
+ import { accountIsCreated } from '../shared/index.js';
45
46
 
46
47
  export const REWARD_ACCRUAL_DISCRIMINATOR: ReadonlyUint8Array = new Uint8Array([
47
48
  158, 244, 116, 30, 227, 132, 105, 182,
@@ -109,9 +110,25 @@ export function getRewardAccrualCodec(): FixedSizeCodec<
109
110
  return combineCodec(getRewardAccrualEncoder(), getRewardAccrualDecoder());
110
111
  }
111
112
 
113
+ /**
114
+ * Decodes a `RewardAccrual` account, throwing when another program owns it or its discriminator
115
+ * does not match.
116
+ *
117
+ * Unlike {@link fetchMaybeRewardAccrual}, this throws for an address that only holds lamports rather
118
+ * than returning the non-existing variant: an account passed as existing must never come back as that
119
+ * variant typed as an `Account`. Apply {@link accountIsCreated} first to accounts fetched another way.
120
+ */
112
121
  export function decodeRewardAccrual<TAddress extends string = string>(
113
122
  encodedAccount: EncodedAccount<TAddress>,
114
123
  ): Account<RewardAccrual, TAddress>;
124
+ /**
125
+ * Decodes a `RewardAccrual` account, throwing when another program owns it or its discriminator
126
+ * does not match.
127
+ *
128
+ * Unlike {@link fetchMaybeRewardAccrual}, this throws for an address that only holds lamports rather
129
+ * than returning the non-existing variant: an account passed as existing must never come back as that
130
+ * variant typed as an `Account`. Apply {@link accountIsCreated} first to accounts fetched another way.
131
+ */
115
132
  export function decodeRewardAccrual<TAddress extends string = string>(
116
133
  encodedAccount: MaybeEncodedAccount<TAddress>,
117
134
  ): MaybeAccount<RewardAccrual, TAddress>;
@@ -142,6 +159,7 @@ export function decodeRewardAccrual<TAddress extends string = string>(
142
159
  );
143
160
  }
144
161
 
162
+ /** Fetches a `RewardAccrual` account, throwing when it does not exist or only holds lamports. */
145
163
  export async function fetchRewardAccrual<TAddress extends string = string>(
146
164
  rpc: Parameters<typeof fetchEncodedAccount>[0],
147
165
  address: Address<TAddress>,
@@ -152,15 +170,25 @@ export async function fetchRewardAccrual<TAddress extends string = string>(
152
170
  return maybeAccount;
153
171
  }
154
172
 
173
+ /**
174
+ * Fetches a `RewardAccrual` account, or the non-existing variant when the address holds no
175
+ * account or only lamports (see {@link accountIsCreated}).
176
+ * {@link decodeRewardAccrual} throws for a lamports-only account instead.
177
+ */
155
178
  export async function fetchMaybeRewardAccrual<TAddress extends string = string>(
156
179
  rpc: Parameters<typeof fetchEncodedAccount>[0],
157
180
  address: Address<TAddress>,
158
181
  config?: FetchAccountConfig,
159
182
  ): Promise<MaybeAccount<RewardAccrual, TAddress>> {
160
183
  const maybeAccount = await fetchEncodedAccount(rpc, address, config);
161
- return decodeRewardAccrual(maybeAccount);
184
+ return decodeRewardAccrual(
185
+ accountIsCreated(maybeAccount)
186
+ ? maybeAccount
187
+ : { address, exists: false },
188
+ );
162
189
  }
163
190
 
191
+ /** Fetches `RewardAccrual` accounts, throwing when any does not exist or only holds lamports. */
164
192
  export async function fetchAllRewardAccrual(
165
193
  rpc: Parameters<typeof fetchEncodedAccounts>[0],
166
194
  addresses: Array<Address>,
@@ -175,6 +203,11 @@ export async function fetchAllRewardAccrual(
175
203
  return maybeAccounts;
176
204
  }
177
205
 
206
+ /**
207
+ * Fetches `RewardAccrual` accounts, with the non-existing variant for each address that holds
208
+ * no account or only lamports (see {@link accountIsCreated}).
209
+ * {@link decodeRewardAccrual} throws for a lamports-only account instead.
210
+ */
178
211
  export async function fetchAllMaybeRewardAccrual(
179
212
  rpc: Parameters<typeof fetchEncodedAccounts>[0],
180
213
  addresses: Array<Address>,
@@ -182,7 +215,11 @@ export async function fetchAllMaybeRewardAccrual(
182
215
  ): Promise<MaybeAccount<RewardAccrual>[]> {
183
216
  const maybeAccounts = await fetchEncodedAccounts(rpc, addresses, config);
184
217
  return maybeAccounts.map((maybeAccount) =>
185
- decodeRewardAccrual(maybeAccount),
218
+ decodeRewardAccrual(
219
+ accountIsCreated(maybeAccount)
220
+ ? maybeAccount
221
+ : { address: maybeAccount.address, exists: false },
222
+ ),
186
223
  );
187
224
  }
188
225
 
@@ -13,4 +13,5 @@ export * from './instructions/index.js';
13
13
  export * from './pdas/index.js';
14
14
  export * from './plugins/index.js';
15
15
  export * from './programs/index.js';
16
+ export * from './shared/index.js';
16
17
  export * from './types/index.js';
@@ -0,0 +1,52 @@
1
+ /**
2
+ * This code was AUTOGENERATED using the Codama library.
3
+ * Please DO NOT EDIT THIS FILE, instead use visitors
4
+ * to add features, then rerun Codama to update it.
5
+ *
6
+ * @see https://github.com/codama-idl/codama
7
+ */
8
+
9
+ import type { Address, EncodedAccount, MaybeEncodedAccount } from '@solana/kit';
10
+
11
+ /** The System Program, which owns every address nobody has created an account at yet. */
12
+ const SYSTEM_PROGRAM_ADDRESS =
13
+ '11111111111111111111111111111111' as Address<'11111111111111111111111111111111'>;
14
+
15
+ /**
16
+ * Whether an encoded account was created on-chain, rather than merely sent lamports.
17
+ *
18
+ * Anyone can send lamports to an address before its account is created, which leaves it on-chain,
19
+ * owned by the System Program, with no data. This returns `false` for that shape, as it does for an
20
+ * account that does not exist. Any other existing account counts as created, whoever owns it; the
21
+ * `decode*` functions still check the owner.
22
+ *
23
+ * The generated `fetchMaybe*` and `fetchAllMaybe*` helpers apply this rule themselves. Apply it to
24
+ * accounts you fetch or receive another way (subscriptions, batch loaders, caches) before passing
25
+ * them to a `decode*` function, which throws for a lamports-only account: an account passed to it as
26
+ * existing must never come back as the non-existing variant typed as an `Account`.
27
+ *
28
+ * Only a `true` result narrows the type. A lamports-only account is still `exists: true` at runtime,
29
+ * so a `false` result leaves the type unchanged instead of narrowing it to the non-existing variant.
30
+ * The `uncreated` property in the narrowed type is never present; it exists only to prevent that.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * const maybeAccount = decodeMyAccount(
35
+ * accountIsCreated(encodedAccount) ? encodedAccount : { address: encodedAccount.address, exists: false },
36
+ * );
37
+ * ```
38
+ */
39
+ export function accountIsCreated<
40
+ TAccount extends EncodedAccount | MaybeEncodedAccount,
41
+ >(
42
+ account: TAccount,
43
+ ): account is Exclude<TAccount, { readonly exists: false }> & {
44
+ readonly uncreated?: never;
45
+ } {
46
+ const maybeAccount: EncodedAccount | MaybeEncodedAccount = account;
47
+ if ('exists' in maybeAccount && !maybeAccount.exists) return false;
48
+ return !(
49
+ maybeAccount.programAddress === SYSTEM_PROGRAM_ADDRESS &&
50
+ maybeAccount.data.length === 0
51
+ );
52
+ }
@@ -9,7 +9,7 @@ export interface MigrateParams {
9
9
  caller: TransactionSigner;
10
10
  baseMint: Address;
11
11
  quoteMint: Address;
12
- /** `bondingCurve.creatorFeeConfig`; `migrate` rejects any other address. */
12
+ /** `bondingCurve.creatorFeeConfig`. `migrate` rejects any other address. */
13
13
  creatorFeeConfig: Address;
14
14
  quoteTokenProgram: Address;
15
15
  }
@@ -19,7 +19,7 @@ export interface LaunchpadTradeParams {
19
19
  partner: PartnerInput;
20
20
  platformConfig: Address;
21
21
  quoteTokenProgram: Address;
22
- /** Token-2022 schedule for the epoch the trade lands in; stale or missing skews the slippage bounds. */
22
+ /** The quote mint's transfer fee for the epoch the trade lands in. A missing or old fee gives wrong slippage bounds. */
23
23
  quoteFee?: MintFee;
24
24
  baseFee?: MintFee;
25
25
  /** {@inheritDoc LaunchpadInstructionParams.userQuoteAccount} */
@@ -40,16 +40,16 @@ export interface LaunchpadInstructionParams {
40
40
  partner: PartnerInput;
41
41
  platformConfig: Address;
42
42
  quoteTokenProgram: Address;
43
- /** Any user-owned quote-mint account. Defaults to the ATA, created mid-trade
44
- * if missing (payer funds rent), which only rescues a sell. For WSOL,
45
- * a throwaway `createAccountWithSeed` account is cheaper. */
43
+ /** Any quote-mint token account that `user` owns. Defaults to the user's ATA.
44
+ * The program creates a missing ATA, and `payer` pays the rent. */
46
45
  userQuoteAccount?: Address;
47
- /** Any user-owned base-mint account. Defaults to the ATA, created mid-trade
48
- * if missing (payer funds rent), which only rescues a buy. */
46
+ /** Any base-mint token account that `user` owns. Defaults to the user's ATA.
47
+ * The program creates a missing ATA, and `payer` pays the rent. */
49
48
  userBaseAccount?: Address;
50
49
  }
51
50
 
52
- /** `baseAmountOut` is net to the buyer: the program reads `amount` as `base_to_user`. */
51
+ /** `baseAmountOut` is the base the buyer receives, after the base mint's transfer fee.
52
+ * The buy is capped at the curve's supply left. Read the result from `quote.baseToUser`. */
53
53
  export async function buyExactOut(
54
54
  params: LaunchpadBuyParams & { baseAmountOut: bigint },
55
55
  ): Promise<{ instruction: Instruction; quote: BuyQuote }> {
@@ -62,7 +62,7 @@ export async function buyExactOut(
62
62
  baseFee: params.baseFee,
63
63
  baseReserveCap: params.realBaseReserves,
64
64
  });
65
- // The cap is measured on the gross the buyer sends, quote transfer fee included.
65
+ // The program checks `maxAmountIn` against the gross the buyer sends, quote transfer fee included.
66
66
  const maxQuoteIn = amm.calculateSlippageUp(
67
67
  quote.quoteFromUser,
68
68
  params.slippageBps,
@@ -70,7 +70,7 @@ export async function buyExactOut(
70
70
  return {
71
71
  instruction: await buildBuyExactOutInstruction({
72
72
  ...params,
73
- // Capped at the supply left. The program reverts a short fill.
73
+ // Capped at the supply left. The program rejects a larger amount.
74
74
  amountOut: quote.baseToUser,
75
75
  maxAmountIn: maxQuoteIn,
76
76
  }),
@@ -90,7 +90,7 @@ export async function buyExactIn(
90
90
  baseFee: params.baseFee,
91
91
  baseReserveCap: params.realBaseReserves,
92
92
  });
93
- // The floor is measured on the buyer's credit, not the vault's debit.
93
+ // The program checks `minAmountOut` against what the buyer receives, not the vault's debit.
94
94
  const minBaseOut = amm.calculateSlippageDown(
95
95
  quote.baseToUser,
96
96
  params.slippageBps,
@@ -116,7 +116,7 @@ export async function sellExactIn(
116
116
  quoteFee: params.quoteFee,
117
117
  baseFee: params.baseFee,
118
118
  });
119
- // The floor is measured on the seller's credit, not what leaves the vault.
119
+ // The program checks `minAmountOut` against what the seller receives, not the vault's debit.
120
120
  const minQuoteOut = amm.calculateSlippageDown(
121
121
  quote.quoteToUser,
122
122
  params.slippageBps,
@@ -131,7 +131,7 @@ export async function sellExactIn(
131
131
  };
132
132
  }
133
133
 
134
- /** `quoteAmountOut` is net to the seller: the program reads `amount` as `quote_to_user`. */
134
+ /** `quoteAmountOut` is the quote the seller receives, after the quote mint's transfer fee. */
135
135
  export async function sellExactOut(
136
136
  params: LaunchpadTradeParams & { quoteAmountOut: bigint },
137
137
  ): Promise<{ instruction: Instruction; quote: SellQuote }> {
@@ -143,7 +143,7 @@ export async function sellExactOut(
143
143
  quoteFee: params.quoteFee,
144
144
  baseFee: params.baseFee,
145
145
  });
146
- // The cap is measured on the gross the seller sends, base transfer fee included.
146
+ // The program checks `maxAmountIn` against the gross the seller sends, base transfer fee included.
147
147
  const maxBaseIn = amm.calculateSlippageUp(
148
148
  quote.baseFromUser,
149
149
  params.slippageBps,
@@ -214,7 +214,7 @@ export async function buildSellExactOutInstruction(
214
214
  });
215
215
  }
216
216
 
217
- /** Percent of the curve's real base sold (0-100); 100 is the migration threshold. */
217
+ /** Returns the percent of the curve's real base sold, from 0 to 100. At 100 the curve can migrate. */
218
218
  export function calculateBondingCurveProgress(params: {
219
219
  realBaseReserves: bigint;
220
220
  initialRealBase: bigint;
@@ -237,7 +237,7 @@ function resolveSharedAccounts(params: LaunchpadInstructionParams) {
237
237
  partner: params.partner,
238
238
  platformConfig: params.platformConfig,
239
239
  quoteTokenProgram: params.quoteTokenProgram,
240
- // Left undefined so the generated client derives the ATA itself.
240
+ // When omitted, the generated client derives the ATA.
241
241
  ...(params.userQuoteAccount !== undefined && {
242
242
  userQuoteAccount: params.userQuoteAccount,
243
243
  }),
package/src/math/amm.ts CHANGED
@@ -4,7 +4,6 @@ const BPS_DIVISOR = 10_000n;
4
4
 
5
5
  const U64_MAX = 18_446_744_073_709_551_615n;
6
6
 
7
- /** Each call mirrors a `u64::try_from` in the Rust twin; drop one and an oversized leg fails in the encoder. */
8
7
  function assertU64(label: string, value: bigint): bigint {
9
8
  if (value > U64_MAX) {
10
9
  throw new RangeError(`${label}: overflows u64`);
@@ -12,15 +11,15 @@ function assertU64(label: string, value: bigint): bigint {
12
11
  return value;
13
12
  }
14
13
 
15
- /** Token-2022 `TransferFeeConfig` for the epoch the trade lands in; a stale one misprices the trade. */
14
+ /** A Token-2022 transfer fee for one epoch. Use the fee for the epoch the trade lands in. */
16
15
  export interface MintFee {
17
16
  /** 0 to 10_000. */
18
17
  bps: number;
19
- /** Cap on the withheld amount, in the mint's raw units. */
18
+ /** Maximum fee per transfer, in the mint's raw units. */
20
19
  maximumFee: bigint;
21
20
  }
22
21
 
23
- /** No transfer lands exactly the requested amount; approximating one hands the program a bound it rejects. */
22
+ /** Thrown when no transfer delivers exactly `amount` after the transfer fee. */
24
23
  export class TransferFeeNotSettleableError extends RangeError {
25
24
  readonly amount: bigint;
26
25
  readonly mintFee: MintFee;
@@ -42,7 +41,8 @@ function assertMintFee(fee: MintFee): void {
42
41
  }
43
42
  }
44
43
 
45
- /** Rounds up, then caps (SPL's order); swapping them lets a split booking over-credit. */
44
+ /** Returns the transfer fee on `amount`. Rounds up, then caps at `maximumFee`, in SPL's order.
45
+ * Returns 0 without `fee`. Throws `RangeError` if `fee` is out of range. */
46
46
  export function feeOn(amount: bigint, fee?: MintFee): bigint {
47
47
  if (fee === undefined) return 0n;
48
48
  assertMintFee(fee);
@@ -52,18 +52,18 @@ export function feeOn(amount: bigint, fee?: MintFee): bigint {
52
52
  return raw < fee.maximumFee ? raw : fee.maximumFee;
53
53
  }
54
54
 
55
- /** What lands when `amount` is sent; use `grossUp` to land an exact amount. */
55
+ /** Returns what the recipient receives when `amount` is sent. `grossUp` is the inverse. */
56
56
  export function amountAfterFee(amount: bigint, fee?: MintFee): bigint {
57
57
  return amount - feeOn(amount, fee);
58
58
  }
59
59
 
60
- /** Line-for-line mirror of SPL's `TransferFee::calculate_pre_fee_amount`; `undefined` when no u64 answer exists. */
60
+ /** Matches SPL `TransferFee::calculate_pre_fee_amount`. `undefined` if the result does not fit a u64. */
61
61
  function preFeeAmount(amount: bigint, fee: MintFee): bigint | undefined {
62
62
  const bps = BigInt(fee.bps);
63
63
  if (bps === 0n) return amount;
64
- // Unreachable via `grossUp`; SPL has it, so the mirror keeps it.
64
+ // Unreachable from `grossUp`. Kept to match SPL.
65
65
  if (amount === 0n) return 0n;
66
- // 100%: only the cap can be settled.
66
+ // At 100%, the fee is always `maximumFee`.
67
67
  if (bps === BPS_DIVISOR) {
68
68
  const capped = amount + fee.maximumFee;
69
69
  return capped > U64_MAX ? undefined : capped;
@@ -77,7 +77,8 @@ function preFeeAmount(amount: bigint, fee: MintFee): bigint | undefined {
77
77
  return rawPreFee > U64_MAX ? undefined : rawPreFee;
78
78
  }
79
79
 
80
- /** What must be sent for exactly `amount` to land; throws {@link TransferFeeNotSettleableError} if none does. */
80
+ /** Returns the amount to send so that exactly `amount` arrives. Returns `amount` without `fee`.
81
+ * Throws {@link TransferFeeNotSettleableError} if no such amount exists. */
81
82
  export function grossUp(amount: bigint, fee?: MintFee): bigint {
82
83
  if (fee === undefined) return amount;
83
84
  assertMintFee(fee);
@@ -87,7 +88,7 @@ export function grossUp(amount: bigint, fee?: MintFee): bigint {
87
88
  if (preFee === undefined)
88
89
  throw new TransferFeeNotSettleableError(amount, fee);
89
90
 
90
- // SPL's inverse is inexact (`feeOn(x) >= inverse(x - feeOn(x))`): re-derive forward and reject a mismatch.
91
+ // SPL's inverse is inexact (`feeOn(x) >= inverse(x - feeOn(x))`). Check it forward and reject a mismatch.
91
92
  const impliedFee = feeOn(preFee, fee);
92
93
  const gross = amount + impliedFee;
93
94
  if (gross > U64_MAX || feeOn(gross, fee) !== impliedFee) {
@@ -96,8 +97,8 @@ export function grossUp(amount: bigint, fee?: MintFee): bigint {
96
97
  return gross;
97
98
  }
98
99
 
99
- /** `baseAmount`/`quoteAmount` are the user's transfers, as in on-chain `TradeResult`. Read the net
100
- * fields on {@link BuyQuote}/{@link SellQuote}; re-deriving them drifts from the program by a rounding step. */
100
+ /** `baseAmount` and `quoteAmount` are the user's transfers.
101
+ * Read the net amounts from the fields of {@link BuyQuote} and {@link SellQuote}. Do not calculate them again. */
101
102
  export interface TradeQuote {
102
103
  /** Base sent: vault to user on a buy, user to vault on a sell. */
103
104
  baseAmount: bigint;
@@ -110,20 +111,20 @@ export interface TradeQuote {
110
111
  }
111
112
 
112
113
  export interface BuyQuote extends TradeQuote {
113
- /** Priced on the quote reaching the vault. */
114
+ /** Priced on the quote that reaches the vault. */
114
115
  fee: bigint;
115
- /** Base credited to the buyer, after the base mint's cut. */
116
+ /** Base the buyer receives, after the base mint's transfer fee. */
116
117
  baseToUser: bigint;
117
118
  /** Equals `quoteAmount`. */
118
119
  quoteFromUser: bigint;
119
120
  }
120
121
 
121
122
  export interface SellQuote extends TradeQuote {
122
- /** Priced on the quote leaving the vault. */
123
+ /** Priced on the AMM's quote output, before this fee. */
123
124
  fee: bigint;
124
125
  /** Equals `baseAmount`. */
125
126
  baseFromUser: bigint;
126
- /** Quote credited to the seller, after the quote mint's cut. */
127
+ /** Quote the seller receives, after the quote mint's transfer fee. */
127
128
  quoteToUser: bigint;
128
129
  }
129
130
 
@@ -139,7 +140,8 @@ function sqrtBigInt(value: bigint): bigint {
139
140
  return x;
140
141
  }
141
142
 
142
- /** Output rounds down (the new reserve rounds up), so `k` never shrinks. */
143
+ /** Returns the constant-product output for `amountIn`, rounded down. Throws `RangeError` on a zero
144
+ * reserve, a zero `amountIn`, a zero output, or an output past u64. */
143
145
  export function calculateOutput(
144
146
  reserveIn: bigint,
145
147
  reserveOut: bigint,
@@ -169,7 +171,8 @@ export function calculateOutput(
169
171
  return assertU64('calculateOutput', amountOut);
170
172
  }
171
173
 
172
- /** Required input rounds up so the user pays enough. */
174
+ /** Returns the input needed for `amountOut`, rounded up. Throws `RangeError` on a zero reserve, a zero
175
+ * `amountOut`, an `amountOut` not below `reserveOut`, or an input past u64. */
173
176
  export function calculateInputForOutput(
174
177
  reserveIn: bigint,
175
178
  reserveOut: bigint,
@@ -207,7 +210,7 @@ interface BaseQuoteParams {
207
210
  baseFee?: MintFee;
208
211
  }
209
212
 
210
- /** Launchpad only: `bondingCurve.realBaseReserves`. A DEX pool has no cap. */
213
+ /** `baseReserveCap` is `bondingCurve.realBaseReserves`. A DEX pool has no cap. */
211
214
  interface BaseReserveCap {
212
215
  baseReserveCap?: bigint;
213
216
  }
@@ -234,7 +237,7 @@ function ammBuyExactOut(
234
237
  throw new RangeError('buyExactOut: invalid feeBps');
235
238
  }
236
239
 
237
- // A `feeBps` near 10_000 amplifies the leg up to 10_000x, so this can overflow u64.
240
+ // A `feeBps` near 10_000 multiplies the leg by up to 10_000, so this can pass u64.
238
241
  const totalQuote = assertU64(
239
242
  'buyExactOut',
240
243
  ceilDiv(quoteBeforeFee * BPS_DIVISOR, divisor),
@@ -251,7 +254,7 @@ function ammBuyExactIn(
251
254
  params: { reserveQuote: bigint; reserveBase: bigint; feeBps: number },
252
255
  quoteAmountIn: bigint,
253
256
  ): AmmLegs {
254
- // Fee comes off before the swap; the net rounds down, so the fee keeps the remainder.
257
+ // The fee comes off before the swap. The net rounds down, and the fee takes the remainder.
255
258
  const netFactor = BPS_DIVISOR - BigInt(params.feeBps);
256
259
  if (netFactor <= 0n) {
257
260
  throw new RangeError('buyExactIn: invalid feeBps');
@@ -337,7 +340,9 @@ function buyQuote(
337
340
  };
338
341
  }
339
342
 
340
- /** Guard with `calculateSlippageUp(quoteAmount, bps)`; it already carries the quote mint's cut. */
343
+ /** Quotes a buy where the buyer receives `baseAmountOut`. With `baseReserveCap`, the vault sends at
344
+ * most the cap. Guard with `calculateSlippageUp(quoteAmount, bps)`. `quoteAmount` includes the quote
345
+ * mint's transfer fee. */
341
346
  export function buyExactOut(
342
347
  params: BaseQuoteParams & BaseReserveCap & { baseAmountOut: bigint },
343
348
  ): BuyQuote {
@@ -352,7 +357,7 @@ export function buyExactOut(
352
357
  cap !== undefined && baseOutOfVault > cap ? cap : baseOutOfVault;
353
358
 
354
359
  const legs = ammBuyExactOut(params, baseAmount);
355
- // The priced total must land, so the user is debited more than it.
360
+ // The priced total must reach the vault, so the user sends it grossed up.
356
361
  const quoteFromUser = grossUp(legs.quoteAmount, params.quoteFee);
357
362
 
358
363
  return buyQuote(
@@ -362,7 +367,8 @@ export function buyExactOut(
362
367
  );
363
368
  }
364
369
 
365
- /** Guard with `calculateSlippageDown(baseToUser, bps)`, not `baseAmount`: the program bounds what the buyer nets. */
370
+ /** Quotes a buy that spends `quoteAmountIn`. Guard with `calculateSlippageDown(baseToUser, bps)`, not
371
+ * `baseAmount`. The program checks the minimum against what the buyer receives. */
366
372
  export function buyExactIn(
367
373
  params: BaseQuoteParams & BaseReserveCap & { quoteAmountIn: bigint },
368
374
  ): BuyQuote {
@@ -371,7 +377,7 @@ export function buyExactIn(
371
377
  throw new RangeError('buyExactIn: invalid amount');
372
378
  }
373
379
 
374
- // The AMM only ever prices what reaches the vault.
380
+ // The AMM prices only what reaches the vault.
375
381
  const quoteIntoVault = amountAfterFee(
376
382
  params.quoteAmountIn,
377
383
  params.quoteFee,
@@ -382,7 +388,7 @@ export function buyExactIn(
382
388
 
383
389
  const uncapped = ammBuyExactIn(params, quoteIntoVault);
384
390
 
385
- // Capped: re-price exact-out at the cap and gross up; clamping the base output overstates the quote leg.
391
+ // Over the cap: price exact-out at the cap, then gross up. Clamping the base output alone overstates the quote leg.
386
392
  const cap = params.baseReserveCap;
387
393
  if (cap === undefined || uncapped.baseAmount <= cap) {
388
394
  return buyQuote(
@@ -402,7 +408,7 @@ export function buyExactIn(
402
408
  );
403
409
  }
404
410
 
405
- /** Guard with `calculateSlippageDown(quoteToUser, bps)`, not `quoteAmount`. */
411
+ /** Quotes a sell of `baseAmountIn`. Guard with `calculateSlippageDown(quoteToUser, bps)`, not `quoteAmount`. */
406
412
  export function sellExactIn(
407
413
  params: BaseQuoteParams & { baseAmountIn: bigint },
408
414
  ): SellQuote {
@@ -417,7 +423,7 @@ export function sellExactIn(
417
423
  }
418
424
 
419
425
  const legs = ammSellExactIn(params, baseIntoVault);
420
- // `feeOn`, never `grossUp`: the AMM's output leaves the vault as priced.
426
+ // `feeOn`, not `grossUp`: the AMM's output leaves the vault as priced.
421
427
  const quoteTransferFee = feeOn(legs.quoteAmount, params.quoteFee);
422
428
 
423
429
  return {
@@ -431,7 +437,8 @@ export function sellExactIn(
431
437
  };
432
438
  }
433
439
 
434
- /** Guard with `calculateSlippageUp(baseFromUser, bps)`, which already carries the base mint's cut. */
440
+ /** Quotes a sell where the seller receives `quoteAmountOut`. Guard with
441
+ * `calculateSlippageUp(baseFromUser, bps)`. `baseFromUser` includes the base mint's transfer fee. */
435
442
  export function sellExactOut(
436
443
  params: BaseQuoteParams & { quoteAmountOut: bigint },
437
444
  ): SellQuote {
@@ -478,7 +485,7 @@ export function calculateSlippageDown(
478
485
  );
479
486
  }
480
487
 
481
- /** Floors, as the program's `isqrt` does. */
488
+ /** Returns `sqrt(quoteAmount * baseAmount)`, rounded down. */
482
489
  export function calculateInitialLp(
483
490
  quoteAmount: bigint,
484
491
  baseAmount: bigint,
@@ -486,7 +493,7 @@ export function calculateInitialLp(
486
493
  return sqrtBigInt(quoteAmount * baseAmount);
487
494
  }
488
495
 
489
- /** Floating-point quote per base, for display only. */
496
+ /** Returns the price of one base token in quote tokens, as a float. For display only. */
490
497
  export function calculatePrice(params: {
491
498
  quoteReserves: bigint;
492
499
  baseReserves: bigint;
@@ -499,7 +506,7 @@ export function calculatePrice(params: {
499
506
  return rawRatio * 10 ** (baseDecimals - quoteDecimals);
500
507
  }
501
508
 
502
- /** Market cap in raw quote-token units. */
509
+ /** Returns the market cap in raw quote units. */
503
510
  export function calculateMarketCap(params: {
504
511
  quoteReserves: bigint;
505
512
  baseReserves: bigint;
@@ -1,6 +1,7 @@
1
1
  import { ceilDiv } from './internal.js';
2
2
 
3
- /** Premium in bps, rounded up. */
3
+ /** Returns the fee decay premium in bps, rounded up. The premium falls quadratically from
4
+ * `decayStartBps - standardFeeBps` at creation to 0 after `decaySeconds`. */
4
5
  export function calculateFeeDecayPremium(params: {
5
6
  currentTimestamp: bigint;
6
7
  createdAtTimestamp: bigint;
@@ -23,7 +24,7 @@ export function calculateFeeDecayPremium(params: {
23
24
  const standardBps = BigInt(standardFeeBps);
24
25
  const decaySecondsBig = BigInt(decaySeconds);
25
26
 
26
- // Future creation timestamps pay the full premium, not a discount.
27
+ // A creation time in the future pays the full premium.
27
28
  if (createdAtTimestamp > currentTimestamp) {
28
29
  if (startBps <= standardBps) return 0n;
29
30
  return startBps - standardBps;
package/src/math/fees.ts CHANGED
@@ -10,11 +10,13 @@ export interface FeeSplit {
10
10
  protocol: bigint;
11
11
  lp: bigint;
12
12
  creator: bigint;
13
- /** Decay share already included in `protocol`, not a separate payout. */
13
+ /** The decay premium share. `protocol` includes it. It is not a separate payout. */
14
14
  sniper: bigint;
15
15
  }
16
16
 
17
- /** LP/creator round down; protocol absorbs the decay premium and all remainders. */
17
+ /** Splits `feeAmount` into protocol, LP and creator shares. LP and creator round down. Protocol gets
18
+ * the decay premium and all remainders. Throws `RangeError` on a negative or non-integer input,
19
+ * a zero total bps, or `protocolBps + lpBps` above `baseTotalBps`. */
18
20
  export function splitFeeAmount(args: FeeSplitArgs): FeeSplit {
19
21
  const { feeAmount } = args;
20
22
  const protocolBps = toBps(args.protocolBps, 'protocolBps');
@@ -2,7 +2,7 @@ export function floorDiv(a: bigint, b: bigint): bigint {
2
2
  return a / b;
3
3
  }
4
4
 
5
- // Assumes non-negative inputs.
5
+ // Inputs must be non-negative.
6
6
  export function ceilDiv(a: bigint, b: bigint): bigint {
7
7
  return a / b + (a % b > 0n ? 1n : 0n);
8
8
  }
@@ -8,7 +8,8 @@ import { findPartnerConfigPda } from './generated/pdas/partnerConfig.js';
8
8
  import type { DexFees } from './generated/types/dexFees.js';
9
9
  import type { LaunchpadFees } from './generated/types/launchpadFees.js';
10
10
 
11
- /** Throws if the pair is unregistered. Take `platformConfig` from the market being priced: a partner's fees differ per platform. */
11
+ /** Fetches the `PartnerConfig` of `partner` on `platformConfig`. Throws if it does not exist.
12
+ * Use the `platformConfig` of the market you price. A partner's fees differ per platform. */
12
13
  export async function fetchPartnerFees(
13
14
  rpc: Rpc<GetAccountInfoApi>,
14
15
  partner: Address,
@@ -27,8 +28,9 @@ export async function fetchPartnerFees(
27
28
  return maybeAccount.data;
28
29
  }
29
30
 
30
- /** The `feeBps` a trade pays: the standard rate plus the decay premium from the market's
31
- * `createdAt`. Pass `fees.launchpad` for a curve, `fees.dex` for a pool; times in unix seconds. */
31
+ /** Returns the `feeBps` a trade pays: the standard rate plus the decay premium. `schedule` is the
32
+ * `PartnerConfig`'s `launchpad` for a curve or `dex` for a pool. `createdAt` is the curve's or
33
+ * pool's `createdAt`. Both times are in unix seconds. */
32
34
  export function effectiveFeeBps(
33
35
  schedule: LaunchpadFees | DexFees,
34
36
  createdAt: bigint,