@augustdigital/sdk 9.0.0 → 9.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/adapters/stellar/actions.d.ts +13 -0
- package/lib/adapters/stellar/actions.js +51 -2
- package/lib/adapters/stellar/index.d.ts +12 -0
- package/lib/adapters/stellar/index.js +12 -0
- package/lib/adapters/stellar/invocation.d.ts +55 -0
- package/lib/adapters/stellar/invocation.js +92 -0
- package/lib/adapters/stellar/soroban.d.ts +8 -0
- package/lib/adapters/stellar/soroban.js +18 -4
- package/lib/adapters/stellar/submit.d.ts +8 -0
- package/lib/adapters/stellar/submit.js +46 -0
- package/lib/core/analytics/env.d.ts +17 -0
- package/lib/core/analytics/env.js +88 -0
- package/lib/core/analytics/sentry.js +1 -21
- package/lib/core/analytics/version.d.ts +1 -1
- package/lib/core/analytics/version.js +1 -1
- package/lib/core/base.class.d.ts +22 -0
- package/lib/core/base.class.js +11 -0
- package/lib/core/logger/curator-alert.d.ts +217 -0
- package/lib/core/logger/curator-alert.js +276 -0
- package/lib/core/logger/index.d.ts +1 -0
- package/lib/core/logger/index.js +20 -1
- package/lib/main.d.ts +6 -0
- package/lib/main.js +6 -0
- package/lib/modules/vaults/getters.d.ts +9 -0
- package/lib/modules/vaults/getters.js +25 -0
- package/lib/sdk.d.ts +153 -0
- package/lib/services/untangled/fetcher.d.ts +40 -0
- package/lib/services/untangled/fetcher.js +110 -0
- package/lib/services/untangled/index.d.ts +3 -0
- package/lib/services/untangled/index.js +20 -0
- package/lib/services/untangled/types.d.ts +82 -0
- package/lib/services/untangled/types.js +10 -0
- package/lib/services/untangled/utils.d.ts +55 -0
- package/lib/services/untangled/utils.js +157 -0
- package/package.json +1 -1
|
@@ -22,6 +22,19 @@ export declare function handleStellarDeposit(params: IStellarDepositParams): Pro
|
|
|
22
22
|
* `redeem(shares: i128, receiver, owner, operator) -> i128`; divergence
|
|
23
23
|
* surfaces as a generic Soroban simulation error.
|
|
24
24
|
*
|
|
25
|
+
* Side effect on failure: when the *vault* cannot serve the redemption — it
|
|
26
|
+
* rejected the call, or its ledger state needs restoring — the failure is
|
|
27
|
+
* reported to that vault's curator. Stellar vaults are instant-redeem only, so
|
|
28
|
+
* this is the curator's only signal that their depositors cannot get out
|
|
29
|
+
* (AUGUST-7162). Fire-and-forget and deduped; see
|
|
30
|
+
* {@link reportRedeemFailure} and {@link REPORTED_BUILD_STAGES}.
|
|
31
|
+
*
|
|
32
|
+
* Note this includes a caller-caused rejection such as redeeming more shares
|
|
33
|
+
* than the wallet holds: the contract trapping is what we can observe, and
|
|
34
|
+
* telling that apart from a vault-side problem needs the vault's error
|
|
35
|
+
* taxonomy. A bad address, an unfunded account, and an exhausted RPC failover
|
|
36
|
+
* are excluded — none of those reached the vault.
|
|
37
|
+
*
|
|
25
38
|
* @returns Base64-encoded XDR ready for wallet signing; pass the signed
|
|
26
39
|
* XDR to {@link submitStellarTransaction}.
|
|
27
40
|
*/
|
|
@@ -12,6 +12,21 @@ const stellar_sdk_1 = require("@stellar/stellar-sdk");
|
|
|
12
12
|
const soroban_1 = require("./soroban");
|
|
13
13
|
const utils_1 = require("./utils");
|
|
14
14
|
const core_1 = require("../../core");
|
|
15
|
+
const curator_alert_1 = require("../../core/logger/curator-alert");
|
|
16
|
+
/**
|
|
17
|
+
* Build failures that mean the *vault* cannot serve a redemption right now, as
|
|
18
|
+
* opposed to a caller problem or a transport problem.
|
|
19
|
+
*
|
|
20
|
+
* `simulation` is the contract rejecting the call. `restore-required` is its
|
|
21
|
+
* ledger state having been archived, which blocks every redemption on the vault
|
|
22
|
+
* until someone submits a restore footprint — different remedy, same
|
|
23
|
+
* consequence for a depositor trying to get out.
|
|
24
|
+
*
|
|
25
|
+
* Deliberately excluded: `rpc-failover` (our transport, not their vault),
|
|
26
|
+
* `assembly` (an SDK-side fault), and validation errors, which carry no stage
|
|
27
|
+
* at all.
|
|
28
|
+
*/
|
|
29
|
+
const REPORTED_BUILD_STAGES = new Set(['simulation', 'restore-required']);
|
|
15
30
|
function validateContractAddress(contractId) {
|
|
16
31
|
if (!(0, utils_1.isStellarAddress)(contractId) || contractId[0] !== 'C') {
|
|
17
32
|
throw new core_1.AugustValidationError('INVALID_ADDRESS', `Invalid contract address: expected a Stellar contract (C-prefix), got "${contractId}"`);
|
|
@@ -63,6 +78,19 @@ async function handleStellarDeposit(params) {
|
|
|
63
78
|
* `redeem(shares: i128, receiver, owner, operator) -> i128`; divergence
|
|
64
79
|
* surfaces as a generic Soroban simulation error.
|
|
65
80
|
*
|
|
81
|
+
* Side effect on failure: when the *vault* cannot serve the redemption — it
|
|
82
|
+
* rejected the call, or its ledger state needs restoring — the failure is
|
|
83
|
+
* reported to that vault's curator. Stellar vaults are instant-redeem only, so
|
|
84
|
+
* this is the curator's only signal that their depositors cannot get out
|
|
85
|
+
* (AUGUST-7162). Fire-and-forget and deduped; see
|
|
86
|
+
* {@link reportRedeemFailure} and {@link REPORTED_BUILD_STAGES}.
|
|
87
|
+
*
|
|
88
|
+
* Note this includes a caller-caused rejection such as redeeming more shares
|
|
89
|
+
* than the wallet holds: the contract trapping is what we can observe, and
|
|
90
|
+
* telling that apart from a vault-side problem needs the vault's error
|
|
91
|
+
* taxonomy. A bad address, an unfunded account, and an exhausted RPC failover
|
|
92
|
+
* are excluded — none of those reached the vault.
|
|
93
|
+
*
|
|
66
94
|
* @returns Base64-encoded XDR ready for wallet signing; pass the signed
|
|
67
95
|
* XDR to {@link submitStellarTransaction}.
|
|
68
96
|
*/
|
|
@@ -71,7 +99,28 @@ async function handleStellarRedeem(params) {
|
|
|
71
99
|
validateContractAddress(contractId);
|
|
72
100
|
validateAccountAddress(receiverAddress, 'receiver');
|
|
73
101
|
const config = (0, soroban_1.resolveNetworkConfig)(network, sorobanRpcUrl);
|
|
74
|
-
|
|
75
|
-
|
|
102
|
+
// Normalized once and reused for both the invocation and the report: the
|
|
103
|
+
// caller's string may be any form `BigInt` accepts (`'0x10'`, `' 42 '`), and
|
|
104
|
+
// relaying that verbatim hands the reporting endpoint something it rejects.
|
|
105
|
+
const shareAmount = (0, soroban_1.toBigIntAmount)(shares, 'redeem shares');
|
|
106
|
+
const args = buildSelfOperationArgs(shareAmount, receiverAddress);
|
|
107
|
+
try {
|
|
108
|
+
return await (0, soroban_1.buildSorobanTx)(config, receiverAddress, contractId, 'redeem', args);
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
if (err instanceof core_1.AugustSDKError &&
|
|
112
|
+
REPORTED_BUILD_STAGES.has(String(err.context?.stage))) {
|
|
113
|
+
(0, curator_alert_1.reportRedeemFailure)({
|
|
114
|
+
phase: 'simulation',
|
|
115
|
+
chain: 'stellar',
|
|
116
|
+
network,
|
|
117
|
+
contractId,
|
|
118
|
+
eoa: receiverAddress,
|
|
119
|
+
sharesRaw: shareAmount.toString(),
|
|
120
|
+
error: String(err.message),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
throw err;
|
|
124
|
+
}
|
|
76
125
|
}
|
|
77
126
|
//# sourceMappingURL=actions.js.map
|
|
@@ -64,10 +64,22 @@ export declare class StellarAdapter {
|
|
|
64
64
|
vaultDeposit(params: Omit<IStellarDepositParams, 'network'>): Promise<string>;
|
|
65
65
|
/**
|
|
66
66
|
* Build an unsigned redeem transaction for a Stellar vault.
|
|
67
|
+
*
|
|
68
|
+
* Side effect on failure: a redemption the vault cannot serve is reported to
|
|
69
|
+
* that vault's curator, since Stellar vaults are instant-redeem only and this
|
|
70
|
+
* is the curator's only signal. Fire-and-forget — it never delays or alters
|
|
71
|
+
* the error you receive. On by default in production; see
|
|
72
|
+
* `monitoring.curatorAlerts` and the Curator Notifications section of the
|
|
73
|
+
* Stellar Actions guide for exactly what is sent and how to opt out.
|
|
74
|
+
*
|
|
67
75
|
* @returns Base64-encoded XDR of the unsigned transaction.
|
|
68
76
|
*/
|
|
69
77
|
vaultRedeem(params: Omit<IStellarRedeemParams, 'network'>): Promise<string>;
|
|
70
78
|
/**
|
|
79
|
+
* Side effect: a submitted transaction that the network reports as a failed
|
|
80
|
+
* `redeem` operation is reported to that vault's curator, on the same
|
|
81
|
+
* fire-and-forget terms as {@link StellarAdapter.vaultRedeem}.
|
|
82
|
+
*
|
|
71
83
|
* Submit a signed Soroban transaction and poll until the network confirms it.
|
|
72
84
|
*
|
|
73
85
|
* Submits on the network this adapter was constructed with, so `signedXdr`
|
|
@@ -110,6 +110,14 @@ class StellarAdapter {
|
|
|
110
110
|
}
|
|
111
111
|
/**
|
|
112
112
|
* Build an unsigned redeem transaction for a Stellar vault.
|
|
113
|
+
*
|
|
114
|
+
* Side effect on failure: a redemption the vault cannot serve is reported to
|
|
115
|
+
* that vault's curator, since Stellar vaults are instant-redeem only and this
|
|
116
|
+
* is the curator's only signal. Fire-and-forget — it never delays or alters
|
|
117
|
+
* the error you receive. On by default in production; see
|
|
118
|
+
* `monitoring.curatorAlerts` and the Curator Notifications section of the
|
|
119
|
+
* Stellar Actions guide for exactly what is sent and how to opt out.
|
|
120
|
+
*
|
|
113
121
|
* @returns Base64-encoded XDR of the unsigned transaction.
|
|
114
122
|
*/
|
|
115
123
|
async vaultRedeem(params) {
|
|
@@ -121,6 +129,10 @@ class StellarAdapter {
|
|
|
121
129
|
});
|
|
122
130
|
}
|
|
123
131
|
/**
|
|
132
|
+
* Side effect: a submitted transaction that the network reports as a failed
|
|
133
|
+
* `redeem` operation is reported to that vault's curator, on the same
|
|
134
|
+
* fire-and-forget terms as {@link StellarAdapter.vaultRedeem}.
|
|
135
|
+
*
|
|
124
136
|
* Submit a signed Soroban transaction and poll until the network confirms it.
|
|
125
137
|
*
|
|
126
138
|
* Submits on the network this adapter was constructed with, so `signedXdr`
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stellar Invocation Decoding
|
|
3
|
+
*
|
|
4
|
+
* Reads back what a built transaction actually invokes. `submitStellarTransaction`
|
|
5
|
+
* takes an opaque signed XDR — it has no idea whether it is broadcasting a
|
|
6
|
+
* deposit, a redeem, or something else — so a failure there cannot be attributed
|
|
7
|
+
* to a vault operation without looking inside the envelope.
|
|
8
|
+
*/
|
|
9
|
+
import type { FeeBumpTransaction, Transaction } from '@stellar/stellar-sdk';
|
|
10
|
+
/** A vault contract invocation recovered from a transaction envelope. */
|
|
11
|
+
export interface IVaultInvocation {
|
|
12
|
+
/** Invoked contract (C-address). */
|
|
13
|
+
contractId: string;
|
|
14
|
+
/** Contract function name, e.g. `'redeem'` or `'deposit'`. */
|
|
15
|
+
functionName: string;
|
|
16
|
+
/**
|
|
17
|
+
* First argument as a decimal string, when it decodes to an integer. Both
|
|
18
|
+
* `deposit(assets, …)` and `redeem(shares, …)` lead with the `i128` amount.
|
|
19
|
+
* `undefined` when the argument is absent or not an integer.
|
|
20
|
+
*/
|
|
21
|
+
amountRaw?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Second argument as a Stellar address, when it decodes to one — the
|
|
24
|
+
* `receiver` in both vault signatures. `undefined` otherwise.
|
|
25
|
+
*/
|
|
26
|
+
receiver?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Third argument as a Stellar address, when it decodes to one — `owner` in
|
|
29
|
+
* `redeem(shares, receiver, owner, operator)`, i.e. the account whose shares
|
|
30
|
+
* are burned. Identical to `receiver` for the self-redeem the SDK builds, but
|
|
31
|
+
* they differ for a delegated redemption. `undefined` otherwise.
|
|
32
|
+
*/
|
|
33
|
+
owner?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Decode the single Soroban contract invocation in a transaction.
|
|
37
|
+
*
|
|
38
|
+
* Every transaction the SDK builds carries exactly one `invokeHostFunction`
|
|
39
|
+
* operation (see `buildSorobanTx`), so the first one found is authoritative.
|
|
40
|
+
* Purely local — no RPC, no network.
|
|
41
|
+
*
|
|
42
|
+
* Written defensively rather than optimistically: this is called from an error
|
|
43
|
+
* path where the envelope may be anything a caller handed us, and a throw here
|
|
44
|
+
* would replace the real transaction failure with a decoding error. Anything
|
|
45
|
+
* unexpected — a fee-bump wrapper, a classic payment, a shape-drifted SDK
|
|
46
|
+
* union, a non-address argument — yields `undefined`.
|
|
47
|
+
*
|
|
48
|
+
* @param tx - Parsed transaction (e.g. `TransactionBuilder.fromXDR(...)`, whose
|
|
49
|
+
* return type includes the fee-bump wrapper — that case yields `undefined`,
|
|
50
|
+
* since the invocation lives in the inner transaction and the SDK never
|
|
51
|
+
* builds one).
|
|
52
|
+
* @returns The invocation, or `undefined` when the transaction does not carry a
|
|
53
|
+
* decodable contract call.
|
|
54
|
+
*/
|
|
55
|
+
export declare function describeVaultInvocation(tx: Transaction | FeeBumpTransaction): IVaultInvocation | undefined;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Stellar Invocation Decoding
|
|
4
|
+
*
|
|
5
|
+
* Reads back what a built transaction actually invokes. `submitStellarTransaction`
|
|
6
|
+
* takes an opaque signed XDR — it has no idea whether it is broadcasting a
|
|
7
|
+
* deposit, a redeem, or something else — so a failure there cannot be attributed
|
|
8
|
+
* to a vault operation without looking inside the envelope.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.describeVaultInvocation = describeVaultInvocation;
|
|
12
|
+
const stellar_sdk_1 = require("@stellar/stellar-sdk");
|
|
13
|
+
/**
|
|
14
|
+
* Decode the single Soroban contract invocation in a transaction.
|
|
15
|
+
*
|
|
16
|
+
* Every transaction the SDK builds carries exactly one `invokeHostFunction`
|
|
17
|
+
* operation (see `buildSorobanTx`), so the first one found is authoritative.
|
|
18
|
+
* Purely local — no RPC, no network.
|
|
19
|
+
*
|
|
20
|
+
* Written defensively rather than optimistically: this is called from an error
|
|
21
|
+
* path where the envelope may be anything a caller handed us, and a throw here
|
|
22
|
+
* would replace the real transaction failure with a decoding error. Anything
|
|
23
|
+
* unexpected — a fee-bump wrapper, a classic payment, a shape-drifted SDK
|
|
24
|
+
* union, a non-address argument — yields `undefined`.
|
|
25
|
+
*
|
|
26
|
+
* @param tx - Parsed transaction (e.g. `TransactionBuilder.fromXDR(...)`, whose
|
|
27
|
+
* return type includes the fee-bump wrapper — that case yields `undefined`,
|
|
28
|
+
* since the invocation lives in the inner transaction and the SDK never
|
|
29
|
+
* builds one).
|
|
30
|
+
* @returns The invocation, or `undefined` when the transaction does not carry a
|
|
31
|
+
* decodable contract call.
|
|
32
|
+
*/
|
|
33
|
+
function describeVaultInvocation(tx) {
|
|
34
|
+
try {
|
|
35
|
+
if (!('operations' in tx))
|
|
36
|
+
return undefined;
|
|
37
|
+
for (const op of tx.operations ?? []) {
|
|
38
|
+
if (op.type !== 'invokeHostFunction')
|
|
39
|
+
continue;
|
|
40
|
+
const hostFn = op.func;
|
|
41
|
+
if (hostFn.switch() !==
|
|
42
|
+
stellar_sdk_1.xdr.HostFunctionType.hostFunctionTypeInvokeContract()) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const invocation = hostFn.invokeContract();
|
|
46
|
+
const contractId = stellar_sdk_1.Address.fromScAddress(invocation.contractAddress()).toString();
|
|
47
|
+
const functionName = invocation.functionName().toString();
|
|
48
|
+
const args = invocation.args();
|
|
49
|
+
return {
|
|
50
|
+
contractId,
|
|
51
|
+
functionName,
|
|
52
|
+
amountRaw: decodeInteger(args[0]),
|
|
53
|
+
receiver: decodeAddress(args[1]),
|
|
54
|
+
owner: decodeAddress(args[2]),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Shape drift or an envelope we don't recognise. The caller falls back to
|
|
60
|
+
// reporting nothing rather than misattributing the failure.
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
/** `bigint` / `number` ScVal → decimal string; `undefined` for anything else. */
|
|
65
|
+
function decodeInteger(value) {
|
|
66
|
+
if (!value)
|
|
67
|
+
return undefined;
|
|
68
|
+
try {
|
|
69
|
+
const native = (0, stellar_sdk_1.scValToNative)(value);
|
|
70
|
+
if (typeof native === 'bigint')
|
|
71
|
+
return native.toString();
|
|
72
|
+
if (typeof native === 'number' && Number.isInteger(native)) {
|
|
73
|
+
return String(native);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// Not an integer ScVal.
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
/** Address ScVal → strkey; `undefined` for anything else. */
|
|
82
|
+
function decodeAddress(value) {
|
|
83
|
+
if (!value)
|
|
84
|
+
return undefined;
|
|
85
|
+
try {
|
|
86
|
+
return stellar_sdk_1.Address.fromScVal(value).toString();
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=invocation.js.map
|
|
@@ -119,5 +119,13 @@ export declare function queryContract(config: ISorobanNetworkConfig, contractId:
|
|
|
119
119
|
* Fetches the real source account from the network (the account must exist and be funded).
|
|
120
120
|
* Simulates via Soroban RPC to attach resource footprint and auth entries,
|
|
121
121
|
* using MAX_FEE_STROOPS as the fee ceiling and incorporating the simulated resource fee.
|
|
122
|
+
*
|
|
123
|
+
* Thrown errors carry `context.stage` naming where the build broke:
|
|
124
|
+
* `'simulation'` (the contract itself rejected the call), `'restore-required'`,
|
|
125
|
+
* `'assembly'`, or `'rpc-failover'` (no endpoint answered). Callers use it to
|
|
126
|
+
* separate a vault-level rejection from a transport problem — see the curator
|
|
127
|
+
* alerting in `handleStellarRedeem`. Validation errors (e.g.
|
|
128
|
+
* `ACCOUNT_NOT_FUNDED`) carry no stage, so treat a missing one as "not a
|
|
129
|
+
* vault-level failure" rather than assuming the field is always present.
|
|
122
130
|
*/
|
|
123
131
|
export declare function buildSorobanTx(config: ISorobanNetworkConfig, sourceAddress: string, contractId: string, method: string, args: xdr.ScVal[]): Promise<string>;
|
|
@@ -367,7 +367,10 @@ async function withEndpointFailover(config, method, operation, options = {}) {
|
|
|
367
367
|
let lastError;
|
|
368
368
|
// A terminal "everything failed" error, scrubbed so a keyed endpoint can't
|
|
369
369
|
// leak through the message or chained cause.
|
|
370
|
-
const failAllEndpoints = () => new core_1.AugustSDKError('UNKNOWN', `Soroban ${method} failed on all endpoints: ${scrub(String(lastError))}`, {
|
|
370
|
+
const failAllEndpoints = () => new core_1.AugustSDKError('UNKNOWN', `Soroban ${method} failed on all endpoints: ${scrub(String(lastError))}`, {
|
|
371
|
+
cause: scrubErrorUrls(lastError, scrub),
|
|
372
|
+
context: { method, stage: 'rpc-failover' },
|
|
373
|
+
});
|
|
371
374
|
// Fast path: the health-gated, cross-call-cached endpoint.
|
|
372
375
|
const { server: primaryServer, url: primaryUrl, healthy, } = await resolveHealthyServer(config);
|
|
373
376
|
try {
|
|
@@ -545,6 +548,14 @@ async function getNetworkCloseTime(server) {
|
|
|
545
548
|
* Fetches the real source account from the network (the account must exist and be funded).
|
|
546
549
|
* Simulates via Soroban RPC to attach resource footprint and auth entries,
|
|
547
550
|
* using MAX_FEE_STROOPS as the fee ceiling and incorporating the simulated resource fee.
|
|
551
|
+
*
|
|
552
|
+
* Thrown errors carry `context.stage` naming where the build broke:
|
|
553
|
+
* `'simulation'` (the contract itself rejected the call), `'restore-required'`,
|
|
554
|
+
* `'assembly'`, or `'rpc-failover'` (no endpoint answered). Callers use it to
|
|
555
|
+
* separate a vault-level rejection from a transport problem — see the curator
|
|
556
|
+
* alerting in `handleStellarRedeem`. Validation errors (e.g.
|
|
557
|
+
* `ACCOUNT_NOT_FUNDED`) carry no stage, so treat a missing one as "not a
|
|
558
|
+
* vault-level failure" rather than assuming the field is always present.
|
|
548
559
|
*/
|
|
549
560
|
async function buildSorobanTx(config, sourceAddress, contractId, method, args) {
|
|
550
561
|
const contract = new stellar_sdk_1.Contract(contractId);
|
|
@@ -595,18 +606,21 @@ async function buildSorobanTx(config, sourceAddress, contractId, method, args) {
|
|
|
595
606
|
// retryable error so the failover wrapper tries another endpoint.
|
|
596
607
|
throw new Error(`Soroban simulation node error for ${method}: ${errorText}`);
|
|
597
608
|
}
|
|
598
|
-
throw new core_1.AugustSDKError('UNKNOWN', `Soroban simulation failed: ${errorText}`, { context: { method, contractId } });
|
|
609
|
+
throw new core_1.AugustSDKError('UNKNOWN', `Soroban simulation failed: ${errorText}`, { context: { method, contractId, stage: 'simulation' } });
|
|
599
610
|
}
|
|
600
611
|
if ('restorePreamble' in simulated && simulated.restorePreamble) {
|
|
601
612
|
throw new core_1.AugustSDKError('UNKNOWN', `Contract ledger state needs restoration before invoking ${method}. ` +
|
|
602
|
-
'Submit a restore footprint transaction first.', { context: { method, contractId } });
|
|
613
|
+
'Submit a restore footprint transaction first.', { context: { method, contractId, stage: 'restore-required' } });
|
|
603
614
|
}
|
|
604
615
|
try {
|
|
605
616
|
const assembled = stellar_sdk_1.rpc.assembleTransaction(tx, simulated).build();
|
|
606
617
|
return assembled.toXDR();
|
|
607
618
|
}
|
|
608
619
|
catch (assemblyErr) {
|
|
609
|
-
throw new core_1.AugustSDKError('UNKNOWN', `Failed to assemble Soroban transaction for ${method}: ${String(assemblyErr)}`, {
|
|
620
|
+
throw new core_1.AugustSDKError('UNKNOWN', `Failed to assemble Soroban transaction for ${method}: ${String(assemblyErr)}`, {
|
|
621
|
+
cause: assemblyErr,
|
|
622
|
+
context: { method, contractId, stage: 'assembly' },
|
|
623
|
+
});
|
|
610
624
|
}
|
|
611
625
|
});
|
|
612
626
|
}
|
|
@@ -20,6 +20,14 @@ import type { IStellarNetwork } from './types';
|
|
|
20
20
|
* plus, when the result XDR decodes, a `resultCode` string holding the
|
|
21
21
|
* transaction-level reason (e.g. `"txBadSeq"`, `"txTooLate"`); `resultCode`
|
|
22
22
|
* is `undefined` when the code cannot be decoded.
|
|
23
|
+
*
|
|
24
|
+
* Side effect: a transaction whose `resultCode` is `txFailed` — the operation
|
|
25
|
+
* itself ran and failed — and whose envelope shows a vault `redeem` is relayed
|
|
26
|
+
* to that vault's curator (fire-and-forget, deduped — see
|
|
27
|
+
* {@link reportRedeemFailure}). Nothing else alerts: an RPC-rejected
|
|
28
|
+
* broadcast is a retryable race, a poll timeout is indeterminate (the
|
|
29
|
+
* transaction may yet succeed), and any other result code means the redeem
|
|
30
|
+
* never executed.
|
|
23
31
|
* @example
|
|
24
32
|
* ```ts
|
|
25
33
|
* try {
|
|
@@ -8,7 +8,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
8
8
|
exports.submitStellarTransaction = submitStellarTransaction;
|
|
9
9
|
const stellar_sdk_1 = require("@stellar/stellar-sdk");
|
|
10
10
|
const core_1 = require("../../core");
|
|
11
|
+
const curator_alert_1 = require("../../core/logger/curator-alert");
|
|
11
12
|
const soroban_1 = require("./soroban");
|
|
13
|
+
const invocation_1 = require("./invocation");
|
|
12
14
|
const constants_1 = require("./constants");
|
|
13
15
|
/**
|
|
14
16
|
* Best-effort decode of the transaction-level result code (the XDR union
|
|
@@ -64,6 +66,14 @@ function safeStringify(value) {
|
|
|
64
66
|
* plus, when the result XDR decodes, a `resultCode` string holding the
|
|
65
67
|
* transaction-level reason (e.g. `"txBadSeq"`, `"txTooLate"`); `resultCode`
|
|
66
68
|
* is `undefined` when the code cannot be decoded.
|
|
69
|
+
*
|
|
70
|
+
* Side effect: a transaction whose `resultCode` is `txFailed` — the operation
|
|
71
|
+
* itself ran and failed — and whose envelope shows a vault `redeem` is relayed
|
|
72
|
+
* to that vault's curator (fire-and-forget, deduped — see
|
|
73
|
+
* {@link reportRedeemFailure}). Nothing else alerts: an RPC-rejected
|
|
74
|
+
* broadcast is a retryable race, a poll timeout is indeterminate (the
|
|
75
|
+
* transaction may yet succeed), and any other result code means the redeem
|
|
76
|
+
* never executed.
|
|
67
77
|
* @example
|
|
68
78
|
* ```ts
|
|
69
79
|
* try {
|
|
@@ -137,6 +147,42 @@ async function submitStellarTransaction(signedXdr, network, sorobanRpcUrl) {
|
|
|
137
147
|
network,
|
|
138
148
|
resultCode,
|
|
139
149
|
});
|
|
150
|
+
// A redemption that executed and failed is the strongest form of the signal
|
|
151
|
+
// the curator needs — and the only one carrying a hash the relay can verify
|
|
152
|
+
// on chain. Two gates, because a FAILED confirmation is not by itself a vault
|
|
153
|
+
// failure:
|
|
154
|
+
// - `txFailed` is the only result code meaning the operations were applied
|
|
155
|
+
// and one of them failed. Everything else at this layer is
|
|
156
|
+
// transaction-level — `txBadSeq`, `txInsufficientBalance`,
|
|
157
|
+
// `txTooLate`, resource-limit overruns — where the redeem never ran, and
|
|
158
|
+
// which the caller resolves by retrying. An undecodable code is treated
|
|
159
|
+
// the same way: without it we cannot tell the two apart, and silence
|
|
160
|
+
// beats paging a curator over a sequence-number race.
|
|
161
|
+
// - the envelope must actually be a redeem: this function is handed an
|
|
162
|
+
// opaque XDR and would otherwise misattribute deposits.
|
|
163
|
+
const invocation = resultCode === 'txFailed' ? (0, invocation_1.describeVaultInvocation)(tx) : undefined;
|
|
164
|
+
// A partially-decoded envelope is not reported at all: an alert naming no
|
|
165
|
+
// wallet or no amount is one a curator cannot act on, and the reporting
|
|
166
|
+
// endpoint rejects it anyway.
|
|
167
|
+
const redeemer = invocation?.owner ?? invocation?.receiver;
|
|
168
|
+
if (invocation?.functionName === 'redeem' &&
|
|
169
|
+
redeemer &&
|
|
170
|
+
invocation.amountRaw) {
|
|
171
|
+
(0, curator_alert_1.reportRedeemFailure)({
|
|
172
|
+
phase: 'submission',
|
|
173
|
+
chain: 'stellar',
|
|
174
|
+
network,
|
|
175
|
+
contractId: invocation.contractId,
|
|
176
|
+
// `owner` is the account whose shares burn; `receiver` only receives the
|
|
177
|
+
// assets. They are the same for a self-redeem, and owner is the right one
|
|
178
|
+
// to name when they differ.
|
|
179
|
+
eoa: redeemer,
|
|
180
|
+
sharesRaw: invocation.amountRaw,
|
|
181
|
+
txHash: sendResult.hash,
|
|
182
|
+
resultCode,
|
|
183
|
+
error: msg,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
140
186
|
throw new core_1.AugustSDKError('UNKNOWN', msg, {
|
|
141
187
|
context: {
|
|
142
188
|
hash: sendResult.hash,
|
|
@@ -27,3 +27,20 @@ export declare function isAnalyticsDisabledViaEnv(): boolean;
|
|
|
27
27
|
* `NODE_ENV` undefined / `'production'` / `'staging'` / anything else → false.
|
|
28
28
|
*/
|
|
29
29
|
export declare function isNodeDevOrTestEnv(): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* True when the page is being served from a developer's own machine or private
|
|
32
|
+
* network.
|
|
33
|
+
*
|
|
34
|
+
* The browser counterpart to {@link isNodeDevOrTestEnv}: bundlers strip or
|
|
35
|
+
* stub `process.env`, so `NODE_ENV` cannot tell a `next dev` tab apart from a
|
|
36
|
+
* production deploy. The hostname can. Always false in Node, where
|
|
37
|
+
* `NODE_ENV` is the gate.
|
|
38
|
+
*
|
|
39
|
+
* Recognises `localhost` and the `.localhost` TLD (RFC 6761 reserves both for
|
|
40
|
+
* loopback), mDNS `.local` names, and any loopback / private / link-local IP
|
|
41
|
+
* literal in either address family. Anything else — including a public host
|
|
42
|
+
* that merely *contains* one of those names, like `localhost.acme.com` — is
|
|
43
|
+
* treated as a real deployment, because suppressing a genuine production
|
|
44
|
+
* origin is the more costly mistake of the two.
|
|
45
|
+
*/
|
|
46
|
+
export declare function isLocalhost(): boolean;
|
|
@@ -8,6 +8,7 @@ exports.readEnv = readEnv;
|
|
|
8
8
|
exports.isAnalyticsForcedOnViaEnv = isAnalyticsForcedOnViaEnv;
|
|
9
9
|
exports.isAnalyticsDisabledViaEnv = isAnalyticsDisabledViaEnv;
|
|
10
10
|
exports.isNodeDevOrTestEnv = isNodeDevOrTestEnv;
|
|
11
|
+
exports.isLocalhost = isLocalhost;
|
|
11
12
|
/**
|
|
12
13
|
* Read a process env var without crashing in browsers or in bundles where
|
|
13
14
|
* `process.env` is a frozen / proxied object that throws on missing keys.
|
|
@@ -56,4 +57,91 @@ function isNodeDevOrTestEnv() {
|
|
|
56
57
|
const env = readEnv('NODE_ENV');
|
|
57
58
|
return env === 'development' || env === 'test';
|
|
58
59
|
}
|
|
60
|
+
/** Dotted-quad IPv4 literal. Anchored, so `10.acme.com` is not an address. */
|
|
61
|
+
const IPV4_LITERAL = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
|
|
62
|
+
/**
|
|
63
|
+
* True for an IPv4 literal that cannot be a public production origin: loopback
|
|
64
|
+
* (`127.0.0.0/8` — `127.0.0.1` is only the most common of 16.7M), the
|
|
65
|
+
* "this host" block (`0.0.0.0/8`), the three RFC 1918 private ranges, and
|
|
66
|
+
* link-local (`169.254.0.0/16`).
|
|
67
|
+
*
|
|
68
|
+
* Ranges are checked by octet rather than by string prefix on purpose: a
|
|
69
|
+
* `startsWith('10.')` test both misses `172.20.0.2` (a default Docker bridge
|
|
70
|
+
* address, and a very ordinary way to reach a container) and misfires on a
|
|
71
|
+
* public host that merely begins with those digits.
|
|
72
|
+
*/
|
|
73
|
+
function isPrivateIpv4(hostname) {
|
|
74
|
+
const octets = IPV4_LITERAL.exec(hostname);
|
|
75
|
+
if (!octets)
|
|
76
|
+
return false;
|
|
77
|
+
const [a, b, c, d] = octets.slice(1).map(Number);
|
|
78
|
+
// The regex counts digits, not values, so an out-of-range quad still matches
|
|
79
|
+
// its shape. `127.0.0.999` is not an address — the URL parser rejects it
|
|
80
|
+
// rather than handing it over as a `hostname` — so it is a name, and names
|
|
81
|
+
// that are not the reserved ones are treated as deployed.
|
|
82
|
+
if (a > 255 || b > 255 || c > 255 || d > 255)
|
|
83
|
+
return false;
|
|
84
|
+
return (a === 127 ||
|
|
85
|
+
a === 0 ||
|
|
86
|
+
a === 10 ||
|
|
87
|
+
(a === 172 && b >= 16 && b <= 31) ||
|
|
88
|
+
(a === 192 && b === 168) ||
|
|
89
|
+
(a === 169 && b === 254));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* True for an IPv6 literal that cannot be a public production origin:
|
|
93
|
+
* loopback (`::1`, in either notation), the unspecified address, an
|
|
94
|
+
* IPv4-mapped private address, unique-local (`fc00::/7`) and link-local
|
|
95
|
+
* (`fe80::/10`).
|
|
96
|
+
*
|
|
97
|
+
* `hostname` carries the brackets for an IPv6 host — `http://[::1]:3000/`
|
|
98
|
+
* yields `[::1]`, per the URL standard's host serialization — so they are
|
|
99
|
+
* stripped before matching.
|
|
100
|
+
*/
|
|
101
|
+
function isLocalIpv6(hostname) {
|
|
102
|
+
const host = hostname.replace(/^\[/, '').replace(/\]$/, '');
|
|
103
|
+
if (!host.includes(':'))
|
|
104
|
+
return false;
|
|
105
|
+
if (host === '::1' || host === '::')
|
|
106
|
+
return true;
|
|
107
|
+
if (/^(?:0{1,4}:){7}0{0,3}1$/.test(host))
|
|
108
|
+
return true;
|
|
109
|
+
if (host.startsWith('::ffff:'))
|
|
110
|
+
return isPrivateIpv4(host.slice(7));
|
|
111
|
+
return /^f[cd]/.test(host) || /^fe[89ab]/.test(host);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* True when the page is being served from a developer's own machine or private
|
|
115
|
+
* network.
|
|
116
|
+
*
|
|
117
|
+
* The browser counterpart to {@link isNodeDevOrTestEnv}: bundlers strip or
|
|
118
|
+
* stub `process.env`, so `NODE_ENV` cannot tell a `next dev` tab apart from a
|
|
119
|
+
* production deploy. The hostname can. Always false in Node, where
|
|
120
|
+
* `NODE_ENV` is the gate.
|
|
121
|
+
*
|
|
122
|
+
* Recognises `localhost` and the `.localhost` TLD (RFC 6761 reserves both for
|
|
123
|
+
* loopback), mDNS `.local` names, and any loopback / private / link-local IP
|
|
124
|
+
* literal in either address family. Anything else — including a public host
|
|
125
|
+
* that merely *contains* one of those names, like `localhost.acme.com` — is
|
|
126
|
+
* treated as a real deployment, because suppressing a genuine production
|
|
127
|
+
* origin is the more costly mistake of the two.
|
|
128
|
+
*/
|
|
129
|
+
function isLocalhost() {
|
|
130
|
+
try {
|
|
131
|
+
if (typeof window !== 'undefined' && window.location) {
|
|
132
|
+
// Hostnames are case-insensitive; browsers normalize, other runtimes
|
|
133
|
+
// hosting a `window` shim may not.
|
|
134
|
+
const hostname = String(window.location.hostname).toLowerCase();
|
|
135
|
+
return (hostname === 'localhost' ||
|
|
136
|
+
hostname.endsWith('.localhost') ||
|
|
137
|
+
hostname.endsWith('.local') ||
|
|
138
|
+
isPrivateIpv4(hostname) ||
|
|
139
|
+
isLocalIpv6(hostname));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// Some embedded/sandboxed runtimes throw on `window.location`. Not local.
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
59
147
|
//# sourceMappingURL=env.js.map
|
|
@@ -36,26 +36,6 @@ function getSDKVersion() {
|
|
|
36
36
|
return 'development';
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
-
/**
|
|
40
|
-
* Check if running on localhost.
|
|
41
|
-
*/
|
|
42
|
-
function isLocalhost() {
|
|
43
|
-
try {
|
|
44
|
-
if (typeof window !== 'undefined' && window.location) {
|
|
45
|
-
const hostname = window.location.hostname;
|
|
46
|
-
return (hostname === 'localhost' ||
|
|
47
|
-
hostname === '127.0.0.1' ||
|
|
48
|
-
hostname === '0.0.0.0' ||
|
|
49
|
-
hostname.startsWith('192.168.') ||
|
|
50
|
-
hostname.startsWith('10.') ||
|
|
51
|
-
hostname.endsWith('.local'));
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
// Silently fail
|
|
56
|
-
}
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
39
|
/**
|
|
60
40
|
* Safely set a Sentry tag. Silently fails on error.
|
|
61
41
|
*/
|
|
@@ -375,7 +355,7 @@ function initializeSentry(config, environment, appName, walletAddress, apiKey) {
|
|
|
375
355
|
});
|
|
376
356
|
return;
|
|
377
357
|
}
|
|
378
|
-
if (!forced && isLocalhost()) {
|
|
358
|
+
if (!forced && (0, env_1.isLocalhost)()) {
|
|
379
359
|
isEnabled = false;
|
|
380
360
|
isInitialized = true;
|
|
381
361
|
logger_1.Logger.log.info('analytics.disabled', { reason: 'localhost' });
|
package/lib/core/base.class.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { ISolanaConfig, IStellarConfig, IAddress, IChainId, IEnv, IProvider
|
|
|
2
2
|
import { type IAnalyticsConfig } from './analytics';
|
|
3
3
|
import { type IVersionCheckConfig } from './version-check';
|
|
4
4
|
import { type IAttributionConfig } from './attribution';
|
|
5
|
+
import { type ICuratorAlertsConfig } from './logger/curator-alert';
|
|
5
6
|
interface IKeys {
|
|
6
7
|
august?: string;
|
|
7
8
|
graph?: string;
|
|
@@ -10,6 +11,27 @@ interface IKeys {
|
|
|
10
11
|
interface IMonitoring extends IWSMonitorHeaders {
|
|
11
12
|
slackWebhookUrl?: string;
|
|
12
13
|
env?: IEnv;
|
|
14
|
+
/**
|
|
15
|
+
* Curator notifications for failed vault redemptions. Enabled by default
|
|
16
|
+
* (opt-out model, like `analytics`).
|
|
17
|
+
*
|
|
18
|
+
* Stellar vaults are instant-redeem only, so when a redemption fails the
|
|
19
|
+
* vault's curator has no other way to learn about it. The SDK relays the
|
|
20
|
+
* failure to August's notification service, which routes it to that curator's
|
|
21
|
+
* own channel — no credentials or curator identities are held here.
|
|
22
|
+
*
|
|
23
|
+
* Set `enabled: false` (or, in Node, the
|
|
24
|
+
* `AUGUST_SDK_DISABLE_CURATOR_ALERTS` env var) to stop emitting; set
|
|
25
|
+
* `endpoint` only to point a non-prod deployment at a test relay. Nothing is
|
|
26
|
+
* emitted unless `monitoring.env` is `PROD`, `NODE_ENV` is neither
|
|
27
|
+
* `development` nor `test`, and — in a browser, where `NODE_ENV` is not
|
|
28
|
+
* readable — the page is not served from localhost.
|
|
29
|
+
*
|
|
30
|
+
* `enabled: true` is the one exception: it bypasses all three gates, so a
|
|
31
|
+
* `DEV` or test integration that sets it can page a real curator. See
|
|
32
|
+
* {@link ICuratorAlertsConfig}.
|
|
33
|
+
*/
|
|
34
|
+
curatorAlerts?: ICuratorAlertsConfig;
|
|
13
35
|
}
|
|
14
36
|
export interface IAugustBase {
|
|
15
37
|
/**
|