@send-fun/sdk 1.0.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/LICENSE +21 -0
- package/README.md +32 -0
- package/dist/index.cjs +12808 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +4406 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +4405 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +12749 -0
- package/dist/index.mjs.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/package.json +72 -0
- package/src/constants.ts +38 -0
- package/src/dex/generated/accounts/globalConfig.ts +189 -0
- package/src/dex/generated/accounts/index.ts +12 -0
- package/src/dex/generated/accounts/pool.ts +244 -0
- package/src/dex/generated/accounts/rewardAccrual.ts +191 -0
- package/src/dex/generated/accounts/sendDex.accounts.ts +47 -0
- package/src/dex/generated/errors/index.ts +9 -0
- package/src/dex/generated/errors/sendDex.ts +161 -0
- package/src/dex/generated/events/anchorEventCpiDiscriminator.framing.ts +28 -0
- package/src/dex/generated/events/creatorClaimEvent.ts +119 -0
- package/src/dex/generated/events/depositQuoteEvent.ts +115 -0
- package/src/dex/generated/events/index.ts +16 -0
- package/src/dex/generated/events/poolCreateEvent.ts +128 -0
- package/src/dex/generated/events/protocolClaimEvent.ts +111 -0
- package/src/dex/generated/events/sendDex.events.ts +184 -0
- package/src/dex/generated/events/tokenCreateEvent.ts +143 -0
- package/src/dex/generated/events/tradeEvent.ts +143 -0
- package/src/dex/generated/index.ts +16 -0
- package/src/dex/generated/instructions/buyExactIn.ts +815 -0
- package/src/dex/generated/instructions/buyExactOut.ts +815 -0
- package/src/dex/generated/instructions/claimCreatorFees.ts +648 -0
- package/src/dex/generated/instructions/claimProtocolFees.ts +640 -0
- package/src/dex/generated/instructions/index.ts +15 -0
- package/src/dex/generated/instructions/sellExactIn.ts +815 -0
- package/src/dex/generated/instructions/sellExactOut.ts +815 -0
- package/src/dex/generated/instructions/sendDex.instructions.ts +173 -0
- package/src/dex/generated/pdas/eventAuthority.ts +20 -0
- package/src/dex/generated/pdas/globalConfig.ts +20 -0
- package/src/dex/generated/pdas/index.ts +14 -0
- package/src/dex/generated/pdas/lpMint.ts +36 -0
- package/src/dex/generated/pdas/pool.ts +34 -0
- package/src/dex/generated/pdas/rewardAccrual.ts +37 -0
- package/src/dex/generated/pdas/wsolRewardAccrual.ts +20 -0
- package/src/dex/generated/plugins/index.ts +9 -0
- package/src/dex/generated/plugins/sendDex.ts +282 -0
- package/src/dex/generated/programs/index.ts +9 -0
- package/src/dex/generated/programs/sendDex.ts +12 -0
- package/src/dex/generated/types/index.ts +10 -0
- package/src/dex/generated/types/poolStatus.ts +38 -0
- package/src/dex/generated/types/tradeDirection.ts +38 -0
- package/src/dex/index.ts +8 -0
- package/src/dex/trade.ts +215 -0
- package/src/index.ts +8 -0
- package/src/launchpad/create.ts +116 -0
- package/src/launchpad/generated/accounts/bondingCurve.ts +273 -0
- package/src/launchpad/generated/accounts/globalConfig.ts +211 -0
- package/src/launchpad/generated/accounts/index.ts +12 -0
- package/src/launchpad/generated/accounts/rewardAccrual.ts +191 -0
- package/src/launchpad/generated/accounts/sendLaunchpad.accounts.ts +50 -0
- package/src/launchpad/generated/errors/index.ts +9 -0
- package/src/launchpad/generated/errors/sendLaunchpad.ts +175 -0
- package/src/launchpad/generated/events/anchorEventCpiDiscriminator.framing.ts +28 -0
- package/src/launchpad/generated/events/completeEvent.ts +120 -0
- package/src/launchpad/generated/events/creatorClaimEvent.ts +122 -0
- package/src/launchpad/generated/events/index.ts +17 -0
- package/src/launchpad/generated/events/migrateEvent.ts +121 -0
- package/src/launchpad/generated/events/protocolClaimEvent.ts +114 -0
- package/src/launchpad/generated/events/recycleEvent.ts +113 -0
- package/src/launchpad/generated/events/sendLaunchpad.events.ts +207 -0
- package/src/launchpad/generated/events/tokenCreateEvent.ts +156 -0
- package/src/launchpad/generated/events/tradeEvent.ts +148 -0
- package/src/launchpad/generated/index.ts +16 -0
- package/src/launchpad/generated/instructions/buyExactIn.ts +818 -0
- package/src/launchpad/generated/instructions/buyExactOut.ts +818 -0
- package/src/launchpad/generated/instructions/claimCreatorFees.ts +651 -0
- package/src/launchpad/generated/instructions/claimProtocolFees.ts +640 -0
- package/src/launchpad/generated/instructions/createToken.ts +1150 -0
- package/src/launchpad/generated/instructions/index.ts +17 -0
- package/src/launchpad/generated/instructions/migrate.ts +840 -0
- package/src/launchpad/generated/instructions/sellExactIn.ts +818 -0
- package/src/launchpad/generated/instructions/sellExactOut.ts +818 -0
- package/src/launchpad/generated/instructions/sendLaunchpad.instructions.ts +210 -0
- package/src/launchpad/generated/pdas/bondingCurve.ts +39 -0
- package/src/launchpad/generated/pdas/eventAuthority.ts +20 -0
- package/src/launchpad/generated/pdas/globalConfig.ts +20 -0
- package/src/launchpad/generated/pdas/index.ts +14 -0
- package/src/launchpad/generated/pdas/migrationAuthority.ts +20 -0
- package/src/launchpad/generated/pdas/rewardAccrual.ts +37 -0
- package/src/launchpad/generated/pdas/wsolRewardAccrual.ts +20 -0
- package/src/launchpad/generated/plugins/index.ts +9 -0
- package/src/launchpad/generated/plugins/sendLaunchpad.ts +315 -0
- package/src/launchpad/generated/programs/index.ts +9 -0
- package/src/launchpad/generated/programs/sendLaunchpad.ts +12 -0
- package/src/launchpad/generated/types/bondingCurveStatus.ts +43 -0
- package/src/launchpad/generated/types/index.ts +10 -0
- package/src/launchpad/generated/types/tradeDirection.ts +38 -0
- package/src/launchpad/index.ts +10 -0
- package/src/launchpad/migrate.ts +54 -0
- package/src/launchpad/trade.ts +247 -0
- package/src/math/amm.ts +511 -0
- package/src/math/fee-decay.ts +49 -0
- package/src/math/fees.ts +75 -0
- package/src/math/index.ts +3 -0
- package/src/math/internal.ts +16 -0
- package/src/nexus/fee-helpers.ts +49 -0
- package/src/nexus/generated/accounts/altRegistry.ts +177 -0
- package/src/nexus/generated/accounts/creatorFeeConfig.ts +200 -0
- package/src/nexus/generated/accounts/feePreset.ts +196 -0
- package/src/nexus/generated/accounts/globalConfig.ts +188 -0
- package/src/nexus/generated/accounts/index.ts +19 -0
- package/src/nexus/generated/accounts/partnerConfig.ts +209 -0
- package/src/nexus/generated/accounts/partnerMetadata.ts +216 -0
- package/src/nexus/generated/accounts/rewardState.ts +209 -0
- package/src/nexus/generated/accounts/sendNexus.accounts.ts +85 -0
- package/src/nexus/generated/accounts/stakingConfig.ts +197 -0
- package/src/nexus/generated/accounts/userRewardDebt.ts +221 -0
- package/src/nexus/generated/accounts/userStakePosition.ts +218 -0
- package/src/nexus/generated/constants/index.ts +9 -0
- package/src/nexus/generated/constants/sendNexus.ts +13 -0
- package/src/nexus/generated/errors/index.ts +9 -0
- package/src/nexus/generated/errors/sendNexus.ts +229 -0
- package/src/nexus/generated/events/anchorEventCpiDiscriminator.framing.ts +28 -0
- package/src/nexus/generated/events/claimEvent.ts +106 -0
- package/src/nexus/generated/events/index.ts +13 -0
- package/src/nexus/generated/events/sendNexus.events.ts +121 -0
- package/src/nexus/generated/events/stakeEvent.ts +106 -0
- package/src/nexus/generated/events/unstakeEvent.ts +106 -0
- package/src/nexus/generated/index.ts +17 -0
- package/src/nexus/generated/instructions/claim.ts +740 -0
- package/src/nexus/generated/instructions/createUserRewardDebt.ts +498 -0
- package/src/nexus/generated/instructions/index.ts +14 -0
- package/src/nexus/generated/instructions/sendNexus.instructions.ts +146 -0
- package/src/nexus/generated/instructions/settle.ts +517 -0
- package/src/nexus/generated/instructions/stake.ts +615 -0
- package/src/nexus/generated/instructions/unstake.ts +593 -0
- package/src/nexus/generated/pdas/altRegistry.ts +20 -0
- package/src/nexus/generated/pdas/creatorFeeConfig.ts +39 -0
- package/src/nexus/generated/pdas/defaultFeePreset.ts +37 -0
- package/src/nexus/generated/pdas/defaultPartner.ts +40 -0
- package/src/nexus/generated/pdas/defaultPartnerMetadata.ts +42 -0
- package/src/nexus/generated/pdas/eventAuthority.ts +20 -0
- package/src/nexus/generated/pdas/feePreset.ts +39 -0
- package/src/nexus/generated/pdas/globalConfig.ts +20 -0
- package/src/nexus/generated/pdas/index.ts +22 -0
- package/src/nexus/generated/pdas/partnerConfig.ts +36 -0
- package/src/nexus/generated/pdas/partnerMetadata.ts +38 -0
- package/src/nexus/generated/pdas/rewardState.ts +36 -0
- package/src/nexus/generated/pdas/stakingConfig.ts +20 -0
- package/src/nexus/generated/pdas/userRewardDebt.ts +41 -0
- package/src/nexus/generated/pdas/userStakePosition.ts +39 -0
- package/src/nexus/generated/plugins/index.ts +9 -0
- package/src/nexus/generated/plugins/sendNexus.ts +494 -0
- package/src/nexus/generated/programs/index.ts +9 -0
- package/src/nexus/generated/programs/sendNexus.ts +12 -0
- package/src/nexus/generated/types/authPlatformEntry.ts +52 -0
- package/src/nexus/generated/types/callerType.ts +38 -0
- package/src/nexus/generated/types/dexFees.ts +64 -0
- package/src/nexus/generated/types/feeConfig.ts +73 -0
- package/src/nexus/generated/types/index.ts +13 -0
- package/src/nexus/generated/types/launchpadFees.ts +63 -0
- package/src/nexus/index.ts +9 -0
- package/src/nexus/staking.ts +581 -0
- package/src/platform.ts +19 -0
- package/src/transfer-fee.ts +216 -0
- package/src/utils/chunk.ts +21 -0
- package/src/utils/creator-hash.ts +57 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/mint-info.ts +102 -0
- package/src/utils/partner.ts +5 -0
- package/src/utils/pda.ts +24 -0
package/src/math/amm.ts
ADDED
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
import { assertBps, ceilDiv, floorDiv } from './internal.js';
|
|
2
|
+
|
|
3
|
+
const BPS_DIVISOR = 10_000n;
|
|
4
|
+
|
|
5
|
+
const U64_MAX = 18_446_744_073_709_551_615n;
|
|
6
|
+
|
|
7
|
+
/** Each call mirrors a `u64::try_from` in the Rust twin; drop one and an oversized leg fails in the encoder. */
|
|
8
|
+
function assertU64(label: string, value: bigint): bigint {
|
|
9
|
+
if (value > U64_MAX) {
|
|
10
|
+
throw new RangeError(`${label}: overflows u64`);
|
|
11
|
+
}
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Token-2022 `TransferFeeConfig` for the epoch the trade lands in; a stale one misprices the trade. */
|
|
16
|
+
export interface MintFee {
|
|
17
|
+
/** 0 to 10_000. */
|
|
18
|
+
bps: number;
|
|
19
|
+
/** Cap on the withheld amount, in the mint's raw units. */
|
|
20
|
+
maximumFee: bigint;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** No transfer lands exactly the requested amount; approximating one hands the program a bound it rejects. */
|
|
24
|
+
export class TransferFeeNotSettleableError extends RangeError {
|
|
25
|
+
readonly amount: bigint;
|
|
26
|
+
readonly mintFee: MintFee;
|
|
27
|
+
|
|
28
|
+
constructor(amount: bigint, mintFee: MintFee) {
|
|
29
|
+
super(
|
|
30
|
+
`transfer fee not settleable: no transfer lands exactly ${amount} at ${mintFee.bps} bps with a ${mintFee.maximumFee} cap`,
|
|
31
|
+
);
|
|
32
|
+
this.name = 'TransferFeeNotSettleableError';
|
|
33
|
+
this.amount = amount;
|
|
34
|
+
this.mintFee = mintFee;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function assertMintFee(fee: MintFee): void {
|
|
39
|
+
assertBps('MintFee', 'bps', fee.bps);
|
|
40
|
+
if (fee.maximumFee < 0n || fee.maximumFee > U64_MAX) {
|
|
41
|
+
throw new RangeError('MintFee: maximumFee must fit a u64');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Rounds up, then caps (SPL's order); swapping them lets a split booking over-credit. */
|
|
46
|
+
export function feeOn(amount: bigint, fee?: MintFee): bigint {
|
|
47
|
+
if (fee === undefined) return 0n;
|
|
48
|
+
assertMintFee(fee);
|
|
49
|
+
if (amount === 0n || fee.bps === 0) return 0n;
|
|
50
|
+
|
|
51
|
+
const raw = ceilDiv(amount * BigInt(fee.bps), BPS_DIVISOR);
|
|
52
|
+
return raw < fee.maximumFee ? raw : fee.maximumFee;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** What lands when `amount` is sent; use `grossUp` to land an exact amount. */
|
|
56
|
+
export function amountAfterFee(amount: bigint, fee?: MintFee): bigint {
|
|
57
|
+
return amount - feeOn(amount, fee);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Line-for-line mirror of SPL's `TransferFee::calculate_pre_fee_amount`; `undefined` when no u64 answer exists. */
|
|
61
|
+
function preFeeAmount(amount: bigint, fee: MintFee): bigint | undefined {
|
|
62
|
+
const bps = BigInt(fee.bps);
|
|
63
|
+
if (bps === 0n) return amount;
|
|
64
|
+
// Unreachable via `grossUp`; SPL has it, so the mirror keeps it.
|
|
65
|
+
if (amount === 0n) return 0n;
|
|
66
|
+
// 100%: only the cap can be settled.
|
|
67
|
+
if (bps === BPS_DIVISOR) {
|
|
68
|
+
const capped = amount + fee.maximumFee;
|
|
69
|
+
return capped > U64_MAX ? undefined : capped;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const rawPreFee = ceilDiv(amount * BPS_DIVISOR, BPS_DIVISOR - bps);
|
|
73
|
+
if (rawPreFee - amount >= fee.maximumFee) {
|
|
74
|
+
const capped = amount + fee.maximumFee;
|
|
75
|
+
return capped > U64_MAX ? undefined : capped;
|
|
76
|
+
}
|
|
77
|
+
return rawPreFee > U64_MAX ? undefined : rawPreFee;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** What must be sent for exactly `amount` to land; throws {@link TransferFeeNotSettleableError} if none does. */
|
|
81
|
+
export function grossUp(amount: bigint, fee?: MintFee): bigint {
|
|
82
|
+
if (fee === undefined) return amount;
|
|
83
|
+
assertMintFee(fee);
|
|
84
|
+
if (amount === 0n) return 0n;
|
|
85
|
+
|
|
86
|
+
const preFee = preFeeAmount(amount, fee);
|
|
87
|
+
if (preFee === undefined)
|
|
88
|
+
throw new TransferFeeNotSettleableError(amount, fee);
|
|
89
|
+
|
|
90
|
+
// SPL's inverse is inexact (`feeOn(x) >= inverse(x - feeOn(x))`): re-derive forward and reject a mismatch.
|
|
91
|
+
const impliedFee = feeOn(preFee, fee);
|
|
92
|
+
const gross = amount + impliedFee;
|
|
93
|
+
if (gross > U64_MAX || feeOn(gross, fee) !== impliedFee) {
|
|
94
|
+
throw new TransferFeeNotSettleableError(amount, fee);
|
|
95
|
+
}
|
|
96
|
+
return gross;
|
|
97
|
+
}
|
|
98
|
+
|
|
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. */
|
|
101
|
+
export interface TradeQuote {
|
|
102
|
+
/** Base sent: vault to user on a buy, user to vault on a sell. */
|
|
103
|
+
baseAmount: bigint;
|
|
104
|
+
/** Quote sent: user to vault on a buy, vault to user on a sell. */
|
|
105
|
+
quoteAmount: bigint;
|
|
106
|
+
/** Platform fee, in quote units. */
|
|
107
|
+
fee: bigint;
|
|
108
|
+
baseTransferFee: bigint;
|
|
109
|
+
quoteTransferFee: bigint;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface BuyQuote extends TradeQuote {
|
|
113
|
+
/** Priced on the quote reaching the vault. */
|
|
114
|
+
fee: bigint;
|
|
115
|
+
/** Base credited to the buyer, after the base mint's cut. */
|
|
116
|
+
baseToUser: bigint;
|
|
117
|
+
/** Equals `quoteAmount`. */
|
|
118
|
+
quoteFromUser: bigint;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface SellQuote extends TradeQuote {
|
|
122
|
+
/** Priced on the quote leaving the vault. */
|
|
123
|
+
fee: bigint;
|
|
124
|
+
/** Equals `baseAmount`. */
|
|
125
|
+
baseFromUser: bigint;
|
|
126
|
+
/** Quote credited to the seller, after the quote mint's cut. */
|
|
127
|
+
quoteToUser: bigint;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function sqrtBigInt(value: bigint): bigint {
|
|
131
|
+
if (value < 0n) throw new RangeError('sqrtBigInt: negative input');
|
|
132
|
+
if (value === 0n) return 0n;
|
|
133
|
+
let x = value;
|
|
134
|
+
let y = (x + 1n) / 2n;
|
|
135
|
+
while (y < x) {
|
|
136
|
+
x = y;
|
|
137
|
+
y = (x + value / x) / 2n;
|
|
138
|
+
}
|
|
139
|
+
return x;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Output rounds down (the new reserve rounds up), so `k` never shrinks. */
|
|
143
|
+
export function calculateOutput(
|
|
144
|
+
reserveIn: bigint,
|
|
145
|
+
reserveOut: bigint,
|
|
146
|
+
amountIn: bigint,
|
|
147
|
+
): bigint {
|
|
148
|
+
if (reserveIn === 0n || reserveOut === 0n) {
|
|
149
|
+
throw new RangeError('calculateOutput: insufficient liquidity');
|
|
150
|
+
}
|
|
151
|
+
if (amountIn === 0n) {
|
|
152
|
+
throw new RangeError('calculateOutput: invalid amount');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const k = reserveIn * reserveOut;
|
|
156
|
+
const newReserveIn = reserveIn + amountIn;
|
|
157
|
+
|
|
158
|
+
const newReserveOut = ceilDiv(k, newReserveIn);
|
|
159
|
+
|
|
160
|
+
if (newReserveOut >= reserveOut) {
|
|
161
|
+
throw new RangeError('calculateOutput: insufficient liquidity');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const amountOut = reserveOut - newReserveOut;
|
|
165
|
+
if (amountOut === 0n) {
|
|
166
|
+
throw new RangeError('calculateOutput: insufficient liquidity');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return assertU64('calculateOutput', amountOut);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Required input rounds up so the user pays enough. */
|
|
173
|
+
export function calculateInputForOutput(
|
|
174
|
+
reserveIn: bigint,
|
|
175
|
+
reserveOut: bigint,
|
|
176
|
+
amountOut: bigint,
|
|
177
|
+
): bigint {
|
|
178
|
+
if (reserveIn === 0n || reserveOut === 0n) {
|
|
179
|
+
throw new RangeError('calculateInputForOutput: insufficient liquidity');
|
|
180
|
+
}
|
|
181
|
+
if (amountOut === 0n || amountOut >= reserveOut) {
|
|
182
|
+
throw new RangeError('calculateInputForOutput: invalid amount');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const k = reserveIn * reserveOut;
|
|
186
|
+
const newReserveOut = reserveOut - amountOut;
|
|
187
|
+
|
|
188
|
+
const newReserveIn = ceilDiv(k, newReserveOut);
|
|
189
|
+
|
|
190
|
+
if (newReserveIn <= reserveIn) {
|
|
191
|
+
throw new RangeError('calculateInputForOutput: insufficient liquidity');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const amountIn = newReserveIn - reserveIn;
|
|
195
|
+
if (amountIn === 0n) {
|
|
196
|
+
throw new RangeError('calculateInputForOutput: insufficient liquidity');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return assertU64('calculateInputForOutput', amountIn);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
interface BaseQuoteParams {
|
|
203
|
+
reserveQuote: bigint;
|
|
204
|
+
reserveBase: bigint;
|
|
205
|
+
feeBps: number;
|
|
206
|
+
quoteFee?: MintFee;
|
|
207
|
+
baseFee?: MintFee;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Launchpad only: `bondingCurve.realBaseReserves`. A DEX pool has no cap. */
|
|
211
|
+
interface BaseReserveCap {
|
|
212
|
+
baseReserveCap?: bigint;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Vault-side legs, before either mint's transfer fee. */
|
|
216
|
+
interface AmmLegs {
|
|
217
|
+
baseAmount: bigint;
|
|
218
|
+
quoteAmount: bigint;
|
|
219
|
+
fee: bigint;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function ammBuyExactOut(
|
|
223
|
+
params: { reserveQuote: bigint; reserveBase: bigint; feeBps: number },
|
|
224
|
+
baseAmountOut: bigint,
|
|
225
|
+
): AmmLegs {
|
|
226
|
+
const quoteBeforeFee = calculateInputForOutput(
|
|
227
|
+
params.reserveQuote,
|
|
228
|
+
params.reserveBase,
|
|
229
|
+
baseAmountOut,
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
const divisor = BPS_DIVISOR - BigInt(params.feeBps);
|
|
233
|
+
if (divisor <= 0n) {
|
|
234
|
+
throw new RangeError('buyExactOut: invalid feeBps');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// A `feeBps` near 10_000 amplifies the leg up to 10_000x, so this can overflow u64.
|
|
238
|
+
const totalQuote = assertU64(
|
|
239
|
+
'buyExactOut',
|
|
240
|
+
ceilDiv(quoteBeforeFee * BPS_DIVISOR, divisor),
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
baseAmount: baseAmountOut,
|
|
245
|
+
quoteAmount: totalQuote,
|
|
246
|
+
fee: totalQuote - quoteBeforeFee,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function ammBuyExactIn(
|
|
251
|
+
params: { reserveQuote: bigint; reserveBase: bigint; feeBps: number },
|
|
252
|
+
quoteAmountIn: bigint,
|
|
253
|
+
): AmmLegs {
|
|
254
|
+
// Fee comes off before the swap; the net rounds down, so the fee keeps the remainder.
|
|
255
|
+
const netFactor = BPS_DIVISOR - BigInt(params.feeBps);
|
|
256
|
+
if (netFactor <= 0n) {
|
|
257
|
+
throw new RangeError('buyExactIn: invalid feeBps');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const netQuote = floorDiv(quoteAmountIn * netFactor, BPS_DIVISOR);
|
|
261
|
+
if (netQuote === 0n) {
|
|
262
|
+
throw new RangeError('buyExactIn: invalid amount');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
baseAmount: calculateOutput(
|
|
267
|
+
params.reserveQuote,
|
|
268
|
+
params.reserveBase,
|
|
269
|
+
netQuote,
|
|
270
|
+
),
|
|
271
|
+
quoteAmount: quoteAmountIn,
|
|
272
|
+
fee: quoteAmountIn - netQuote,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function ammSellExactIn(
|
|
277
|
+
params: { reserveQuote: bigint; reserveBase: bigint; feeBps: number },
|
|
278
|
+
baseAmountIn: bigint,
|
|
279
|
+
): AmmLegs {
|
|
280
|
+
const quoteBeforeFee = calculateOutput(
|
|
281
|
+
params.reserveBase,
|
|
282
|
+
params.reserveQuote,
|
|
283
|
+
baseAmountIn,
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
const feeAmount = ceilDiv(
|
|
287
|
+
quoteBeforeFee * BigInt(params.feeBps),
|
|
288
|
+
BPS_DIVISOR,
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
baseAmount: baseAmountIn,
|
|
293
|
+
quoteAmount: quoteBeforeFee - feeAmount,
|
|
294
|
+
fee: feeAmount,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function ammSellExactOut(
|
|
299
|
+
params: { reserveQuote: bigint; reserveBase: bigint; feeBps: number },
|
|
300
|
+
quoteAmountOut: bigint,
|
|
301
|
+
): AmmLegs {
|
|
302
|
+
const divisor = BPS_DIVISOR - BigInt(params.feeBps);
|
|
303
|
+
if (divisor <= 0n) {
|
|
304
|
+
throw new RangeError('sellExactOut: invalid feeBps');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const quoteBeforeFee = assertU64(
|
|
308
|
+
'sellExactOut',
|
|
309
|
+
ceilDiv(quoteAmountOut * BPS_DIVISOR, divisor),
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
baseAmount: calculateInputForOutput(
|
|
314
|
+
params.reserveBase,
|
|
315
|
+
params.reserveQuote,
|
|
316
|
+
quoteBeforeFee,
|
|
317
|
+
),
|
|
318
|
+
quoteAmount: quoteAmountOut,
|
|
319
|
+
fee: quoteBeforeFee - quoteAmountOut,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function buyQuote(
|
|
324
|
+
legs: AmmLegs,
|
|
325
|
+
quoteTransferFee: bigint,
|
|
326
|
+
baseFee?: MintFee,
|
|
327
|
+
): BuyQuote {
|
|
328
|
+
const baseTransferFee = feeOn(legs.baseAmount, baseFee);
|
|
329
|
+
return {
|
|
330
|
+
baseAmount: legs.baseAmount,
|
|
331
|
+
quoteAmount: legs.quoteAmount,
|
|
332
|
+
fee: legs.fee,
|
|
333
|
+
baseTransferFee,
|
|
334
|
+
quoteTransferFee,
|
|
335
|
+
baseToUser: legs.baseAmount - baseTransferFee,
|
|
336
|
+
quoteFromUser: legs.quoteAmount,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** Guard with `calculateSlippageUp(quoteAmount, bps)`; it already carries the quote mint's cut. */
|
|
341
|
+
export function buyExactOut(
|
|
342
|
+
params: BaseQuoteParams & BaseReserveCap & { baseAmountOut: bigint },
|
|
343
|
+
): BuyQuote {
|
|
344
|
+
assertBps('buyExactOut', 'feeBps', params.feeBps);
|
|
345
|
+
if (params.baseAmountOut === 0n) {
|
|
346
|
+
throw new RangeError('buyExactOut: invalid amount');
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const baseOutOfVault = grossUp(params.baseAmountOut, params.baseFee);
|
|
350
|
+
const cap = params.baseReserveCap;
|
|
351
|
+
const baseAmount =
|
|
352
|
+
cap !== undefined && baseOutOfVault > cap ? cap : baseOutOfVault;
|
|
353
|
+
|
|
354
|
+
const legs = ammBuyExactOut(params, baseAmount);
|
|
355
|
+
// The priced total must land, so the user is debited more than it.
|
|
356
|
+
const quoteFromUser = grossUp(legs.quoteAmount, params.quoteFee);
|
|
357
|
+
|
|
358
|
+
return buyQuote(
|
|
359
|
+
{ ...legs, quoteAmount: quoteFromUser },
|
|
360
|
+
quoteFromUser - legs.quoteAmount,
|
|
361
|
+
params.baseFee,
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** Guard with `calculateSlippageDown(baseToUser, bps)`, not `baseAmount`: the program bounds what the buyer nets. */
|
|
366
|
+
export function buyExactIn(
|
|
367
|
+
params: BaseQuoteParams & BaseReserveCap & { quoteAmountIn: bigint },
|
|
368
|
+
): BuyQuote {
|
|
369
|
+
assertBps('buyExactIn', 'feeBps', params.feeBps);
|
|
370
|
+
if (params.quoteAmountIn === 0n) {
|
|
371
|
+
throw new RangeError('buyExactIn: invalid amount');
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// The AMM only ever prices what reaches the vault.
|
|
375
|
+
const quoteIntoVault = amountAfterFee(
|
|
376
|
+
params.quoteAmountIn,
|
|
377
|
+
params.quoteFee,
|
|
378
|
+
);
|
|
379
|
+
if (quoteIntoVault === 0n) {
|
|
380
|
+
throw new RangeError('buyExactIn: invalid amount');
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const uncapped = ammBuyExactIn(params, quoteIntoVault);
|
|
384
|
+
|
|
385
|
+
// Capped: re-price exact-out at the cap and gross up; clamping the base output overstates the quote leg.
|
|
386
|
+
const cap = params.baseReserveCap;
|
|
387
|
+
if (cap === undefined || uncapped.baseAmount <= cap) {
|
|
388
|
+
return buyQuote(
|
|
389
|
+
{ ...uncapped, quoteAmount: params.quoteAmountIn },
|
|
390
|
+
params.quoteAmountIn - quoteIntoVault,
|
|
391
|
+
params.baseFee,
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const capped = ammBuyExactOut(params, cap);
|
|
396
|
+
const quoteFromUser = grossUp(capped.quoteAmount, params.quoteFee);
|
|
397
|
+
|
|
398
|
+
return buyQuote(
|
|
399
|
+
{ ...capped, quoteAmount: quoteFromUser },
|
|
400
|
+
quoteFromUser - capped.quoteAmount,
|
|
401
|
+
params.baseFee,
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/** Guard with `calculateSlippageDown(quoteToUser, bps)`, not `quoteAmount`. */
|
|
406
|
+
export function sellExactIn(
|
|
407
|
+
params: BaseQuoteParams & { baseAmountIn: bigint },
|
|
408
|
+
): SellQuote {
|
|
409
|
+
assertBps('sellExactIn', 'feeBps', params.feeBps);
|
|
410
|
+
if (params.baseAmountIn === 0n) {
|
|
411
|
+
throw new RangeError('sellExactIn: invalid amount');
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const baseIntoVault = amountAfterFee(params.baseAmountIn, params.baseFee);
|
|
415
|
+
if (baseIntoVault === 0n) {
|
|
416
|
+
throw new RangeError('sellExactIn: invalid amount');
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const legs = ammSellExactIn(params, baseIntoVault);
|
|
420
|
+
// `feeOn`, never `grossUp`: the AMM's output leaves the vault as priced.
|
|
421
|
+
const quoteTransferFee = feeOn(legs.quoteAmount, params.quoteFee);
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
baseAmount: params.baseAmountIn,
|
|
425
|
+
quoteAmount: legs.quoteAmount,
|
|
426
|
+
fee: legs.fee,
|
|
427
|
+
baseTransferFee: params.baseAmountIn - baseIntoVault,
|
|
428
|
+
quoteTransferFee,
|
|
429
|
+
baseFromUser: params.baseAmountIn,
|
|
430
|
+
quoteToUser: legs.quoteAmount - quoteTransferFee,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/** Guard with `calculateSlippageUp(baseFromUser, bps)`, which already carries the base mint's cut. */
|
|
435
|
+
export function sellExactOut(
|
|
436
|
+
params: BaseQuoteParams & { quoteAmountOut: bigint },
|
|
437
|
+
): SellQuote {
|
|
438
|
+
assertBps('sellExactOut', 'feeBps', params.feeBps);
|
|
439
|
+
if (params.quoteAmountOut === 0n) {
|
|
440
|
+
throw new RangeError('sellExactOut: invalid amount');
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const quoteOutOfVault = grossUp(params.quoteAmountOut, params.quoteFee);
|
|
444
|
+
const legs = ammSellExactOut(params, quoteOutOfVault);
|
|
445
|
+
const baseFromUser = grossUp(legs.baseAmount, params.baseFee);
|
|
446
|
+
|
|
447
|
+
return {
|
|
448
|
+
baseAmount: baseFromUser,
|
|
449
|
+
quoteAmount: quoteOutOfVault,
|
|
450
|
+
fee: legs.fee,
|
|
451
|
+
baseTransferFee: baseFromUser - legs.baseAmount,
|
|
452
|
+
quoteTransferFee: quoteOutOfVault - params.quoteAmountOut,
|
|
453
|
+
baseFromUser,
|
|
454
|
+
quoteToUser: params.quoteAmountOut,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export function calculateSlippageUp(
|
|
459
|
+
amount: bigint,
|
|
460
|
+
slippageBps: number,
|
|
461
|
+
): bigint {
|
|
462
|
+
assertBps('calculateSlippageUp', 'slippageBps', slippageBps);
|
|
463
|
+
return assertU64(
|
|
464
|
+
'calculateSlippageUp',
|
|
465
|
+
ceilDiv(amount * (BPS_DIVISOR + BigInt(slippageBps)), BPS_DIVISOR),
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export function calculateSlippageDown(
|
|
470
|
+
amount: bigint,
|
|
471
|
+
slippageBps: number,
|
|
472
|
+
): bigint {
|
|
473
|
+
assertBps('calculateSlippageDown', 'slippageBps', slippageBps);
|
|
474
|
+
const factor = BPS_DIVISOR - BigInt(slippageBps);
|
|
475
|
+
return assertU64(
|
|
476
|
+
'calculateSlippageDown',
|
|
477
|
+
floorDiv(amount * factor, BPS_DIVISOR),
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/** Floors, as the program's `isqrt` does. */
|
|
482
|
+
export function calculateInitialLp(
|
|
483
|
+
quoteAmount: bigint,
|
|
484
|
+
baseAmount: bigint,
|
|
485
|
+
): bigint {
|
|
486
|
+
return sqrtBigInt(quoteAmount * baseAmount);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/** Floating-point quote per base, for display only. */
|
|
490
|
+
export function calculatePrice(params: {
|
|
491
|
+
quoteReserves: bigint;
|
|
492
|
+
baseReserves: bigint;
|
|
493
|
+
quoteDecimals: number;
|
|
494
|
+
baseDecimals: number;
|
|
495
|
+
}): number {
|
|
496
|
+
const { quoteReserves, baseReserves, quoteDecimals, baseDecimals } = params;
|
|
497
|
+
if (baseReserves === 0n) return 0;
|
|
498
|
+
const rawRatio = Number(quoteReserves) / Number(baseReserves);
|
|
499
|
+
return rawRatio * 10 ** (baseDecimals - quoteDecimals);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/** Market cap in raw quote-token units. */
|
|
503
|
+
export function calculateMarketCap(params: {
|
|
504
|
+
quoteReserves: bigint;
|
|
505
|
+
baseReserves: bigint;
|
|
506
|
+
baseSupply: bigint;
|
|
507
|
+
}): bigint {
|
|
508
|
+
const { quoteReserves, baseReserves, baseSupply } = params;
|
|
509
|
+
if (baseReserves === 0n) return 0n;
|
|
510
|
+
return floorDiv(quoteReserves * baseSupply, baseReserves);
|
|
511
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ceilDiv } from './internal.js';
|
|
2
|
+
|
|
3
|
+
/** Premium in bps, rounded up. */
|
|
4
|
+
export function calculateFeeDecayPremium(params: {
|
|
5
|
+
currentTimestamp: bigint;
|
|
6
|
+
createdAtTimestamp: bigint;
|
|
7
|
+
decaySeconds: number;
|
|
8
|
+
decayStartBps: number;
|
|
9
|
+
standardFeeBps: number;
|
|
10
|
+
}): bigint {
|
|
11
|
+
const {
|
|
12
|
+
currentTimestamp,
|
|
13
|
+
createdAtTimestamp,
|
|
14
|
+
decaySeconds,
|
|
15
|
+
decayStartBps,
|
|
16
|
+
standardFeeBps,
|
|
17
|
+
} = params;
|
|
18
|
+
if (decaySeconds === 0) {
|
|
19
|
+
return 0n;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const startBps = BigInt(decayStartBps);
|
|
23
|
+
const standardBps = BigInt(standardFeeBps);
|
|
24
|
+
const decaySecondsBig = BigInt(decaySeconds);
|
|
25
|
+
|
|
26
|
+
// Future creation timestamps pay the full premium, not a discount.
|
|
27
|
+
if (createdAtTimestamp > currentTimestamp) {
|
|
28
|
+
if (startBps <= standardBps) return 0n;
|
|
29
|
+
return startBps - standardBps;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const elapsed = currentTimestamp - createdAtTimestamp;
|
|
33
|
+
|
|
34
|
+
if (elapsed >= decaySecondsBig) {
|
|
35
|
+
return 0n;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (startBps <= standardBps) {
|
|
39
|
+
return 0n;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const range = startBps - standardBps;
|
|
43
|
+
const remaining = decaySecondsBig - elapsed;
|
|
44
|
+
|
|
45
|
+
const numerator = remaining * remaining * range;
|
|
46
|
+
const denominator = decaySecondsBig * decaySecondsBig;
|
|
47
|
+
|
|
48
|
+
return ceilDiv(numerator, denominator);
|
|
49
|
+
}
|
package/src/math/fees.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export interface FeeSplitArgs {
|
|
2
|
+
feeAmount: bigint;
|
|
3
|
+
protocolBps: number;
|
|
4
|
+
lpBps: number;
|
|
5
|
+
baseTotalBps: number;
|
|
6
|
+
decayPremiumBps: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface FeeSplit {
|
|
10
|
+
protocol: bigint;
|
|
11
|
+
lp: bigint;
|
|
12
|
+
creator: bigint;
|
|
13
|
+
/** Decay share already included in `protocol`, not a separate payout. */
|
|
14
|
+
sniper: bigint;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** LP/creator round down; protocol absorbs the decay premium and all remainders. */
|
|
18
|
+
export function splitFeeAmount(args: FeeSplitArgs): FeeSplit {
|
|
19
|
+
const { feeAmount } = args;
|
|
20
|
+
const protocolBps = toBps(args.protocolBps, 'protocolBps');
|
|
21
|
+
const lpBps = toBps(args.lpBps, 'lpBps');
|
|
22
|
+
const baseTotalBps = toBps(args.baseTotalBps, 'baseTotalBps');
|
|
23
|
+
const decayPremiumBps = toBps(args.decayPremiumBps, 'decayPremiumBps');
|
|
24
|
+
|
|
25
|
+
if (feeAmount < 0n) {
|
|
26
|
+
throw new RangeError('splitFeeAmount: feeAmount must be non-negative');
|
|
27
|
+
}
|
|
28
|
+
if (feeAmount === 0n) {
|
|
29
|
+
return { protocol: 0n, lp: 0n, creator: 0n, sniper: 0n };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const effectiveTotalBps = baseTotalBps + decayPremiumBps;
|
|
33
|
+
if (effectiveTotalBps === 0n) {
|
|
34
|
+
throw new RangeError('splitFeeAmount: effective total bps is zero');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (baseTotalBps === 0n) {
|
|
38
|
+
return {
|
|
39
|
+
protocol: feeAmount,
|
|
40
|
+
lp: 0n,
|
|
41
|
+
creator: 0n,
|
|
42
|
+
sniper: feeAmount,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const lpFee = (feeAmount * lpBps) / effectiveTotalBps;
|
|
47
|
+
|
|
48
|
+
const creatorBps = baseTotalBps - protocolBps - lpBps;
|
|
49
|
+
if (creatorBps < 0n) {
|
|
50
|
+
throw new RangeError(
|
|
51
|
+
'splitFeeAmount: protocolBps + lpBps exceeds baseTotalBps',
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
const creatorFee = (feeAmount * creatorBps) / effectiveTotalBps;
|
|
55
|
+
|
|
56
|
+
const sniperFee = (feeAmount * decayPremiumBps) / effectiveTotalBps;
|
|
57
|
+
|
|
58
|
+
const protocolFee = feeAmount - lpFee - creatorFee;
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
protocol: protocolFee,
|
|
62
|
+
lp: lpFee,
|
|
63
|
+
creator: creatorFee,
|
|
64
|
+
sniper: sniperFee,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function toBps(value: number, name: string): bigint {
|
|
69
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
70
|
+
throw new RangeError(
|
|
71
|
+
`splitFeeAmount: ${name} must be a non-negative integer`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
return BigInt(value);
|
|
75
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function floorDiv(a: bigint, b: bigint): bigint {
|
|
2
|
+
return a / b;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
// Assumes non-negative inputs.
|
|
6
|
+
export function ceilDiv(a: bigint, b: bigint): bigint {
|
|
7
|
+
return a / b + (a % b > 0n ? 1n : 0n);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function assertBps(fn: string, name: string, bps: number): void {
|
|
11
|
+
if (!Number.isInteger(bps) || bps < 0 || bps > 10_000) {
|
|
12
|
+
throw new RangeError(
|
|
13
|
+
`${fn}: ${name} must be an integer between 0 and 10_000`,
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Address, GetAccountInfoApi, Rpc } from '@solana/kit';
|
|
2
|
+
import { calculateFeeDecayPremium } from '../math/fee-decay.js';
|
|
3
|
+
import {
|
|
4
|
+
fetchMaybePartnerConfig,
|
|
5
|
+
type PartnerConfig,
|
|
6
|
+
} from './generated/accounts/partnerConfig.js';
|
|
7
|
+
import { findPartnerConfigPda } from './generated/pdas/partnerConfig.js';
|
|
8
|
+
import type { DexFees } from './generated/types/dexFees.js';
|
|
9
|
+
import type { LaunchpadFees } from './generated/types/launchpadFees.js';
|
|
10
|
+
|
|
11
|
+
/** Throws if the pair is unregistered. Take `platformConfig` from the market being priced: a partner's fees differ per platform. */
|
|
12
|
+
export async function fetchPartnerFees(
|
|
13
|
+
rpc: Rpc<GetAccountInfoApi>,
|
|
14
|
+
partner: Address,
|
|
15
|
+
platformConfig: Address,
|
|
16
|
+
): Promise<PartnerConfig> {
|
|
17
|
+
const [partnerPda] = await findPartnerConfigPda({
|
|
18
|
+
partner,
|
|
19
|
+
platformConfig,
|
|
20
|
+
});
|
|
21
|
+
const maybeAccount = await fetchMaybePartnerConfig(rpc, partnerPda);
|
|
22
|
+
if (!maybeAccount.exists) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`No fee config for partner ${partner} on platform ${platformConfig}`,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
return maybeAccount.data;
|
|
28
|
+
}
|
|
29
|
+
|
|
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. */
|
|
32
|
+
export function effectiveFeeBps(
|
|
33
|
+
schedule: LaunchpadFees | DexFees,
|
|
34
|
+
createdAt: bigint,
|
|
35
|
+
now: bigint,
|
|
36
|
+
): number {
|
|
37
|
+
const standardFeeBps =
|
|
38
|
+
schedule.protocolFeeBps +
|
|
39
|
+
schedule.creatorFeeBps +
|
|
40
|
+
('lpFeeBps' in schedule ? schedule.lpFeeBps : 0);
|
|
41
|
+
const premium = calculateFeeDecayPremium({
|
|
42
|
+
currentTimestamp: now,
|
|
43
|
+
createdAtTimestamp: createdAt,
|
|
44
|
+
decaySeconds: schedule.feeDecaySeconds,
|
|
45
|
+
decayStartBps: schedule.feeDecayStartBps,
|
|
46
|
+
standardFeeBps,
|
|
47
|
+
});
|
|
48
|
+
return standardFeeBps + Number(premium);
|
|
49
|
+
}
|