@augustdigital/sdk 8.26.0 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
- const args = buildSelfOperationArgs((0, soroban_1.toBigIntAmount)(shares, 'redeem shares'), receiverAddress);
75
- return (0, soroban_1.buildSorobanTx)(config, receiverAddress, contractId, 'redeem', args);
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))}`, { cause: scrubErrorUrls(lastError, scrub), context: { method } });
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)}`, { cause: assemblyErr, context: { method, contractId } });
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,
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Validate an `appName` slug at SDK boundary points (the `AugustSDK`
3
+ * constructor and the standalone {@link initializeSentry} entry point).
4
+ *
5
+ * Rules (kept intentionally narrow so the value is safe to use as a Sentry
6
+ * tag, an HTTP header value, and a filesystem-safe identifier — this is
7
+ * why we accept slugs only, not display names):
8
+ * - non-empty after trim
9
+ * - 3..64 characters
10
+ * - only `[a-zA-Z0-9._-]`
11
+ * - not shaped like an EVM address (`0x` + 40 hex chars) — an address
12
+ * passes the character rules but is a high-cardinality wallet
13
+ * identifier, not an application name. This also catches callers of
14
+ * `initializeSentry` still passing the pre-v9 positional argument
15
+ * order, where a wallet address occupied the slot `appName` now holds.
16
+ *
17
+ * Throws a descriptive `AugustValidationError` (code `INVALID_INPUT`) that
18
+ * names the failed rule and includes a remediation hint pointing at the
19
+ * docs link.
20
+ *
21
+ * @param value - The candidate app name, as received from the caller.
22
+ * @returns The trimmed, validated slug.
23
+ * @throws AugustValidationError when the value is missing, empty,
24
+ * mis-typed, out of the 3–64 length range, contains disallowed
25
+ * characters, or is shaped like an EVM address.
26
+ */
27
+ export declare function validateAppName(value: unknown): string;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateAppName = validateAppName;
4
+ const errors_1 = require("../errors");
5
+ /**
6
+ * Validate an `appName` slug at SDK boundary points (the `AugustSDK`
7
+ * constructor and the standalone {@link initializeSentry} entry point).
8
+ *
9
+ * Rules (kept intentionally narrow so the value is safe to use as a Sentry
10
+ * tag, an HTTP header value, and a filesystem-safe identifier — this is
11
+ * why we accept slugs only, not display names):
12
+ * - non-empty after trim
13
+ * - 3..64 characters
14
+ * - only `[a-zA-Z0-9._-]`
15
+ * - not shaped like an EVM address (`0x` + 40 hex chars) — an address
16
+ * passes the character rules but is a high-cardinality wallet
17
+ * identifier, not an application name. This also catches callers of
18
+ * `initializeSentry` still passing the pre-v9 positional argument
19
+ * order, where a wallet address occupied the slot `appName` now holds.
20
+ *
21
+ * Throws a descriptive `AugustValidationError` (code `INVALID_INPUT`) that
22
+ * names the failed rule and includes a remediation hint pointing at the
23
+ * docs link.
24
+ *
25
+ * @param value - The candidate app name, as received from the caller.
26
+ * @returns The trimmed, validated slug.
27
+ * @throws AugustValidationError when the value is missing, empty,
28
+ * mis-typed, out of the 3–64 length range, contains disallowed
29
+ * characters, or is shaped like an EVM address.
30
+ */
31
+ function validateAppName(value) {
32
+ const docsHint = 'See https://docs.augustdigital.io/developers/typescript-sdk#app-name';
33
+ if (typeof value !== 'string' || value.trim().length === 0) {
34
+ throw new errors_1.AugustValidationError('INVALID_INPUT', `August SDK: \`appName\` is required. Pass a stable kebab-case slug identifying your application (e.g. "acme-trader"). ${docsHint}`);
35
+ }
36
+ const trimmed = value.trim();
37
+ if (trimmed.length < 3 || trimmed.length > 64) {
38
+ throw new errors_1.AugustValidationError('INVALID_INPUT', `August SDK: \`appName\` must be 3-64 characters (got ${trimmed.length}). ${docsHint}`);
39
+ }
40
+ if (!/^[a-zA-Z0-9._-]+$/.test(trimmed)) {
41
+ throw new errors_1.AugustValidationError('INVALID_INPUT', `August SDK: \`appName\` may only contain letters, digits, '.', '_' and '-' (got "${trimmed}"). Use a slug like "acme-trader", not a display name. ${docsHint}`);
42
+ }
43
+ if (/^0x[0-9a-fA-F]{40}$/.test(trimmed)) {
44
+ throw new errors_1.AugustValidationError('INVALID_INPUT', `August SDK: \`appName\` looks like a wallet address ("${trimmed}"). Pass an application slug like "acme-trader" — if you are calling initializeSentry directly, note that since v9 the argument order is (config, environment, appName, walletAddress?, apiKey?). ${docsHint}`);
45
+ }
46
+ return trimmed;
47
+ }
48
+ //# sourceMappingURL=app-name.js.map
@@ -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