@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.
- package/README.md +8 -9
- package/dist/index.cjs +526 -98
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +595 -93
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +595 -93
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +526 -98
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/dex/generated/accounts/globalConfig.ts +39 -2
- package/src/dex/generated/accounts/pool.ts +41 -2
- package/src/dex/generated/accounts/rewardAccrual.ts +39 -2
- package/src/dex/generated/index.ts +1 -0
- package/src/dex/generated/shared/index.ts +52 -0
- package/src/dex/trade.ts +12 -13
- package/src/launchpad/create.ts +8 -8
- package/src/launchpad/generated/accounts/bondingCurve.ts +39 -2
- package/src/launchpad/generated/accounts/globalConfig.ts +39 -2
- package/src/launchpad/generated/accounts/rewardAccrual.ts +39 -2
- package/src/launchpad/generated/index.ts +1 -0
- package/src/launchpad/generated/shared/index.ts +52 -0
- package/src/launchpad/migrate.ts +1 -1
- package/src/launchpad/trade.ts +15 -15
- package/src/math/amm.ts +40 -33
- package/src/math/fee-decay.ts +3 -2
- package/src/math/fees.ts +4 -2
- package/src/math/internal.ts +1 -1
- package/src/nexus/fee-helpers.ts +5 -3
- package/src/nexus/generated/accounts/altRegistry.ts +41 -2
- package/src/nexus/generated/accounts/creatorFeeConfig.ts +39 -2
- package/src/nexus/generated/accounts/feePreset.ts +41 -2
- package/src/nexus/generated/accounts/globalConfig.ts +39 -2
- package/src/nexus/generated/accounts/partnerConfig.ts +39 -2
- package/src/nexus/generated/accounts/partnerMetadata.ts +39 -2
- package/src/nexus/generated/accounts/rewardState.ts +41 -2
- package/src/nexus/generated/accounts/stakingConfig.ts +39 -2
- package/src/nexus/generated/accounts/userRewardDebt.ts +39 -2
- package/src/nexus/generated/accounts/userStakePosition.ts +39 -2
- package/src/nexus/generated/index.ts +1 -0
- package/src/nexus/generated/shared/index.ts +52 -0
- package/src/nexus/staking.ts +45 -41
- package/src/platform.ts +2 -2
- package/src/transfer-fee.ts +19 -17
- package/src/utils/chunk.ts +1 -1
- package/src/utils/creator-hash.ts +6 -3
- package/src/utils/index.ts +2 -0
- package/src/utils/mint-info.ts +8 -7
- package/src/utils/partner.ts +1 -1
- 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(
|
|
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(
|
|
218
|
+
decodeRewardAccrual(
|
|
219
|
+
accountIsCreated(maybeAccount)
|
|
220
|
+
? maybeAccount
|
|
221
|
+
: { address: maybeAccount.address, exists: false },
|
|
222
|
+
),
|
|
186
223
|
);
|
|
187
224
|
}
|
|
188
225
|
|
|
@@ -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
|
+
}
|
package/src/launchpad/migrate.ts
CHANGED
|
@@ -9,7 +9,7 @@ export interface MigrateParams {
|
|
|
9
9
|
caller: TransactionSigner;
|
|
10
10
|
baseMint: Address;
|
|
11
11
|
quoteMint: Address;
|
|
12
|
-
/** `bondingCurve.creatorFeeConfig
|
|
12
|
+
/** `bondingCurve.creatorFeeConfig`. `migrate` rejects any other address. */
|
|
13
13
|
creatorFeeConfig: Address;
|
|
14
14
|
quoteTokenProgram: Address;
|
|
15
15
|
}
|
package/src/launchpad/trade.ts
CHANGED
|
@@ -19,7 +19,7 @@ export interface LaunchpadTradeParams {
|
|
|
19
19
|
partner: PartnerInput;
|
|
20
20
|
platformConfig: Address;
|
|
21
21
|
quoteTokenProgram: Address;
|
|
22
|
-
/**
|
|
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
|
|
44
|
-
*
|
|
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
|
|
48
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
//
|
|
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
|
|
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
|
-
/**
|
|
18
|
+
/** Maximum fee per transfer, in the mint's raw units. */
|
|
20
19
|
maximumFee: bigint;
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
/**
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
64
|
+
// Unreachable from `grossUp`. Kept to match SPL.
|
|
65
65
|
if (amount === 0n) return 0n;
|
|
66
|
-
// 100
|
|
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
|
-
/**
|
|
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))`)
|
|
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
|
|
100
|
-
* fields
|
|
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
|
|
114
|
+
/** Priced on the quote that reaches the vault. */
|
|
114
115
|
fee: bigint;
|
|
115
|
-
/** Base
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
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
|
-
//
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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`,
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
509
|
+
/** Returns the market cap in raw quote units. */
|
|
503
510
|
export function calculateMarketCap(params: {
|
|
504
511
|
quoteReserves: bigint;
|
|
505
512
|
baseReserves: bigint;
|
package/src/math/fee-decay.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ceilDiv } from './internal.js';
|
|
2
2
|
|
|
3
|
-
/**
|
|
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
|
-
//
|
|
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
|
-
/**
|
|
13
|
+
/** The decay premium share. `protocol` includes it. It is not a separate payout. */
|
|
14
14
|
sniper: bigint;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
/**
|
|
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');
|
package/src/math/internal.ts
CHANGED
package/src/nexus/fee-helpers.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
/**
|
|
31
|
-
* `
|
|
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,
|