pangu-sdk 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 the Pangu team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,217 @@
1
+ # pangu-sdk
2
+
3
+ The TypeScript client for Pangu, the rules program that sits on a stock token's
4
+ first sale on Meteora's Dynamic Bonding Curve. It finds a sale's accounts, reads
5
+ the sale and its buyers, builds Pangu's instructions, and turns a failed
6
+ transaction into a sentence a buyer can read. It never signs and never sends.
7
+
8
+ Safe to import on a server and in a browser: no top level work, no DOM.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm install pangu-sdk @solana/web3.js
14
+ ```
15
+
16
+ ## Read a sale
17
+
18
+ ```ts
19
+ import { Connection, PublicKey } from "@solana/web3.js";
20
+ import { getSale, listBuyerRecords, saleStanding } from "pangu-sdk";
21
+
22
+ const connection = new Connection(process.env.RPC_URL!);
23
+ const mint = new PublicKey("...");
24
+
25
+ const sale = await getSale(connection, mint);
26
+ if (sale !== null) {
27
+ const standing = saleStanding(sale, await listBuyerRecords(connection, mint));
28
+ console.log(sale.cap, standing.buyers, standing.largestShare);
29
+ }
30
+ ```
31
+
32
+ ## List every sale
33
+
34
+ ```ts
35
+ import { saleDirectory } from "pangu-sdk";
36
+
37
+ for (const entry of await saleDirectory(connection)) {
38
+ console.log(entry.symbol, entry.name, entry.running, entry.graduated, entry.offeringOver);
39
+ }
40
+ ```
41
+
42
+ No file of addresses needed: one scan finds every sale's rules, then the mints,
43
+ the pools and the chain's clock are read in calls of 100, so fifty sales cost
44
+ three calls. Names and symbols come from the metadata DBC writes on the mint;
45
+ they are whatever the issuer typed, so the mint is what identifies a sale.
46
+ `graduated` is null when the pool cannot be read as the one selling the mint. A
47
+ rules account from a layout this package does not read is left out rather than
48
+ thrown; pass `onSkipped` to hear about each one. `listSales` and
49
+ `saleTokenInfo` are the two halves on their own.
50
+
51
+ ## Open a sale
52
+
53
+ ```ts
54
+ import { createSaleInstruction, ACCESS_MODE } from "pangu-sdk";
55
+
56
+ const instruction = createSaleInstruction({
57
+ issuer: wallet.publicKey,
58
+ pool,
59
+ mint,
60
+ cap: 100_000_000n,
61
+ accessMode: ACCESS_MODE.issuerList,
62
+ dbcConfig,
63
+ quoteMint,
64
+ endsAt: Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60,
65
+ });
66
+ ```
67
+
68
+ `dbcConfig` is the launch template the pool was opened on and `quoteMint` is the
69
+ paying token it names. Both are needed on every sale: the program stores the
70
+ paying token and refuses one the issuer can freeze. `endsAt` is the end of the
71
+ offering period in unix seconds, after which every rule lifts; leave it out for
72
+ no end, and the rules hold until graduation. The cap must stay below the
73
+ curve's supply, since a cap covering the whole sale is no cap at all.
74
+
75
+ The builder refuses anything the program would refuse, before it builds. Pass a
76
+ `band` to add the price ceiling against the real stock price:
77
+
78
+ ```ts
79
+ band: {
80
+ bps: 1_000,
81
+ priceFeedId: "49f6b65c...5688",
82
+ maxPriceAgeSecs: 60,
83
+ maxConfBps: 100,
84
+ }
85
+ ```
86
+
87
+ The Pyth price account is derived for you from the feed id and the shard, which
88
+ defaults to Pangu's own, so there is no address to get wrong.
89
+
90
+ ## Verify buyers
91
+
92
+ A sale in credential mode sells only to wallets a verifier has attested on the
93
+ Solana Attestation Service. These four are everything a verifier does; the app
94
+ runs them at `/verify`.
95
+
96
+ ### Set up
97
+
98
+ ```ts
99
+ const credential = credentialAddress(verifier, "Acme KYC");
100
+ const schema = schemaAddress(credential, "pangu-buyer");
101
+ tx.add(createCredentialInstruction({ payer: verifier, authority: verifier, name: "Acme KYC", signers: [verifier] }));
102
+ tx.add(createSchemaInstruction({ payer: verifier, authority: verifier, credential, name: "pangu-buyer", description: "checked buyers" }));
103
+ ```
104
+
105
+ ### Issue
106
+
107
+ ```ts
108
+ tx.add(createAttestationInstruction({ payer: verifier, authorizedSigner: verifier, credential, schema, wallet: buyer, expiry }));
109
+ ```
110
+
111
+ `expiry` is unix seconds, or zero for never. The buyer's wallet is the nonce,
112
+ which is the one address Pangu's hook looks at.
113
+
114
+ ### Check
115
+
116
+ ```ts
117
+ const standing = await credentialStatus(connection, credential, schema, buyer); // "valid" | "expired" | "absent"
118
+ const issued = await listAttestations(connection, credential, schema); // wallet, expiry, created, standing
119
+ ```
120
+
121
+ ### Revoke
122
+
123
+ ```ts
124
+ tx.add(closeAttestationInstruction({ payer: verifier, authorizedSigner: verifier, credential, schema, wallet: buyer }));
125
+ ```
126
+
127
+ Closing is the service's only revocation. The deposit returns to the payer and
128
+ the wallet's next buy is refused with `CredentialInvalid`.
129
+
130
+ ## Run a sale on Meteora
131
+
132
+ `pangu-sdk/dbc` is the second entry point. It pulls in Meteora's Dynamic Bonding
133
+ Curve SDK, so a page that only reads a sale never downloads it. Every function
134
+ returns an unsigned transaction and its size in bytes.
135
+
136
+ ```ts
137
+ import {
138
+ launchTemplateTransaction,
139
+ openSaleTransaction,
140
+ buyTransaction,
141
+ preflightBuy,
142
+ claimFeesTransaction,
143
+ graduateTransaction,
144
+ saleProgress,
145
+ } from "pangu-sdk/dbc";
146
+
147
+ const { transaction, config } = await launchTemplateTransaction({
148
+ connection,
149
+ partner: wallet.publicKey,
150
+ quoteMint,
151
+ curve,
152
+ });
153
+
154
+ const sale = await openSaleTransaction({
155
+ connection,
156
+ creator: wallet.publicKey,
157
+ config: config.publicKey,
158
+ name: "Acme Shares",
159
+ symbol: "ACME",
160
+ uri,
161
+ sale: { capShareBps: 1_000, accessMode: 1, endsAt },
162
+ });
163
+ ```
164
+
165
+ The template forces the settings a Pangu sale depends on and throws if you try
166
+ to set them otherwise: Token-2022, Pangu as the hook, graduation to DAMM v2,
167
+ fees collected in the paying token only, and a token authority option that
168
+ leaves nobody able to mint more. The pool and the sale's rules land in one
169
+ transaction, so nobody else can set the rules for your mint.
170
+
171
+ `preflightBuy` reads the chain and tells a buyer what would happen before they
172
+ sign: no record, not approved, an approval that is missing, expired or signed by
173
+ a key the verifier has dropped, over the cap with the room left, price above the
174
+ ceiling, price too uncertain, or price stale. A stale price is also what a shut
175
+ stock market looks like: Pyth stops publishing an equity outside its trading
176
+ sessions, so the account stops moving and ages out.
177
+
178
+ ### Buy and sell
179
+
180
+ ```ts
181
+ const buy = await buyTransaction({ connection, buyer, mint, amountIn, minimumAmountOut });
182
+ const sell = await sellTransaction({ connection, seller, mint, amountIn, minimumQuoteOut });
183
+ ```
184
+
185
+ The floor is the least the trade may return before the chain refuses it. Set
186
+ it from the quote the person was shown, less the slippage you told them, so the
187
+ floor is what they agreed to. Leave it out and the builder takes 1 percent under
188
+ a fresh quote of its own (`slippageBps` changes the 1 percent), which can sit
189
+ below what a page showed a few seconds earlier. With a floor set, the builder
190
+ reads the market again and throws `PanguInputError` ("the market moved") when
191
+ the trade already returns less, so no wallet is asked to sign a trade that
192
+ would fail.
193
+
194
+ ## Keep the price fresh
195
+
196
+ `pangu-sdk/price` is the third entry point, for a sale with a price band. It is
197
+ Node and server only, so call it from a server route.
198
+
199
+ ```ts
200
+ import { refreshPriceTransaction } from "pangu-sdk/price";
201
+
202
+ const { transactions, priceAccount } = await refreshPriceTransaction({
203
+ connection,
204
+ payer: serverWallet.publicKey,
205
+ sale,
206
+ });
207
+ ```
208
+
209
+ It reads Pyth's Hermes service, which has needed an API key since 26 August
210
+ 2026. The key is taken from `PYTH_API_KEY` in the server's environment and is
211
+ never a value the browser holds or the package prints. A refresh is two
212
+ transactions, not one: the guardian-signed update goes into a holding account
213
+ first and the price account is written from it second, and the pair does not fit
214
+ in one transaction. Sign and send them in the order given, then send the buy.
215
+
216
+ Reading the price back is `readPrice` in the core entry point. It decodes the
217
+ price account itself, so it is safe in a browser and needs no Pyth package.
@@ -0,0 +1,206 @@
1
+ import { PublicKey, Connection } from '@solana/web3.js';
2
+
3
+ /** A Pyth feed id, either 32 raw bytes or the same bytes written as hex. */
4
+ type FeedId = string | Uint8Array | number[];
5
+ /**
6
+ * The one place a feed id is turned into bytes.
7
+ *
8
+ * Everything that derives an address or encodes an instruction goes through
9
+ * this, so a hex string and a byte array can never be compared after two
10
+ * different readings. A leading "0x" is accepted because that is how the feed
11
+ * scripts print ids. Anything that is not exactly 32 bytes is refused.
12
+ */
13
+ declare function feedIdBytes(id: FeedId): Uint8Array;
14
+ /** The same id as lowercase hex with no prefix, which is how the feed scripts print it. */
15
+ declare function feedIdHex(id: FeedId): string;
16
+ /** The rules account of one sale. Seeds "sale" and the mint. */
17
+ declare function saleRulesAddress(mint: PublicKey): PublicKey;
18
+ /** One wallet's record in one sale. Seeds "buyer", the mint and the wallet. */
19
+ declare function buyerRecordAddress(mint: PublicKey, wallet: PublicKey): PublicKey;
20
+ /** The transfer hook's published account list. Seeds fixed by the SPL interface. */
21
+ declare function extraAccountListAddress(mint: PublicKey): PublicKey;
22
+ /**
23
+ * The one address an attestation for this credential, schema and wallet can have.
24
+ * Derived under the attestation service, not under Pangu.
25
+ */
26
+ declare function attestationAddress(credential: PublicKey, schema: PublicKey, wallet: PublicKey): PublicKey;
27
+ /** The pool's base token vault. Derived under DBC, which is what owns it. */
28
+ declare function dbcBaseVaultAddress(mint: PublicKey, pool: PublicKey): PublicKey;
29
+ /**
30
+ * The one price feed account a Pyth shard and feed id can produce.
31
+ *
32
+ * The seeds are the shard id as two little endian bytes and then the 32 byte
33
+ * feed id, under Pyth's price feed program. The payer is not a seed, so the
34
+ * address is fixed before anybody has refreshed it and nobody can create a
35
+ * rival account for the same shard and feed. This is the same derivation
36
+ * `price_feed_address` runs in programs/pangu/src/price.rs, and the same one
37
+ * `getPriceFeedAccountForProgram` runs in `@pythnetwork/pyth-solana-receiver`.
38
+ *
39
+ * The shard defaults to Pangu's own, which is the shard Pangu's refresher
40
+ * writes. Throws PanguInputError for a shard outside the two bytes it is
41
+ * written into, or a feed id that is not 32 bytes.
42
+ */
43
+ declare function priceFeedAddress(feedId: FeedId, shard?: number): PublicKey;
44
+
45
+ /**
46
+ * Thrown before anything is built when an input could never pass on chain.
47
+ *
48
+ * Every message names the rule and the value, because the caller is usually a
49
+ * form in the app and the text goes straight to the person filling it in.
50
+ */
51
+ declare class PanguInputError extends Error {
52
+ constructor(message: string);
53
+ }
54
+
55
+ /**
56
+ * Thrown when a SaleRules account was not written by the layout this package
57
+ * reads: the wrong length, or a layout version it does not know.
58
+ *
59
+ * Both are the same failure seen from two sides. Anchor's decoder reads every
60
+ * field at a fixed offset and does not care what wrote the bytes, so an account
61
+ * from another build comes back as a sale with a nonsense cap or a nonsense
62
+ * band rather than as an error. It is a PanguInputError, so a caller that
63
+ * already handles those keeps working.
64
+ */
65
+ declare class PanguLayoutError extends PanguInputError {
66
+ constructor(message: string);
67
+ }
68
+ /** One sale's rules, as the chain holds them. Written once, never updated. */
69
+ interface Sale {
70
+ mint: PublicKey;
71
+ pool: PublicKey;
72
+ baseVault: PublicKey;
73
+ issuer: PublicKey;
74
+ /** Raw token units, so a six decimal token's cap of 100 reads as 100000000n. */
75
+ cap: bigint;
76
+ accessMode: number;
77
+ credential: PublicKey;
78
+ schema: PublicKey;
79
+ /** Band only: the Pyth price feed account the hook reads. */
80
+ priceAccount: PublicKey;
81
+ bandBps: number;
82
+ /** Lowercase hex, no prefix, the way the feed scripts print an id. */
83
+ priceFeedId: string;
84
+ /** The Pyth shard the price account was derived under. */
85
+ priceShard: number;
86
+ /** How old the published price may be on a buy, in seconds. */
87
+ maxPriceAgeSecs: number;
88
+ /** The widest confidence interval this sale buys against, in basis points. */
89
+ maxConfBps: number;
90
+ /**
91
+ * Decimals of the sale token. The program only stores these on a sale with a
92
+ * price band, so the chain holds zero for every other sale. `getSale` fills
93
+ * that in from the mint itself; `decodeSale`, which only has the bytes in
94
+ * front of it, hands back the zero the account really holds.
95
+ */
96
+ baseDecimals: number;
97
+ /** Decimals of the paying token, stored only on a sale with a price band. */
98
+ quoteDecimals: number;
99
+ buyers: number;
100
+ totalNetBought: bigint;
101
+ bump: number;
102
+ /** Which layout wrote this account: 1 or 2. */
103
+ layoutVersion: number;
104
+ /**
105
+ * The token buyers pay in, stored by layout 2 onwards. Null on a version 1
106
+ * sale, which never recorded it; its launch template still names it.
107
+ */
108
+ quoteMint: PublicKey | null;
109
+ /**
110
+ * Unix seconds at which the offering period ends and every rule lifts. Null
111
+ * when the sale has no end, which includes every version 1 sale.
112
+ */
113
+ endsAt: number | null;
114
+ /** Spare bytes the program keeps so the account can grow later. */
115
+ reserved: Uint8Array;
116
+ /** True when this sale has a price band, matching SaleRules::has_band. */
117
+ hasBand: boolean;
118
+ }
119
+ /** One wallet's standing in one sale. */
120
+ interface BuyerRecord {
121
+ mint: PublicKey;
122
+ wallet: PublicKey;
123
+ approved: boolean;
124
+ /** Tokens received from the pool minus tokens sold back, in raw units. */
125
+ netBought: bigint;
126
+ bump: number;
127
+ }
128
+ /**
129
+ * Reads a SaleRules account's bytes.
130
+ *
131
+ * The discriminator says these are a SaleRules, then the length and the layout
132
+ * version say which build wrote them. Versions 1 and 2 are read; on version 1
133
+ * the paying token and the end of the offering come back as null, because
134
+ * those bytes were spare zeros then. Anything else throws a `PanguLayoutError`,
135
+ * because an account from another build sits at the same address behind the
136
+ * same discriminator: Anchor reads it without complaint and hands back fields
137
+ * taken from the wrong offsets.
138
+ *
139
+ * Throws `PanguInputError` when the bytes are not a SaleRules at all.
140
+ */
141
+ declare function decodeSale(data: Uint8Array): Sale;
142
+ /** Reads a BuyerRecord account's bytes. Throws when they are not a BuyerRecord. */
143
+ declare function decodeBuyerRecord(data: Uint8Array): BuyerRecord;
144
+ /**
145
+ * The rules of the sale for this mint, or null when no sale was ever opened.
146
+ *
147
+ * An account sitting at the rules address that Pangu does not own is refused
148
+ * rather than decoded, because at that point the reader cannot tell what the
149
+ * bytes mean.
150
+ *
151
+ * A sale with no price band stores no decimals, because the hook never needs
152
+ * them, so one more read fills `baseDecimals` from the mint. That keeps every
153
+ * caller turning raw units into an amount a person reads off one field instead
154
+ * of each one remembering the exception. A mint that cannot be read leaves the
155
+ * zero in place.
156
+ */
157
+ declare function getSale(connection: Connection, mint: PublicKey): Promise<Sale | null>;
158
+ /** One wallet's record in this sale, or null when the wallet has none yet. */
159
+ declare function getBuyerRecord(connection: Connection, mint: PublicKey, wallet: PublicKey): Promise<BuyerRecord | null>;
160
+ /**
161
+ * Every buyer record of one sale.
162
+ *
163
+ * The filter is the record discriminator followed by the sale's mint, which is
164
+ * the record's first field, so the node only returns this sale's records. The
165
+ * caller pays for one scan, and an RPC that refuses getProgramAccounts will
166
+ * throw rather than return a short list.
167
+ */
168
+ declare function listBuyerRecords(connection: Connection, mint: PublicKey): Promise<BuyerRecord[]>;
169
+ /** What `listSales` can be told, beyond the connection. */
170
+ interface ListSalesOptions {
171
+ /**
172
+ * Called once for every rules account left out of the list, with its address
173
+ * and the reason in words. Count the calls to know how many were skipped.
174
+ */
175
+ onSkipped?: (address: PublicKey, reason: string) => void;
176
+ }
177
+ /**
178
+ * Every sale the Pangu program holds rules for, in no particular order.
179
+ *
180
+ * One scan, filtered by the node on the SaleRules discriminator and on the size
181
+ * this build writes, so buyer records and the larger accounts an earlier build
182
+ * left behind never come back. An account of the right size that this package
183
+ * cannot read, a layout version it does not know, or one that does not sit at
184
+ * the rules address of the mint it names, is skipped and reported through
185
+ * `onSkipped` rather than thrown, so one stray account cannot hide every other
186
+ * sale. Each sale comes back exactly as `decodeSale` reads it, so a sale with no
187
+ * price band still shows zero decimals here; `getSale` or `saleDirectory` fill
188
+ * them from the mint.
189
+ *
190
+ * Throws when the node refuses the scan, because a short list would look like
191
+ * a complete one.
192
+ */
193
+ declare function listSales(connection: Connection, options?: ListSalesOptions): Promise<Sale[]>;
194
+ /**
195
+ * Whether the sale is still running, read off the token itself.
196
+ *
197
+ * The rules live on while the mint names Pangu as its transfer hook. DBC clears
198
+ * that name in the trade that completes the curve, and from then on the token
199
+ * moves freely and any buyer can close their record. This is not a gate on
200
+ * closing: a record holding nothing closes while the sale is still running. A
201
+ * mint that does not exist, is not a Token-2022 mint, or names another hook is
202
+ * not a running Pangu sale.
203
+ */
204
+ declare function isSaleRunning(connection: Connection, mint: PublicKey): Promise<boolean>;
205
+
206
+ export { type BuyerRecord as B, type FeedId as F, type ListSalesOptions as L, PanguInputError as P, type Sale as S, PanguLayoutError as a, attestationAddress as b, buyerRecordAddress as c, dbcBaseVaultAddress as d, decodeBuyerRecord as e, decodeSale as f, extraAccountListAddress as g, feedIdBytes as h, feedIdHex as i, getBuyerRecord as j, getSale as k, isSaleRunning as l, listBuyerRecords as m, listSales as n, priceFeedAddress as p, saleRulesAddress as s };
@@ -0,0 +1,206 @@
1
+ import { PublicKey, Connection } from '@solana/web3.js';
2
+
3
+ /** A Pyth feed id, either 32 raw bytes or the same bytes written as hex. */
4
+ type FeedId = string | Uint8Array | number[];
5
+ /**
6
+ * The one place a feed id is turned into bytes.
7
+ *
8
+ * Everything that derives an address or encodes an instruction goes through
9
+ * this, so a hex string and a byte array can never be compared after two
10
+ * different readings. A leading "0x" is accepted because that is how the feed
11
+ * scripts print ids. Anything that is not exactly 32 bytes is refused.
12
+ */
13
+ declare function feedIdBytes(id: FeedId): Uint8Array;
14
+ /** The same id as lowercase hex with no prefix, which is how the feed scripts print it. */
15
+ declare function feedIdHex(id: FeedId): string;
16
+ /** The rules account of one sale. Seeds "sale" and the mint. */
17
+ declare function saleRulesAddress(mint: PublicKey): PublicKey;
18
+ /** One wallet's record in one sale. Seeds "buyer", the mint and the wallet. */
19
+ declare function buyerRecordAddress(mint: PublicKey, wallet: PublicKey): PublicKey;
20
+ /** The transfer hook's published account list. Seeds fixed by the SPL interface. */
21
+ declare function extraAccountListAddress(mint: PublicKey): PublicKey;
22
+ /**
23
+ * The one address an attestation for this credential, schema and wallet can have.
24
+ * Derived under the attestation service, not under Pangu.
25
+ */
26
+ declare function attestationAddress(credential: PublicKey, schema: PublicKey, wallet: PublicKey): PublicKey;
27
+ /** The pool's base token vault. Derived under DBC, which is what owns it. */
28
+ declare function dbcBaseVaultAddress(mint: PublicKey, pool: PublicKey): PublicKey;
29
+ /**
30
+ * The one price feed account a Pyth shard and feed id can produce.
31
+ *
32
+ * The seeds are the shard id as two little endian bytes and then the 32 byte
33
+ * feed id, under Pyth's price feed program. The payer is not a seed, so the
34
+ * address is fixed before anybody has refreshed it and nobody can create a
35
+ * rival account for the same shard and feed. This is the same derivation
36
+ * `price_feed_address` runs in programs/pangu/src/price.rs, and the same one
37
+ * `getPriceFeedAccountForProgram` runs in `@pythnetwork/pyth-solana-receiver`.
38
+ *
39
+ * The shard defaults to Pangu's own, which is the shard Pangu's refresher
40
+ * writes. Throws PanguInputError for a shard outside the two bytes it is
41
+ * written into, or a feed id that is not 32 bytes.
42
+ */
43
+ declare function priceFeedAddress(feedId: FeedId, shard?: number): PublicKey;
44
+
45
+ /**
46
+ * Thrown before anything is built when an input could never pass on chain.
47
+ *
48
+ * Every message names the rule and the value, because the caller is usually a
49
+ * form in the app and the text goes straight to the person filling it in.
50
+ */
51
+ declare class PanguInputError extends Error {
52
+ constructor(message: string);
53
+ }
54
+
55
+ /**
56
+ * Thrown when a SaleRules account was not written by the layout this package
57
+ * reads: the wrong length, or a layout version it does not know.
58
+ *
59
+ * Both are the same failure seen from two sides. Anchor's decoder reads every
60
+ * field at a fixed offset and does not care what wrote the bytes, so an account
61
+ * from another build comes back as a sale with a nonsense cap or a nonsense
62
+ * band rather than as an error. It is a PanguInputError, so a caller that
63
+ * already handles those keeps working.
64
+ */
65
+ declare class PanguLayoutError extends PanguInputError {
66
+ constructor(message: string);
67
+ }
68
+ /** One sale's rules, as the chain holds them. Written once, never updated. */
69
+ interface Sale {
70
+ mint: PublicKey;
71
+ pool: PublicKey;
72
+ baseVault: PublicKey;
73
+ issuer: PublicKey;
74
+ /** Raw token units, so a six decimal token's cap of 100 reads as 100000000n. */
75
+ cap: bigint;
76
+ accessMode: number;
77
+ credential: PublicKey;
78
+ schema: PublicKey;
79
+ /** Band only: the Pyth price feed account the hook reads. */
80
+ priceAccount: PublicKey;
81
+ bandBps: number;
82
+ /** Lowercase hex, no prefix, the way the feed scripts print an id. */
83
+ priceFeedId: string;
84
+ /** The Pyth shard the price account was derived under. */
85
+ priceShard: number;
86
+ /** How old the published price may be on a buy, in seconds. */
87
+ maxPriceAgeSecs: number;
88
+ /** The widest confidence interval this sale buys against, in basis points. */
89
+ maxConfBps: number;
90
+ /**
91
+ * Decimals of the sale token. The program only stores these on a sale with a
92
+ * price band, so the chain holds zero for every other sale. `getSale` fills
93
+ * that in from the mint itself; `decodeSale`, which only has the bytes in
94
+ * front of it, hands back the zero the account really holds.
95
+ */
96
+ baseDecimals: number;
97
+ /** Decimals of the paying token, stored only on a sale with a price band. */
98
+ quoteDecimals: number;
99
+ buyers: number;
100
+ totalNetBought: bigint;
101
+ bump: number;
102
+ /** Which layout wrote this account: 1 or 2. */
103
+ layoutVersion: number;
104
+ /**
105
+ * The token buyers pay in, stored by layout 2 onwards. Null on a version 1
106
+ * sale, which never recorded it; its launch template still names it.
107
+ */
108
+ quoteMint: PublicKey | null;
109
+ /**
110
+ * Unix seconds at which the offering period ends and every rule lifts. Null
111
+ * when the sale has no end, which includes every version 1 sale.
112
+ */
113
+ endsAt: number | null;
114
+ /** Spare bytes the program keeps so the account can grow later. */
115
+ reserved: Uint8Array;
116
+ /** True when this sale has a price band, matching SaleRules::has_band. */
117
+ hasBand: boolean;
118
+ }
119
+ /** One wallet's standing in one sale. */
120
+ interface BuyerRecord {
121
+ mint: PublicKey;
122
+ wallet: PublicKey;
123
+ approved: boolean;
124
+ /** Tokens received from the pool minus tokens sold back, in raw units. */
125
+ netBought: bigint;
126
+ bump: number;
127
+ }
128
+ /**
129
+ * Reads a SaleRules account's bytes.
130
+ *
131
+ * The discriminator says these are a SaleRules, then the length and the layout
132
+ * version say which build wrote them. Versions 1 and 2 are read; on version 1
133
+ * the paying token and the end of the offering come back as null, because
134
+ * those bytes were spare zeros then. Anything else throws a `PanguLayoutError`,
135
+ * because an account from another build sits at the same address behind the
136
+ * same discriminator: Anchor reads it without complaint and hands back fields
137
+ * taken from the wrong offsets.
138
+ *
139
+ * Throws `PanguInputError` when the bytes are not a SaleRules at all.
140
+ */
141
+ declare function decodeSale(data: Uint8Array): Sale;
142
+ /** Reads a BuyerRecord account's bytes. Throws when they are not a BuyerRecord. */
143
+ declare function decodeBuyerRecord(data: Uint8Array): BuyerRecord;
144
+ /**
145
+ * The rules of the sale for this mint, or null when no sale was ever opened.
146
+ *
147
+ * An account sitting at the rules address that Pangu does not own is refused
148
+ * rather than decoded, because at that point the reader cannot tell what the
149
+ * bytes mean.
150
+ *
151
+ * A sale with no price band stores no decimals, because the hook never needs
152
+ * them, so one more read fills `baseDecimals` from the mint. That keeps every
153
+ * caller turning raw units into an amount a person reads off one field instead
154
+ * of each one remembering the exception. A mint that cannot be read leaves the
155
+ * zero in place.
156
+ */
157
+ declare function getSale(connection: Connection, mint: PublicKey): Promise<Sale | null>;
158
+ /** One wallet's record in this sale, or null when the wallet has none yet. */
159
+ declare function getBuyerRecord(connection: Connection, mint: PublicKey, wallet: PublicKey): Promise<BuyerRecord | null>;
160
+ /**
161
+ * Every buyer record of one sale.
162
+ *
163
+ * The filter is the record discriminator followed by the sale's mint, which is
164
+ * the record's first field, so the node only returns this sale's records. The
165
+ * caller pays for one scan, and an RPC that refuses getProgramAccounts will
166
+ * throw rather than return a short list.
167
+ */
168
+ declare function listBuyerRecords(connection: Connection, mint: PublicKey): Promise<BuyerRecord[]>;
169
+ /** What `listSales` can be told, beyond the connection. */
170
+ interface ListSalesOptions {
171
+ /**
172
+ * Called once for every rules account left out of the list, with its address
173
+ * and the reason in words. Count the calls to know how many were skipped.
174
+ */
175
+ onSkipped?: (address: PublicKey, reason: string) => void;
176
+ }
177
+ /**
178
+ * Every sale the Pangu program holds rules for, in no particular order.
179
+ *
180
+ * One scan, filtered by the node on the SaleRules discriminator and on the size
181
+ * this build writes, so buyer records and the larger accounts an earlier build
182
+ * left behind never come back. An account of the right size that this package
183
+ * cannot read, a layout version it does not know, or one that does not sit at
184
+ * the rules address of the mint it names, is skipped and reported through
185
+ * `onSkipped` rather than thrown, so one stray account cannot hide every other
186
+ * sale. Each sale comes back exactly as `decodeSale` reads it, so a sale with no
187
+ * price band still shows zero decimals here; `getSale` or `saleDirectory` fill
188
+ * them from the mint.
189
+ *
190
+ * Throws when the node refuses the scan, because a short list would look like
191
+ * a complete one.
192
+ */
193
+ declare function listSales(connection: Connection, options?: ListSalesOptions): Promise<Sale[]>;
194
+ /**
195
+ * Whether the sale is still running, read off the token itself.
196
+ *
197
+ * The rules live on while the mint names Pangu as its transfer hook. DBC clears
198
+ * that name in the trade that completes the curve, and from then on the token
199
+ * moves freely and any buyer can close their record. This is not a gate on
200
+ * closing: a record holding nothing closes while the sale is still running. A
201
+ * mint that does not exist, is not a Token-2022 mint, or names another hook is
202
+ * not a running Pangu sale.
203
+ */
204
+ declare function isSaleRunning(connection: Connection, mint: PublicKey): Promise<boolean>;
205
+
206
+ export { type BuyerRecord as B, type FeedId as F, type ListSalesOptions as L, PanguInputError as P, type Sale as S, PanguLayoutError as a, attestationAddress as b, buyerRecordAddress as c, dbcBaseVaultAddress as d, decodeBuyerRecord as e, decodeSale as f, extraAccountListAddress as g, feedIdBytes as h, feedIdHex as i, getBuyerRecord as j, getSale as k, isSaleRunning as l, listBuyerRecords as m, listSales as n, priceFeedAddress as p, saleRulesAddress as s };