@ember-finance/sdk 2.38.0 → 2.40.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.
Files changed (32) hide show
  1. package/dist/src/common/constants.d.ts +1 -1
  2. package/dist/src/ember-vaults.d.ts +85 -5
  3. package/dist/src/ember-vaults.js +144 -0
  4. package/dist/src/index.d.ts +1 -0
  5. package/dist/src/index.js +1 -0
  6. package/dist/src/solana-vaults/index.d.ts +4 -0
  7. package/dist/src/solana-vaults/index.js +4 -0
  8. package/dist/src/solana-vaults/interfaces/index.d.ts +260 -0
  9. package/dist/src/solana-vaults/interfaces/index.js +37 -0
  10. package/dist/src/solana-vaults/on-chain-calls/index.d.ts +3 -0
  11. package/dist/src/solana-vaults/on-chain-calls/index.js +3 -0
  12. package/dist/src/solana-vaults/on-chain-calls/tx-builder.d.ts +62 -0
  13. package/dist/src/solana-vaults/on-chain-calls/tx-builder.js +190 -0
  14. package/dist/src/solana-vaults/on-chain-calls/user.d.ts +150 -0
  15. package/dist/src/solana-vaults/on-chain-calls/user.js +288 -0
  16. package/dist/src/solana-vaults/on-chain-calls/vault-reader.d.ts +190 -0
  17. package/dist/src/solana-vaults/on-chain-calls/vault-reader.js +432 -0
  18. package/dist/src/solana-vaults/signers/index.d.ts +1 -0
  19. package/dist/src/solana-vaults/signers/index.js +1 -0
  20. package/dist/src/solana-vaults/signers/solana-signer.d.ts +44 -0
  21. package/dist/src/solana-vaults/signers/solana-signer.js +61 -0
  22. package/dist/src/solana-vaults/utils/borsh.d.ts +65 -0
  23. package/dist/src/solana-vaults/utils/borsh.js +237 -0
  24. package/dist/src/solana-vaults/utils/deployment-parser.d.ts +94 -0
  25. package/dist/src/solana-vaults/utils/deployment-parser.js +174 -0
  26. package/dist/src/solana-vaults/utils/index.d.ts +3 -0
  27. package/dist/src/solana-vaults/utils/index.js +3 -0
  28. package/dist/src/solana-vaults/utils/math.d.ts +77 -0
  29. package/dist/src/solana-vaults/utils/math.js +111 -0
  30. package/dist/src/solana-vaults/utils/pdas.d.ts +63 -0
  31. package/dist/src/solana-vaults/utils/pdas.js +114 -0
  32. package/package.json +10 -2
@@ -0,0 +1,237 @@
1
+ import { PublicKey } from "@solana/web3.js";
2
+ import { accountDiscriminator } from "./pdas.js";
3
+ /**
4
+ * Borsh decoding for `ember_vaults` accounts.
5
+ *
6
+ * Decoded by hand rather than through Anchor's client. The program's IDL is
7
+ * not published on chain — `anchor idl init` cannot get an IDL this size past
8
+ * the public RPCs — so a client cannot fetch it, and pulling in the whole
9
+ * Anchor runtime to read a few dozen fields is not worth the dependency.
10
+ *
11
+ * Field order here must track `state.rs`. The discriminator check catches a
12
+ * wrong account type; it cannot catch a field reordered within the right one,
13
+ * so `SolanaAccountDecoding.test.ts` pins the offsets against fixtures taken
14
+ * from a real deployment.
15
+ */
16
+ /** A forward-only reader over account data. */
17
+ export class Cursor {
18
+ constructor(data, offset = 0) {
19
+ this.data = data;
20
+ this.offset = offset;
21
+ }
22
+ skip(n) {
23
+ this.offset += n;
24
+ return this;
25
+ }
26
+ u8() {
27
+ return this.data.readUInt8(this.offset++);
28
+ }
29
+ bool() {
30
+ // Borsh admits only 0 or 1. Anything else means the cursor has drifted
31
+ // out of alignment, and reading it as false would hide that.
32
+ const b = this.u8();
33
+ if (b > 1)
34
+ throw new Error(`invalid bool byte ${b}: the layout is misaligned`);
35
+ return b === 1;
36
+ }
37
+ u64() {
38
+ const v = this.data.readBigUInt64LE(this.offset);
39
+ this.offset += 8;
40
+ return v;
41
+ }
42
+ u128() {
43
+ const lo = this.data.readBigUInt64LE(this.offset);
44
+ const hi = this.data.readBigUInt64LE(this.offset + 8);
45
+ this.offset += 16;
46
+ return (hi << 64n) | lo;
47
+ }
48
+ pubkey() {
49
+ const k = new PublicKey(this.data.subarray(this.offset, this.offset + 32));
50
+ this.offset += 32;
51
+ return k;
52
+ }
53
+ string() {
54
+ const len = this.data.readUInt32LE(this.offset);
55
+ const s = this.data
56
+ .subarray(this.offset + 4, this.offset + 4 + len)
57
+ .toString("utf8");
58
+ this.offset += 4 + len;
59
+ return s;
60
+ }
61
+ vec(item) {
62
+ const len = this.data.readUInt32LE(this.offset);
63
+ this.offset += 4;
64
+ return Array.from({ length: len }, () => item(this));
65
+ }
66
+ /** How far in the cursor has read. For the layout tests. */
67
+ position() {
68
+ return this.offset;
69
+ }
70
+ }
71
+ /** Fails unless `data` carries the discriminator for `name`. */
72
+ function expect(data, name) {
73
+ if (!data.subarray(0, 8).equals(accountDiscriminator(name))) {
74
+ throw new Error(`account is not a ${name}`);
75
+ }
76
+ return new Cursor(data, 8);
77
+ }
78
+ const withdrawalRequest = (c) => ({
79
+ owner: c.pubkey(),
80
+ receiver: c.pubkey(),
81
+ shares: c.u64(),
82
+ estimatedWithdrawAmount: c.u64(),
83
+ timestamp: c.u64(),
84
+ sequenceNumber: c.u128()
85
+ });
86
+ /**
87
+ * `state::Vault`, in declaration order.
88
+ *
89
+ * Returns the cursor's final position alongside the value, so a caller can
90
+ * check it against the account's own length. Stopping early still decodes
91
+ * without error — the withdrawal-fee tail was missed exactly that way — so
92
+ * "it parsed" is not evidence the layout is right.
93
+ */
94
+ export function decodeVaultWithPosition(data) {
95
+ const vault = decodeVaultInner(data);
96
+ return vault;
97
+ }
98
+ /** `state::Vault`, in declaration order. */
99
+ export function decodeVault(data) {
100
+ return decodeVaultInner(data).vault;
101
+ }
102
+ function decodeVaultInner(data) {
103
+ const c = expect(data, "Vault");
104
+ c.u8(); // bump
105
+ const name = c.string();
106
+ const admin = c.pubkey();
107
+ const operator = c.pubkey();
108
+ const rateManager = c.pubkey();
109
+ const collateralMint = c.pubkey();
110
+ const shareMint = c.pubkey();
111
+ const blacklisted = c.vec(x => x.pubkey());
112
+ const paused = c.bool();
113
+ const pausedDeposits = c.bool();
114
+ const pausedWithdrawals = c.bool();
115
+ const pausedPrivileged = c.bool();
116
+ const subAccounts = c.vec(x => x.pubkey());
117
+ const rate = {
118
+ value: c.u64(),
119
+ maxRateChangePerUpdate: c.u64(),
120
+ rateUpdateInterval: c.u64(),
121
+ lastUpdatedAt: c.u64()
122
+ };
123
+ const feePercentage = c.u64();
124
+ const balance = c.u64();
125
+ const pendingSharesToBurn = c.u64();
126
+ const fee = { accrued: c.u64(), lastChargedAt: c.u64() };
127
+ const minWithdrawalShares = c.u64();
128
+ const maxTvl = c.u64();
129
+ const sequenceNumber = c.u128();
130
+ const parkingLot = c.pubkey();
131
+ const depositUserList = c.vec(x => x.pubkey());
132
+ const withdrawalUserList = c.vec(x => x.pubkey());
133
+ // The tail the parking-lot change appended. Decoded rather than skipped:
134
+ // a vault with fees configured would otherwise be indistinguishable from a
135
+ // fee-free one.
136
+ const withdrawalFee = {
137
+ permanentFeePercentage: c.u64(),
138
+ timeBasedFeePercentage: c.u64(),
139
+ timeBasedFeeThreshold: c.u64(),
140
+ feeExempt: c.vec(x => x.pubkey())
141
+ };
142
+ const vault = {
143
+ name,
144
+ admin,
145
+ operator,
146
+ rateManager,
147
+ collateralMint,
148
+ shareMint,
149
+ blacklisted,
150
+ paused,
151
+ pausedDeposits,
152
+ pausedWithdrawals,
153
+ pausedPrivileged,
154
+ subAccounts,
155
+ rate,
156
+ feePercentage,
157
+ balance,
158
+ pendingSharesToBurn,
159
+ fee,
160
+ minWithdrawalShares,
161
+ maxTvl,
162
+ sequenceNumber,
163
+ parkingLot,
164
+ depositUserList,
165
+ withdrawalUserList,
166
+ withdrawalFee
167
+ };
168
+ return { vault, position: c.position() };
169
+ }
170
+ /** `state::VaultAccount`. */
171
+ export function decodeVaultAccount(data) {
172
+ const c = expect(data, "VaultAccount");
173
+ c.u8(); // bump
174
+ c.pubkey(); // vault
175
+ c.pubkey(); // owner
176
+ return {
177
+ lastDepositTs: c.u64(),
178
+ totalPendingWithdrawalShares: c.u64(),
179
+ pendingWithdrawalRequests: c.vec(withdrawalRequest),
180
+ cancelWithdrawRequest: c.vec(x => x.u128()),
181
+ nextRequestNonce: c.u64()
182
+ };
183
+ }
184
+ /** `state::WithdrawalRequestAccount`. */
185
+ export function decodeWithdrawalRequestAccount(address, data) {
186
+ const c = expect(data, "WithdrawalRequestAccount");
187
+ c.u8(); // bump
188
+ return {
189
+ address,
190
+ vault: c.pubkey(),
191
+ nonce: c.u64(),
192
+ queueIndex: c.u64(),
193
+ request: withdrawalRequest(c)
194
+ };
195
+ }
196
+ /** `state::WithdrawalQueue`: head and tail, the entries live in their own accounts. */
197
+ export function decodeWithdrawalQueue(data) {
198
+ const c = expect(data, "WithdrawalQueue");
199
+ c.u8(); // bump
200
+ return { vault: c.pubkey(), head: c.u64(), tail: c.u64() };
201
+ }
202
+ /** `state::ProtocolConfig`. */
203
+ export function decodeProtocolConfig(data) {
204
+ const c = expect(data, "ProtocolConfig");
205
+ c.u8(); // bump
206
+ // Order is load-bearing: platform_fee_recipient sits *after* version and
207
+ // the pause flag, and max_rate precedes default_rate. Getting either wrong
208
+ // decodes silently into plausible-looking nonsense.
209
+ return {
210
+ admin: c.pubkey(),
211
+ pendingAdmin: c.pubkey(),
212
+ guardian: c.pubkey(),
213
+ version: c.u64(),
214
+ pauseNonAdminOperations: c.bool(),
215
+ platformFeeRecipient: c.pubkey(),
216
+ minRate: c.u64(),
217
+ maxRate: c.u64(),
218
+ defaultRate: c.u64(),
219
+ minRateInterval: c.u64(),
220
+ maxRateInterval: c.u64(),
221
+ maxFeePercentage: c.u64()
222
+ };
223
+ }
224
+ /**
225
+ * The classic 82-byte SPL mint layout. The first 82 bytes of a Token-2022
226
+ * mint are identical, so this reads both; it just ignores any extensions.
227
+ */
228
+ export function decodeMint(data) {
229
+ const option = (o) => data.readUInt32LE(o) === 1 ? new PublicKey(data.subarray(o + 4, o + 36)) : null;
230
+ return {
231
+ mintAuthority: option(0),
232
+ supply: data.readBigUInt64LE(36),
233
+ decimals: data.readUInt8(44),
234
+ isInitialized: data.readUInt8(45) === 1,
235
+ freezeAuthority: option(46)
236
+ };
237
+ }
@@ -0,0 +1,94 @@
1
+ import { PublicKey } from "@solana/web3.js";
2
+ import { type ISolanaDeployment, type ISolanaTokenInfo, type ISolanaVaultInfo, type ISolanaVaultPdas } from "../interfaces/index.js";
3
+ /**
4
+ * Reads a Solana deployment manifest.
5
+ *
6
+ * The manifest is `deployments/<cluster>.json` from ember-contracts, served
7
+ * unchanged through the vaults API. Every lookup is local: the parser never
8
+ * touches the network, and anything it cannot answer from the manifest it
9
+ * derives, since every vault address is a pure function of its share mint.
10
+ */
11
+ export declare class SolanaDeploymentParser {
12
+ private readonly deployment;
13
+ readonly programId: PublicKey;
14
+ constructor(deployment: ISolanaDeployment);
15
+ /** The raw manifest, as supplied. */
16
+ getDeployment(): ISolanaDeployment;
17
+ /**
18
+ * The cluster, when the manifest carries it. The API serves it as the
19
+ * enclosing chain identifier rather than inside the deployment, so a
20
+ * parser built from an API response will not have it.
21
+ */
22
+ getCluster(): string | undefined;
23
+ /** The RPC endpoint the deploy ran through. Manifest only. */
24
+ getRpcUrl(): string | undefined;
25
+ /**
26
+ * The protocol config PDA.
27
+ *
28
+ * Derived rather than read from the manifest: it is a pure function of the
29
+ * program id, so deriving it cannot disagree with the chain even if the
30
+ * manifest is stale.
31
+ */
32
+ getProtocolConfigAddress(): PublicKey;
33
+ /**
34
+ * The upgrade authority.
35
+ *
36
+ * Three outcomes, and they are not the same thing: a key, `null` for a
37
+ * program explicitly recorded as immutable, and `undefined` when the
38
+ * deployment simply does not say. The API serves only `programId` as
39
+ * guaranteed, so an omitted field must not be read as proof of
40
+ * immutability.
41
+ */
42
+ getUpgradeAuthority(): PublicKey | null | undefined;
43
+ /**
44
+ * The slot the program was first deployed at — where an indexer should
45
+ * start its backfill. Never moved by an upgrade; only a new program id
46
+ * resets it.
47
+ */
48
+ getDeploySlot(): number | undefined;
49
+ getTokens(): Record<string, ISolanaTokenInfo>;
50
+ getTokenBySymbol(symbol: string): ISolanaTokenInfo | undefined;
51
+ getTokenByMint(mint: PublicKey | string): ISolanaTokenInfo | undefined;
52
+ /** The token program that owns a mint, so ATAs derive correctly. */
53
+ getTokenProgram(mint: PublicKey | string): PublicKey | undefined;
54
+ /** Every vault, keyed by share mint. */
55
+ getVaults(): Record<string, ISolanaVaultInfo>;
56
+ getVaultCount(): number;
57
+ /** A vault by its share mint, which is its identity. */
58
+ getVault(shareMint: PublicKey | string): ISolanaVaultInfo | undefined;
59
+ /**
60
+ * A vault by name. Names are not unique on chain and the vault admin can
61
+ * change them, so this throws rather than guessing when two match.
62
+ */
63
+ getVaultByName(name: string): ISolanaVaultInfo | undefined;
64
+ /** A vault by its vault PDA, for resolving an address seen on chain. */
65
+ getVaultByAddress(vault: PublicKey | string): ISolanaVaultInfo | undefined;
66
+ /**
67
+ * The vault's PDAs, derived from the share mint.
68
+ *
69
+ * The manifest records these too, and they are checked against the
70
+ * derivation: a mismatch means the manifest was written by a different
71
+ * program id, which would otherwise surface as a confusing on-chain
72
+ * failure much later.
73
+ */
74
+ getVaultPdas(shareMint: PublicKey | string): ISolanaVaultPdas;
75
+ /** The collateral mint a vault takes. */
76
+ getCollateralMint(shareMint: PublicKey | string): PublicKey;
77
+ /** A vault's roles, as addresses. `rateManager` is null until it is set. */
78
+ getVaultRoles(shareMint: PublicKey | string): {
79
+ admin: PublicKey;
80
+ operator: PublicKey;
81
+ rateManager: PublicKey | null;
82
+ subAccounts: PublicKey[];
83
+ };
84
+ /**
85
+ * The share token's name and symbol, or null when the vault was created
86
+ * without metadata. A classic SPL mint carries neither itself; both live
87
+ * in a Metaplex account alongside it.
88
+ */
89
+ getShareTokenMetadata(shareMint: PublicKey | string): {
90
+ name: string;
91
+ symbol: string;
92
+ address: PublicKey;
93
+ } | null;
94
+ }
@@ -0,0 +1,174 @@
1
+ import { PublicKey } from "@solana/web3.js";
2
+ import { deriveProtocolConfig, deriveVaultPdas, metadataAddress } from "./pdas.js";
3
+ /**
4
+ * Reads a Solana deployment manifest.
5
+ *
6
+ * The manifest is `deployments/<cluster>.json` from ember-contracts, served
7
+ * unchanged through the vaults API. Every lookup is local: the parser never
8
+ * touches the network, and anything it cannot answer from the manifest it
9
+ * derives, since every vault address is a pure function of its share mint.
10
+ */
11
+ export class SolanaDeploymentParser {
12
+ constructor(deployment) {
13
+ if (!deployment?.program?.programId) {
14
+ throw new Error("solana deployment is missing program.programId");
15
+ }
16
+ this.deployment = deployment;
17
+ this.programId = new PublicKey(deployment.program.programId);
18
+ }
19
+ /** The raw manifest, as supplied. */
20
+ getDeployment() {
21
+ return this.deployment;
22
+ }
23
+ /**
24
+ * The cluster, when the manifest carries it. The API serves it as the
25
+ * enclosing chain identifier rather than inside the deployment, so a
26
+ * parser built from an API response will not have it.
27
+ */
28
+ getCluster() {
29
+ return this.deployment.cluster;
30
+ }
31
+ /** The RPC endpoint the deploy ran through. Manifest only. */
32
+ getRpcUrl() {
33
+ return this.deployment.rpcUrl;
34
+ }
35
+ /**
36
+ * The protocol config PDA.
37
+ *
38
+ * Derived rather than read from the manifest: it is a pure function of the
39
+ * program id, so deriving it cannot disagree with the chain even if the
40
+ * manifest is stale.
41
+ */
42
+ getProtocolConfigAddress() {
43
+ return deriveProtocolConfig(this.programId);
44
+ }
45
+ /**
46
+ * The upgrade authority.
47
+ *
48
+ * Three outcomes, and they are not the same thing: a key, `null` for a
49
+ * program explicitly recorded as immutable, and `undefined` when the
50
+ * deployment simply does not say. The API serves only `programId` as
51
+ * guaranteed, so an omitted field must not be read as proof of
52
+ * immutability.
53
+ */
54
+ getUpgradeAuthority() {
55
+ const a = this.deployment.program.upgradeAuthority;
56
+ if (a === undefined)
57
+ return undefined;
58
+ return a === null ? null : new PublicKey(a);
59
+ }
60
+ /**
61
+ * The slot the program was first deployed at — where an indexer should
62
+ * start its backfill. Never moved by an upgrade; only a new program id
63
+ * resets it.
64
+ */
65
+ getDeploySlot() {
66
+ return this.deployment.program.deploySlot;
67
+ }
68
+ // --- tokens ---
69
+ getTokens() {
70
+ return this.deployment.tokens ?? {};
71
+ }
72
+ getTokenBySymbol(symbol) {
73
+ return this.getTokens()[symbol];
74
+ }
75
+ getTokenByMint(mint) {
76
+ const key = typeof mint === "string" ? mint : mint.toBase58();
77
+ return Object.values(this.getTokens()).find(t => t.mint === key);
78
+ }
79
+ /** The token program that owns a mint, so ATAs derive correctly. */
80
+ getTokenProgram(mint) {
81
+ const token = this.getTokenByMint(mint);
82
+ return token ? new PublicKey(token.tokenProgram) : undefined;
83
+ }
84
+ // --- vaults ---
85
+ /** Every vault, keyed by share mint. */
86
+ getVaults() {
87
+ return this.deployment.vaults ?? {};
88
+ }
89
+ getVaultCount() {
90
+ return Object.keys(this.getVaults()).length;
91
+ }
92
+ /** A vault by its share mint, which is its identity. */
93
+ getVault(shareMint) {
94
+ const key = typeof shareMint === "string" ? shareMint : shareMint.toBase58();
95
+ return this.getVaults()[key];
96
+ }
97
+ /**
98
+ * A vault by name. Names are not unique on chain and the vault admin can
99
+ * change them, so this throws rather than guessing when two match.
100
+ */
101
+ getVaultByName(name) {
102
+ const hits = Object.values(this.getVaults()).filter(v => v.name === name);
103
+ if (hits.length > 1) {
104
+ throw new Error(`${hits.length} vaults are named ${JSON.stringify(name)}; address one by share mint`);
105
+ }
106
+ return hits[0];
107
+ }
108
+ /** A vault by its vault PDA, for resolving an address seen on chain. */
109
+ getVaultByAddress(vault) {
110
+ const key = typeof vault === "string" ? vault : vault.toBase58();
111
+ return Object.values(this.getVaults()).find(v => v.vault === key);
112
+ }
113
+ /**
114
+ * The vault's PDAs, derived from the share mint.
115
+ *
116
+ * The manifest records these too, and they are checked against the
117
+ * derivation: a mismatch means the manifest was written by a different
118
+ * program id, which would otherwise surface as a confusing on-chain
119
+ * failure much later.
120
+ */
121
+ getVaultPdas(shareMint) {
122
+ const mint = typeof shareMint === "string" ? new PublicKey(shareMint) : shareMint;
123
+ const pdas = deriveVaultPdas(this.programId, mint);
124
+ const recorded = this.getVault(mint);
125
+ if (recorded && recorded.vault !== pdas.vault.toBase58()) {
126
+ throw new Error(`manifest records vault ${recorded.vault} for share mint ${mint.toBase58()}, ` +
127
+ `but program ${this.programId.toBase58()} derives ${pdas.vault.toBase58()}. ` +
128
+ `The manifest belongs to a different deployment.`);
129
+ }
130
+ return pdas;
131
+ }
132
+ /** The collateral mint a vault takes. */
133
+ getCollateralMint(shareMint) {
134
+ const vault = this.getVault(shareMint);
135
+ if (!vault) {
136
+ const key = typeof shareMint === "string" ? shareMint : shareMint.toBase58();
137
+ throw new Error(`no vault recorded for share mint ${key}`);
138
+ }
139
+ return new PublicKey(vault.collateralMint);
140
+ }
141
+ /** A vault's roles, as addresses. `rateManager` is null until it is set. */
142
+ getVaultRoles(shareMint) {
143
+ const vault = this.getVault(shareMint);
144
+ if (!vault) {
145
+ const key = typeof shareMint === "string" ? shareMint : shareMint.toBase58();
146
+ throw new Error(`no vault recorded for share mint ${key}`);
147
+ }
148
+ return {
149
+ admin: new PublicKey(vault.admin),
150
+ operator: new PublicKey(vault.operator),
151
+ rateManager: vault.rateManager ? new PublicKey(vault.rateManager) : null,
152
+ subAccounts: vault.subAccounts.map(a => new PublicKey(a))
153
+ };
154
+ }
155
+ /**
156
+ * The share token's name and symbol, or null when the vault was created
157
+ * without metadata. A classic SPL mint carries neither itself; both live
158
+ * in a Metaplex account alongside it.
159
+ */
160
+ getShareTokenMetadata(shareMint) {
161
+ const vault = this.getVault(shareMint);
162
+ if (!vault?.shareName || !vault.shareSymbol)
163
+ return null;
164
+ const mint = typeof shareMint === "string" ? new PublicKey(shareMint) : shareMint;
165
+ return {
166
+ name: vault.shareName,
167
+ symbol: vault.shareSymbol,
168
+ // Derived rather than read: the metadata account is a PDA of the
169
+ // share mint, so the manifest's copy is a convenience the API does
170
+ // not serve.
171
+ address: metadataAddress(mint)
172
+ };
173
+ }
174
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./pdas.js";
2
+ export * from "./borsh.js";
3
+ export * from "./deployment-parser.js";
@@ -0,0 +1,3 @@
1
+ export * from "./pdas.js";
2
+ export * from "./borsh.js";
3
+ export * from "./deployment-parser.js";
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The program's fixed-point arithmetic, mirrored exactly.
3
+ *
4
+ * Every quote the reader answers locally has an on-chain counterpart in
5
+ * `quote.rs`, and the two must agree to the unit: a preview that rounds the
6
+ * other way sends a user into a transaction the program then rejects. So
7
+ * these are transcribed from `math.rs` rather than reimplemented, including
8
+ * which operations floor and which ceil.
9
+ *
10
+ * `bigint` throughout. The program works in u128 internally and casts back to
11
+ * u64, so doing this in `number` would lose precision above 2^53 — a vault
12
+ * holding more than ~9 million tokens at 9 decimals.
13
+ */
14
+ /** `math::BASE`: 1.0 in the program's fixed point. */
15
+ export declare const BASE = 1000000000n;
16
+ /** `math::mul`: `a * b / BASE`, flooring. */
17
+ export declare function mul(a: bigint, b: bigint): bigint;
18
+ /** `math::mul_ceil`. */
19
+ export declare function mulCeil(a: bigint, b: bigint): bigint;
20
+ /** `math::div`: `a * BASE / b`, flooring. */
21
+ export declare function div(a: bigint, b: bigint): bigint;
22
+ /** `math::div_ceil`. */
23
+ export declare function divCeil(a: bigint, b: bigint): bigint;
24
+ /** `Vault::get_vault_tvl`. */
25
+ export declare const vaultTvl: (totalShares: bigint, rate: bigint) => bigint;
26
+ /** `Vault::calculate_shares_from_amount`. */
27
+ export declare const sharesFromAmount: (amount: bigint, rate: bigint) => bigint;
28
+ /** `Vault::calculate_shares_from_amount_ceil`. */
29
+ export declare const sharesFromAmountCeil: (amount: bigint, rate: bigint) => bigint;
30
+ /** `Vault::calculate_amount_from_shares_floor`: what settlement actually pays. */
31
+ export declare const amountFromSharesFloor: (shares: bigint, rate: bigint) => bigint;
32
+ /** `Vault::calculate_amount_from_shares`: ceil, what minting costs. */
33
+ export declare const amountFromShares: (shares: bigint, rate: bigint) => bigint;
34
+ /**
35
+ * `Vault::max_mint_shares`: shares still issuable under `maxTvl`.
36
+ *
37
+ * Saturating, not checked: a vault already over its cap (because the cap was
38
+ * lowered, or the rate moved) reports 0 rather than erroring.
39
+ */
40
+ export declare function maxMintShares(totalShares: bigint, rate: bigint, maxTvl: bigint): bigint;
41
+ /**
42
+ * `Vault::max_deposit_amount`: collateral still depositable under `maxTvl`.
43
+ *
44
+ * The `+1 ... -1` is the program's, not a transcription slip: it is the
45
+ * largest amount whose floored share conversion still fits the headroom, so
46
+ * depositing exactly this much lands on the cap rather than one unit over.
47
+ */
48
+ export declare function maxDepositAmount(totalShares: bigint, rate: bigint, maxTvl: bigint): bigint;
49
+ /** What settlement withholds from a gross payout. */
50
+ export interface IWithdrawalFeeBreakdown {
51
+ permanent: bigint;
52
+ timeBased: bigint;
53
+ /** What the receiver actually gets. */
54
+ net: bigint;
55
+ }
56
+ /**
57
+ * The fee settlement deducts, for one holder redeeming `gross`.
58
+ *
59
+ * Both fees are taken off the gross amount and stay in the vault, so they
60
+ * accrue to the remaining holders rather than leaving. A fee-exempt owner
61
+ * pays neither.
62
+ *
63
+ * The time-based fee is charged while the holder is still *inside*
64
+ * `threshold` of their last deposit — it penalises leaving early, so it
65
+ * applies to recent depositors, not long-standing ones. A holder with no
66
+ * recorded deposit at all (`lastDepositTs === 0`, which is how shares received
67
+ * by transfer look) is treated as inside the window and pays it.
68
+ */
69
+ export declare function withdrawalFee(gross: bigint, fee: {
70
+ permanentFeePercentage: bigint;
71
+ timeBasedFeePercentage: bigint;
72
+ timeBasedFeeThreshold: bigint;
73
+ }, options: {
74
+ exempt: boolean;
75
+ lastDepositTs: bigint;
76
+ nowMs: bigint;
77
+ }): IWithdrawalFeeBreakdown;