@send-fun/sdk 1.0.1 → 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 +13 -5
- package/dist/index.cjs +797 -369
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +596 -94
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +596 -94
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +527 -99
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -6
- 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/instructions/buyExactIn.ts +1 -1
- package/src/dex/generated/instructions/buyExactOut.ts +1 -1
- package/src/dex/generated/instructions/claimCreatorFees.ts +1 -1
- package/src/dex/generated/instructions/claimProtocolFees.ts +1 -1
- package/src/dex/generated/instructions/sellExactIn.ts +1 -1
- package/src/dex/generated/instructions/sellExactOut.ts +1 -1
- package/src/dex/generated/plugins/sendDex.ts +1 -1
- 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/instructions/buyExactIn.ts +1 -1
- package/src/launchpad/generated/instructions/buyExactOut.ts +1 -1
- package/src/launchpad/generated/instructions/claimCreatorFees.ts +1 -1
- package/src/launchpad/generated/instructions/claimProtocolFees.ts +1 -1
- package/src/launchpad/generated/instructions/createToken.ts +1 -1
- package/src/launchpad/generated/instructions/migrate.ts +1 -1
- package/src/launchpad/generated/instructions/sellExactIn.ts +1 -1
- package/src/launchpad/generated/instructions/sellExactOut.ts +1 -1
- package/src/launchpad/generated/plugins/sendLaunchpad.ts +1 -1
- 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/instructions/claim.ts +1 -1
- package/src/nexus/generated/instructions/createUserRewardDebt.ts +1 -1
- package/src/nexus/generated/instructions/settle.ts +1 -1
- package/src/nexus/generated/instructions/stake.ts +1 -1
- package/src/nexus/generated/instructions/unstake.ts +1 -1
- package/src/nexus/generated/plugins/sendNexus.ts +1 -1
- 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
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
getAccountMetaFactory,
|
|
41
41
|
getAddressFromResolvedInstructionAccount,
|
|
42
42
|
type ResolvedInstructionAccount,
|
|
43
|
-
} from '@solana/program-client-core';
|
|
43
|
+
} from '@solana/kit/program-client-core';
|
|
44
44
|
import {
|
|
45
45
|
findUserRewardDebtPda,
|
|
46
46
|
findUserStakePositionPda,
|
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
getAccountMetaFactory,
|
|
45
45
|
getAddressFromResolvedInstructionAccount,
|
|
46
46
|
type ResolvedInstructionAccount,
|
|
47
|
-
} from '@solana/program-client-core';
|
|
47
|
+
} from '@solana/kit/program-client-core';
|
|
48
48
|
import {
|
|
49
49
|
EVENT_AUTHORITY_PDA_ADDRESS,
|
|
50
50
|
findUserStakePositionPda,
|
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
getAccountMetaFactory,
|
|
45
45
|
getAddressFromResolvedInstructionAccount,
|
|
46
46
|
type ResolvedInstructionAccount,
|
|
47
|
-
} from '@solana/program-client-core';
|
|
47
|
+
} from '@solana/kit/program-client-core';
|
|
48
48
|
import {
|
|
49
49
|
EVENT_AUTHORITY_PDA_ADDRESS,
|
|
50
50
|
findUserStakePositionPda,
|
|
@@ -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/nexus/staking.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from '../constants.js';
|
|
18
18
|
import { fetchInChunks } from '../utils/chunk.js';
|
|
19
19
|
import { findAssociatedTokenPda } from '../utils/pda.js';
|
|
20
|
+
import { accountIsCreated } from './generated/shared/index.js';
|
|
20
21
|
import { fetchStakingConfig } from './generated/accounts/stakingConfig.js';
|
|
21
22
|
import {
|
|
22
23
|
fetchMaybeUserStakePosition,
|
|
@@ -42,10 +43,10 @@ import { findRewardAccrualPda as findLaunchpadRewardAccrualPda } from '../launch
|
|
|
42
43
|
import { getRewardAccrualDecoder } from '../launchpad/generated/accounts/rewardAccrual.js';
|
|
43
44
|
import { findRewardAccrualPda as findDexRewardAccrualPda } from '../dex/generated/pdas/rewardAccrual.js';
|
|
44
45
|
|
|
45
|
-
//
|
|
46
|
+
// Fixed-point scale of `RewardAccrual.accPerToken` in both programs.
|
|
46
47
|
const PRECISION = 1_000_000_000_000n;
|
|
47
48
|
|
|
48
|
-
/**
|
|
49
|
+
/** An RPC with `getAccountInfo` and, optionally, `getProgramAccounts`. Without `getProgramAccounts`, pass `rewardMints`. */
|
|
49
50
|
export type RewardMintRpc = Rpc<GetAccountInfoApi> &
|
|
50
51
|
Partial<Rpc<GetProgramAccountsApi>>;
|
|
51
52
|
|
|
@@ -55,7 +56,7 @@ function hasProgramAccounts(
|
|
|
55
56
|
return typeof rpc.getProgramAccounts === 'function';
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
//
|
|
59
|
+
// Field offsets, including the 8-byte discriminator. tests/staking.test.ts pins them.
|
|
59
60
|
const REWARD_STATE_STAKING_CONFIG_OFFSET = 10n;
|
|
60
61
|
const REWARD_STATE_REWARD_MINT_OFFSET = 42;
|
|
61
62
|
|
|
@@ -65,9 +66,8 @@ function compareAddresses(a: Address, b: Address): number {
|
|
|
65
66
|
return 0;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
/**
|
|
69
|
-
*
|
|
70
|
-
* `StakingConfig.rewardCount` rather than settle a short list. */
|
|
69
|
+
/** Returns every registered reward mint, disabled mints included, sorted by address.
|
|
70
|
+
* Throws if the count differs from `StakingConfig.rewardCount`. */
|
|
71
71
|
export async function getRewardMints(
|
|
72
72
|
rpc: Rpc<GetAccountInfoApi> & Rpc<GetProgramAccountsApi>,
|
|
73
73
|
): Promise<readonly Address[]> {
|
|
@@ -98,7 +98,7 @@ export async function getRewardMints(
|
|
|
98
98
|
const mints: Address[] = [];
|
|
99
99
|
for (const { account } of accounts) {
|
|
100
100
|
const data = base64.encode(account.data[0]);
|
|
101
|
-
// Another nexus account of the same size
|
|
101
|
+
// Another nexus account of the same size can pass both filters.
|
|
102
102
|
if (!containsBytes(data, REWARD_STATE_DISCRIMINATOR, 0)) continue;
|
|
103
103
|
mints.push(
|
|
104
104
|
addressDecoder.decode(data, REWARD_STATE_REWARD_MINT_OFFSET),
|
|
@@ -136,13 +136,14 @@ async function resolveRewardMints(
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
export interface SettleParams {
|
|
139
|
-
/** `settle` is permissionless
|
|
139
|
+
/** Does not sign. `settle` is permissionless. */
|
|
140
140
|
user: Address;
|
|
141
141
|
payer: TransactionSigner;
|
|
142
142
|
stakingMint: Address;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
/**
|
|
145
|
+
/** Builds one `settle` per reward mint. `source` is the mint list, or an RPC to read it from.
|
|
146
|
+
* `settle` is idempotent. With a partial list, `stake` and `unstake` fail with `RewardsNotSettled`. */
|
|
146
147
|
export async function buildSettleInstructions(
|
|
147
148
|
source: RewardMintSource,
|
|
148
149
|
params: SettleParams,
|
|
@@ -187,7 +188,7 @@ export async function fetchMissingUserRewardDebts(
|
|
|
187
188
|
|
|
188
189
|
const missingMints: Address[] = [];
|
|
189
190
|
for (const [index, rewardMint] of rewardMints.entries()) {
|
|
190
|
-
if (!encodedAccounts[index]
|
|
191
|
+
if (!accountIsCreated(encodedAccounts[index])) {
|
|
191
192
|
missingMints.push(rewardMint);
|
|
192
193
|
}
|
|
193
194
|
}
|
|
@@ -199,16 +200,17 @@ export interface PrepareStakingParams {
|
|
|
199
200
|
user: Address;
|
|
200
201
|
payer: TransactionSigner;
|
|
201
202
|
stakingMint: Address;
|
|
202
|
-
/**
|
|
203
|
+
/** If given, must hold every registered mint. With a subset, `stake` and `unstake` fail. */
|
|
203
204
|
rewardMints?: readonly Address[];
|
|
204
205
|
}
|
|
205
206
|
|
|
206
|
-
/**
|
|
207
|
+
/** Builds a `create_user_reward_debt` for each missing `UserRewardDebt`, then a `settle` for every
|
|
208
|
+
* reward mint. The user does not sign these instructions. */
|
|
207
209
|
export async function buildStakingPreflightInstructions(
|
|
208
210
|
rpc: RewardMintRpc & Rpc<GetMultipleAccountsApi>,
|
|
209
211
|
params: PrepareStakingParams,
|
|
210
212
|
): Promise<Instruction[]> {
|
|
211
|
-
//
|
|
213
|
+
// Read the mints once. Two reads can return different lists.
|
|
212
214
|
const rewardMints = await resolveRewardMints(rpc, params.rewardMints);
|
|
213
215
|
|
|
214
216
|
const missingMints = await fetchMissingUserRewardDebts(
|
|
@@ -234,7 +236,7 @@ export async function buildStakingPreflightInstructions(
|
|
|
234
236
|
return [...createIxs, ...settleIxs];
|
|
235
237
|
}
|
|
236
238
|
|
|
237
|
-
/**
|
|
239
|
+
/** Returns true if `stake` and `unstake` pass the `settledCount == rewardCount` check. */
|
|
238
240
|
export async function isFullySettled(
|
|
239
241
|
rpc: Rpc<GetAccountInfoApi>,
|
|
240
242
|
user: Address,
|
|
@@ -255,7 +257,8 @@ export interface StakeParams {
|
|
|
255
257
|
amount: bigint;
|
|
256
258
|
}
|
|
257
259
|
|
|
258
|
-
/**
|
|
260
|
+
/** The instruction fails with `RewardsNotSettled` until every mint is settled at the current
|
|
261
|
+
* `stakeVersion`. Run `buildStakingPreflightInstructions` first. */
|
|
259
262
|
export async function buildStakeInstruction(
|
|
260
263
|
params: StakeParams,
|
|
261
264
|
): Promise<Instruction> {
|
|
@@ -274,8 +277,9 @@ export interface UnstakeParams {
|
|
|
274
277
|
amount: bigint;
|
|
275
278
|
}
|
|
276
279
|
|
|
277
|
-
/**
|
|
278
|
-
*
|
|
280
|
+
/** Run `buildStakingPreflightInstructions` immediately before. The check also passes on an older
|
|
281
|
+
* settle. Rewards since that settle then accrue on the smaller post-unstake amount, and the
|
|
282
|
+
* difference is lost. */
|
|
279
283
|
export async function buildUnstakeInstruction(
|
|
280
284
|
params: UnstakeParams,
|
|
281
285
|
): Promise<Instruction> {
|
|
@@ -291,12 +295,12 @@ export interface ClaimRewardsParams {
|
|
|
291
295
|
user: TransactionSigner;
|
|
292
296
|
payer?: TransactionSigner;
|
|
293
297
|
stakingMint: Address;
|
|
294
|
-
/** Defaults to every registered mint
|
|
298
|
+
/** Defaults to every registered mint. A subset needs no preflight: each `claim` settles its own mint. */
|
|
295
299
|
rewardMints?: readonly Address[];
|
|
296
300
|
}
|
|
297
301
|
|
|
298
|
-
/**
|
|
299
|
-
* `
|
|
302
|
+
/** Builds one `claim` per reward mint. A `create_user_reward_debt` comes before each `claim` whose
|
|
303
|
+
* `UserRewardDebt` is missing: `claim` fails without it. Throws if a mint has no `RewardState`. */
|
|
300
304
|
export async function buildClaimRewardsInstructions(
|
|
301
305
|
rpc: RewardMintRpc & Rpc<GetMultipleAccountsApi>,
|
|
302
306
|
params: ClaimRewardsParams,
|
|
@@ -337,7 +341,7 @@ export async function buildClaimRewardsInstructions(
|
|
|
337
341
|
const perMint = await Promise.all(
|
|
338
342
|
rewardMints.map(async (rewardMint, index) => {
|
|
339
343
|
const encodedRewardState = fetched[index];
|
|
340
|
-
if (!encodedRewardState
|
|
344
|
+
if (!accountIsCreated(encodedRewardState)) {
|
|
341
345
|
throw new Error(`RewardState not found for mint ${rewardMint}`);
|
|
342
346
|
}
|
|
343
347
|
const { vault } = rewardStateDecoder.decode(
|
|
@@ -356,8 +360,8 @@ export async function buildClaimRewardsInstructions(
|
|
|
356
360
|
|
|
357
361
|
const instructions: Instruction[] = [];
|
|
358
362
|
|
|
359
|
-
// Directly before its own `claim`, so
|
|
360
|
-
if (!fetched[rewardMints.length * 2 + index]
|
|
363
|
+
// Directly before its own `claim`, so a split of the list by mint keeps each pair together.
|
|
364
|
+
if (!accountIsCreated(fetched[rewardMints.length * 2 + index])) {
|
|
361
365
|
instructions.push(
|
|
362
366
|
await getCreateUserRewardDebtInstructionAsync({
|
|
363
367
|
user: params.user.address,
|
|
@@ -395,12 +399,13 @@ export interface WithdrawFeesAccounts {
|
|
|
395
399
|
tokenProgram: Address;
|
|
396
400
|
}
|
|
397
401
|
|
|
398
|
-
/**
|
|
402
|
+
/** Returns the accounts of `withdraw_fees` for one reward mint. Without `tokenProgram`, reads it
|
|
403
|
+
* from the mint account. Throws if that account does not exist. */
|
|
399
404
|
export async function buildWithdrawFeesAccounts(
|
|
400
405
|
rpc: Rpc<GetAccountInfoApi>,
|
|
401
406
|
params: {
|
|
402
407
|
rewardMint: Address;
|
|
403
|
-
/**
|
|
408
|
+
/** A wallet, not a token account. The SDK derives its ATA. */
|
|
404
409
|
destination: Address;
|
|
405
410
|
tokenProgram?: Address;
|
|
406
411
|
},
|
|
@@ -467,13 +472,12 @@ export async function fetchUserStakePositionData(
|
|
|
467
472
|
return maybePosition.exists ? maybePosition.data : null;
|
|
468
473
|
}
|
|
469
474
|
|
|
470
|
-
/**
|
|
471
|
-
* can never store: it requires a token mint. */
|
|
475
|
+
/** Returns false for the system program address, the value of an unbound staking mint. */
|
|
472
476
|
export function isStakingMintBound(stakingMint: Address): boolean {
|
|
473
477
|
return stakingMint !== SYSTEM_PROGRAM_ADDRESS;
|
|
474
478
|
}
|
|
475
479
|
|
|
476
|
-
/**
|
|
480
|
+
/** Returns the staking mint. Throws if no staking mint is bound. A bound staking mint cannot change. */
|
|
477
481
|
export async function fetchSendMint(
|
|
478
482
|
rpc: Rpc<GetAccountInfoApi>,
|
|
479
483
|
): Promise<Address> {
|
|
@@ -488,13 +492,14 @@ export async function fetchSendMint(
|
|
|
488
492
|
|
|
489
493
|
export interface PendingReward {
|
|
490
494
|
rewardMint: Address;
|
|
491
|
-
/**
|
|
492
|
-
*
|
|
495
|
+
/** Amount the vault sends, in raw units of `rewardMint`. Equals `ClaimEvent.amount`. A mint with a
|
|
496
|
+
* transfer fee delivers less. Get the fee with `transferFee.fetchMintFees`. */
|
|
493
497
|
pending: bigint;
|
|
494
498
|
}
|
|
495
499
|
|
|
496
|
-
/**
|
|
497
|
-
* its own mint
|
|
500
|
+
/** Returns one entry per reward mint, in `knownRewardMints` order, else sorted by address. Each amount
|
|
501
|
+
* is in the raw units of its own mint. Do not add them together. Each amount is what the vault sends,
|
|
502
|
+
* before the mint's transfer fee. */
|
|
498
503
|
export async function fetchPendingRewards(
|
|
499
504
|
rpc: RewardMintRpc & Rpc<GetMultipleAccountsApi>,
|
|
500
505
|
user: Address,
|
|
@@ -541,33 +546,32 @@ export async function fetchPendingRewards(
|
|
|
541
546
|
);
|
|
542
547
|
|
|
543
548
|
const userRewardDebtDecoder = getUserRewardDebtDecoder();
|
|
544
|
-
// One decoder for both: the two `RewardAccrual` types
|
|
549
|
+
// One decoder for both: the two `RewardAccrual` types have the same layout and discriminator.
|
|
545
550
|
const rewardAccrualDecoder = getRewardAccrualDecoder();
|
|
546
551
|
|
|
547
552
|
return pdas.map(({ rewardMint }, index) => {
|
|
548
553
|
const base = index * 3;
|
|
549
554
|
|
|
550
555
|
const encodedDebt = encodedAccounts[base];
|
|
551
|
-
// A missing debt is not zero owed
|
|
552
|
-
// `accSnapshot = 0
|
|
553
|
-
const debt = encodedDebt
|
|
556
|
+
// A missing debt is not zero owed. `create_user_reward_debt` opens it at
|
|
557
|
+
// `accSnapshot = 0` and `amountSnapshot = position.amount`, and the claim pays from there.
|
|
558
|
+
const debt = accountIsCreated(encodedDebt)
|
|
554
559
|
? userRewardDebtDecoder.decode(encodedDebt.data)
|
|
555
560
|
: { owed: 0n, accSnapshot: 0n, amountSnapshot: stakeAmount };
|
|
556
561
|
|
|
557
562
|
const encodedLaunchpadAccrual = encodedAccounts[base + 1];
|
|
558
|
-
const launchpadAcc = encodedLaunchpadAccrual
|
|
563
|
+
const launchpadAcc = accountIsCreated(encodedLaunchpadAccrual)
|
|
559
564
|
? rewardAccrualDecoder.decode(encodedLaunchpadAccrual.data)
|
|
560
565
|
.accPerToken
|
|
561
566
|
: 0n;
|
|
562
567
|
|
|
563
568
|
const encodedDexAccrual = encodedAccounts[base + 2];
|
|
564
|
-
const dexAcc = encodedDexAccrual
|
|
569
|
+
const dexAcc = accountIsCreated(encodedDexAccrual)
|
|
565
570
|
? rewardAccrualDecoder.decode(encodedDexAccrual.data).accPerToken
|
|
566
571
|
: 0n;
|
|
567
572
|
|
|
568
|
-
//
|
|
569
|
-
//
|
|
570
|
-
// where the program would fail.
|
|
573
|
+
// Matches `settle`: the basis is the smaller amount, floored once over the summed
|
|
574
|
+
// accumulators. A negative delta quotes 0, where the program fails.
|
|
571
575
|
const basis =
|
|
572
576
|
debt.amountSnapshot < stakeAmount
|
|
573
577
|
? debt.amountSnapshot
|
package/src/platform.ts
CHANGED
|
@@ -2,10 +2,10 @@ import { getProgramDerivedAddress } from '@solana/kit';
|
|
|
2
2
|
import type { ProgramDerivedAddress } from '@solana/kit';
|
|
3
3
|
import { SEND_NEXUS_PROGRAM_ADDRESS } from './nexus/generated/programs/index.js';
|
|
4
4
|
|
|
5
|
-
/**
|
|
5
|
+
/** Seed of the platform PDA. A platform has no on-chain account. */
|
|
6
6
|
export const PLATFORM_SEED = 'platform';
|
|
7
7
|
|
|
8
|
-
/**
|
|
8
|
+
/** Derives the platform key for `name` under the nexus program. Each name gives a different platform. */
|
|
9
9
|
export async function findPlatformAddress(
|
|
10
10
|
name: string,
|
|
11
11
|
): Promise<ProgramDerivedAddress> {
|
package/src/transfer-fee.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
1
|
+
// Parses Token-2022 mint data by hand. The SDK does not depend on
|
|
2
|
+
// `@solana-program/token-2022`.
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
getAddressDecoder,
|
|
@@ -20,8 +20,8 @@ import { fetchInChunks } from './utils/chunk.js';
|
|
|
20
20
|
|
|
21
21
|
const MINT_BASE_LENGTH = 82;
|
|
22
22
|
|
|
23
|
-
// Token-2022 pads the base mint to the
|
|
24
|
-
//
|
|
23
|
+
// Token-2022 pads the base mint to 165 bytes, the token-account length. The
|
|
24
|
+
// account-type byte follows, then the TLV entries.
|
|
25
25
|
const ACCOUNT_TYPE_OFFSET = 165;
|
|
26
26
|
|
|
27
27
|
const ACCOUNT_TYPE_MINT = 1;
|
|
@@ -58,7 +58,7 @@ export interface TransferFeeEntry {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
export interface TransferFeeConfig {
|
|
61
|
-
/** `undefined`
|
|
61
|
+
/** `undefined` if the config has no authority. Then the schedule cannot change. */
|
|
62
62
|
readonly authority: Address | undefined;
|
|
63
63
|
readonly older: TransferFeeEntry;
|
|
64
64
|
readonly newer: TransferFeeEntry;
|
|
@@ -80,13 +80,14 @@ function readEntry(view: DataView, start: number): TransferFeeEntry {
|
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
/** `
|
|
84
|
-
*
|
|
83
|
+
/** Decodes the `TransferFeeConfig` extension of a mint. `owner` is the program that owns the account.
|
|
84
|
+
* Returns `undefined` if the mint has no such extension. Throws `RangeError` if `owner` is not a
|
|
85
|
+
* token program or `data` is not a valid mint. */
|
|
85
86
|
export function decodeTransferFeeConfig(
|
|
86
87
|
data: Uint8Array,
|
|
87
88
|
owner: Address,
|
|
88
89
|
): TransferFeeConfig | undefined {
|
|
89
|
-
// Classic SPL
|
|
90
|
+
// Classic SPL mints have no extensions.
|
|
90
91
|
if (owner === TOKEN_PROGRAM_ADDRESS) return undefined;
|
|
91
92
|
if (owner !== TOKEN_2022_PROGRAM_ADDRESS) {
|
|
92
93
|
throw new RangeError(
|
|
@@ -94,14 +95,14 @@ export function decodeTransferFeeConfig(
|
|
|
94
95
|
);
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
// Token-2022
|
|
98
|
+
// A Token-2022 mint with no extensions is 82 bytes.
|
|
98
99
|
if (data.length === MINT_BASE_LENGTH) return undefined;
|
|
99
100
|
if (data.length < TLV_START) {
|
|
100
101
|
throw new RangeError(
|
|
101
102
|
`decodeTransferFeeConfig: a Token-2022 mint holds ${data.length} bytes, expected ${MINT_BASE_LENGTH} or at least ${TLV_START}`,
|
|
102
103
|
);
|
|
103
104
|
}
|
|
104
|
-
// Token accounts
|
|
105
|
+
// Token accounts use the same TLV layout.
|
|
105
106
|
if (data[ACCOUNT_TYPE_OFFSET] !== ACCOUNT_TYPE_MINT) {
|
|
106
107
|
throw new RangeError(
|
|
107
108
|
`decodeTransferFeeConfig: account type ${data[ACCOUNT_TYPE_OFFSET]} is not a mint`,
|
|
@@ -112,7 +113,7 @@ export function decodeTransferFeeConfig(
|
|
|
112
113
|
|
|
113
114
|
let offset = TLV_START;
|
|
114
115
|
while (offset < data.length) {
|
|
115
|
-
//
|
|
116
|
+
// SPL reads a tail too short for a type, or a zero type, as the end of the list.
|
|
116
117
|
if (offset + TLV_TYPE > data.length) return undefined;
|
|
117
118
|
const extensionType = view.getUint16(offset, true);
|
|
118
119
|
if (extensionType === UNINITIALIZED_TYPE) return undefined;
|
|
@@ -133,7 +134,7 @@ export function decodeTransferFeeConfig(
|
|
|
133
134
|
}
|
|
134
135
|
|
|
135
136
|
if (extensionType === TRANSFER_FEE_CONFIG_TYPE) {
|
|
136
|
-
//
|
|
137
|
+
// The extension is always 108 bytes.
|
|
137
138
|
if (length !== TRANSFER_FEE_CONFIG_LENGTH) {
|
|
138
139
|
throw new RangeError(
|
|
139
140
|
`decodeTransferFeeConfig: TransferFeeConfig holds ${length} bytes, expected ${TRANSFER_FEE_CONFIG_LENGTH}`,
|
|
@@ -152,7 +153,7 @@ export function decodeTransferFeeConfig(
|
|
|
152
153
|
return undefined;
|
|
153
154
|
}
|
|
154
155
|
|
|
155
|
-
/**
|
|
156
|
+
/** Returns the fee for `epoch`, as SPL `get_epoch_fee` does. The newer entry applies from its own epoch on. */
|
|
156
157
|
export function transferFeeAtEpoch(
|
|
157
158
|
config: TransferFeeConfig,
|
|
158
159
|
epoch: bigint,
|
|
@@ -170,8 +171,8 @@ export function mintFeeAtEpoch(
|
|
|
170
171
|
return config === undefined ? undefined : transferFeeAtEpoch(config, epoch);
|
|
171
172
|
}
|
|
172
173
|
|
|
173
|
-
// Omit the config,
|
|
174
|
-
//
|
|
174
|
+
// Omit the config, not `{ commitment: undefined }`. Kit deletes an undefined key, so the
|
|
175
|
+
// server default `finalized` applies. With no config, the client default applies.
|
|
175
176
|
function currentEpoch(
|
|
176
177
|
rpc: Rpc<GetEpochInfoApi>,
|
|
177
178
|
commitment: Commitment | undefined,
|
|
@@ -182,8 +183,9 @@ function currentEpoch(
|
|
|
182
183
|
.then((info) => info.epoch);
|
|
183
184
|
}
|
|
184
185
|
|
|
185
|
-
/**
|
|
186
|
-
* the cluster
|
|
186
|
+
/** Reads the transfer fee of each mint for `epoch`. The result is valid only for that epoch.
|
|
187
|
+
* `epoch` defaults to the current cluster epoch, read with `getEpochInfo`. A mint without a
|
|
188
|
+
* transfer fee maps to `undefined`. Throws if a mint account is missing or does not decode. */
|
|
187
189
|
export async function fetchMintFees(
|
|
188
190
|
rpc: Rpc<GetMultipleAccountsApi & GetEpochInfoApi>,
|
|
189
191
|
mints: readonly Address[],
|
package/src/utils/chunk.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { Address } from '@solana/kit';
|
|
|
3
3
|
/** RPCs reject a `getMultipleAccounts` call past this many addresses. */
|
|
4
4
|
const MAX_ACCOUNTS_PER_REQUEST = 100;
|
|
5
5
|
|
|
6
|
-
/**
|
|
6
|
+
/** Calls `read` once per 100 addresses. Results follow `addresses` order. */
|
|
7
7
|
export async function fetchInChunks<T>(
|
|
8
8
|
addresses: readonly Address[],
|
|
9
9
|
read: (chunk: Address[]) => Promise<readonly T[]>,
|
|
@@ -3,7 +3,8 @@ import type { Address, ReadonlyUint8Array } from '@solana/kit';
|
|
|
3
3
|
|
|
4
4
|
const MAX_CREATOR_PLATFORM_LEN = 32;
|
|
5
5
|
|
|
6
|
-
/** SHA-256 of
|
|
6
|
+
/** Returns the SHA-256 of `creatorPlatform` and `creatorId`, each prefixed with its byte length as a
|
|
7
|
+
* little-endian u32. Throws `RangeError` if `creatorPlatform` is more than 32 bytes. */
|
|
7
8
|
export async function creatorHashFromId(
|
|
8
9
|
creatorPlatform: string,
|
|
9
10
|
creatorId: string,
|
|
@@ -34,7 +35,8 @@ export async function creatorHashFromId(
|
|
|
34
35
|
return addressDecoder.decode(hash);
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
/**
|
|
38
|
+
/** Pads `text` with NUL bytes to `length` bytes, the form of `CreatorFeeConfig.platformId`.
|
|
39
|
+
* Throws `RangeError` if `text` is longer. */
|
|
38
40
|
export function encodeCreatorId(text: string, length: number): Uint8Array {
|
|
39
41
|
const bytes = new TextEncoder().encode(text);
|
|
40
42
|
if (bytes.length > length) {
|
|
@@ -47,7 +49,8 @@ export function encodeCreatorId(text: string, length: number): Uint8Array {
|
|
|
47
49
|
return out;
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
/**
|
|
52
|
+
/** Removes the trailing NUL bytes and decodes the rest as UTF-8. Pass the result, not the padded
|
|
53
|
+
* bytes, to {@link creatorHashFromId}. */
|
|
51
54
|
export function decodeCreatorId(bytes: ReadonlyUint8Array): string {
|
|
52
55
|
let end = bytes.length;
|
|
53
56
|
while (end > 0 && bytes[end - 1] === 0) {
|
package/src/utils/index.ts
CHANGED
package/src/utils/mint-info.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
//
|
|
2
|
-
// transfer cannot reach.
|
|
1
|
+
// Reads the token program from the mint account. Do not hard-code it.
|
|
3
2
|
import {
|
|
4
3
|
getBase64Encoder,
|
|
5
4
|
type Address,
|
|
@@ -15,14 +14,14 @@ import {
|
|
|
15
14
|
} from '../constants.js';
|
|
16
15
|
import { fetchInChunks } from './chunk.js';
|
|
17
16
|
|
|
18
|
-
// Both token programs
|
|
17
|
+
// Both token programs use the same base mint: 36-byte `COption<Pubkey>` authority,
|
|
19
18
|
// u64 supply, then decimals.
|
|
20
19
|
const MINT_BASE_LENGTH = 82;
|
|
21
20
|
const MINT_DECIMALS_OFFSET = 44;
|
|
22
21
|
|
|
23
22
|
export interface QuoteMintInfo {
|
|
24
23
|
mint: Address;
|
|
25
|
-
/**
|
|
24
|
+
/** Cannot change after the mint is created. */
|
|
26
25
|
decimals: number;
|
|
27
26
|
/** The account's owner: `TOKEN_PROGRAM_ADDRESS` or `TOKEN_2022_PROGRAM_ADDRESS`. */
|
|
28
27
|
tokenProgram: Address;
|
|
@@ -41,7 +40,7 @@ function decodeMintInfo(
|
|
|
41
40
|
`fetchQuoteMintInfo: ${mint} is owned by ${owner}, which is not a token program`,
|
|
42
41
|
);
|
|
43
42
|
}
|
|
44
|
-
// A
|
|
43
|
+
// A minimum: Token-2022 mints with extensions are longer.
|
|
45
44
|
if (data.length < MINT_BASE_LENGTH) {
|
|
46
45
|
throw new Error(
|
|
47
46
|
`fetchQuoteMintInfo: ${mint} holds ${data.length} bytes, too short for a mint`,
|
|
@@ -50,7 +49,8 @@ function decodeMintInfo(
|
|
|
50
49
|
return { mint, decimals: data[MINT_DECIMALS_OFFSET], tokenProgram: owner };
|
|
51
50
|
}
|
|
52
51
|
|
|
53
|
-
/**
|
|
52
|
+
/** Reads the decimals and token program of `mint`. Throws if the account does not exist, a token
|
|
53
|
+
* program does not own it, or it is too short for a mint. */
|
|
54
54
|
export async function fetchQuoteMintInfo(
|
|
55
55
|
rpc: Rpc<GetAccountInfoApi>,
|
|
56
56
|
mint: Address,
|
|
@@ -71,7 +71,8 @@ export async function fetchQuoteMintInfo(
|
|
|
71
71
|
return decodeMintInfo(mint, data, value.owner);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
/**
|
|
74
|
+
/** Reads the decimals and token program of each mint. Results follow `mints` order. Throws as
|
|
75
|
+
* {@link fetchQuoteMintInfo} does. */
|
|
75
76
|
export async function fetchQuoteMintInfos(
|
|
76
77
|
rpc: Rpc<GetMultipleAccountsApi>,
|
|
77
78
|
mints: readonly Address[],
|
package/src/utils/partner.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TransactionSigner } from '@solana/kit';
|
|
2
2
|
import type { DEFAULT_PARTNER } from '../constants.js';
|
|
3
3
|
|
|
4
|
-
/**
|
|
4
|
+
/** A partner signer, or `DEFAULT_PARTNER` as a bare address. Any other partner must sign. */
|
|
5
5
|
export type PartnerInput = TransactionSigner | typeof DEFAULT_PARTNER;
|
package/src/utils/pda.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
|
|
8
8
|
const addressEncoder = getAddressEncoder();
|
|
9
9
|
|
|
10
|
-
/**
|
|
10
|
+
/** Derives the associated token account of `wallet` for `mint` under `tokenProgram`. */
|
|
11
11
|
export async function findAssociatedTokenPda(
|
|
12
12
|
wallet: Address,
|
|
13
13
|
mint: Address,
|