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 +21 -0
- package/README.md +217 -0
- package/dist/accounts-BuD-oCcr.d.mts +206 -0
- package/dist/accounts-BuD-oCcr.d.ts +206 -0
- package/dist/dbc.d.mts +505 -0
- package/dist/dbc.d.ts +505 -0
- package/dist/dbc.js +3003 -0
- package/dist/dbc.js.map +1 -0
- package/dist/dbc.mjs +3027 -0
- package/dist/dbc.mjs.map +1 -0
- package/dist/feed-CY-pcUU2.d.mts +269 -0
- package/dist/feed-Zwu1irEx.d.ts +269 -0
- package/dist/index.d.mts +415 -0
- package/dist/index.d.ts +415 -0
- package/dist/index.js +2809 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2803 -0
- package/dist/index.mjs.map +1 -0
- package/dist/price.d.mts +86 -0
- package/dist/price.d.ts +86 -0
- package/dist/price.js +1554 -0
- package/dist/price.js.map +1 -0
- package/dist/price.mjs +1533 -0
- package/dist/price.mjs.map +1 -0
- package/package.json +104 -0
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 };
|