@augustdigital/sdk 9.10.1 → 9.12.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 (45) hide show
  1. package/README.md +8 -6
  2. package/lib/adapters/evm/index.d.ts +11 -3
  3. package/lib/adapters/evm/index.js +11 -3
  4. package/lib/adapters/solana/constants.d.ts +12 -4
  5. package/lib/adapters/solana/constants.js +8 -3
  6. package/lib/adapters/solana/getters.d.ts +5 -0
  7. package/lib/adapters/solana/getters.js +35 -1
  8. package/lib/adapters/solana/idl/vault-idl.d.ts +334 -7
  9. package/lib/adapters/solana/idl/vault-idl.js +600 -59
  10. package/lib/adapters/solana/idl/withdrawal-queue-idl.d.ts +790 -0
  11. package/lib/adapters/solana/idl/withdrawal-queue-idl.js +1868 -0
  12. package/lib/adapters/solana/index.d.ts +108 -10
  13. package/lib/adapters/solana/index.js +137 -12
  14. package/lib/adapters/solana/pricing.d.ts +11 -0
  15. package/lib/adapters/solana/pricing.js +24 -8
  16. package/lib/adapters/solana/queue.actions.d.ts +98 -0
  17. package/lib/adapters/solana/queue.actions.js +488 -0
  18. package/lib/adapters/solana/token-reads.d.ts +108 -0
  19. package/lib/adapters/solana/token-reads.js +145 -0
  20. package/lib/adapters/solana/types.d.ts +200 -1
  21. package/lib/adapters/solana/utils.d.ts +16 -0
  22. package/lib/adapters/solana/utils.js +41 -4
  23. package/lib/adapters/solana/vault.actions.d.ts +17 -0
  24. package/lib/adapters/solana/vault.actions.js +14 -125
  25. package/lib/adapters/solana/withdrawal-queue.d.ts +125 -0
  26. package/lib/adapters/solana/withdrawal-queue.js +301 -0
  27. package/lib/core/analytics/method-taxonomy.js +2 -0
  28. package/lib/core/analytics/version.d.ts +1 -1
  29. package/lib/core/analytics/version.js +1 -1
  30. package/lib/core/helpers/swap-router.d.ts +17 -0
  31. package/lib/core/helpers/swap-router.js +44 -0
  32. package/lib/main.d.ts +16 -0
  33. package/lib/main.js +16 -0
  34. package/lib/modules/vaults/getters.d.ts +2 -2
  35. package/lib/modules/vaults/getters.js +103 -3
  36. package/lib/modules/vaults/main.d.ts +12 -0
  37. package/lib/modules/vaults/main.js +17 -1
  38. package/lib/modules/vaults/types.d.ts +14 -1
  39. package/lib/modules/vaults/write.actions.d.ts +27 -17
  40. package/lib/modules/vaults/write.actions.js +45 -16
  41. package/lib/sdk.d.ts +2420 -1734
  42. package/lib/types/vaults.d.ts +47 -1
  43. package/lib/types/vaults.js +5 -0
  44. package/lib/types/webserver.d.ts +8 -0
  45. package/package.json +1 -1
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.describeSolanaError = describeSolanaError;
4
4
  exports.slippageHint = slippageHint;
5
+ exports.validateExplicitFloor = validateExplicitFloor;
5
6
  exports.handleSolanaDeposit = handleSolanaDeposit;
6
7
  exports.handleSolanaRedeem = handleSolanaRedeem;
7
8
  const anchor_1 = require("@coral-xyz/anchor");
@@ -11,6 +12,7 @@ const constants_1 = require("./constants");
11
12
  const pricing_1 = require("./pricing");
12
13
  const utils_1 = require("./utils");
13
14
  const core_1 = require("../../core");
15
+ const token_reads_1 = require("./token-reads");
14
16
  /**
15
17
  * User-facing fallback for a throwable that carries no extractable reason.
16
18
  * Surfaces as e.g. `"Solana deposit failed: please try again"` — actionable,
@@ -145,128 +147,6 @@ function sendsCheckedInstruction({ program, checkedMethod, floor, slippageBps, h
145
147
  'enable slippage protection.');
146
148
  return false;
147
149
  }
148
- /**
149
- * The vault state and share-mint supply as of a **single slot**.
150
- *
151
- * Both figures price the quote, so they have to come from one snapshot. Read
152
- * independently — even concurrently, as two RPC requests — a deposit or
153
- * redemption landing in between combines a supply and an asset total that
154
- * never coexisted, and the quote is wrong in whichever direction the
155
- * interleaving fell. Seeing a 10% deposit's new assets against the old supply
156
- * quotes ~10% high, and the floor then rejects a redemption whose share price
157
- * never moved; the opposite ordering quotes low and silently weakens the
158
- * protection. `getMultipleAccountsInfo` is served from one slot, so the pair
159
- * is always coherent — and it costs one round trip instead of two.
160
- *
161
- * @param program - Anchor program, used only for its account coder; the
162
- * provider it was built with is irrelevant, so no read-only program is needed.
163
- * @param vaultStatePda - Vault state account to decode.
164
- * @param shareMintAddr - Share mint whose supply and decimals are read.
165
- * @returns The decoded `VaultState`, the share mint's raw `supply`, and its
166
- * `decimals` — all from the same slot.
167
- * @throws {@link AugustSDKError} when either account is absent, or is not the
168
- * account type it should be.
169
- */
170
- async function readPricingSnapshot({ connection, program, vaultStatePda, shareMintAddr, }) {
171
- const [vaultStateInfo, shareMintInfo] = await connection.getMultipleAccountsInfo([vaultStatePda, shareMintAddr]);
172
- if (!vaultStateInfo) {
173
- throw new core_1.AugustSDKError('UNKNOWN', `Vault state account ${vaultStatePda.toBase58()} does not exist; it cannot be priced.`);
174
- }
175
- if (!shareMintInfo) {
176
- throw new core_1.AugustSDKError('UNKNOWN', `Share mint ${shareMintAddr.toBase58()} does not exist; it cannot be priced.`);
177
- }
178
- // `vaultState`, not the IDL's own `VaultState`: `Program` runs the IDL
179
- // through `convertIdlToCamelCase`, so its coder only answers to the
180
- // camelCase name and the IDL spelling throws `Account not found`. (A coder
181
- // built directly from the raw IDL behaves the opposite way — which is why
182
- // this has to be checked against a real `Program`, as its test does.)
183
- //
184
- // Decoded by name rather than `decodeAny`, so a `vaultAddress` pointing at
185
- // some other account of this program fails here instead of being decoded as
186
- // whatever it happens to be.
187
- let vaultStateData;
188
- try {
189
- vaultStateData = program.coder.accounts.decode('vaultState', vaultStateInfo.data);
190
- }
191
- catch (e) {
192
- throw new core_1.AugustSDKError('UNKNOWN', `Account ${vaultStatePda.toBase58()} is not a vault state account for this program.`, { cause: e });
193
- }
194
- let mint;
195
- try {
196
- mint = (0, spl_token_1.unpackMint)(shareMintAddr, shareMintInfo, spl_token_1.TOKEN_PROGRAM_ID);
197
- }
198
- catch (e) {
199
- throw new core_1.AugustSDKError('UNKNOWN', `Account ${shareMintAddr.toBase58()} is not an SPL Token mint.`, { cause: e });
200
- }
201
- return {
202
- vaultStateData,
203
- supply: mint.supply,
204
- shareDecimals: mint.decimals,
205
- };
206
- }
207
- /**
208
- * Pick which token account to hand the vault program for a given mint, and
209
- * describe how to create it when the user has none.
210
- *
211
- * The vault program constrains these accounts by `token::mint` +
212
- * `token::authority` (not `associated_token::…`), so any account the signer
213
- * owns qualifies. We therefore keep an existing account when there is one —
214
- * that is where the user's balance already lives — and fall back to the
215
- * associated token account otherwise.
216
- *
217
- * Two deliberate choices:
218
- *
219
- * - **The create is returned, not sent.** Prepended to the deposit/redeem via
220
- * `.preInstructions()` it is atomic with the transfer, so there is no window
221
- * in which the vault instruction can reference an account that has not
222
- * landed. Sent as its own transaction — as this module used to — it raced
223
- * its own confirmation. Observed in production (2026-08-11):
224
- * a deposit failed with `Simulation failed … Logs: []` while the share-ATA
225
- * create was still in flight, then succeeded 17s later on retry. The empty
226
- * log array means the transaction never reached program execution, so there
227
- * was nothing on-chain to debug from.
228
- * - **Idempotent.** `createAssociatedTokenAccountIdempotentInstruction` is a
229
- * no-op when the account already exists, so a concurrent create — another
230
- * tab, a wallet's auto-provisioning — cannot turn into an
231
- * `SystemError::AccountAlreadyInUse` ("already in use") failure between our
232
- * read and our submit.
233
- *
234
- * Which account wins when several exist is decided by
235
- * {@link selectRichestTokenAccount} — the same rule every balance *read* in
236
- * this adapter uses, so the account a UI reports and the account a transfer
237
- * names are never different accounts.
238
- *
239
- * Classic SPL Token only: the ATA is derived under `TOKEN_PROGRAM_ID`, matching
240
- * the `tokenProgram` both handlers pass, even though the program's
241
- * `TokenInterface` would also accept Token-2022.
242
- *
243
- * @param owner - Wallet that owns the account and pays rent for any creation.
244
- * @throws AugustSDKError when a candidate account's balance cannot be read
245
- * (propagated from the shared selector), and `TokenOwnerOffCurveError` when
246
- * `owner` is off-curve and an ATA has to be derived.
247
- */
248
- async function resolveUserTokenAccount({ connection, owner, mint, }) {
249
- const existing = await connection.getParsedTokenAccountsByOwner(owner, {
250
- mint,
251
- });
252
- const richest = (0, utils_1.selectRichestTokenAccount)(existing.value);
253
- if (richest)
254
- return {
255
- address: richest.pubkey,
256
- balance: richest.amount,
257
- frozen: richest.frozen,
258
- };
259
- const ata = await (0, spl_token_1.getAssociatedTokenAddress)(mint, owner);
260
- return {
261
- address: ata,
262
- balance: BigInt(0),
263
- frozen: false,
264
- createIx: (0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(owner, // payer
265
- ata, // associated token account
266
- owner, // owner
267
- mint),
268
- };
269
- }
270
150
  /**
271
151
  * Resolve the two token accounts a vault instruction needs, concurrently.
272
152
  *
@@ -283,7 +163,7 @@ async function resolveUserTokenAccount({ connection, owner, mint, }) {
283
163
  * @param mints - `[first, second]`; the returned tuple matches this order.
284
164
  */
285
165
  async function resolveUserTokenAccountPair({ connection, owner, mints, }) {
286
- const settled = await Promise.allSettled(mints.map((mint) => resolveUserTokenAccount({ connection, owner, mint })));
166
+ const settled = await Promise.allSettled(mints.map((mint) => (0, token_reads_1.resolveUserTokenAccount)({ connection, owner, mint })));
287
167
  const resolved = settled.map((outcome) => {
288
168
  if (outcome.status === 'rejected')
289
169
  throw outcome.reason;
@@ -466,7 +346,7 @@ async function handleSolanaDeposit({ provider, connection, network = constants_1
466
346
  // pricing against a mismatched pair is worse than not quoting at all —
467
347
  // see `readPricingSnapshot`. A caller already holding a quote can pass
468
348
  // `minSharesOut` and skip it.
469
- const { vaultStateData, supply } = await readPricingSnapshot({
349
+ const { vaultStateData, supply } = await (0, token_reads_1.readPricingSnapshot)({
470
350
  connection,
471
351
  program,
472
352
  vaultStatePda,
@@ -613,12 +493,21 @@ async function handleSolanaRedeem({ provider, connection, vaultProgramId, vaultA
613
493
  // replaces a `getParsedAccountInfo` here and a `vaultState.fetch` further
614
494
  // down — one round trip rather than two, and supply and assets can no
615
495
  // longer be read either side of someone else's deposit.
616
- const { vaultStateData, supply: shareSupply, shareDecimals, } = await readPricingSnapshot({
496
+ const { vaultStateData, supply: shareSupply, shareDecimals, } = await (0, token_reads_1.readPricingSnapshot)({
617
497
  connection,
618
498
  program,
619
499
  vaultStatePda,
620
500
  shareMintAddr,
621
501
  });
502
+ // A vault with a queue attached accepts a redeem from the queue alone, so
503
+ // a holder's direct redeem can only revert with `WithdrawalQueueRequired`.
504
+ // Say so before the wallet is asked to sign.
505
+ const gate = vaultStateData.withdrawalQueueAuthority;
506
+ if (gate && !new web3_js_1.PublicKey(gate).equals(web3_js_1.PublicKey.default)) {
507
+ throw new core_1.AugustValidationError('INVALID_INPUT', 'This vault redeems through its withdrawal queue: request with ' +
508
+ '`vaultRequestRedeem`, then claim the request with `vaultRedeem` and ' +
509
+ '`options.request` once its cooldown has run.');
510
+ }
622
511
  let redeemSharesRaw;
623
512
  if (typeof redeemShares === 'bigint') {
624
513
  redeemSharesRaw = new anchor_1.BN(redeemShares.toString());
@@ -0,0 +1,125 @@
1
+ import { type Connection, PublicKey } from '@solana/web3.js';
2
+ import type { IAddress, ISolanaNetwork, IVaultAvailableRedemption } from '../../types';
3
+ import { VaultRedemptionStatus } from '../../types/vaults';
4
+ import type { IVaultWithdrawalQueue } from '../../types/vaults';
5
+ import type { ISolanaVaultState, ISolanaWithdrawalRequestState } from './types';
6
+ /**
7
+ * The queue PDA for a vault: `["withdrawal_queue", vault_state]` under the queue
8
+ * program. The vault only ever attaches this address.
9
+ *
10
+ * @param vaultState - The vault's `VaultState` account.
11
+ * @param queueProgramId - The august_withdrawal_queue program id for the network.
12
+ */
13
+ export declare function deriveWithdrawalQueuePda(vaultState: PublicKey, queueProgramId: PublicKey): PublicKey;
14
+ /**
15
+ * Where a request stands at `nowSeconds`, by the program's own rules
16
+ * (`WithdrawalRequest::is_expired` / `is_eligible`): a non-zero `expiresAt` at
17
+ * or before now is expired, and that is checked first because an expired
18
+ * request can only be cancelled; otherwise it is claimable from `eligibleAt`.
19
+ *
20
+ * @param request - Decoded request; `eligibleAt` / `expiresAt` in Unix seconds.
21
+ * @param nowSeconds - Unix seconds. Wall-clock time stands in for the chain's
22
+ * clock, so a request within seconds of a boundary may be classified a slot
23
+ * early or late.
24
+ */
25
+ export declare function classifyWithdrawalRequest(request: Pick<ISolanaWithdrawalRequestState, 'eligibleAt' | 'expiresAt'>, nowSeconds: number): VaultRedemptionStatus;
26
+ /**
27
+ * What finalizing `shares` would pay the recipient at the vault's current
28
+ * state: the offset-aware `assets_for_redeem`, less the ceil-rounded
29
+ * withdrawal fee. Finalize prices at execution, so this is an estimate.
30
+ *
31
+ * @returns Raw deposit-mint units, net of fee.
32
+ * @throws AugustValidationError when the vault has no share price.
33
+ */
34
+ export declare function estimateRequestPayout(input: {
35
+ vaultState: Pick<ISolanaVaultState, 'localAum' | 'deployedAum' | 'withdrawalFee' | 'shareOffset'>;
36
+ supply: bigint;
37
+ shares: bigint;
38
+ }): bigint;
39
+ /**
40
+ * The withdrawal queue program id a Solana service names, or `undefined` when
41
+ * it names none: the queue is not deployed on its network (mainnet-beta until
42
+ * its upgrade), or the service has neither `network` nor `getProgramId`. Read
43
+ * paths treat that as "no queue" rather than a failed read, so they make no RPC
44
+ * call and log nothing.
45
+ *
46
+ * Prefers `network`, read straight from `programIds`: `getProgramId` is an
47
+ * instrumented public method, and calling it where it throws by design would
48
+ * record an error breadcrumb on every vault read.
49
+ *
50
+ * @param service - The SolanaAdapter (or a stand-in) the read was given.
51
+ * @returns The queue program id, or `undefined`.
52
+ * @throws When the service names a malformed id; callers read inside their
53
+ * own `try`, so that is logged as a failed queue read.
54
+ */
55
+ export declare function resolveQueueProgramId(service: {
56
+ network?: ISolanaNetwork;
57
+ getProgramId?: (type: 'vault' | 'withdrawalQueue') => string;
58
+ }): PublicKey | undefined;
59
+ /**
60
+ * The vault's withdrawal queue summary for `IVault.withdrawalQueue`, or
61
+ * `undefined` when the vault has no queue.
62
+ *
63
+ * `enabled` is whether the vault's redeem gate points at this queue, read from
64
+ * the vault state in the same call as the queue, so an attach or release shows
65
+ * at once rather than when a cached vault state expires. A queue that was
66
+ * released keeps its account and may still hold pending requests, so it is
67
+ * reported with `enabled: false`.
68
+ *
69
+ * RPC: one `getMultipleAccountsInfo` (the queue and the vault state). Not
70
+ * cached: the counters change with every request.
71
+ *
72
+ * @param input - The vault and where to read.
73
+ */
74
+ export declare function getSolanaWithdrawalQueueSummary(input: {
75
+ connection: Connection;
76
+ queueProgramId: PublicKey;
77
+ /** The vault's `VaultState` account. */
78
+ vaultState: PublicKey;
79
+ /** Decimals of the vault's share mint, for `pendingShares`. */
80
+ shareDecimals: number;
81
+ }): Promise<IVaultWithdrawalQueue | undefined>;
82
+ /**
83
+ * `owner`'s pending requests on a vault's queue, split the way the EVM path
84
+ * splits them: `availableRedemptions` holds requests still waiting
85
+ * (`AWAITING_COOLDOWN`) or past their window (`EXPIRED`, cancel-only);
86
+ * `pendingRedemptions` holds requests finalizable now (`READY_TO_CLAIM`).
87
+ *
88
+ * Each entry's `amount` is the escrowed shares and `estimatedAssets` the net
89
+ * payout at the current share price (see {@link estimateRequestPayout}),
90
+ * priced from the vault state and share supply of a single slot, never from a
91
+ * cached state. `receiver` is the wallet that owns the request's payout token
92
+ * account, which need not be the requester; if that account is gone it is the
93
+ * account address itself. `id` and `hash` are the request account's address:
94
+ * the program keeps no creation transaction to point at. `date` and
95
+ * `day` / `month` / `year` are the eligibility time (UTC); `timestamp` is when
96
+ * the request was made.
97
+ *
98
+ * RPC, worst case: one `getAccountInfo` (skipped once the queue is known to
99
+ * exist), one `getProgramAccounts` filtered on queue and owner, and, when the
100
+ * owner has any request, one `getMultipleAccountsInfo` carrying the vault
101
+ * state, share mint and up to 98 distinct payout accounts. Each further 100
102
+ * distinct payout accounts cost one more `getMultipleAccountsInfo`, at most
103
+ * four in flight. A vault without a queue costs the single `getAccountInfo`.
104
+ *
105
+ * @param input - The vault, the owner, and where to read.
106
+ */
107
+ export declare function getSolanaWithdrawalRedemptions(input: {
108
+ connection: Connection;
109
+ queueProgramId: PublicKey;
110
+ /** The vault's `VaultState` account, read fresh for pricing. */
111
+ vaultStateAddress: PublicKey;
112
+ /** The vault's share mint, read in the same call for the supply. */
113
+ shareMint: PublicKey;
114
+ /** The holder whose requests to list. */
115
+ owner: PublicKey;
116
+ /** The vault address to stamp on each entry. */
117
+ vault: IAddress;
118
+ /** Deposit mint decimals, for `estimatedAssets`. */
119
+ depositDecimals: number;
120
+ /** Unix seconds to classify against; defaults to now. */
121
+ nowSeconds?: number;
122
+ }): Promise<{
123
+ availableRedemptions: IVaultAvailableRedemption[];
124
+ pendingRedemptions: IVaultAvailableRedemption[];
125
+ }>;
@@ -0,0 +1,301 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deriveWithdrawalQueuePda = deriveWithdrawalQueuePda;
4
+ exports.classifyWithdrawalRequest = classifyWithdrawalRequest;
5
+ exports.estimateRequestPayout = estimateRequestPayout;
6
+ exports.resolveQueueProgramId = resolveQueueProgramId;
7
+ exports.getSolanaWithdrawalQueueSummary = getSolanaWithdrawalQueueSummary;
8
+ exports.getSolanaWithdrawalRedemptions = getSolanaWithdrawalRedemptions;
9
+ /**
10
+ * Read paths for the august_withdrawal_queue program: the per-vault queue
11
+ * summary and a holder's pending withdrawal requests.
12
+ *
13
+ * Internal to the SDK. `getSolanaVault` uses {@link getSolanaWithdrawalQueueSummary};
14
+ * `getVaultPositions` and `getVaultAvailableRedemptions` use
15
+ * {@link getSolanaWithdrawalRedemptions}. Nothing here signs or writes.
16
+ */
17
+ const anchor_1 = require("@coral-xyz/anchor");
18
+ const spl_token_1 = require("@solana/spl-token");
19
+ const web3_js_1 = require("@solana/web3.js");
20
+ const core_1 = require("../../core");
21
+ const cache_1 = require("../../core/cache");
22
+ const concurrency_1 = require("../../core/helpers/concurrency");
23
+ const vaults_1 = require("../../types/vaults");
24
+ const vault_idl_1 = require("./idl/vault-idl");
25
+ const withdrawal_queue_idl_1 = require("./idl/withdrawal-queue-idl");
26
+ const pricing_1 = require("./pricing");
27
+ const constants_1 = require("./constants");
28
+ const utils_1 = require("./utils");
29
+ const token_reads_1 = require("./token-reads");
30
+ const WITHDRAWAL_QUEUE_SEED = 'withdrawal_queue';
31
+ /** Byte offsets of `queue` and `owner` in a `WithdrawalRequest`, after the 8-byte discriminator. */
32
+ const REQUEST_QUEUE_OFFSET = 8;
33
+ const REQUEST_OWNER_OFFSET = 40;
34
+ /** `getMultipleAccountsInfo` accepts at most this many accounts per call. */
35
+ const MAX_ACCOUNTS_PER_CALL = 100;
36
+ /** Payout-account batches beyond the first, in flight at once. */
37
+ const PAYOUT_BATCH_CONCURRENCY = 4;
38
+ /**
39
+ * The queue PDA for a vault: `["withdrawal_queue", vault_state]` under the queue
40
+ * program. The vault only ever attaches this address.
41
+ *
42
+ * @param vaultState - The vault's `VaultState` account.
43
+ * @param queueProgramId - The august_withdrawal_queue program id for the network.
44
+ */
45
+ function deriveWithdrawalQueuePda(vaultState, queueProgramId) {
46
+ return web3_js_1.PublicKey.findProgramAddressSync([Buffer.from(WITHDRAWAL_QUEUE_SEED), vaultState.toBuffer()], queueProgramId)[0];
47
+ }
48
+ /** A read-only queue program client on `connection`. No wallet, no RPC on construction. */
49
+ function queueProgram(connection, queueProgramId) {
50
+ return new anchor_1.Program({ ...withdrawal_queue_idl_1.withdrawalQueueIdl, address: queueProgramId.toBase58() }, utils_1.SolanaUtils.getReadOnlyProvider({ connection }));
51
+ }
52
+ /** A read-only vault program client, used only for its account coder. */
53
+ function vaultProgram(connection) {
54
+ return new anchor_1.Program(vault_idl_1.vaultIdl, utils_1.SolanaUtils.getReadOnlyProvider({ connection }));
55
+ }
56
+ /**
57
+ * Where a request stands at `nowSeconds`, by the program's own rules
58
+ * (`WithdrawalRequest::is_expired` / `is_eligible`): a non-zero `expiresAt` at
59
+ * or before now is expired, and that is checked first because an expired
60
+ * request can only be cancelled; otherwise it is claimable from `eligibleAt`.
61
+ *
62
+ * @param request - Decoded request; `eligibleAt` / `expiresAt` in Unix seconds.
63
+ * @param nowSeconds - Unix seconds. Wall-clock time stands in for the chain's
64
+ * clock, so a request within seconds of a boundary may be classified a slot
65
+ * early or late.
66
+ */
67
+ function classifyWithdrawalRequest(request, nowSeconds) {
68
+ const expiresAt = BigInt(request.expiresAt.toString());
69
+ const now = BigInt(nowSeconds);
70
+ if (expiresAt !== 0n && now >= expiresAt) {
71
+ return vaults_1.VaultRedemptionStatus.EXPIRED;
72
+ }
73
+ if (now >= BigInt(request.eligibleAt.toString())) {
74
+ return vaults_1.VaultRedemptionStatus.READY_TO_CLAIM;
75
+ }
76
+ return vaults_1.VaultRedemptionStatus.AWAITING_COOLDOWN;
77
+ }
78
+ /**
79
+ * What finalizing `shares` would pay the recipient at the vault's current
80
+ * state: the offset-aware `assets_for_redeem`, less the ceil-rounded
81
+ * withdrawal fee. Finalize prices at execution, so this is an estimate.
82
+ *
83
+ * @returns Raw deposit-mint units, net of fee.
84
+ * @throws AugustValidationError when the vault has no share price.
85
+ */
86
+ function estimateRequestPayout(input) {
87
+ const { vaultState, supply, shares } = input;
88
+ const gross = (0, pricing_1.assetsForRedeem)({
89
+ supply,
90
+ totalAssets: (0, pricing_1.totalAssetsOf)(vaultState),
91
+ shares,
92
+ offset: (0, pricing_1.resolveShareOffset)(vaultState.shareOffset),
93
+ });
94
+ return (gross -
95
+ (0, pricing_1.withdrawalFee)(gross, (0, pricing_1.toBigInt)(vaultState.withdrawalFee, 'withdrawalFee')));
96
+ }
97
+ /**
98
+ * Whether the vault's queue account exists, remembering a yes. A queue is never
99
+ * closed once created (release keeps it so the vault can re-attach), so a
100
+ * positive answer is safe to cache; a negative one is not, and costs one
101
+ * `getAccountInfo` each time.
102
+ */
103
+ async function queueExists(connection, queue) {
104
+ const cacheKey = `withdrawalQueueExists-${queue.toBase58()}`;
105
+ if (await (0, cache_1.getSafeCache)(cacheKey))
106
+ return true;
107
+ const exists = (await connection.getAccountInfo(queue)) !== null;
108
+ if (exists)
109
+ await (0, cache_1.setSafeCache)(cacheKey, true);
110
+ return exists;
111
+ }
112
+ /**
113
+ * The wallet a request pays: the owner (authority) of its payout token account.
114
+ * A request may pay someone other than its requester, so this is read from the
115
+ * account rather than assumed. If the account is gone or is not a token
116
+ * account, its address is the best identification there is, and is returned
117
+ * as is; finalizing such a request would fail until the account is restored.
118
+ */
119
+ function payoutWallet(tokenAccount, info) {
120
+ if (!info)
121
+ return tokenAccount.toBase58();
122
+ try {
123
+ return (0, spl_token_1.unpackAccount)(tokenAccount, info, info.owner).owner.toBase58();
124
+ }
125
+ catch {
126
+ return tokenAccount.toBase58();
127
+ }
128
+ }
129
+ /**
130
+ * The withdrawal queue program id a Solana service names, or `undefined` when
131
+ * it names none: the queue is not deployed on its network (mainnet-beta until
132
+ * its upgrade), or the service has neither `network` nor `getProgramId`. Read
133
+ * paths treat that as "no queue" rather than a failed read, so they make no RPC
134
+ * call and log nothing.
135
+ *
136
+ * Prefers `network`, read straight from `programIds`: `getProgramId` is an
137
+ * instrumented public method, and calling it where it throws by design would
138
+ * record an error breadcrumb on every vault read.
139
+ *
140
+ * @param service - The SolanaAdapter (or a stand-in) the read was given.
141
+ * @returns The queue program id, or `undefined`.
142
+ * @throws When the service names a malformed id; callers read inside their
143
+ * own `try`, so that is logged as a failed queue read.
144
+ */
145
+ function resolveQueueProgramId(service) {
146
+ if (service.network) {
147
+ const id = (0, constants_1.getDeployedProgramIds)(service.network)?.withdrawalQueue;
148
+ return id ? new web3_js_1.PublicKey(id) : undefined;
149
+ }
150
+ if (!service.getProgramId)
151
+ return undefined;
152
+ try {
153
+ return new web3_js_1.PublicKey(service.getProgramId('withdrawalQueue'));
154
+ }
155
+ catch (error) {
156
+ if (error instanceof core_1.AugustValidationError)
157
+ return undefined;
158
+ throw error;
159
+ }
160
+ }
161
+ /**
162
+ * The vault's withdrawal queue summary for `IVault.withdrawalQueue`, or
163
+ * `undefined` when the vault has no queue.
164
+ *
165
+ * `enabled` is whether the vault's redeem gate points at this queue, read from
166
+ * the vault state in the same call as the queue, so an attach or release shows
167
+ * at once rather than when a cached vault state expires. A queue that was
168
+ * released keeps its account and may still hold pending requests, so it is
169
+ * reported with `enabled: false`.
170
+ *
171
+ * RPC: one `getMultipleAccountsInfo` (the queue and the vault state). Not
172
+ * cached: the counters change with every request.
173
+ *
174
+ * @param input - The vault and where to read.
175
+ */
176
+ async function getSolanaWithdrawalQueueSummary(input) {
177
+ const queue = deriveWithdrawalQueuePda(input.vaultState, input.queueProgramId);
178
+ const [queueInfo, vaultStateInfo] = await input.connection.getMultipleAccountsInfo([queue, input.vaultState]);
179
+ if (!queueInfo || !vaultStateInfo)
180
+ return undefined;
181
+ // Decoded by name, so an account of another type at either address throws
182
+ // rather than being read as this one.
183
+ const state = queueProgram(input.connection, input.queueProgramId).coder.accounts.decode('withdrawalQueue', queueInfo.data);
184
+ const gate = vaultProgram(input.connection).coder.accounts.decode('vaultState', vaultStateInfo.data).withdrawalQueueAuthority;
185
+ return {
186
+ address: queue.toBase58(),
187
+ enabled: gate ? new web3_js_1.PublicKey(gate).equals(queue) : false,
188
+ cooldownSeconds: state.cooldownSeconds.toNumber(),
189
+ fulfillmentWindowSeconds: state.fulfillmentWindowSeconds.toNumber(),
190
+ pendingCount: state.pendingRequests.toNumber(),
191
+ pendingShares: (0, core_1.toNormalizedBn)(BigInt(state.pendingShares.toString()), input.shareDecimals),
192
+ };
193
+ }
194
+ /**
195
+ * `owner`'s pending requests on a vault's queue, split the way the EVM path
196
+ * splits them: `availableRedemptions` holds requests still waiting
197
+ * (`AWAITING_COOLDOWN`) or past their window (`EXPIRED`, cancel-only);
198
+ * `pendingRedemptions` holds requests finalizable now (`READY_TO_CLAIM`).
199
+ *
200
+ * Each entry's `amount` is the escrowed shares and `estimatedAssets` the net
201
+ * payout at the current share price (see {@link estimateRequestPayout}),
202
+ * priced from the vault state and share supply of a single slot, never from a
203
+ * cached state. `receiver` is the wallet that owns the request's payout token
204
+ * account, which need not be the requester; if that account is gone it is the
205
+ * account address itself. `id` and `hash` are the request account's address:
206
+ * the program keeps no creation transaction to point at. `date` and
207
+ * `day` / `month` / `year` are the eligibility time (UTC); `timestamp` is when
208
+ * the request was made.
209
+ *
210
+ * RPC, worst case: one `getAccountInfo` (skipped once the queue is known to
211
+ * exist), one `getProgramAccounts` filtered on queue and owner, and, when the
212
+ * owner has any request, one `getMultipleAccountsInfo` carrying the vault
213
+ * state, share mint and up to 98 distinct payout accounts. Each further 100
214
+ * distinct payout accounts cost one more `getMultipleAccountsInfo`, at most
215
+ * four in flight. A vault without a queue costs the single `getAccountInfo`.
216
+ *
217
+ * @param input - The vault, the owner, and where to read.
218
+ */
219
+ async function getSolanaWithdrawalRedemptions(input) {
220
+ const availableRedemptions = [];
221
+ const pendingRedemptions = [];
222
+ const queue = deriveWithdrawalQueuePda(input.vaultStateAddress, input.queueProgramId);
223
+ if (!(await queueExists(input.connection, queue))) {
224
+ return { availableRedemptions, pendingRedemptions };
225
+ }
226
+ const requests = await queueProgram(input.connection, input.queueProgramId).account.withdrawalRequest.all([
227
+ { memcmp: { offset: REQUEST_QUEUE_OFFSET, bytes: queue.toBase58() } },
228
+ { memcmp: { offset: REQUEST_OWNER_OFFSET, bytes: input.owner.toBase58() } },
229
+ ]);
230
+ if (requests.length === 0) {
231
+ return { availableRedemptions, pendingRedemptions };
232
+ }
233
+ // Payout accounts, deduplicated: requests often share one. The first batch
234
+ // rides with the vault state and share mint, which must share a slot to
235
+ // price coherently; the rest only name receivers, so they need not.
236
+ const payoutAccounts = [
237
+ ...new Map(requests.map(({ account }) => [
238
+ account.recipientTokenAccount.toBase58(),
239
+ account.recipientTokenAccount,
240
+ ])).values(),
241
+ ];
242
+ const firstBatch = payoutAccounts.slice(0, MAX_ACCOUNTS_PER_CALL - 2);
243
+ const laterBatches = [];
244
+ for (let i = firstBatch.length; i < payoutAccounts.length; i += MAX_ACCOUNTS_PER_CALL) {
245
+ laterBatches.push(payoutAccounts.slice(i, i + MAX_ACCOUNTS_PER_CALL));
246
+ }
247
+ const [snapshot, laterInfos] = await Promise.all([
248
+ (0, token_reads_1.readPricingSnapshot)({
249
+ connection: input.connection,
250
+ program: vaultProgram(input.connection),
251
+ vaultStatePda: input.vaultStateAddress,
252
+ shareMintAddr: input.shareMint,
253
+ extraAccounts: firstBatch,
254
+ }),
255
+ (0, concurrency_1.mapWithConcurrency)(laterBatches, PAYOUT_BATCH_CONCURRENCY, (batch) => input.connection.getMultipleAccountsInfo(batch)),
256
+ ]);
257
+ const { vaultStateData, supply, shareDecimals } = snapshot;
258
+ const payoutInfos = [...snapshot.extraAccountInfos, ...laterInfos.flat()];
259
+ const payoutInfoOf = new Map(payoutAccounts.map((address, i) => [address.toBase58(), payoutInfos[i]]));
260
+ const now = input.nowSeconds ?? Math.floor(Date.now() / 1000);
261
+ for (const { publicKey, account } of requests) {
262
+ const shares = BigInt(account.shares.toString());
263
+ const eligibleAt = account.eligibleAt.toNumber();
264
+ const date = new Date(eligibleAt * 1000);
265
+ const status = classifyWithdrawalRequest(account, now);
266
+ let estimatedAssets;
267
+ try {
268
+ estimatedAssets = (0, core_1.toNormalizedBn)(estimateRequestPayout({ vaultState: vaultStateData, supply, shares }), input.depositDecimals);
269
+ }
270
+ catch (error) {
271
+ // A vault with no share price (assets zero against outstanding shares)
272
+ // has nothing to quote; the request itself is still listed.
273
+ core_1.Logger.log.warn('getSolanaWithdrawalRedemptions', 'No payout estimate', {
274
+ request: publicKey.toBase58(),
275
+ error,
276
+ });
277
+ }
278
+ const entry = {
279
+ id: publicKey.toBase58(),
280
+ hash: publicKey.toBase58(),
281
+ timestamp: account.requestedAt.toNumber(),
282
+ receiver: payoutWallet(account.recipientTokenAccount, payoutInfoOf.get(account.recipientTokenAccount.toBase58()) ?? null),
283
+ day: (0, core_1.toNormalizedBn)(date.getUTCDate(), 0),
284
+ month: (0, core_1.toNormalizedBn)(date.getUTCMonth() + 1, 0),
285
+ year: (0, core_1.toNormalizedBn)(date.getUTCFullYear(), 0),
286
+ amount: (0, core_1.toNormalizedBn)(shares, shareDecimals),
287
+ date,
288
+ vault: input.vault,
289
+ status,
290
+ ...(estimatedAssets && { estimatedAssets }),
291
+ };
292
+ if (status === vaults_1.VaultRedemptionStatus.READY_TO_CLAIM) {
293
+ pendingRedemptions.push(entry);
294
+ }
295
+ else {
296
+ availableRedemptions.push(entry);
297
+ }
298
+ }
299
+ return { availableRedemptions, pendingRedemptions };
300
+ }
301
+ //# sourceMappingURL=withdrawal-queue.js.map
@@ -141,6 +141,8 @@ exports.METHOD_CATEGORIES = {
141
141
  vaultRedeem: 'write.redeem',
142
142
  claimWithdrawal: 'write.redeem',
143
143
  vaultRequestRedeem: 'write.redeem',
144
+ // Solana withdrawal queue: cancelling a request is part of the redeem flow.
145
+ vaultCancelRedeemRequest: 'write.redeem',
144
146
  rwaRedeemAsset: 'write.redeem',
145
147
  vaultApprove: 'write.approve',
146
148
  approve: 'write.approve',
@@ -3,4 +3,4 @@
3
3
  * Generated during publish from package.json version
4
4
  * This file is gitignored and created at publish time
5
5
  */
6
- export declare const SDK_VERSION = "9.10.1";
6
+ export declare const SDK_VERSION = "9.12.0";
@@ -6,5 +6,5 @@ exports.SDK_VERSION = void 0;
6
6
  * Generated during publish from package.json version
7
7
  * This file is gitignored and created at publish time
8
8
  */
9
- exports.SDK_VERSION = '9.10.1';
9
+ exports.SDK_VERSION = '9.12.0';
10
10
  //# sourceMappingURL=version.js.map
@@ -169,6 +169,23 @@ export declare function readSwapRouterVaultInfo(runner: IContractRunner, chainId
169
169
  * @internal
170
170
  */
171
171
  export declare function readSwapRouterTokenWhitelisted(runner: IContractRunner, chainId: number, token: IAddress): Promise<boolean>;
172
+ /**
173
+ * Read whether a chain's `SwapRouter` is paused (`isPaused()`), cached for 15
174
+ * seconds. Every router entry point (`deposit`, `swapAndDeposit`,
175
+ * `depositNativeToken`) reverts with `ContractIsPaused` while the router is
176
+ * paused, so callers check this before routing a deposit or sending an
177
+ * approval to the router.
178
+ *
179
+ * @param runner - Contract runner (provider or signer) used for the read.
180
+ * @param chainId - EVM chain ID.
181
+ * @returns `true` while the router is paused; `false` when it is not, or on a
182
+ * chain with no SwapRouter (no RPC).
183
+ * @throws When the `isPaused()` read fails. Callers wrap it in
184
+ * {@link AugustSDKError}.
185
+ * @worstCaseRpcCalls 1 — one `isPaused()` call; none on a cache hit.
186
+ * @internal
187
+ */
188
+ export declare function readSwapRouterPaused(runner: IContractRunner, chainId: number): Promise<boolean>;
172
189
  /**
173
190
  * Resolve the ERC-20 tokens currently whitelisted for the `SwapRouter` on a
174
191
  * chain — the any-token set a UI can offer as swap-and-deposit inputs.