@augustdigital/sdk 9.9.0 → 9.10.1
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/lib/adapters/solana/confirm.d.ts +88 -0
- package/lib/adapters/solana/confirm.js +291 -0
- package/lib/adapters/solana/index.d.ts +1 -1
- package/lib/adapters/solana/utils.d.ts +2 -1
- package/lib/adapters/solana/utils.js +3 -1
- package/lib/adapters/solana/vault.actions.d.ts +6 -0
- package/lib/adapters/solana/vault.actions.js +6 -0
- package/lib/adapters/stellar/soroban.js +3 -13
- package/lib/core/analytics/version.d.ts +1 -1
- package/lib/core/analytics/version.js +1 -1
- package/lib/core/constants/core.d.ts +1 -0
- package/lib/core/constants/core.js +1 -0
- package/lib/core/errors/index.d.ts +29 -1
- package/lib/core/errors/index.js +36 -1
- package/lib/core/helpers/concurrency.d.ts +14 -0
- package/lib/core/helpers/concurrency.js +21 -0
- package/lib/modules/vaults/getters.d.ts +40 -17
- package/lib/modules/vaults/getters.js +48 -25
- package/lib/sdk.d.ts +119 -23
- package/lib/services/octavfi/fetcher.d.ts +30 -6
- package/lib/services/octavfi/fetcher.js +27 -91
- package/lib/types/sub-accounts.d.ts +13 -3
- package/package.json +1 -1
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { AnchorProvider } from '@coral-xyz/anchor';
|
|
2
|
+
import { type Commitment, type ConfirmOptions, type Connection, type Signer, type Transaction, type TransactionSignature, type VersionedTransaction } from '@solana/web3.js';
|
|
3
|
+
/** Delay between signature-status polls. */
|
|
4
|
+
export declare const CONFIRM_POLL_INTERVAL_MS = 2000;
|
|
5
|
+
/**
|
|
6
|
+
* Wall-clock backstop, also bounding every in-flight RPC read (web3.js sets no
|
|
7
|
+
* request timeout). Expiry is normally seen first: a blockhash lasts ~150
|
|
8
|
+
* blocks (~60s, longer when slots are skipped), and the finalized height it is checked against lags ~13s.
|
|
9
|
+
*/
|
|
10
|
+
export declare const CONFIRM_TIMEOUT_MS = 120000;
|
|
11
|
+
/** Cap on a single RPC read, so a hung request counts as a failed read rather than eating the backstop. */
|
|
12
|
+
export declare const CONFIRM_READ_TIMEOUT_MS = 10000;
|
|
13
|
+
/** Ceiling for the doubling wait after polls whose reads failed. */
|
|
14
|
+
export declare const CONFIRM_MAX_BACKOFF_MS = 16000;
|
|
15
|
+
/**
|
|
16
|
+
* Wait for a sent transaction to reach `commitment`, over HTTP only.
|
|
17
|
+
*
|
|
18
|
+
* Anchor's `.rpc()` confirms by signature alone: a WebSocket
|
|
19
|
+
* `signatureSubscribe` raced against a fixed 30s (`confirmed`) or 60s
|
|
20
|
+
* (`finalized`) timer, so a transaction that landed is reported as failed
|
|
21
|
+
* whenever the notification is missed. This polls `getSignatureStatuses`, and
|
|
22
|
+
* ends on the blockhash's real expiry rather than a timer.
|
|
23
|
+
*
|
|
24
|
+
* It never reports that resubmitting is safe. An expired transaction that no
|
|
25
|
+
* lookup can find very likely never landed, but an RPC node with incomplete
|
|
26
|
+
* history cannot prove it, and a wrong "safe" duplicates a deposit or redeem.
|
|
27
|
+
*
|
|
28
|
+
* @param connection - Connection the transaction was sent through.
|
|
29
|
+
* @param signature - Signature returned by `sendRawTransaction`.
|
|
30
|
+
* @param lastValidBlockHeight - Expiry of the blockhash the signed
|
|
31
|
+
* transaction carries, checked against the finalized block height. Omit
|
|
32
|
+
* when unknown (or for a durable-nonce transaction): the wait then ends only
|
|
33
|
+
* on confirmation, failure or `timeoutMs`.
|
|
34
|
+
* @param commitment - Level the transaction must reach.
|
|
35
|
+
* @param pollIntervalMs - Delay between polls; must be positive. Defaults to {@link CONFIRM_POLL_INTERVAL_MS}.
|
|
36
|
+
* @param timeoutMs - Wall-clock backstop; must be positive. Defaults to {@link CONFIRM_TIMEOUT_MS}.
|
|
37
|
+
* @param readTimeoutMs - Cap on each RPC read; must be positive. Defaults to {@link CONFIRM_READ_TIMEOUT_MS}.
|
|
38
|
+
* @returns `signature`, once its status reaches `commitment` with no error.
|
|
39
|
+
* @throws {@link SendTransactionError} when the transaction failed at
|
|
40
|
+
* `commitment`, with logs attached for Anchor's `translateError`.
|
|
41
|
+
* @throws {@link AugustTransactionError} `TRANSACTION_UNCONFIRMED` when no
|
|
42
|
+
* outcome was established: the blockhash expired with the transaction
|
|
43
|
+
* nowhere to be found, or `timeoutMs` passed. `context.reason` is
|
|
44
|
+
* `expired_not_found` (very likely never landed — still check before
|
|
45
|
+
* resubmitting), `below_commitment` (seen landing without error),
|
|
46
|
+
* `failed_below_commitment` (seen failing, not yet final), `rpc_failing`
|
|
47
|
+
* (reads failed) or `not_visible`; `context` also carries the last status
|
|
48
|
+
* seen and read counts, and `cause` the last failed read.
|
|
49
|
+
* @throws {@link AugustValidationError} for a non-positive or non-finite interval or timeout.
|
|
50
|
+
* @remarks **RPC cost.** The first poll runs right after sending. Per poll: one `getSignatureStatuses`, plus one
|
|
51
|
+
* finalized `getBlockHeight` while the transaction is not visible (and, past
|
|
52
|
+
* expiry, one history lookup). Typically 1–3 calls at `confirmed`, ~7–10 at
|
|
53
|
+
* `finalized`, ~60 for an expiring transaction; the `timeoutMs` backstop
|
|
54
|
+
* allows ~60 polls of 1–3 calls, fewer while reads fail: the wait doubles
|
|
55
|
+
* after each poll with a failed read, up to {@link CONFIRM_MAX_BACKOFF_MS}. Failed reads are tolerated, so a transient RPC
|
|
56
|
+
* error never misreports a landed transaction.
|
|
57
|
+
*/
|
|
58
|
+
export declare function confirmByBlockhash({ connection, signature, lastValidBlockHeight, commitment, pollIntervalMs, timeoutMs, readTimeoutMs, }: {
|
|
59
|
+
connection: Connection;
|
|
60
|
+
signature: TransactionSignature;
|
|
61
|
+
lastValidBlockHeight?: number;
|
|
62
|
+
commitment: Commitment;
|
|
63
|
+
pollIntervalMs?: number;
|
|
64
|
+
timeoutMs?: number;
|
|
65
|
+
readTimeoutMs?: number;
|
|
66
|
+
}): Promise<TransactionSignature>;
|
|
67
|
+
/**
|
|
68
|
+
* `AnchorProvider` whose `sendAndConfirm` — what `program.methods…rpc()` calls —
|
|
69
|
+
* confirms with {@link confirmByBlockhash} instead of Anchor's signature-only
|
|
70
|
+
* WebSocket wait. Signing and Anchor's error translation are unchanged.
|
|
71
|
+
*/
|
|
72
|
+
export declare class BlockhashConfirmingProvider extends AnchorProvider {
|
|
73
|
+
/**
|
|
74
|
+
* Sign, send and confirm a transaction.
|
|
75
|
+
*
|
|
76
|
+
* @param tx - Transaction to send. Its blockhash (and fee payer, if unset)
|
|
77
|
+
* are set here. A `VersionedTransaction` already carries a blockhash this
|
|
78
|
+
* cannot learn the expiry of, so it takes Anchor's default path.
|
|
79
|
+
* @param signers - Extra signers besides the wallet.
|
|
80
|
+
* @param opts - Defaults to the provider's options.
|
|
81
|
+
* @returns The transaction signature.
|
|
82
|
+
* @throws See {@link confirmByBlockhash}; also whatever the wallet or
|
|
83
|
+
* `sendRawTransaction` throws (user rejection, preflight failure).
|
|
84
|
+
* @remarks **RPC cost.** `getLatestBlockhash` + `sendRawTransaction`, then
|
|
85
|
+
* the confirmation polls described on {@link confirmByBlockhash}.
|
|
86
|
+
*/
|
|
87
|
+
sendAndConfirm(tx: Transaction | VersionedTransaction, signers?: Signer[], opts?: ConfirmOptions): Promise<TransactionSignature>;
|
|
88
|
+
}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BlockhashConfirmingProvider = exports.CONFIRM_MAX_BACKOFF_MS = exports.CONFIRM_READ_TIMEOUT_MS = exports.CONFIRM_TIMEOUT_MS = exports.CONFIRM_POLL_INTERVAL_MS = void 0;
|
|
4
|
+
exports.confirmByBlockhash = confirmByBlockhash;
|
|
5
|
+
const anchor_1 = require("@coral-xyz/anchor");
|
|
6
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
7
|
+
const core_1 = require("../../core");
|
|
8
|
+
const concurrency_1 = require("../../core/helpers/concurrency");
|
|
9
|
+
/** Delay between signature-status polls. */
|
|
10
|
+
exports.CONFIRM_POLL_INTERVAL_MS = 2_000;
|
|
11
|
+
/**
|
|
12
|
+
* Wall-clock backstop, also bounding every in-flight RPC read (web3.js sets no
|
|
13
|
+
* request timeout). Expiry is normally seen first: a blockhash lasts ~150
|
|
14
|
+
* blocks (~60s, longer when slots are skipped), and the finalized height it is checked against lags ~13s.
|
|
15
|
+
*/
|
|
16
|
+
exports.CONFIRM_TIMEOUT_MS = 120_000;
|
|
17
|
+
/** Cap on a single RPC read, so a hung request counts as a failed read rather than eating the backstop. */
|
|
18
|
+
exports.CONFIRM_READ_TIMEOUT_MS = 10_000;
|
|
19
|
+
/** Ceiling for the doubling wait after polls whose reads failed. */
|
|
20
|
+
exports.CONFIRM_MAX_BACKOFF_MS = 16_000;
|
|
21
|
+
const COMMITMENT_RANK = {
|
|
22
|
+
processed: 0,
|
|
23
|
+
recent: 0,
|
|
24
|
+
confirmed: 1,
|
|
25
|
+
single: 1,
|
|
26
|
+
singleGossip: 1,
|
|
27
|
+
finalized: 2,
|
|
28
|
+
max: 2,
|
|
29
|
+
root: 2,
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* The level a status has reached. Older RPCs omit `confirmationStatus`; the
|
|
33
|
+
* level is then inferred from `confirmations` as Agave's
|
|
34
|
+
* `TransactionStatus::satisfies_commitment` does: `null` is rooted, more than
|
|
35
|
+
* one confirmation is `confirmed`, anything else `processed`.
|
|
36
|
+
*/
|
|
37
|
+
function levelOf(status) {
|
|
38
|
+
if (status.confirmationStatus)
|
|
39
|
+
return status.confirmationStatus;
|
|
40
|
+
if (status.confirmations === null)
|
|
41
|
+
return 'finalized';
|
|
42
|
+
return status.confirmations > 1 ? 'confirmed' : 'processed';
|
|
43
|
+
}
|
|
44
|
+
function reaches(status, commitment) {
|
|
45
|
+
return COMMITMENT_RANK[levelOf(status)] >= COMMITMENT_RANK[commitment];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Wait for a sent transaction to reach `commitment`, over HTTP only.
|
|
49
|
+
*
|
|
50
|
+
* Anchor's `.rpc()` confirms by signature alone: a WebSocket
|
|
51
|
+
* `signatureSubscribe` raced against a fixed 30s (`confirmed`) or 60s
|
|
52
|
+
* (`finalized`) timer, so a transaction that landed is reported as failed
|
|
53
|
+
* whenever the notification is missed. This polls `getSignatureStatuses`, and
|
|
54
|
+
* ends on the blockhash's real expiry rather than a timer.
|
|
55
|
+
*
|
|
56
|
+
* It never reports that resubmitting is safe. An expired transaction that no
|
|
57
|
+
* lookup can find very likely never landed, but an RPC node with incomplete
|
|
58
|
+
* history cannot prove it, and a wrong "safe" duplicates a deposit or redeem.
|
|
59
|
+
*
|
|
60
|
+
* @param connection - Connection the transaction was sent through.
|
|
61
|
+
* @param signature - Signature returned by `sendRawTransaction`.
|
|
62
|
+
* @param lastValidBlockHeight - Expiry of the blockhash the signed
|
|
63
|
+
* transaction carries, checked against the finalized block height. Omit
|
|
64
|
+
* when unknown (or for a durable-nonce transaction): the wait then ends only
|
|
65
|
+
* on confirmation, failure or `timeoutMs`.
|
|
66
|
+
* @param commitment - Level the transaction must reach.
|
|
67
|
+
* @param pollIntervalMs - Delay between polls; must be positive. Defaults to {@link CONFIRM_POLL_INTERVAL_MS}.
|
|
68
|
+
* @param timeoutMs - Wall-clock backstop; must be positive. Defaults to {@link CONFIRM_TIMEOUT_MS}.
|
|
69
|
+
* @param readTimeoutMs - Cap on each RPC read; must be positive. Defaults to {@link CONFIRM_READ_TIMEOUT_MS}.
|
|
70
|
+
* @returns `signature`, once its status reaches `commitment` with no error.
|
|
71
|
+
* @throws {@link SendTransactionError} when the transaction failed at
|
|
72
|
+
* `commitment`, with logs attached for Anchor's `translateError`.
|
|
73
|
+
* @throws {@link AugustTransactionError} `TRANSACTION_UNCONFIRMED` when no
|
|
74
|
+
* outcome was established: the blockhash expired with the transaction
|
|
75
|
+
* nowhere to be found, or `timeoutMs` passed. `context.reason` is
|
|
76
|
+
* `expired_not_found` (very likely never landed — still check before
|
|
77
|
+
* resubmitting), `below_commitment` (seen landing without error),
|
|
78
|
+
* `failed_below_commitment` (seen failing, not yet final), `rpc_failing`
|
|
79
|
+
* (reads failed) or `not_visible`; `context` also carries the last status
|
|
80
|
+
* seen and read counts, and `cause` the last failed read.
|
|
81
|
+
* @throws {@link AugustValidationError} for a non-positive or non-finite interval or timeout.
|
|
82
|
+
* @remarks **RPC cost.** The first poll runs right after sending. Per poll: one `getSignatureStatuses`, plus one
|
|
83
|
+
* finalized `getBlockHeight` while the transaction is not visible (and, past
|
|
84
|
+
* expiry, one history lookup). Typically 1–3 calls at `confirmed`, ~7–10 at
|
|
85
|
+
* `finalized`, ~60 for an expiring transaction; the `timeoutMs` backstop
|
|
86
|
+
* allows ~60 polls of 1–3 calls, fewer while reads fail: the wait doubles
|
|
87
|
+
* after each poll with a failed read, up to {@link CONFIRM_MAX_BACKOFF_MS}. Failed reads are tolerated, so a transient RPC
|
|
88
|
+
* error never misreports a landed transaction.
|
|
89
|
+
*/
|
|
90
|
+
async function confirmByBlockhash({ connection, signature, lastValidBlockHeight, commitment, pollIntervalMs = exports.CONFIRM_POLL_INTERVAL_MS, timeoutMs = exports.CONFIRM_TIMEOUT_MS, readTimeoutMs = exports.CONFIRM_READ_TIMEOUT_MS, }) {
|
|
91
|
+
for (const [name, value] of [
|
|
92
|
+
['pollIntervalMs', pollIntervalMs],
|
|
93
|
+
['timeoutMs', timeoutMs],
|
|
94
|
+
['readTimeoutMs', readTimeoutMs],
|
|
95
|
+
]) {
|
|
96
|
+
if (!Number.isFinite(value) || value <= 0)
|
|
97
|
+
throw new core_1.AugustValidationError('INVALID_INPUT', `confirmByBlockhash: ${name} must be a positive finite number, got ${value}.`);
|
|
98
|
+
}
|
|
99
|
+
const deadline = Date.now() + timeoutMs;
|
|
100
|
+
let reads = 0;
|
|
101
|
+
let failedReads = 0;
|
|
102
|
+
let lastReadError;
|
|
103
|
+
let lastSeen;
|
|
104
|
+
/** Finalized block height at which the transaction was found expired and absent. */
|
|
105
|
+
let expiredAt;
|
|
106
|
+
/** One RPC read; `undefined` when it failed or hung (outcome unknown). */
|
|
107
|
+
const read = async (call) => {
|
|
108
|
+
reads += 1;
|
|
109
|
+
const remaining = deadline - Date.now();
|
|
110
|
+
// Records whether the RPC itself rejected, so a timeout is told apart from
|
|
111
|
+
// a failure by which promise lost, not by reading the clock.
|
|
112
|
+
let rpcFailure;
|
|
113
|
+
const pending = call().catch((error) => {
|
|
114
|
+
rpcFailure = { error };
|
|
115
|
+
throw error;
|
|
116
|
+
});
|
|
117
|
+
try {
|
|
118
|
+
return await (0, concurrency_1.withTimeout)(pending, Math.min(readTimeoutMs, remaining), 'Solana RPC read');
|
|
119
|
+
}
|
|
120
|
+
catch (timeout) {
|
|
121
|
+
// The timer was the backstop's, not the per-read cap: not an RPC failure.
|
|
122
|
+
if (!rpcFailure && remaining < readTimeoutMs)
|
|
123
|
+
return undefined;
|
|
124
|
+
failedReads += 1;
|
|
125
|
+
lastReadError = rpcFailure ? rpcFailure.error : timeout;
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const statusOf = (searchTransactionHistory = false) => read(() => connection
|
|
130
|
+
.getSignatureStatuses([signature], { searchTransactionHistory })
|
|
131
|
+
.then((r) => r.value[0]));
|
|
132
|
+
/**
|
|
133
|
+
* `true` once `status` is final at `commitment`; throws if it failed there.
|
|
134
|
+
* A failure seen below `commitment` is not final yet, as in Anchor.
|
|
135
|
+
*/
|
|
136
|
+
const settled = async (status) => {
|
|
137
|
+
lastSeen = status;
|
|
138
|
+
if (!reaches(status, commitment))
|
|
139
|
+
return false;
|
|
140
|
+
if (!status.err)
|
|
141
|
+
return true;
|
|
142
|
+
const tx = await read(() => connection.getTransaction(signature, {
|
|
143
|
+
commitment: 'confirmed',
|
|
144
|
+
maxSupportedTransactionVersion: 0,
|
|
145
|
+
}));
|
|
146
|
+
// Keep `JSON.stringify(status.err)` in the message: without logs, Anchor's
|
|
147
|
+
// `ProgramError.parse` recovers the program error code from its
|
|
148
|
+
// `"Custom":N}`.
|
|
149
|
+
throw new web3_js_1.SendTransactionError({
|
|
150
|
+
action: 'send',
|
|
151
|
+
signature,
|
|
152
|
+
transactionMessage: `Transaction failed: ${JSON.stringify(status.err)}`,
|
|
153
|
+
logs: tx?.meta?.logMessages ?? undefined,
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
/** One poll: `true` once confirmed; throws on a definitive failure. */
|
|
157
|
+
const pollOnce = async () => {
|
|
158
|
+
// `undefined` = read failed (unknown); `null` = not visible to the RPC.
|
|
159
|
+
const status = await statusOf();
|
|
160
|
+
if (status)
|
|
161
|
+
return settled(status);
|
|
162
|
+
// Seen before but gone now (dropped fork): no longer evidence it landed.
|
|
163
|
+
if (status === null)
|
|
164
|
+
lastSeen = undefined;
|
|
165
|
+
if (status === undefined || lastValidBlockHeight === undefined)
|
|
166
|
+
return false;
|
|
167
|
+
// Finalized, whatever `commitment` is: a `processed`/`confirmed` height can
|
|
168
|
+
// belong to a fork that is later dropped while the transaction still lands
|
|
169
|
+
// on the surviving one. Past the finalized height, every fork is expired.
|
|
170
|
+
const height = await read(() => connection.getBlockHeight('finalized'));
|
|
171
|
+
if (height === undefined || height <= lastValidBlockHeight)
|
|
172
|
+
return false;
|
|
173
|
+
// Past expiry. Last look, including history, in case it landed between polls.
|
|
174
|
+
const final = await statusOf(true);
|
|
175
|
+
if (final)
|
|
176
|
+
return settled(final);
|
|
177
|
+
if (final === null)
|
|
178
|
+
expiredAt = height;
|
|
179
|
+
return false;
|
|
180
|
+
};
|
|
181
|
+
// Poll first: a transaction often lands within a second of sending. After a
|
|
182
|
+
// poll with a failed read, the wait doubles (up to the ceiling) so a failing
|
|
183
|
+
// RPC is not hit at full cadence; a clean poll restores the normal interval.
|
|
184
|
+
let backoff = 0;
|
|
185
|
+
for (;;) {
|
|
186
|
+
const failedBefore = failedReads;
|
|
187
|
+
if (await pollOnce())
|
|
188
|
+
return signature;
|
|
189
|
+
if (expiredAt !== undefined)
|
|
190
|
+
break;
|
|
191
|
+
backoff = failedReads > failedBefore ? backoff + 1 : 0;
|
|
192
|
+
const interval = Math.min(pollIntervalMs * 2 ** backoff, Math.max(pollIntervalMs, exports.CONFIRM_MAX_BACKOFF_MS));
|
|
193
|
+
const wait = Math.min(interval, deadline - Date.now());
|
|
194
|
+
if (wait <= 0)
|
|
195
|
+
break;
|
|
196
|
+
await new Promise((r) => setTimeout(r, wait));
|
|
197
|
+
}
|
|
198
|
+
const seconds = timeoutMs / 1000;
|
|
199
|
+
const seenAt = lastSeen ? levelOf(lastSeen) : null;
|
|
200
|
+
const reason = expiredAt !== undefined
|
|
201
|
+
? 'expired_not_found'
|
|
202
|
+
: !lastSeen
|
|
203
|
+
? failedReads > 0
|
|
204
|
+
? 'rpc_failing'
|
|
205
|
+
: 'not_visible'
|
|
206
|
+
: lastSeen.err
|
|
207
|
+
? 'failed_below_commitment'
|
|
208
|
+
: 'below_commitment';
|
|
209
|
+
const message = {
|
|
210
|
+
expired_not_found: `Transaction ${signature} was not found and its blockhash has expired (block height exceeded), ` +
|
|
211
|
+
'so it very likely never landed. This cannot be proven from the RPC; check the signature before resubmitting.',
|
|
212
|
+
below_commitment: `Transaction ${signature} landed without error (${seenAt}) but was not confirmed in ${seconds} seconds ` +
|
|
213
|
+
`at '${commitment}'. Do not resubmit; it is most likely to finalize.`,
|
|
214
|
+
failed_below_commitment: `Transaction ${signature} was seen failing (${seenAt}) but was not confirmed in ${seconds} seconds ` +
|
|
215
|
+
`at '${commitment}', so the failure is not final yet. Check the signature before resubmitting.`,
|
|
216
|
+
rpc_failing: undefined,
|
|
217
|
+
not_visible: undefined,
|
|
218
|
+
}[reason];
|
|
219
|
+
throw new core_1.AugustTransactionError(message ??
|
|
220
|
+
`Transaction ${signature} was not confirmed in ${seconds} seconds. ` +
|
|
221
|
+
'It is unknown if it succeeded or failed; check the signature before resubmitting.', signature, {
|
|
222
|
+
cause: expiredAt !== undefined
|
|
223
|
+
? new web3_js_1.TransactionExpiredBlockheightExceededError(signature)
|
|
224
|
+
: lastReadError,
|
|
225
|
+
context: {
|
|
226
|
+
signature,
|
|
227
|
+
commitment,
|
|
228
|
+
reason,
|
|
229
|
+
...(expiredAt !== undefined && {
|
|
230
|
+
lastValidBlockHeight,
|
|
231
|
+
blockHeight: expiredAt,
|
|
232
|
+
}),
|
|
233
|
+
lastSeenStatus: seenAt,
|
|
234
|
+
lastSeenErr: lastSeen?.err ?? null,
|
|
235
|
+
reads,
|
|
236
|
+
failedReads,
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* `AnchorProvider` whose `sendAndConfirm` — what `program.methods…rpc()` calls —
|
|
242
|
+
* confirms with {@link confirmByBlockhash} instead of Anchor's signature-only
|
|
243
|
+
* WebSocket wait. Signing and Anchor's error translation are unchanged.
|
|
244
|
+
*/
|
|
245
|
+
class BlockhashConfirmingProvider extends anchor_1.AnchorProvider {
|
|
246
|
+
/**
|
|
247
|
+
* Sign, send and confirm a transaction.
|
|
248
|
+
*
|
|
249
|
+
* @param tx - Transaction to send. Its blockhash (and fee payer, if unset)
|
|
250
|
+
* are set here. A `VersionedTransaction` already carries a blockhash this
|
|
251
|
+
* cannot learn the expiry of, so it takes Anchor's default path.
|
|
252
|
+
* @param signers - Extra signers besides the wallet.
|
|
253
|
+
* @param opts - Defaults to the provider's options.
|
|
254
|
+
* @returns The transaction signature.
|
|
255
|
+
* @throws See {@link confirmByBlockhash}; also whatever the wallet or
|
|
256
|
+
* `sendRawTransaction` throws (user rejection, preflight failure).
|
|
257
|
+
* @remarks **RPC cost.** `getLatestBlockhash` + `sendRawTransaction`, then
|
|
258
|
+
* the confirmation polls described on {@link confirmByBlockhash}.
|
|
259
|
+
*/
|
|
260
|
+
async sendAndConfirm(tx, signers, opts = this.opts) {
|
|
261
|
+
if ('version' in tx)
|
|
262
|
+
return super.sendAndConfirm(tx, signers, opts);
|
|
263
|
+
const commitment = opts.commitment ?? this.connection.commitment ?? 'finalized';
|
|
264
|
+
const preflightCommitment = opts.preflightCommitment ?? commitment;
|
|
265
|
+
const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash(preflightCommitment);
|
|
266
|
+
tx.feePayer = tx.feePayer ?? this.wallet.publicKey;
|
|
267
|
+
tx.recentBlockhash = blockhash;
|
|
268
|
+
for (const signer of signers ?? [])
|
|
269
|
+
tx.partialSign(signer);
|
|
270
|
+
const signed = await this.wallet.signTransaction(tx);
|
|
271
|
+
const signature = await this.connection.sendRawTransaction(signed.serialize(), {
|
|
272
|
+
skipPreflight: opts.skipPreflight,
|
|
273
|
+
preflightCommitment,
|
|
274
|
+
maxRetries: opts.maxRetries,
|
|
275
|
+
minContextSlot: opts.minContextSlot,
|
|
276
|
+
});
|
|
277
|
+
return confirmByBlockhash({
|
|
278
|
+
connection: this.connection,
|
|
279
|
+
signature,
|
|
280
|
+
// The expiry fetched here only bounds a tx that carries this blockhash.
|
|
281
|
+
// A wallet may re-stamp it when signing, and a durable-nonce tx
|
|
282
|
+
// serializes its nonce instead; either way it could still land.
|
|
283
|
+
lastValidBlockHeight: !signed.nonceInfo && signed.recentBlockhash === blockhash
|
|
284
|
+
? lastValidBlockHeight
|
|
285
|
+
: undefined,
|
|
286
|
+
commitment,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
exports.BlockhashConfirmingProvider = BlockhashConfirmingProvider;
|
|
291
|
+
//# sourceMappingURL=confirm.js.map
|
|
@@ -25,7 +25,7 @@ export declare const Solana: {
|
|
|
25
25
|
getProvider: ({ connection, publicKey, signTransaction, }: {
|
|
26
26
|
publicKey: PublicKey;
|
|
27
27
|
signTransaction: (<T extends Transaction | web3.VersionedTransaction>(transaction: T) => Promise<T>) | undefined;
|
|
28
|
-
} & import("./types").ISolanaConnectionOptions) =>
|
|
28
|
+
} & import("./types").ISolanaConnectionOptions) => import("./confirm").BlockhashConfirmingProvider;
|
|
29
29
|
getReadOnlyProvider: ({ connection }: import("./types").ISolanaConnectionOptions) => AnchorProvider;
|
|
30
30
|
deriveShareMintPda: (vaultProgramId: PublicKey | string) => web3.PublicKey;
|
|
31
31
|
deriveVaultStatePda: (vaultProgramId: PublicKey | string) => web3.PublicKey;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AnchorProvider, BN, Program, type web3 } from '@coral-xyz/anchor';
|
|
2
2
|
import { PublicKey, type ParsedAccountData, type Transaction } from '@solana/web3.js';
|
|
3
3
|
import type { ISolanaConnectionOptions, ISolanaVaultState } from './types';
|
|
4
|
+
import { BlockhashConfirmingProvider } from './confirm';
|
|
4
5
|
declare function getExplorerLink({ signature, type, network, }: {
|
|
5
6
|
signature: string;
|
|
6
7
|
type: 'tx' | 'address' | 'token';
|
|
@@ -16,7 +17,7 @@ declare function getBestRpcEndpoint({ endpoint, network, connection, }: ISolanaC
|
|
|
16
17
|
declare function getProvider({ connection, publicKey, signTransaction, }: {
|
|
17
18
|
publicKey: PublicKey;
|
|
18
19
|
signTransaction: (<T extends Transaction | web3.VersionedTransaction>(transaction: T) => Promise<T>) | undefined;
|
|
19
|
-
} & ISolanaConnectionOptions):
|
|
20
|
+
} & ISolanaConnectionOptions): BlockhashConfirmingProvider;
|
|
20
21
|
declare function getReadOnlyProvider({ connection }: ISolanaConnectionOptions): AnchorProvider;
|
|
21
22
|
declare function deriveVaultStatePda(vaultProgramId: PublicKey | string): web3.PublicKey;
|
|
22
23
|
declare function deriveShareMintPda(vaultProgramId: PublicKey | string): web3.PublicKey;
|
|
@@ -7,6 +7,7 @@ const anchor_1 = require("@coral-xyz/anchor");
|
|
|
7
7
|
const web3_js_1 = require("@solana/web3.js");
|
|
8
8
|
const ethers_1 = require("ethers");
|
|
9
9
|
const constants_1 = require("./constants");
|
|
10
|
+
const confirm_1 = require("./confirm");
|
|
10
11
|
const core_1 = require("../../core");
|
|
11
12
|
// ============================================================================
|
|
12
13
|
// SOLANA UTILITIES
|
|
@@ -96,7 +97,8 @@ function getProvider({ connection, publicKey, signTransaction, }) {
|
|
|
96
97
|
if (!publicKey || !signTransaction) {
|
|
97
98
|
throw new core_1.AugustValidationError('INVALID_INPUT', 'Wallet not connected: publicKey and signTransaction are required for write operations');
|
|
98
99
|
}
|
|
99
|
-
|
|
100
|
+
// Anchor's own confirmation misreports landed txs as timed out; see confirm.ts.
|
|
101
|
+
return new confirm_1.BlockhashConfirmingProvider(connection, {
|
|
100
102
|
publicKey,
|
|
101
103
|
signTransaction,
|
|
102
104
|
signAllTransactions: async (txs) => {
|
|
@@ -45,6 +45,9 @@ export declare function slippageHint(e: unknown): string;
|
|
|
45
45
|
* or when the wallet's deposit-mint balance is below `depositAmount`.
|
|
46
46
|
* @throws AugustSDKError when a candidate token account's balance cannot be
|
|
47
47
|
* read, or wrapping any downstream failure (`cause` preserved).
|
|
48
|
+
* @throws AugustTransactionError (`TRANSACTION_UNCONFIRMED`) when the
|
|
49
|
+
* transaction was sent but its outcome could not be established; check
|
|
50
|
+
* `signature` before resubmitting.
|
|
48
51
|
* @remarks **Side effects.** Submits exactly one transaction, signed by the
|
|
49
52
|
* provider's wallet. When the wallet has no share account, that transaction
|
|
50
53
|
* also creates one, and `publicKey` pays its rent — roughly 0.00204 SOL,
|
|
@@ -92,6 +95,9 @@ export declare function handleSolanaDeposit({ provider, connection, network, vau
|
|
|
92
95
|
* or when the wallet holds fewer shares than `redeemShares`.
|
|
93
96
|
* @throws AugustSDKError when a candidate token account's balance cannot be
|
|
94
97
|
* read, or wrapping any downstream failure (`cause` preserved).
|
|
98
|
+
* @throws AugustTransactionError (`TRANSACTION_UNCONFIRMED`) when the
|
|
99
|
+
* transaction was sent but its outcome could not be established; check
|
|
100
|
+
* `signature` before resubmitting.
|
|
95
101
|
* @remarks **Side effects.** Submits exactly one transaction, signed by the
|
|
96
102
|
* provider's wallet. That transaction also creates the payout account when
|
|
97
103
|
* the wallet has none, and the vault's fee-recipient account when *it* has
|
|
@@ -301,6 +301,9 @@ async function resolveUserTokenAccountPair({ connection, owner, mints, }) {
|
|
|
301
301
|
* or when the wallet's deposit-mint balance is below `depositAmount`.
|
|
302
302
|
* @throws AugustSDKError when a candidate token account's balance cannot be
|
|
303
303
|
* read, or wrapping any downstream failure (`cause` preserved).
|
|
304
|
+
* @throws AugustTransactionError (`TRANSACTION_UNCONFIRMED`) when the
|
|
305
|
+
* transaction was sent but its outcome could not be established; check
|
|
306
|
+
* `signature` before resubmitting.
|
|
304
307
|
* @remarks **Side effects.** Submits exactly one transaction, signed by the
|
|
305
308
|
* provider's wallet. When the wallet has no share account, that transaction
|
|
306
309
|
* also creates one, and `publicKey` pays its rent — roughly 0.00204 SOL,
|
|
@@ -547,6 +550,9 @@ async function handleSolanaDeposit({ provider, connection, network = constants_1
|
|
|
547
550
|
* or when the wallet holds fewer shares than `redeemShares`.
|
|
548
551
|
* @throws AugustSDKError when a candidate token account's balance cannot be
|
|
549
552
|
* read, or wrapping any downstream failure (`cause` preserved).
|
|
553
|
+
* @throws AugustTransactionError (`TRANSACTION_UNCONFIRMED`) when the
|
|
554
|
+
* transaction was sent but its outcome could not be established; check
|
|
555
|
+
* `signature` before resubmitting.
|
|
550
556
|
* @remarks **Side effects.** Submits exactly one transaction, signed by the
|
|
551
557
|
* provider's wallet. That transaction also creates the payout account when
|
|
552
558
|
* the wallet has none, and the vault's fee-recipient account when *it* has
|
|
@@ -19,6 +19,7 @@ exports.queryContract = queryContract;
|
|
|
19
19
|
exports.buildSorobanTx = buildSorobanTx;
|
|
20
20
|
const stellar_sdk_1 = require("@stellar/stellar-sdk");
|
|
21
21
|
const core_1 = require("../../core");
|
|
22
|
+
const concurrency_1 = require("../../core/helpers/concurrency");
|
|
22
23
|
const constants_1 = require("./constants");
|
|
23
24
|
function createServer(rpcUrl) {
|
|
24
25
|
return new stellar_sdk_1.rpc.Server(rpcUrl, {
|
|
@@ -149,17 +150,6 @@ function scrubErrorUrls(value, scrub, depth = 0, seen = new WeakSet()) {
|
|
|
149
150
|
}
|
|
150
151
|
return out;
|
|
151
152
|
}
|
|
152
|
-
/**
|
|
153
|
-
* Race a promise against a timeout so a hung call can't block indefinitely. The
|
|
154
|
-
* timer is always cleared, so a fast-resolving promise leaves nothing pending.
|
|
155
|
-
*/
|
|
156
|
-
function withTimeout(promise, ms, label) {
|
|
157
|
-
let timer;
|
|
158
|
-
const timeout = new Promise((_resolve, reject) => {
|
|
159
|
-
timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
160
|
-
});
|
|
161
|
-
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
|
162
|
-
}
|
|
163
153
|
/**
|
|
164
154
|
* Return a Soroban RPC server backed by a healthy endpoint for `config`. Thin
|
|
165
155
|
* wrapper over {@link resolveHealthyServer} for callers (e.g. submission) that
|
|
@@ -241,7 +231,7 @@ async function probeHealthyServer(rpcUrls, cacheKey) {
|
|
|
241
231
|
firstConstructableUrl = url;
|
|
242
232
|
}
|
|
243
233
|
try {
|
|
244
|
-
const health = await withTimeout(server.getHealth(), HEALTH_PROBE_TIMEOUT_MS, 'getHealth');
|
|
234
|
+
const health = await (0, concurrency_1.withTimeout)(server.getHealth(), HEALTH_PROBE_TIMEOUT_MS, 'getHealth');
|
|
245
235
|
if (health.status === 'healthy') {
|
|
246
236
|
healthyServerCache.set(cacheKey, {
|
|
247
237
|
server,
|
|
@@ -355,7 +345,7 @@ async function withEndpointFailover(config, method, operation, options = {}) {
|
|
|
355
345
|
const rpcUrls = config.rpcUrls && config.rpcUrls.length > 0
|
|
356
346
|
? config.rpcUrls
|
|
357
347
|
: [config.rpcUrl];
|
|
358
|
-
const runOn = (server) => withTimeout(operation(server), timeoutMs, `Soroban ${method}`);
|
|
348
|
+
const runOn = (server) => (0, concurrency_1.withTimeout)(operation(server), timeoutMs, `Soroban ${method}`);
|
|
359
349
|
const cacheKey = rpcUrls.join('|');
|
|
360
350
|
// Reduce every configured endpoint URL to protocol+host in anything surfaced
|
|
361
351
|
// from here (logs, the thrown message, the chained cause). SDK errors embed the
|
|
@@ -117,6 +117,7 @@ export declare const WEBSERVER_ENDPOINTS: {
|
|
|
117
117
|
summary: (vaultAddress: IAddress) => string;
|
|
118
118
|
withdrawals: (chain: string, vaultAddress: IAddress) => string;
|
|
119
119
|
debank: (vaultAddress: IAddress) => string;
|
|
120
|
+
octav: (vaultAddress: IAddress) => string;
|
|
120
121
|
userHistory: (vaultAddress: IAddress, wallet: IAddress) => string;
|
|
121
122
|
archivedVaults: string;
|
|
122
123
|
};
|
|
@@ -184,6 +184,7 @@ exports.WEBSERVER_ENDPOINTS = {
|
|
|
184
184
|
summary: (vaultAddress) => `/tokenized_vault/vault_summary/${(0, ethers_1.getAddress)(vaultAddress)}`,
|
|
185
185
|
withdrawals: (chain, vaultAddress) => `/withdrawals/${chain}/${(0, ethers_1.getAddress)(vaultAddress)}`,
|
|
186
186
|
debank: (vaultAddress) => `/tokenized_vault/${(0, ethers_1.getAddress)(vaultAddress)}/debank`,
|
|
187
|
+
octav: (vaultAddress) => `/tokenized_vault/${subaccountSegment(vaultAddress)}/octav`,
|
|
187
188
|
// Mongo-backed deposit/withdraw history for archived vaults whose
|
|
188
189
|
// subgraph has been (or will be) retired. See fetchArchivedVaults.
|
|
189
190
|
userHistory: (vaultAddress, wallet) => `/upshift/vault_history/${(0, ethers_1.getAddress)(vaultAddress)}?wallet=${(0, ethers_1.getAddress)(wallet)}`,
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* ```
|
|
16
16
|
*/
|
|
17
17
|
/** Stable error codes attached to SDK errors. */
|
|
18
|
-
export type AugustErrorCode = 'AUTH_MISSING_KEY' | 'AUTH_INVALID_KEY' | 'AUTH_FORBIDDEN' | 'AUTH_UNAUTHORIZED' | 'INVALID_URL' | 'INVALID_INPUT' | 'INVALID_CHAIN' | 'INVALID_ADDRESS' | 'ACCOUNT_NOT_FUNDED' | 'NETWORK_ERROR' | 'TIMEOUT' | 'RATE_LIMITED' | 'SERVER_ERROR' | 'HISTORY_UNAVAILABLE' | 'PRICE_UNAVAILABLE' | 'UNKNOWN';
|
|
18
|
+
export type AugustErrorCode = 'AUTH_MISSING_KEY' | 'AUTH_INVALID_KEY' | 'AUTH_FORBIDDEN' | 'AUTH_UNAUTHORIZED' | 'INVALID_URL' | 'INVALID_INPUT' | 'INVALID_CHAIN' | 'INVALID_ADDRESS' | 'ACCOUNT_NOT_FUNDED' | 'NETWORK_ERROR' | 'TIMEOUT' | 'RATE_LIMITED' | 'SERVER_ERROR' | 'TRANSACTION_UNCONFIRMED' | 'HISTORY_UNAVAILABLE' | 'PRICE_UNAVAILABLE' | 'UNKNOWN';
|
|
19
19
|
/** Options accepted by every SDK error constructor. */
|
|
20
20
|
export interface AugustErrorOptions {
|
|
21
21
|
/** Original error preserved for stack-trace chaining. */
|
|
@@ -145,5 +145,33 @@ export declare class AugustPriceUnavailableError extends AugustSDKError {
|
|
|
145
145
|
*/
|
|
146
146
|
constructor(message: string, options?: AugustErrorOptions);
|
|
147
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* A transaction was sent but its outcome could not be established
|
|
150
|
+
* (`TRANSACTION_UNCONFIRMED`). It may have landed: check {@link signature}
|
|
151
|
+
* before resubmitting, or a deposit or redeem can execute twice.
|
|
152
|
+
* `context.reason` says what was seen (e.g. `expired_not_found`: the blockhash
|
|
153
|
+
* expired and no lookup found it, so it very likely never landed).
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```ts
|
|
157
|
+
* try {
|
|
158
|
+
* await sdk.solana.vaultDeposit(programId, idl, wallet, amount, undefined, vault);
|
|
159
|
+
* } catch (err) {
|
|
160
|
+
* if (err instanceof AugustTransactionError) showPending(err.signature);
|
|
161
|
+
* else throw err;
|
|
162
|
+
* }
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
export declare class AugustTransactionError extends AugustSDKError {
|
|
166
|
+
/** Signature of the transaction that was sent. */
|
|
167
|
+
readonly signature: string;
|
|
168
|
+
/**
|
|
169
|
+
* @param message - Human-readable description, naming the signature.
|
|
170
|
+
* @param signature - Signature of the transaction that was sent.
|
|
171
|
+
* @param options - Standard SDK error options.
|
|
172
|
+
*/
|
|
173
|
+
constructor(message: string, signature: string, options?: AugustErrorOptions);
|
|
174
|
+
toJSON(): Record<string, unknown>;
|
|
175
|
+
}
|
|
148
176
|
/** Type guard. Works across realms (e.g. Web Worker / VM contexts). */
|
|
149
177
|
export declare function isAugustSDKError(err: unknown): err is AugustSDKError;
|
package/lib/core/errors/index.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* ```
|
|
17
17
|
*/
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
-
exports.AugustPriceUnavailableError = exports.AugustHistoryUnavailableError = exports.AugustServerError = exports.AugustRateLimitError = exports.AugustValidationError = exports.AugustTimeoutError = exports.AugustNetworkError = exports.AugustAuthError = exports.AugustSDKError = void 0;
|
|
19
|
+
exports.AugustTransactionError = exports.AugustPriceUnavailableError = exports.AugustHistoryUnavailableError = exports.AugustServerError = exports.AugustRateLimitError = exports.AugustValidationError = exports.AugustTimeoutError = exports.AugustNetworkError = exports.AugustAuthError = exports.AugustSDKError = void 0;
|
|
20
20
|
exports.isAugustSDKError = isAugustSDKError;
|
|
21
21
|
/** Base class for all SDK errors. */
|
|
22
22
|
class AugustSDKError extends Error {
|
|
@@ -212,6 +212,41 @@ class AugustPriceUnavailableError extends AugustSDKError {
|
|
|
212
212
|
}
|
|
213
213
|
}
|
|
214
214
|
exports.AugustPriceUnavailableError = AugustPriceUnavailableError;
|
|
215
|
+
/**
|
|
216
|
+
* A transaction was sent but its outcome could not be established
|
|
217
|
+
* (`TRANSACTION_UNCONFIRMED`). It may have landed: check {@link signature}
|
|
218
|
+
* before resubmitting, or a deposit or redeem can execute twice.
|
|
219
|
+
* `context.reason` says what was seen (e.g. `expired_not_found`: the blockhash
|
|
220
|
+
* expired and no lookup found it, so it very likely never landed).
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* ```ts
|
|
224
|
+
* try {
|
|
225
|
+
* await sdk.solana.vaultDeposit(programId, idl, wallet, amount, undefined, vault);
|
|
226
|
+
* } catch (err) {
|
|
227
|
+
* if (err instanceof AugustTransactionError) showPending(err.signature);
|
|
228
|
+
* else throw err;
|
|
229
|
+
* }
|
|
230
|
+
* ```
|
|
231
|
+
*/
|
|
232
|
+
class AugustTransactionError extends AugustSDKError {
|
|
233
|
+
/** Signature of the transaction that was sent. */
|
|
234
|
+
signature;
|
|
235
|
+
/**
|
|
236
|
+
* @param message - Human-readable description, naming the signature.
|
|
237
|
+
* @param signature - Signature of the transaction that was sent.
|
|
238
|
+
* @param options - Standard SDK error options.
|
|
239
|
+
*/
|
|
240
|
+
constructor(message, signature, options) {
|
|
241
|
+
super('TRANSACTION_UNCONFIRMED', message, options);
|
|
242
|
+
this.name = 'AugustTransactionError';
|
|
243
|
+
this.signature = signature;
|
|
244
|
+
}
|
|
245
|
+
toJSON() {
|
|
246
|
+
return { ...super.toJSON(), signature: this.signature };
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
exports.AugustTransactionError = AugustTransactionError;
|
|
215
250
|
/** Type guard. Works across realms (e.g. Web Worker / VM contexts). */
|
|
216
251
|
function isAugustSDKError(err) {
|
|
217
252
|
return (err instanceof AugustSDKError ||
|