@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
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// Hand-parsed so consumers don't inherit `@solana-program/token-2022`. The programs
|
|
2
|
+
// price every leg on what lands after the mint's cut; a quote without it fails slippage.
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
getAddressDecoder,
|
|
6
|
+
getBase64Encoder,
|
|
7
|
+
type Address,
|
|
8
|
+
type Commitment,
|
|
9
|
+
type GetEpochInfoApi,
|
|
10
|
+
type GetMultipleAccountsApi,
|
|
11
|
+
type Rpc,
|
|
12
|
+
} from '@solana/kit';
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
TOKEN_2022_PROGRAM_ADDRESS,
|
|
16
|
+
TOKEN_PROGRAM_ADDRESS,
|
|
17
|
+
} from './constants.js';
|
|
18
|
+
import type { MintFee } from './math/amm.js';
|
|
19
|
+
import { fetchInChunks } from './utils/chunk.js';
|
|
20
|
+
|
|
21
|
+
const MINT_BASE_LENGTH = 82;
|
|
22
|
+
|
|
23
|
+
// Token-2022 pads the base mint to the 165-byte token-account length so the two
|
|
24
|
+
// never share a prefix, then stamps the type byte; TLV entries follow it.
|
|
25
|
+
const ACCOUNT_TYPE_OFFSET = 165;
|
|
26
|
+
|
|
27
|
+
const ACCOUNT_TYPE_MINT = 1;
|
|
28
|
+
|
|
29
|
+
const TLV_START = 166;
|
|
30
|
+
|
|
31
|
+
const TLV_HEADER = 4;
|
|
32
|
+
|
|
33
|
+
const TRANSFER_FEE_CONFIG_TYPE = 1;
|
|
34
|
+
|
|
35
|
+
// Trailing rent slack reads as `ExtensionType::Uninitialized`, ending the list.
|
|
36
|
+
const UNINITIALIZED_TYPE = 0;
|
|
37
|
+
|
|
38
|
+
// Config authority, withdraw authority, `withheld_amount` u64, then older and
|
|
39
|
+
// newer `TransferFee` (epoch u64, maximum_fee u64, bps u16).
|
|
40
|
+
const TRANSFER_FEE_CONFIG_LENGTH = 108;
|
|
41
|
+
|
|
42
|
+
const PUBKEY_LENGTH = 32;
|
|
43
|
+
|
|
44
|
+
const OLDER_FEE = 72;
|
|
45
|
+
const NEWER_FEE = 90;
|
|
46
|
+
|
|
47
|
+
const FEE_MAXIMUM = 8;
|
|
48
|
+
const FEE_BASIS_POINTS = 16;
|
|
49
|
+
|
|
50
|
+
const addressDecoder = getAddressDecoder();
|
|
51
|
+
const base64Encoder = getBase64Encoder();
|
|
52
|
+
|
|
53
|
+
export interface TransferFeeEntry {
|
|
54
|
+
readonly epoch: bigint;
|
|
55
|
+
readonly maximumFee: bigint;
|
|
56
|
+
readonly basisPoints: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface TransferFeeConfig {
|
|
60
|
+
/** `undefined` when the authority is unset: the schedule is frozen forever. */
|
|
61
|
+
readonly authority: Address | undefined;
|
|
62
|
+
readonly older: TransferFeeEntry;
|
|
63
|
+
readonly newer: TransferFeeEntry;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// `OptionalNonZeroPubkey` has no tag byte: all zeros is `None`.
|
|
67
|
+
function readAuthority(data: Uint8Array, start: number): Address | undefined {
|
|
68
|
+
const bytes = data.subarray(start, start + PUBKEY_LENGTH);
|
|
69
|
+
return bytes.some((byte) => byte !== 0)
|
|
70
|
+
? addressDecoder.decode(bytes)
|
|
71
|
+
: undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function readEntry(view: DataView, start: number): TransferFeeEntry {
|
|
75
|
+
return {
|
|
76
|
+
epoch: view.getBigUint64(start, true),
|
|
77
|
+
maximumFee: view.getBigUint64(start + FEE_MAXIMUM, true),
|
|
78
|
+
basisPoints: view.getUint16(start + FEE_BASIS_POINTS, true),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** `owner` is the account's program. `undefined` means no fee extension; anything
|
|
83
|
+
* unparseable throws `RangeError` rather than pricing a charging mint free. */
|
|
84
|
+
export function decodeTransferFeeConfig(
|
|
85
|
+
data: Uint8Array,
|
|
86
|
+
owner: Address,
|
|
87
|
+
): TransferFeeConfig | undefined {
|
|
88
|
+
// Classic SPL can never gain an extension, so this result is permanent.
|
|
89
|
+
if (owner === TOKEN_PROGRAM_ADDRESS) return undefined;
|
|
90
|
+
if (owner !== TOKEN_2022_PROGRAM_ADDRESS) {
|
|
91
|
+
throw new RangeError(
|
|
92
|
+
`decodeTransferFeeConfig: ${owner} is not a token program`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Token-2022 leaves a mint with no extensions unpadded at 82 bytes.
|
|
97
|
+
if (data.length === MINT_BASE_LENGTH) return undefined;
|
|
98
|
+
if (data.length < TLV_START) {
|
|
99
|
+
throw new RangeError(
|
|
100
|
+
`decodeTransferFeeConfig: a Token-2022 mint holds ${data.length} bytes, expected ${MINT_BASE_LENGTH} or at least ${TLV_START}`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
// Token accounts share this TLV layout with different extension types.
|
|
104
|
+
if (data[ACCOUNT_TYPE_OFFSET] !== ACCOUNT_TYPE_MINT) {
|
|
105
|
+
throw new RangeError(
|
|
106
|
+
`decodeTransferFeeConfig: account type ${data[ACCOUNT_TYPE_OFFSET]} is not a mint`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
111
|
+
|
|
112
|
+
let offset = TLV_START;
|
|
113
|
+
while (offset < data.length) {
|
|
114
|
+
if (offset + TLV_HEADER > data.length) {
|
|
115
|
+
throw new RangeError(
|
|
116
|
+
'decodeTransferFeeConfig: a TLV entry header runs past the end of the account',
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const extensionType = view.getUint16(offset, true);
|
|
121
|
+
if (extensionType === UNINITIALIZED_TYPE) return undefined;
|
|
122
|
+
|
|
123
|
+
const length = view.getUint16(offset + 2, true);
|
|
124
|
+
const payload = offset + TLV_HEADER;
|
|
125
|
+
const next = payload + length;
|
|
126
|
+
if (next > data.length) {
|
|
127
|
+
throw new RangeError(
|
|
128
|
+
'decodeTransferFeeConfig: a TLV entry runs past the end of the account',
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (extensionType === TRANSFER_FEE_CONFIG_TYPE) {
|
|
133
|
+
// Exactly 108 or throw: the extension never writes a wider config.
|
|
134
|
+
if (length !== TRANSFER_FEE_CONFIG_LENGTH) {
|
|
135
|
+
throw new RangeError(
|
|
136
|
+
`decodeTransferFeeConfig: TransferFeeConfig holds ${length} bytes, expected ${TRANSFER_FEE_CONFIG_LENGTH}`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
authority: readAuthority(data, payload),
|
|
141
|
+
older: readEntry(view, payload + OLDER_FEE),
|
|
142
|
+
newer: readEntry(view, payload + NEWER_FEE),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
offset = next;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Mirrors SPL `get_epoch_fee`: the newer entry is live from its own epoch on. */
|
|
153
|
+
export function transferFeeAtEpoch(
|
|
154
|
+
config: TransferFeeConfig,
|
|
155
|
+
epoch: bigint,
|
|
156
|
+
): MintFee {
|
|
157
|
+
const entry = epoch >= config.newer.epoch ? config.newer : config.older;
|
|
158
|
+
return { bps: entry.basisPoints, maximumFee: entry.maximumFee };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function mintFeeAtEpoch(
|
|
162
|
+
data: Uint8Array,
|
|
163
|
+
owner: Address,
|
|
164
|
+
epoch: bigint,
|
|
165
|
+
): MintFee | undefined {
|
|
166
|
+
const config = decodeTransferFeeConfig(data, owner);
|
|
167
|
+
return config === undefined ? undefined : transferFeeAtEpoch(config, epoch);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Omit the config, never `{ commitment: undefined }`: Kit strips the falsy key and
|
|
171
|
+
// the server's `finalized` wins, where an absent key gets the client's default.
|
|
172
|
+
function currentEpoch(
|
|
173
|
+
rpc: Rpc<GetEpochInfoApi>,
|
|
174
|
+
commitment: Commitment | undefined,
|
|
175
|
+
): Promise<bigint> {
|
|
176
|
+
return rpc
|
|
177
|
+
.getEpochInfo(commitment === undefined ? undefined : { commitment })
|
|
178
|
+
.send()
|
|
179
|
+
.then((info) => info.epoch);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** A missing account throws, never reads as fee-free. Valid only for `epoch`, which defaults to
|
|
183
|
+
* the cluster's (a second call). */
|
|
184
|
+
export async function fetchMintFees(
|
|
185
|
+
rpc: Rpc<GetMultipleAccountsApi & GetEpochInfoApi>,
|
|
186
|
+
mints: readonly Address[],
|
|
187
|
+
options?: { epoch?: bigint; commitment?: Commitment },
|
|
188
|
+
): Promise<Map<Address, MintFee | undefined>> {
|
|
189
|
+
const commitment = options?.commitment;
|
|
190
|
+
|
|
191
|
+
const [epoch, accounts] = await Promise.all([
|
|
192
|
+
options?.epoch ?? currentEpoch(rpc, commitment),
|
|
193
|
+
fetchInChunks(mints, (chunk) =>
|
|
194
|
+
rpc
|
|
195
|
+
.getMultipleAccounts(
|
|
196
|
+
chunk,
|
|
197
|
+
commitment === undefined
|
|
198
|
+
? { encoding: 'base64' }
|
|
199
|
+
: { commitment, encoding: 'base64' },
|
|
200
|
+
)
|
|
201
|
+
.send()
|
|
202
|
+
.then(({ value }) => value),
|
|
203
|
+
),
|
|
204
|
+
]);
|
|
205
|
+
|
|
206
|
+
const fees = new Map<Address, MintFee | undefined>();
|
|
207
|
+
for (const [index, mint] of mints.entries()) {
|
|
208
|
+
const account = accounts.at(index);
|
|
209
|
+
if (!account) {
|
|
210
|
+
throw new Error(`fetchMintFees: no account at ${mint}`);
|
|
211
|
+
}
|
|
212
|
+
const data = new Uint8Array(base64Encoder.encode(account.data[0]));
|
|
213
|
+
fees.set(mint, mintFeeAtEpoch(data, account.owner, epoch));
|
|
214
|
+
}
|
|
215
|
+
return fees;
|
|
216
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Address } from '@solana/kit';
|
|
2
|
+
|
|
3
|
+
/** RPCs reject a `getMultipleAccounts` call past this many addresses. */
|
|
4
|
+
const MAX_ACCOUNTS_PER_REQUEST = 100;
|
|
5
|
+
|
|
6
|
+
/** Splits a multi-account read into 100-address calls; results follow `addresses` order. */
|
|
7
|
+
export async function fetchInChunks<T>(
|
|
8
|
+
addresses: readonly Address[],
|
|
9
|
+
read: (chunk: Address[]) => Promise<readonly T[]>,
|
|
10
|
+
): Promise<T[]> {
|
|
11
|
+
const chunks: Address[][] = [];
|
|
12
|
+
for (
|
|
13
|
+
let start = 0;
|
|
14
|
+
start < addresses.length;
|
|
15
|
+
start += MAX_ACCOUNTS_PER_REQUEST
|
|
16
|
+
) {
|
|
17
|
+
chunks.push(addresses.slice(start, start + MAX_ACCOUNTS_PER_REQUEST));
|
|
18
|
+
}
|
|
19
|
+
const results = await Promise.all(chunks.map((chunk) => read(chunk)));
|
|
20
|
+
return results.flat();
|
|
21
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { getAddressDecoder } from '@solana/kit';
|
|
2
|
+
import type { Address, ReadonlyUint8Array } from '@solana/kit';
|
|
3
|
+
|
|
4
|
+
const MAX_CREATOR_PLATFORM_LEN = 32;
|
|
5
|
+
|
|
6
|
+
/** SHA-256 of the LE-u32-length-prefixed platform and id. Frozen: it seeds live PDAs. */
|
|
7
|
+
export async function creatorHashFromId(
|
|
8
|
+
creatorPlatform: string,
|
|
9
|
+
creatorId: string,
|
|
10
|
+
): Promise<Address> {
|
|
11
|
+
const encoder = new TextEncoder();
|
|
12
|
+
const platformBuf = encoder.encode(creatorPlatform);
|
|
13
|
+
const idBuf = encoder.encode(creatorId);
|
|
14
|
+
|
|
15
|
+
if (platformBuf.length > MAX_CREATOR_PLATFORM_LEN) {
|
|
16
|
+
throw new RangeError(
|
|
17
|
+
`creatorPlatform exceeds ${MAX_CREATOR_PLATFORM_LEN} bytes (got ${platformBuf.length})`,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
const platformLength = new Uint8Array(4);
|
|
21
|
+
new DataView(platformLength.buffer).setUint32(0, platformBuf.length, true);
|
|
22
|
+
const idLength = new Uint8Array(4);
|
|
23
|
+
new DataView(idLength.buffer).setUint32(0, idBuf.length, true);
|
|
24
|
+
|
|
25
|
+
const buf = new Uint8Array(4 + platformBuf.length + 4 + idBuf.length);
|
|
26
|
+
buf.set(platformLength, 0);
|
|
27
|
+
buf.set(platformBuf, 4);
|
|
28
|
+
buf.set(idLength, 4 + platformBuf.length);
|
|
29
|
+
buf.set(idBuf, 4 + platformBuf.length + 4);
|
|
30
|
+
|
|
31
|
+
const hash = new Uint8Array(await crypto.subtle.digest('SHA-256', buf));
|
|
32
|
+
|
|
33
|
+
const addressDecoder = getAddressDecoder();
|
|
34
|
+
return addressDecoder.decode(hash);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** NUL-pads `text` to `length` bytes, the on-chain `CreatorFeeConfig.platformId` form; throws RangeError if longer. */
|
|
38
|
+
export function encodeCreatorId(text: string, length: number): Uint8Array {
|
|
39
|
+
const bytes = new TextEncoder().encode(text);
|
|
40
|
+
if (bytes.length > length) {
|
|
41
|
+
throw new RangeError(
|
|
42
|
+
`text exceeds ${length} bytes (got ${bytes.length})`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
const out = new Uint8Array(length);
|
|
46
|
+
out.set(bytes, 0);
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Strips NUL padding. Pass this, never the padded array, to {@link creatorHashFromId}: the hash is length-prefixed. */
|
|
51
|
+
export function decodeCreatorId(bytes: ReadonlyUint8Array): string {
|
|
52
|
+
let end = bytes.length;
|
|
53
|
+
while (end > 0 && bytes[end - 1] === 0) {
|
|
54
|
+
end -= 1;
|
|
55
|
+
}
|
|
56
|
+
return new TextDecoder().decode(bytes.slice(0, end));
|
|
57
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export {
|
|
2
|
+
creatorHashFromId,
|
|
3
|
+
decodeCreatorId,
|
|
4
|
+
encodeCreatorId,
|
|
5
|
+
} from './creator-hash.js';
|
|
6
|
+
export { fetchQuoteMintInfo, fetchQuoteMintInfos } from './mint-info.js';
|
|
7
|
+
export type { QuoteMintInfo } from './mint-info.js';
|
|
8
|
+
export type { PartnerInput } from './partner.js';
|
|
9
|
+
export { findAssociatedTokenPda } from './pda.js';
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Read off the mint, never tabulated: a stale `tokenProgram` derives an ATA the
|
|
2
|
+
// transfer cannot reach.
|
|
3
|
+
import {
|
|
4
|
+
getBase64Encoder,
|
|
5
|
+
type Address,
|
|
6
|
+
type Commitment,
|
|
7
|
+
type GetAccountInfoApi,
|
|
8
|
+
type GetMultipleAccountsApi,
|
|
9
|
+
type Rpc,
|
|
10
|
+
} from '@solana/kit';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
TOKEN_2022_PROGRAM_ADDRESS,
|
|
14
|
+
TOKEN_PROGRAM_ADDRESS,
|
|
15
|
+
} from '../constants.js';
|
|
16
|
+
import { fetchInChunks } from './chunk.js';
|
|
17
|
+
|
|
18
|
+
// Both token programs share the base mint: 36-byte `COption<Pubkey>` authority,
|
|
19
|
+
// u64 supply, then decimals.
|
|
20
|
+
const MINT_BASE_LENGTH = 82;
|
|
21
|
+
const MINT_DECIMALS_OFFSET = 44;
|
|
22
|
+
|
|
23
|
+
export interface QuoteMintInfo {
|
|
24
|
+
mint: Address;
|
|
25
|
+
/** Immutable: safe to cache. */
|
|
26
|
+
decimals: number;
|
|
27
|
+
/** The account's owner: `TOKEN_PROGRAM_ADDRESS` or `TOKEN_2022_PROGRAM_ADDRESS`. */
|
|
28
|
+
tokenProgram: Address;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function decodeMintInfo(
|
|
32
|
+
mint: Address,
|
|
33
|
+
data: Uint8Array,
|
|
34
|
+
owner: Address,
|
|
35
|
+
): QuoteMintInfo {
|
|
36
|
+
if (
|
|
37
|
+
owner !== TOKEN_PROGRAM_ADDRESS &&
|
|
38
|
+
owner !== TOKEN_2022_PROGRAM_ADDRESS
|
|
39
|
+
) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`fetchQuoteMintInfo: ${mint} is owned by ${owner}, which is not a token program`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
// A floor, not an equality: Token-2022 mints with extensions run longer.
|
|
45
|
+
if (data.length < MINT_BASE_LENGTH) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`fetchQuoteMintInfo: ${mint} holds ${data.length} bytes, too short for a mint`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return { mint, decimals: data[MINT_DECIMALS_OFFSET], tokenProgram: owner };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Throws when the mint does not exist or is not owned by a token program. */
|
|
54
|
+
export async function fetchQuoteMintInfo(
|
|
55
|
+
rpc: Rpc<GetAccountInfoApi>,
|
|
56
|
+
mint: Address,
|
|
57
|
+
commitment?: Commitment,
|
|
58
|
+
): Promise<QuoteMintInfo> {
|
|
59
|
+
const { value } = await rpc
|
|
60
|
+
.getAccountInfo(
|
|
61
|
+
mint,
|
|
62
|
+
commitment === undefined
|
|
63
|
+
? { encoding: 'base64' }
|
|
64
|
+
: { commitment, encoding: 'base64' },
|
|
65
|
+
)
|
|
66
|
+
.send();
|
|
67
|
+
if (!value) {
|
|
68
|
+
throw new Error(`fetchQuoteMintInfo: no account at ${mint}`);
|
|
69
|
+
}
|
|
70
|
+
const data = new Uint8Array(getBase64Encoder().encode(value.data[0]));
|
|
71
|
+
return decodeMintInfo(mint, data, value.owner);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** One round trip per 100 mints; results follow `mints` order. */
|
|
75
|
+
export async function fetchQuoteMintInfos(
|
|
76
|
+
rpc: Rpc<GetMultipleAccountsApi>,
|
|
77
|
+
mints: readonly Address[],
|
|
78
|
+
commitment?: Commitment,
|
|
79
|
+
): Promise<QuoteMintInfo[]> {
|
|
80
|
+
if (mints.length === 0) return [];
|
|
81
|
+
const value = await fetchInChunks(mints, (chunk) =>
|
|
82
|
+
rpc
|
|
83
|
+
.getMultipleAccounts(
|
|
84
|
+
chunk,
|
|
85
|
+
commitment === undefined
|
|
86
|
+
? { encoding: 'base64' }
|
|
87
|
+
: { commitment, encoding: 'base64' },
|
|
88
|
+
)
|
|
89
|
+
.send()
|
|
90
|
+
.then((response) => response.value),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const base64Encoder = getBase64Encoder();
|
|
94
|
+
return mints.map((mint, index) => {
|
|
95
|
+
const account = value.at(index);
|
|
96
|
+
if (!account) {
|
|
97
|
+
throw new Error(`fetchQuoteMintInfo: no account at ${mint}`);
|
|
98
|
+
}
|
|
99
|
+
const data = new Uint8Array(base64Encoder.encode(account.data[0]));
|
|
100
|
+
return decodeMintInfo(mint, data, account.owner);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { TransactionSigner } from '@solana/kit';
|
|
2
|
+
import type { DEFAULT_PARTNER } from '../constants.js';
|
|
3
|
+
|
|
4
|
+
/** Non-default partners must sign; only `DEFAULT_PARTNER` is valid as a bare address. */
|
|
5
|
+
export type PartnerInput = TransactionSigner | typeof DEFAULT_PARTNER;
|
package/src/utils/pda.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { getAddressEncoder, getProgramDerivedAddress } from '@solana/kit';
|
|
2
|
+
import type { Address, ProgramDerivedAddress } from '@solana/kit';
|
|
3
|
+
import {
|
|
4
|
+
ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
|
|
5
|
+
TOKEN_PROGRAM_ADDRESS,
|
|
6
|
+
} from '../constants.js';
|
|
7
|
+
|
|
8
|
+
const addressEncoder = getAddressEncoder();
|
|
9
|
+
|
|
10
|
+
/** Seed order is [wallet, tokenProgram, mint], not the parameter order. */
|
|
11
|
+
export async function findAssociatedTokenPda(
|
|
12
|
+
wallet: Address,
|
|
13
|
+
mint: Address,
|
|
14
|
+
tokenProgram: Address = TOKEN_PROGRAM_ADDRESS,
|
|
15
|
+
): Promise<ProgramDerivedAddress> {
|
|
16
|
+
return await getProgramDerivedAddress({
|
|
17
|
+
programAddress: ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
|
|
18
|
+
seeds: [
|
|
19
|
+
addressEncoder.encode(wallet),
|
|
20
|
+
addressEncoder.encode(tokenProgram),
|
|
21
|
+
addressEncoder.encode(mint),
|
|
22
|
+
],
|
|
23
|
+
});
|
|
24
|
+
}
|