stellar-shade 0.0.2 → 0.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.
- package/README.md +135 -39
- package/dist/index.cjs +3069 -223
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2234 -124
- package/dist/index.d.ts +2234 -124
- package/dist/index.js +3017 -225
- package/dist/index.js.map +1 -1
- package/package.json +23 -12
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,686 @@
|
|
|
1
|
+
import * as StellarSdk from '@stellar/stellar-sdk';
|
|
2
|
+
import { Networks, Account, Transaction, rpc } from '@stellar/stellar-sdk';
|
|
3
|
+
|
|
4
|
+
/** Static endpoints and passphrase for one supported Stellar network. */
|
|
5
|
+
interface NetworkDefinition {
|
|
6
|
+
networkPassphrase: string;
|
|
7
|
+
rpcUrl: string;
|
|
8
|
+
horizonUrl: string;
|
|
9
|
+
allowHttp: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The single source of truth for the networks this SDK can talk to. Adding a
|
|
13
|
+
* network is ONE new entry here: {@link NetworkName} (and with it every
|
|
14
|
+
* `ClientConfig.network` union across the SDK and its consumers) widens
|
|
15
|
+
* automatically.
|
|
16
|
+
*/
|
|
17
|
+
declare const NETWORKS: {
|
|
18
|
+
readonly testnet: {
|
|
19
|
+
readonly networkPassphrase: Networks.TESTNET;
|
|
20
|
+
readonly rpcUrl: "https://soroban-testnet.stellar.org";
|
|
21
|
+
readonly horizonUrl: "https://horizon-testnet.stellar.org";
|
|
22
|
+
readonly allowHttp: false;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
/** The names of the currently supported networks (today: `'testnet'`). */
|
|
26
|
+
type NetworkName = keyof typeof NETWORKS;
|
|
27
|
+
/** Network configuration resolved from a network name. */
|
|
28
|
+
interface NetworkConfig extends NetworkDefinition {
|
|
29
|
+
server: StellarSdk.rpc.Server;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Reverse lookup: the {@link NETWORKS} name whose passphrase matches, or
|
|
33
|
+
* `undefined` for a passphrase outside the table. Lets components that only
|
|
34
|
+
* hold a passphrase (the delivery adapters) derive the network name a relayer
|
|
35
|
+
* `/health` should report; an unknown passphrase yields `undefined`, which
|
|
36
|
+
* callers treat as "skip the check" rather than a failure (mainnet-open).
|
|
37
|
+
*/
|
|
38
|
+
declare function networkNameForPassphrase(passphrase: string): NetworkName | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Build network config (endpoints + a connected RPC server) from a network
|
|
41
|
+
* name. Unknown names throw {@link UnsupportedNetworkError} — the type system
|
|
42
|
+
* already prevents them, but plain-JS callers and stale configs need the
|
|
43
|
+
* runtime guard too.
|
|
44
|
+
*/
|
|
45
|
+
declare function getNetworkConfig(network: NetworkName): NetworkConfig;
|
|
46
|
+
/** Create a dummy transaction for read-only contract simulation. */
|
|
47
|
+
declare function createSimulationTx(operation: StellarSdk.xdr.Operation, networkPassphrase: string): StellarSdk.Transaction;
|
|
48
|
+
/** Execute a read-only contract call via simulation. Returns the decoded native result. */
|
|
49
|
+
declare function simulateReadOnly(contractId: string, method: string, args: StellarSdk.xdr.ScVal[], server: StellarSdk.rpc.Server, networkPassphrase: string): Promise<unknown | null>;
|
|
50
|
+
/** Resolve an asset string ("native", "XLM", or "CODE:ISSUER") to a SAC contract address. */
|
|
51
|
+
declare function resolveTokenAddress(assetArg: string | undefined, networkPassphrase: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* Give a human-readable label for a SAC token contract address (the opaque
|
|
54
|
+
* `C...` string stored in pool announcements). The native XLM SAC has a
|
|
55
|
+
* deterministic contract id — `Asset.native().contractId(passphrase)` — so we
|
|
56
|
+
* detect it and return `'XLM'`; anything else cannot be reversed to CODE:ISSUER
|
|
57
|
+
* from the address alone, so the original `C...` address is returned unchanged.
|
|
58
|
+
*
|
|
59
|
+
* This is what turns a balance/scan row from an unreadable C-address into a
|
|
60
|
+
* recognizable token name for the common (native) case.
|
|
61
|
+
*
|
|
62
|
+
* @param tokenAddress - The token's SAC contract address, or 'native'/'unknown'.
|
|
63
|
+
* @param networkPassphrase - Passphrase used to derive the native SAC id.
|
|
64
|
+
* @returns 'XLM' for the native SAC (or the literal 'native'); otherwise the input.
|
|
65
|
+
*/
|
|
66
|
+
declare function labelForToken(tokenAddress: string, networkPassphrase: string): string;
|
|
67
|
+
/**
|
|
68
|
+
* The minimal transaction-status surface of `rpc.Server` needed to poll a tx
|
|
69
|
+
* hash to a terminal status. Structural, so confirm-polling callers (e.g. the
|
|
70
|
+
* `RelayerClient` confirm option) can inject a real server or a test stub.
|
|
71
|
+
*/
|
|
72
|
+
interface TransactionStatusSource {
|
|
73
|
+
getTransaction(hash: string): Promise<{
|
|
74
|
+
status: string;
|
|
75
|
+
}>;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Poll for transaction confirmation. Throws on failure or timeout.
|
|
79
|
+
*
|
|
80
|
+
* The timeout is a typed {@link TransactionTimeoutError} carrying the tx hash:
|
|
81
|
+
* a still-PENDING transaction MAY land after we stop polling, so callers must
|
|
82
|
+
* be able to distinguish "gave up waiting" (poll the hash, do NOT resubmit)
|
|
83
|
+
* from a hard failure — a generic error here invites retry loops that
|
|
84
|
+
* double-send funds.
|
|
85
|
+
*/
|
|
86
|
+
declare function waitForTransaction(server: TransactionStatusSource, hash: string): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* Build the withdraw message hash. Must be byte-identical to the contract's
|
|
89
|
+
* `build_withdraw_message`.
|
|
90
|
+
*
|
|
91
|
+
* Layout (concatenated, then SHA-256) — 278-byte preimage:
|
|
92
|
+
* domain_tag[22] ("SHADE-POOL-WITHDRAW-V1" ASCII) || stealth_pk[32]
|
|
93
|
+
* || token_strkey_ascii[56] || amount_be_i128[16]
|
|
94
|
+
* || dest_strkey_ascii[56] || nonce_be_u64[8]
|
|
95
|
+
* || contract_strkey_ascii[56] || network_id[32]
|
|
96
|
+
*
|
|
97
|
+
* The leading static domain tag (SH-3) separates withdraw preimages from any
|
|
98
|
+
* other message a stealth key might sign; the trailing contract address and
|
|
99
|
+
* network id provide domain separation so a signature cannot be replayed on a
|
|
100
|
+
* different deployment or network.
|
|
101
|
+
* `networkId` equals `SHA-256(utf8(networkPassphrase))`, which matches the
|
|
102
|
+
* on-chain `env.ledger().network_id()`.
|
|
103
|
+
*/
|
|
104
|
+
declare function buildWithdrawMessage(stealthPk: Uint8Array, tokenAddress: string, amount: bigint, destination: string, nonce: bigint, contractId: string, networkPassphrase: string): Uint8Array;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Minimal Horizon REST helper with an injectable `fetch`.
|
|
108
|
+
*
|
|
109
|
+
* The account delivery method relies only on Horizon's classic REST API
|
|
110
|
+
* (transaction paging + operations + account probe + submit). Keeping this in a
|
|
111
|
+
* tiny, dependency-free wrapper with an injectable fetch makes the account
|
|
112
|
+
* scanner unit-testable fully offline: tests pass a stub fetch returning
|
|
113
|
+
* synthetic fixtures, no docker network required.
|
|
114
|
+
*/
|
|
115
|
+
/** A subset of a Horizon transaction record relevant to the account method. */
|
|
116
|
+
interface HorizonTx {
|
|
117
|
+
id: string;
|
|
118
|
+
hash: string;
|
|
119
|
+
paging_token: string;
|
|
120
|
+
memo_type: string;
|
|
121
|
+
memo?: string;
|
|
122
|
+
successful?: boolean;
|
|
123
|
+
/** The account that submitted (and pays for) the transaction. */
|
|
124
|
+
source_account?: string;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* A CAP-23 claim predicate as Horizon serializes it. A missing/`unconditional`
|
|
128
|
+
* predicate is always claimable; the time-bounded variants are evaluated
|
|
129
|
+
* against ledger time so an unsatisfiable CB is not reported as income.
|
|
130
|
+
*/
|
|
131
|
+
interface HorizonPredicate {
|
|
132
|
+
unconditional?: boolean;
|
|
133
|
+
and?: HorizonPredicate[];
|
|
134
|
+
or?: HorizonPredicate[];
|
|
135
|
+
not?: HorizonPredicate;
|
|
136
|
+
/** RFC-3339 timestamp: claimable strictly BEFORE this absolute time. */
|
|
137
|
+
abs_before?: string;
|
|
138
|
+
/** Same bound as an epoch-seconds string (Horizon includes both). */
|
|
139
|
+
abs_before_epoch?: string;
|
|
140
|
+
/** Seconds since the CB was created after which it becomes claimable. */
|
|
141
|
+
rel_before?: string;
|
|
142
|
+
}
|
|
143
|
+
/** A single claimant on a create_claimable_balance operation. */
|
|
144
|
+
interface HorizonClaimant {
|
|
145
|
+
destination: string;
|
|
146
|
+
predicate?: HorizonPredicate;
|
|
147
|
+
}
|
|
148
|
+
/** A subset of a Horizon operation record relevant to the account method. */
|
|
149
|
+
interface HorizonOp {
|
|
150
|
+
id: string;
|
|
151
|
+
type: string;
|
|
152
|
+
transaction_hash: string;
|
|
153
|
+
/** create_account: the newly created account */
|
|
154
|
+
account?: string;
|
|
155
|
+
/** create_account: starting balance (string, whole units) */
|
|
156
|
+
starting_balance?: string;
|
|
157
|
+
/** payment: destination account */
|
|
158
|
+
to?: string;
|
|
159
|
+
/** payment: amount (string, whole units) */
|
|
160
|
+
amount?: string;
|
|
161
|
+
/** payment / create_claimable_balance: asset type ('native' for XLM) */
|
|
162
|
+
asset_type?: string;
|
|
163
|
+
/** create_claimable_balance: asset in "CODE:ISSUER" form (or 'native') */
|
|
164
|
+
asset?: string;
|
|
165
|
+
/** create_claimable_balance: the claimants list */
|
|
166
|
+
claimants?: HorizonClaimant[];
|
|
167
|
+
}
|
|
168
|
+
/** A Horizon claimable-balance record (used by the token account method). */
|
|
169
|
+
interface HorizonClaimableBalance {
|
|
170
|
+
id: string;
|
|
171
|
+
asset: string;
|
|
172
|
+
amount: string;
|
|
173
|
+
sponsor?: string;
|
|
174
|
+
claimants: HorizonClaimant[];
|
|
175
|
+
/** Horizon paging token — used to resume the next page of a listing. */
|
|
176
|
+
paging_token?: string;
|
|
177
|
+
}
|
|
178
|
+
/** Horizon account record subset (used to probe existence + sequence). */
|
|
179
|
+
interface HorizonAccount {
|
|
180
|
+
id: string;
|
|
181
|
+
sequence: string;
|
|
182
|
+
balances: Array<{
|
|
183
|
+
asset_type: string;
|
|
184
|
+
balance: string;
|
|
185
|
+
}>;
|
|
186
|
+
}
|
|
187
|
+
/** Injectable fetch signature (compatible with the global `fetch`). */
|
|
188
|
+
type FetchLike = (url: string, init?: {
|
|
189
|
+
method?: string;
|
|
190
|
+
headers?: Record<string, string>;
|
|
191
|
+
body?: string;
|
|
192
|
+
}) => Promise<{
|
|
193
|
+
ok: boolean;
|
|
194
|
+
status: number;
|
|
195
|
+
json(): Promise<unknown>;
|
|
196
|
+
}>;
|
|
197
|
+
/** A thin, injectable-fetch Horizon REST client. */
|
|
198
|
+
declare class HorizonClient {
|
|
199
|
+
private readonly baseUrl;
|
|
200
|
+
private readonly fetchFn;
|
|
201
|
+
/**
|
|
202
|
+
* @param baseUrl - Horizon root URL (no trailing slash required).
|
|
203
|
+
* @param fetchFn - Injectable fetch (defaults to the global `fetch`).
|
|
204
|
+
*/
|
|
205
|
+
constructor(baseUrl: string, fetchFn?: FetchLike);
|
|
206
|
+
private getJson;
|
|
207
|
+
/**
|
|
208
|
+
* Page transactions in ascending order.
|
|
209
|
+
*
|
|
210
|
+
* @param cursor - Paging token to resume from (omit for the beginning).
|
|
211
|
+
* @param limit - Page size (default 200, Horizon's max).
|
|
212
|
+
* @returns The page's transaction records (may be empty).
|
|
213
|
+
*/
|
|
214
|
+
getTransactions(cursor?: string, limit?: number): Promise<HorizonTx[]>;
|
|
215
|
+
/**
|
|
216
|
+
* The paging token of the newest transaction this Horizon serves (one
|
|
217
|
+
* `order=desc&limit=1` page), or undefined on an empty network. The scan
|
|
218
|
+
* uses it to sanity-clamp cursors adopted from indexer-supplied data: a
|
|
219
|
+
* persisted scan cursor must never exceed the chain position Horizon
|
|
220
|
+
* itself reports, or a malicious feed could blind every future scan.
|
|
221
|
+
*/
|
|
222
|
+
getLatestTransactionToken(): Promise<string | undefined>;
|
|
223
|
+
/**
|
|
224
|
+
* Fetch the operations belonging to a single transaction.
|
|
225
|
+
*
|
|
226
|
+
* @param txHash - Transaction hash.
|
|
227
|
+
* @returns The transaction's operation records.
|
|
228
|
+
*/
|
|
229
|
+
getTransactionOperations(txHash: string): Promise<HorizonOp[]>;
|
|
230
|
+
/**
|
|
231
|
+
* Probe an account. Returns null if the account does not exist (404).
|
|
232
|
+
*
|
|
233
|
+
* @param address - Stellar G-address.
|
|
234
|
+
*/
|
|
235
|
+
getAccount(address: string): Promise<HorizonAccount | null>;
|
|
236
|
+
/**
|
|
237
|
+
* List ALL claimable balances for which the given address is a claimant,
|
|
238
|
+
* paging through every Horizon page (not just the first).
|
|
239
|
+
*
|
|
240
|
+
* Used by the token account method: a direct token send lands as a
|
|
241
|
+
* CreateClaimableBalance to the derived stealth address, which the recipient
|
|
242
|
+
* later claims. Returns an empty array when the account has none.
|
|
243
|
+
*
|
|
244
|
+
* Paging matters for correctness, not just completeness: anyone can create a
|
|
245
|
+
* claimable balance naming a (public, on-chain) stealth address as claimant,
|
|
246
|
+
* so an attacker could spam cheap balances to push the genuine payment past
|
|
247
|
+
* the first page and make the scanner silently miss it. Each page is resumed
|
|
248
|
+
* via the last record's `paging_token` (falling back to the `cursor` in
|
|
249
|
+
* `_links.next.href`), stopping on a short page, a non-advancing cursor, or
|
|
250
|
+
* the defensive {@link MAX_CLAIMABLE_BALANCE_PAGES} bound.
|
|
251
|
+
*
|
|
252
|
+
* @param claimant - Stellar G-address to filter claimable balances by.
|
|
253
|
+
*/
|
|
254
|
+
getClaimableBalances(claimant: string): Promise<HorizonClaimableBalance[]>;
|
|
255
|
+
/**
|
|
256
|
+
* Extract the `cursor` query parameter from a Horizon `_links.next.href`.
|
|
257
|
+
* Fallback for full pages whose records carry no `paging_token`.
|
|
258
|
+
*/
|
|
259
|
+
private cursorFromNextLink;
|
|
260
|
+
/**
|
|
261
|
+
* Submit a base64 transaction envelope XDR to Horizon.
|
|
262
|
+
*
|
|
263
|
+
* @param xdr - base64-encoded transaction envelope.
|
|
264
|
+
* @returns The submitted transaction hash.
|
|
265
|
+
*/
|
|
266
|
+
submitTransaction(xdr: string): Promise<{
|
|
267
|
+
hash: string;
|
|
268
|
+
}>;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Signs a canonical challenge message with the funding account's ed25519 key,
|
|
273
|
+
* proving control of that account for a fee-spending relayer request. Returns
|
|
274
|
+
* the signature as base64 or hex (or raw bytes). Kept abstract so an app can
|
|
275
|
+
* back it by a wallet, an HSM, or a raw secret key without the SDK ever holding
|
|
276
|
+
* the funding key.
|
|
277
|
+
*/
|
|
278
|
+
type FundingSigner = (message: string) => Promise<Uint8Array | string> | Uint8Array | string;
|
|
279
|
+
/** Response from `GET /credit/challenge`. */
|
|
280
|
+
interface CreditChallenge {
|
|
281
|
+
account: string;
|
|
282
|
+
nonce: string;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Canonical single-line message the funding account signs to authorize a spend.
|
|
286
|
+
* MUST match the relayer's `challengeMessage` byte-for-byte. When `bind` is
|
|
287
|
+
* supplied (e.g. the inner-tx hash on the `/relay` path) it is appended so the
|
|
288
|
+
* signature is pinned to that specific transaction and cannot be paired with a
|
|
289
|
+
* different attacker-signed inner XDR of the same amount.
|
|
290
|
+
*/
|
|
291
|
+
declare function challengeMessage(endpoint: string, fundingAccount: string, nonce: string, amount: string, bind?: string): string;
|
|
292
|
+
/** Relayer `/health` response. */
|
|
293
|
+
interface RelayerHealth {
|
|
294
|
+
status: string;
|
|
295
|
+
network?: string;
|
|
296
|
+
relayerAddress?: string;
|
|
297
|
+
balance?: string;
|
|
298
|
+
/** Whether the relayer gates fee-spending endpoints on credit. */
|
|
299
|
+
requireCredit?: boolean;
|
|
300
|
+
/** Advertised ceiling (XLM) on a fee-bump — the amount a client authorizes. */
|
|
301
|
+
maxRelayFeeXlm?: number;
|
|
302
|
+
/**
|
|
303
|
+
* The reserve component (7-dp XLM string, e.g. `'1.0000000'`) a credit-gated
|
|
304
|
+
* relayer adds to a sponsor-claim tx's fee when verifying the
|
|
305
|
+
* proof-of-control signature. Clients signing the EXACT `fee + reserve`
|
|
306
|
+
* total should prefer this over mirroring the relayer-side constant.
|
|
307
|
+
*/
|
|
308
|
+
sponsoredReserveEstimate?: string;
|
|
309
|
+
/** Credit-ledger backend: `'postgres'` (durable) or `'json'` (dev fallback). */
|
|
310
|
+
store?: string;
|
|
311
|
+
/** Nonce/rate-limit backend: `'redis'` (multi-instance) or `'memory'`. */
|
|
312
|
+
sharedState?: string;
|
|
313
|
+
}
|
|
314
|
+
/** Options for {@link RelayerClient.relay}. */
|
|
315
|
+
interface RelayOpts {
|
|
316
|
+
/** App account to debit the fee-bump fee against (credit-gated relayers). */
|
|
317
|
+
fundingAccount?: string;
|
|
318
|
+
/** Signer proving control of `fundingAccount` (credit-gated relayers). */
|
|
319
|
+
fundingSigner?: FundingSigner;
|
|
320
|
+
/**
|
|
321
|
+
* The fee/amount (7-dp XLM string) the proof-of-control signature authorizes.
|
|
322
|
+
* MUST equal the fee the relayer will charge, or the relayer rejects with 401.
|
|
323
|
+
*/
|
|
324
|
+
authAmount?: string;
|
|
325
|
+
/**
|
|
326
|
+
* Network passphrase used to compute the inner-tx hash bound into the
|
|
327
|
+
* proof-of-control signature. MUST match the passphrase the relayer verifies
|
|
328
|
+
* under, or a credit-gated relayer rejects with 401. Required to bind the
|
|
329
|
+
* inner tx on a credit-gated `/relay`; omit for the free/default path.
|
|
330
|
+
*/
|
|
331
|
+
networkPassphrase?: string;
|
|
332
|
+
/**
|
|
333
|
+
* Do not trust the relayer's returned txHash at face value: poll it to a
|
|
334
|
+
* terminal on-chain status before resolving (SDK-TXHASH-TRUST). Requires an
|
|
335
|
+
* `rpcServer` in the {@link RelayerClient} constructor opts. Throws
|
|
336
|
+
* `TransactionTimeoutError` (carrying the txHash) if the poll window closes
|
|
337
|
+
* while the tx is still unseen/pending, or an error if it FAILED on-chain.
|
|
338
|
+
* Default `false`: resolve as soon as the relayer responds, exactly as before.
|
|
339
|
+
*/
|
|
340
|
+
confirm?: boolean;
|
|
341
|
+
}
|
|
342
|
+
/** Options for {@link RelayerClient.sponsor}. */
|
|
343
|
+
interface SponsorOpts {
|
|
344
|
+
/** Starting balance for the newly created account (whole XLM). */
|
|
345
|
+
startingBalance?: string;
|
|
346
|
+
/** App account to debit against (credit-gated relayers). */
|
|
347
|
+
fundingAccount?: string;
|
|
348
|
+
/** Signer proving control of `fundingAccount` (credit-gated relayers). */
|
|
349
|
+
fundingSigner?: FundingSigner;
|
|
350
|
+
/**
|
|
351
|
+
* The total (startingBalance + fee, 7-dp XLM string) the proof-of-control
|
|
352
|
+
* signature authorizes. MUST equal what the relayer will charge.
|
|
353
|
+
*/
|
|
354
|
+
authAmount?: string;
|
|
355
|
+
}
|
|
356
|
+
/** Arguments for {@link RelayerClient.sponsorClaimPrepare}. */
|
|
357
|
+
interface SponsorClaimPrepareArgs {
|
|
358
|
+
/** The stealth address that will claim the balance. */
|
|
359
|
+
stealthAddress: string;
|
|
360
|
+
/** Asset to add a trustline for, in "CODE:ISSUER" form. */
|
|
361
|
+
asset: string;
|
|
362
|
+
/** Claimable balance id to claim. */
|
|
363
|
+
balanceId: string;
|
|
364
|
+
/** Destination that the claimed token is paid out to (must trust the asset). */
|
|
365
|
+
destination: string;
|
|
366
|
+
/** Amount of the claimed token to pay out to the destination (7-dp string). */
|
|
367
|
+
amount: string;
|
|
368
|
+
}
|
|
369
|
+
/** Response from {@link RelayerClient.sponsorClaimPrepare}. */
|
|
370
|
+
interface SponsorClaimPrepared {
|
|
371
|
+
/** Unsigned transaction XDR for the client to co-sign with the stealth key. */
|
|
372
|
+
xdr: string;
|
|
373
|
+
/** ISO timestamp after which the prepared tx's timebounds expire. */
|
|
374
|
+
expiresAt: string;
|
|
375
|
+
}
|
|
376
|
+
/** A read-only credit-ledger view for an app funding account. */
|
|
377
|
+
interface CreditView {
|
|
378
|
+
fundingAccount: string;
|
|
379
|
+
balance: string;
|
|
380
|
+
updatedAt?: string;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Thin HTTP client for the reserve relayer service. Every fee-spending endpoint
|
|
384
|
+
* is exposed as a typed method so the SDK (and apps) never hand-roll fetch calls
|
|
385
|
+
* or URL joining. Construct once and reuse.
|
|
386
|
+
*
|
|
387
|
+
* @example
|
|
388
|
+
* ```typescript
|
|
389
|
+
* const relayer = new RelayerClient('http://localhost:3000');
|
|
390
|
+
* const { status } = await relayer.health();
|
|
391
|
+
* ```
|
|
392
|
+
*/
|
|
393
|
+
declare class RelayerClient {
|
|
394
|
+
private readonly baseUrl;
|
|
395
|
+
private readonly fetchFn;
|
|
396
|
+
private readonly fundingSigner?;
|
|
397
|
+
private readonly fundingAccount?;
|
|
398
|
+
private readonly rpcServer?;
|
|
399
|
+
/**
|
|
400
|
+
* @param baseUrl - Relayer root URL. A trailing `/relay` is stripped so a
|
|
401
|
+
* bare relay URL (back-compat) resolves to the service root.
|
|
402
|
+
* @param fetchFn - Injectable fetch (defaults to the global `fetch`).
|
|
403
|
+
* @param opts - Optional default `fundingAccount` + `fundingSigner` used to
|
|
404
|
+
* authenticate fee-spending requests against a credit-gated relayer, and
|
|
405
|
+
* an optional `rpcServer` (an `rpc.Server`, or any object with its
|
|
406
|
+
* `getTransaction`) used to confirm-poll returned tx hashes when a call
|
|
407
|
+
* sets `confirm: true`.
|
|
408
|
+
*/
|
|
409
|
+
constructor(baseUrl: string, fetchFn?: FetchLike, opts?: {
|
|
410
|
+
fundingAccount?: string;
|
|
411
|
+
fundingSigner?: FundingSigner;
|
|
412
|
+
rpcServer?: TransactionStatusSource;
|
|
413
|
+
});
|
|
414
|
+
/**
|
|
415
|
+
* Poll a relayer-returned txHash until it lands on-chain (SDK-TXHASH-TRUST).
|
|
416
|
+
* A relayer could return a fabricated or failed hash with a 200; polling to a
|
|
417
|
+
* terminal status is what turns "the relayer said so" into "the chain says
|
|
418
|
+
* so". Deliberately loud when no poll handle exists — silently skipping the
|
|
419
|
+
* confirmation a caller asked for would defeat its purpose. On timeout the
|
|
420
|
+
* underlying `waitForTransaction` throws `TransactionTimeoutError` carrying
|
|
421
|
+
* the txHash, so the caller can keep polling instead of blindly resubmitting.
|
|
422
|
+
*/
|
|
423
|
+
private confirmOnChain;
|
|
424
|
+
/**
|
|
425
|
+
* Fetch a fresh challenge nonce for `fundingAccount`, sign the canonical
|
|
426
|
+
* message binding `endpoint` + account + nonce + `amount`, and return the
|
|
427
|
+
* `{ fundingAccount, nonce, signature }` to attach to a fee-spending request.
|
|
428
|
+
* Returns `undefined` when no signer is available (non-gated relayer path).
|
|
429
|
+
*/
|
|
430
|
+
private signedAuth;
|
|
431
|
+
private post;
|
|
432
|
+
private get;
|
|
433
|
+
/**
|
|
434
|
+
* Decode a relayer response. A non-JSON body on an error status (e.g. a
|
|
435
|
+
* proxy's HTML 502 page) must not mask the status, so decode failures fall
|
|
436
|
+
* back to an empty body there; a non-JSON body on a 2xx is a broken relayer
|
|
437
|
+
* and surfaces as a transport error.
|
|
438
|
+
*/
|
|
439
|
+
private decode;
|
|
440
|
+
/** Probe the relayer's health, balance, and address. */
|
|
441
|
+
health(): Promise<RelayerHealth>;
|
|
442
|
+
/**
|
|
443
|
+
* Fee-bump and submit a signed transaction envelope.
|
|
444
|
+
*
|
|
445
|
+
* @param xdr - base64 signed transaction envelope.
|
|
446
|
+
* @param opts - Optional funding account to debit the fee against, and
|
|
447
|
+
* `confirm` to poll the returned hash to an on-chain terminal status.
|
|
448
|
+
* @returns The submitted transaction hash.
|
|
449
|
+
*/
|
|
450
|
+
relay(xdr: string, opts?: RelayOpts): Promise<{
|
|
451
|
+
txHash: string;
|
|
452
|
+
}>;
|
|
453
|
+
/** Cached per-client relayer fee ceiling (XLM, 7-dp), fetched from /health. */
|
|
454
|
+
private cachedMaxRelayFee?;
|
|
455
|
+
/**
|
|
456
|
+
* The relayer's advertised max fee-bump fee (`maxRelayFeeXlm` from /health),
|
|
457
|
+
* as the fee ceiling to authorize. Falls back to the protocol default cap
|
|
458
|
+
* (0.1 XLM) when an older relayer does not advertise it. Cached per client.
|
|
459
|
+
*/
|
|
460
|
+
private maxRelayFee;
|
|
461
|
+
/** Cached per-client sponsored-reserve estimate; `null` = fetched, absent. */
|
|
462
|
+
private cachedSponsoredReserve?;
|
|
463
|
+
/**
|
|
464
|
+
* The relayer's advertised sponsored-reserve estimate
|
|
465
|
+
* (`sponsoredReserveEstimate` from /health), parsed to exact stroops.
|
|
466
|
+
* Returns `undefined` when the relayer does not advertise one, the value is
|
|
467
|
+
* unparsable, or /health is unreachable — the caller falls back to its own
|
|
468
|
+
* mirrored constant, so a health fault never breaks a claim. Cached per
|
|
469
|
+
* client, mirroring {@link maxRelayFee}.
|
|
470
|
+
*/
|
|
471
|
+
sponsoredReserveEstimateStroops(): Promise<bigint | undefined>;
|
|
472
|
+
/**
|
|
473
|
+
* Create a stealth account from the relayer's own balance (funded
|
|
474
|
+
* CreateAccount — no sponsorship sandwich).
|
|
475
|
+
*
|
|
476
|
+
* @param address - Stealth G-address to create.
|
|
477
|
+
* @param opts - Starting balance and optional funding account.
|
|
478
|
+
*/
|
|
479
|
+
sponsor(address: string, opts?: SponsorOpts): Promise<{
|
|
480
|
+
txHash: string;
|
|
481
|
+
}>;
|
|
482
|
+
/**
|
|
483
|
+
* Ask the relayer to build the sponsored claimable-balance-claim transaction.
|
|
484
|
+
* Returns UNSIGNED XDR the client must co-sign with the stealth key before
|
|
485
|
+
* calling {@link sponsorClaimSubmit}.
|
|
486
|
+
*/
|
|
487
|
+
sponsorClaimPrepare(args: SponsorClaimPrepareArgs): Promise<SponsorClaimPrepared>;
|
|
488
|
+
/**
|
|
489
|
+
* Submit a sponsor-claim transaction the client has co-signed. The relayer
|
|
490
|
+
* re-derives the expected operations from the trusted inputs
|
|
491
|
+
* (`stealthAddress`, `asset`, `balanceId`) and verifies the submitted ops
|
|
492
|
+
* match field-by-field before adding its signature and submitting.
|
|
493
|
+
*
|
|
494
|
+
* @param xdr - The stealth-co-signed transaction XDR.
|
|
495
|
+
* @param args - The trusted claim inputs (and optional funding account).
|
|
496
|
+
* `confirm: true` polls the returned hash to an on-chain terminal status
|
|
497
|
+
* (requires `rpcServer` in the constructor opts).
|
|
498
|
+
*/
|
|
499
|
+
sponsorClaimSubmit(xdr: string, args: {
|
|
500
|
+
stealthAddress: string;
|
|
501
|
+
asset: string;
|
|
502
|
+
balanceId: string;
|
|
503
|
+
destination: string;
|
|
504
|
+
amount: string;
|
|
505
|
+
fundingAccount?: string;
|
|
506
|
+
fundingSigner?: FundingSigner;
|
|
507
|
+
/** Total (reserve + fee) the proof-of-control signature authorizes. */
|
|
508
|
+
authAmount?: string;
|
|
509
|
+
/** Poll the returned txHash until it lands on-chain (see {@link RelayOpts.confirm}). */
|
|
510
|
+
confirm?: boolean;
|
|
511
|
+
}): Promise<{
|
|
512
|
+
txHash: string;
|
|
513
|
+
}>;
|
|
514
|
+
/**
|
|
515
|
+
* Report a completed on-chain deposit to the relayer so it credits the app's
|
|
516
|
+
* funding account with the paid amount.
|
|
517
|
+
*/
|
|
518
|
+
creditClaim(fundingAccount: string, txHash: string): Promise<CreditView>;
|
|
519
|
+
/** Read the current credit balance for an app funding account. */
|
|
520
|
+
creditBalance(fundingAccount: string): Promise<CreditView>;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* How a healthy relayer is picked from the candidate list. `'random'` is the
|
|
525
|
+
* default deliberately: users spread across the relayer set instead of herding
|
|
526
|
+
* onto the first entry, preserving the anonymity-set benefit of shared
|
|
527
|
+
* relayers. `'first'` gives deterministic priority ordering.
|
|
528
|
+
*/
|
|
529
|
+
type RelayerSelection = 'random' | 'first';
|
|
530
|
+
/** Tuning knobs for a {@link RelayerPool}. Every field has a safe default. */
|
|
531
|
+
interface RelayerPoolOpts {
|
|
532
|
+
/**
|
|
533
|
+
* Network name a candidate's `/health` must report. Omit (or an unknown
|
|
534
|
+
* passphrase upstream) to skip the check; a candidate that reports no
|
|
535
|
+
* network also passes — only an explicit contradiction rejects.
|
|
536
|
+
*/
|
|
537
|
+
network?: NetworkName;
|
|
538
|
+
/** Selection strategy. Default `'random'`. */
|
|
539
|
+
selection?: RelayerSelection;
|
|
540
|
+
/** Injectable fetch (tests); defaults to the global `fetch`. */
|
|
541
|
+
fetchFn?: FetchLike;
|
|
542
|
+
/** Shared parallel `/health` probe budget in ms. Default 2500. */
|
|
543
|
+
probeTimeoutMs?: number;
|
|
544
|
+
/**
|
|
545
|
+
* Per-attempt budget in ms inside {@link RelayerPool.withRelayer}, applied
|
|
546
|
+
* ONLY while a failover alternative remains. Default 10000.
|
|
547
|
+
*/
|
|
548
|
+
attemptTimeoutMs?: number;
|
|
549
|
+
/**
|
|
550
|
+
* Minimum XLM balance a candidate must report to count as healthy — a
|
|
551
|
+
* relayer one fee-bump from empty is operationally dead. Default 1 XLM
|
|
552
|
+
* (~10 fee-bumps at the default 0.1 cap).
|
|
553
|
+
*/
|
|
554
|
+
minBalanceXlm?: number;
|
|
555
|
+
/** Probe cache TTL in ms (per pool instance). Default 30000. */
|
|
556
|
+
probeTtlMs?: number;
|
|
557
|
+
/** Injectable RNG for `'random'` selection (tests). Default `Math.random`. */
|
|
558
|
+
rng?: () => number;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Per-call context the health rule and the built clients need: a credit-gated
|
|
562
|
+
* relayer only counts as healthy when the caller can actually pass its gate
|
|
563
|
+
* (`fundingAccount` + `fundingSigner` present).
|
|
564
|
+
*/
|
|
565
|
+
interface RelayerCallCtx {
|
|
566
|
+
fundingAccount?: string;
|
|
567
|
+
fundingSigner?: FundingSigner;
|
|
568
|
+
rpcServer?: TransactionStatusSource;
|
|
569
|
+
}
|
|
570
|
+
/** One candidate's probe result: a health document, or the rejection reason. */
|
|
571
|
+
interface ProbeOutcome {
|
|
572
|
+
url: string;
|
|
573
|
+
health?: RelayerHealth;
|
|
574
|
+
reason?: string;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Normalize a relayer config value into a clean URL list: trims entries, drops
|
|
578
|
+
* empties, and collapses an empty result to `undefined` so `[]` and `['']`
|
|
579
|
+
* mean "no relayer" everywhere a truthiness check gates relayed behavior
|
|
580
|
+
* (e.g. the account claim's merge-fee arithmetic).
|
|
581
|
+
*/
|
|
582
|
+
declare function normalizeRelayList(relay?: string | string[]): string[] | undefined;
|
|
583
|
+
/**
|
|
584
|
+
* Health-probing selector and failover harness over a list of relayer URLs
|
|
585
|
+
* (A3 BYO-relayer discovery).
|
|
586
|
+
*
|
|
587
|
+
* A candidate is healthy iff its `/health` reports `status === 'ok'`, its
|
|
588
|
+
* network matches the expected one (when both sides are known), its balance
|
|
589
|
+
* meets the minimum, and its credit gate is passable — `requireCredit ===
|
|
590
|
+
* false`, or the caller brought funding auth. `requireCredit` missing is
|
|
591
|
+
* treated as gated (fail-closed, matching the relayer's gating-on default).
|
|
592
|
+
*
|
|
593
|
+
* A single-URL pool is a transparent pass-through: no probe, no timeout —
|
|
594
|
+
* byte-identical behavior to constructing a `RelayerClient` directly, so
|
|
595
|
+
* existing single-relayer callers see no new traffic or failure modes.
|
|
596
|
+
*/
|
|
597
|
+
declare class RelayerPool {
|
|
598
|
+
readonly candidates: readonly string[];
|
|
599
|
+
private readonly network?;
|
|
600
|
+
private readonly selection;
|
|
601
|
+
private readonly fetchFn?;
|
|
602
|
+
private readonly probeTimeoutMs;
|
|
603
|
+
private readonly attemptTimeoutMs;
|
|
604
|
+
private readonly minBalanceXlm;
|
|
605
|
+
private readonly probeTtlMs;
|
|
606
|
+
private readonly rng;
|
|
607
|
+
private cachedProbe?;
|
|
608
|
+
private cachedProbeAt;
|
|
609
|
+
constructor(candidates: string[], opts?: RelayerPoolOpts);
|
|
610
|
+
/**
|
|
611
|
+
* Build a pool from any accepted relayer config shape, or `undefined` when
|
|
612
|
+
* the input normalizes to "no relayer" — the back-compat seam that lets
|
|
613
|
+
* `RelayerPool.from(opts.relay)` slot in wherever a single URL was used.
|
|
614
|
+
*/
|
|
615
|
+
static from(relay: string | string[] | undefined, opts?: RelayerPoolOpts): RelayerPool | undefined;
|
|
616
|
+
/**
|
|
617
|
+
* Probe every candidate's `/health` in parallel under one shared time
|
|
618
|
+
* budget. Results are cached per pool instance for {@link
|
|
619
|
+
* RelayerPoolOpts.probeTtlMs}; `force` refreshes. Only transport-level
|
|
620
|
+
* outcomes are recorded here — health-rule classification happens per call
|
|
621
|
+
* in {@link select}/{@link withRelayer}, because it depends on the caller's
|
|
622
|
+
* funding context.
|
|
623
|
+
*/
|
|
624
|
+
probe(force?: boolean): Promise<ProbeOutcome[]>;
|
|
625
|
+
/**
|
|
626
|
+
* Pick one healthy relayer URL for this call, or throw
|
|
627
|
+
* {@link NoHealthyRelayerError} naming every candidate's rejection reason.
|
|
628
|
+
* A single-candidate pool returns its URL without probing (pass-through).
|
|
629
|
+
*/
|
|
630
|
+
select(ctx?: RelayerCallCtx): Promise<string>;
|
|
631
|
+
/**
|
|
632
|
+
* Run `fn` against a selected relayer, failing over to the next healthy
|
|
633
|
+
* candidate ONLY on transport faults — {@link RelayerNetworkError}, a 5xx
|
|
634
|
+
* {@link RelayerHttpError}, or an attempt timeout. A 4xx (bad request,
|
|
635
|
+
* insufficient credit) or any non-transport error would just repeat and
|
|
636
|
+
* rethrows immediately. At most 2 attempts; the attempt timeout applies
|
|
637
|
+
* only while another candidate remains.
|
|
638
|
+
*
|
|
639
|
+
* Failover after an ambiguous timeout cannot double-spend: both attempts
|
|
640
|
+
* fee-bump the SAME signed inner tx, so its sequence number lets at most
|
|
641
|
+
* one land (`txBAD_SEQ` for the loser).
|
|
642
|
+
*/
|
|
643
|
+
withRelayer<T>(fn: (client: RelayerClient, url: string) => Promise<T>, ctx?: RelayerCallCtx): Promise<T>;
|
|
644
|
+
/** Per-call client carrying the caller's funding auth + confirm handle. */
|
|
645
|
+
private buildClient;
|
|
646
|
+
/**
|
|
647
|
+
* Classify every probed candidate for this call's context and order the
|
|
648
|
+
* healthy ones by the selection strategy.
|
|
649
|
+
*/
|
|
650
|
+
private rankHealthy;
|
|
651
|
+
/** The health rule. Returns the rejection reason, or `undefined` = healthy. */
|
|
652
|
+
private healthReason;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* A delivery method describes HOW a stealth payment reaches its recipient.
|
|
657
|
+
* - `'pool'` — deposit into the Soroban pool contract (default, private, any SAC token).
|
|
658
|
+
* - `'account'` — a direct classic Stellar payment that creates/pays a one-time stealth
|
|
659
|
+
* account, with the ephemeral key carried in a MemoHash. Native XLM only for now.
|
|
660
|
+
* - `'spp'` — reserved slot for a future Stellar Private Payments (ZK shielded pool) integration.
|
|
661
|
+
*/
|
|
662
|
+
type DeliveryMethod = 'pool' | 'account' | 'spp';
|
|
663
|
+
/**
|
|
664
|
+
* An external transaction signer, shaped like Freighter's `signTransaction`.
|
|
665
|
+
*
|
|
666
|
+
* Takes an UNSIGNED transaction XDR and returns a SIGNED transaction XDR. This
|
|
667
|
+
* lets a browser wallet (e.g. Freighter) sign the SENDER and FEE-PAYER legs of
|
|
668
|
+
* an SDK transaction so a dapp never touches a raw Stellar secret. The recovered
|
|
669
|
+
* stealth-key legs still sign locally — a wallet cannot hold the derived stealth
|
|
670
|
+
* scalar — so a signer is only ever applied to the sender / fee-payer legs.
|
|
671
|
+
*
|
|
672
|
+
* @param xdr - The unsigned transaction XDR (base64).
|
|
673
|
+
* @param opts - Network passphrase and, optionally, the G-address expected to
|
|
674
|
+
* sign (the public key the caller passed where a secret normally goes).
|
|
675
|
+
* @returns The signed transaction XDR (base64).
|
|
676
|
+
*/
|
|
677
|
+
type TransactionSigner = (xdr: string, opts: {
|
|
678
|
+
networkPassphrase: string;
|
|
679
|
+
address?: string;
|
|
680
|
+
}) => Promise<string>;
|
|
1
681
|
/** Stealth key material. All keys are hex-encoded strings for easy serialization. */
|
|
2
682
|
interface StealthKeys {
|
|
3
|
-
/** Meta-address string (
|
|
683
|
+
/** Meta-address string (shade:stellar:...) — share this publicly */
|
|
4
684
|
metaAddress: string;
|
|
5
685
|
/** Spend public key (hex) */
|
|
6
686
|
spendPubKey: string;
|
|
@@ -22,12 +702,45 @@ interface SendReceipt {
|
|
|
22
702
|
interface Payment {
|
|
23
703
|
/** The stealth address holding the funds */
|
|
24
704
|
stealthAddress: string;
|
|
25
|
-
/** Ephemeral public key from the announcement (hex) */
|
|
705
|
+
/** Ephemeral public key from the announcement/memo (hex) */
|
|
26
706
|
ephemeralPubKey: string;
|
|
27
|
-
/**
|
|
707
|
+
/**
|
|
708
|
+
* Token identifier — tri-modal, depending on how the payment arrived:
|
|
709
|
+
* - pool method: the SAC token CONTRACT address (`C...`);
|
|
710
|
+
* - account method, native send: the literal string `'native'`;
|
|
711
|
+
* - account method, token send: Horizon's `"CODE:ISSUER"` form.
|
|
712
|
+
*/
|
|
28
713
|
token: string;
|
|
714
|
+
/**
|
|
715
|
+
* Human-oriented asset label. Set on EVERY pool payment (`'XLM'` for the
|
|
716
|
+
* native SAC, otherwise the `C...` address — see `labelForToken`) and on
|
|
717
|
+
* account-method token payments (`"CODE:ISSUER"`, from the claimable
|
|
718
|
+
* balance). Absent only on account-method NATIVE payments, where
|
|
719
|
+
* {@link token} is already `'native'`.
|
|
720
|
+
*/
|
|
721
|
+
asset?: string;
|
|
722
|
+
/**
|
|
723
|
+
* Claimable balance id (account-method token payments only). Its presence is
|
|
724
|
+
* what marks a payment as a token claim rather than a plain XLM send.
|
|
725
|
+
*/
|
|
726
|
+
claimableBalanceId?: string;
|
|
29
727
|
/** Amount in whole units (e.g. 100.0 = 100 XLM) */
|
|
30
728
|
amount: number;
|
|
729
|
+
/**
|
|
730
|
+
* Exact amount as a decimal `bigint` count of stroops (1e-7 units), serialized
|
|
731
|
+
* as a string. ALWAYS set alongside {@link amount}, so callers that need
|
|
732
|
+
* exactness above ~9.007e8 XLM (where a float can no longer represent every
|
|
733
|
+
* stroop) can avoid the lossy `number`. The SDK's scan adapters populate it on
|
|
734
|
+
* every payment; when building a `Payment` by hand (or rehydrating one from an
|
|
735
|
+
* older cache that predates this field), compute it from the exact on-chain
|
|
736
|
+
* amount — claims prefer it over the lossy `number`. The `number` field is
|
|
737
|
+
* retained for display and backwards compatibility.
|
|
738
|
+
*/
|
|
739
|
+
amountStroops: string;
|
|
740
|
+
/** Which delivery method surfaced this payment */
|
|
741
|
+
method: DeliveryMethod;
|
|
742
|
+
/** Transaction hash that delivered the payment (when known) */
|
|
743
|
+
txHash?: string;
|
|
31
744
|
}
|
|
32
745
|
/** Balance entry for a stealth address. */
|
|
33
746
|
interface Balance {
|
|
@@ -37,6 +750,12 @@ interface Balance {
|
|
|
37
750
|
token: string;
|
|
38
751
|
/** Amount in whole units */
|
|
39
752
|
amount: number;
|
|
753
|
+
/**
|
|
754
|
+
* Exact amount as a decimal `bigint` count of stroops, serialized as a string.
|
|
755
|
+
* Always set, so callers can sum/display without a lossy float. See
|
|
756
|
+
* {@link Payment.amountStroops}.
|
|
757
|
+
*/
|
|
758
|
+
amountStroops: string;
|
|
40
759
|
}
|
|
41
760
|
/** Result of a withdrawal. */
|
|
42
761
|
interface WithdrawReceipt {
|
|
@@ -45,161 +764,1552 @@ interface WithdrawReceipt {
|
|
|
45
764
|
/** Amount withdrawn in whole units */
|
|
46
765
|
amount: number;
|
|
47
766
|
}
|
|
48
|
-
/**
|
|
49
|
-
* External transaction signer.
|
|
50
|
-
*
|
|
51
|
-
* Given the base64 XDR of a prepared transaction, return the base64 XDR of the
|
|
52
|
-
* signed transaction. This is exactly the shape of a browser wallet's signing
|
|
53
|
-
* call (e.g. Freighter's `signTransaction`), allowing the client to sign
|
|
54
|
-
* without ever handling a raw secret key.
|
|
55
|
-
*/
|
|
56
|
-
type TransactionSigner = (xdr: string, context: {
|
|
57
|
-
/** Network passphrase the transaction must be signed against. */
|
|
58
|
-
networkPassphrase: string;
|
|
59
|
-
/** The account expected to sign (public key, G...). */
|
|
60
|
-
address: string;
|
|
61
|
-
}) => Promise<string>;
|
|
62
767
|
/** Options for sending to a stealth address. */
|
|
63
768
|
interface SendOpts {
|
|
769
|
+
/**
|
|
770
|
+
* Delivery method to use. REQUIRED — callers must pick a method on every send.
|
|
771
|
+
* Pass `'auto'` to let the client resolve one (native + amount > 1 + 'account'
|
|
772
|
+
* enabled -> 'account'; otherwise 'pool').
|
|
773
|
+
*/
|
|
774
|
+
method: DeliveryMethod | 'auto';
|
|
64
775
|
/** Asset to send. Default: native XLM. Format: "CODE:ISSUER" */
|
|
65
776
|
asset?: string;
|
|
66
777
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
778
|
+
* External signer for the SENDER leg (Freighter-style). When set, the caller
|
|
779
|
+
* passes a PUBLIC key (G...) where a secret is normally expected — `send()`'s
|
|
780
|
+
* `senderSecret` positional carries the sender's G-address — and the SDK
|
|
781
|
+
* delegates signing to this function instead of `Keypair.fromSecret`. The
|
|
782
|
+
* stealth-key legs (claim/withdraw signatures) are unaffected: a wallet cannot
|
|
783
|
+
* hold the derived stealth scalar, so those always sign locally.
|
|
70
784
|
*/
|
|
71
785
|
signTransaction?: TransactionSigner;
|
|
786
|
+
/**
|
|
787
|
+
* The G-address of the fee payer when {@link signTransaction} is used on a
|
|
788
|
+
* flow that needs a distinct fee payer. Unused by `send()` (whose fee payer is
|
|
789
|
+
* the sender itself); present here for symmetry with {@link ClaimOpts}.
|
|
790
|
+
*/
|
|
791
|
+
feePayerAddress?: string;
|
|
72
792
|
}
|
|
73
|
-
/** Options for withdrawing from a stealth address. */
|
|
793
|
+
/** Options for withdrawing from a stealth address (pool method). */
|
|
74
794
|
interface WithdrawOpts {
|
|
75
795
|
/** Stealth keys (need view + spend private keys) */
|
|
76
796
|
keys: StealthKeys;
|
|
797
|
+
/** Secret key of account paying the Soroban invocation fee */
|
|
798
|
+
feePayer: string;
|
|
77
799
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
800
|
+
* Relay URL(s) for fee-bumped submission (privacy-preserving). A list is
|
|
801
|
+
* health-probed and routed like {@link ClientConfig.relayer}.
|
|
80
802
|
*/
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Sign the fee-payer side with an external wallet instead of `feePayer`.
|
|
84
|
-
* The stealth withdraw authorization is still produced internally from
|
|
85
|
-
* `keys`; only the transaction envelope signature is delegated.
|
|
86
|
-
*/
|
|
87
|
-
signTransaction?: TransactionSigner;
|
|
88
|
-
/** Fee payer's public key (G...). Required when using `signTransaction`. */
|
|
89
|
-
feePayerAddress?: string;
|
|
90
|
-
/** Relay URL for fee-bumped submission (privacy-preserving) */
|
|
91
|
-
relay?: string;
|
|
803
|
+
relay?: string | string[];
|
|
92
804
|
/** Asset to withdraw. Default: native XLM. Format: "CODE:ISSUER" */
|
|
93
805
|
asset?: string;
|
|
94
806
|
/** Amount to withdraw. Default: full balance */
|
|
95
807
|
amount?: number;
|
|
808
|
+
/**
|
|
809
|
+
* App funding account (G-address) to debit the relayer fee against when the
|
|
810
|
+
* relayer is credit-gated. Threaded into the `/relay` call so a gated relayer
|
|
811
|
+
* does not respond 402 `insufficient_credit`. Ignored by a non-gated relayer.
|
|
812
|
+
* See {@link ClaimOpts.fundingAccount}.
|
|
813
|
+
*/
|
|
814
|
+
fundingAccount?: string;
|
|
815
|
+
/**
|
|
816
|
+
* Signer proving control of {@link fundingAccount}: a credit-gated relayer
|
|
817
|
+
* requires a fresh challenge nonce signed by the funding account before it
|
|
818
|
+
* debits credit for the fee-bump (proof-of-control; without it every gated
|
|
819
|
+
* `/relay` call is rejected 402 `missing_auth`). Omit for non-gated relayers.
|
|
820
|
+
*/
|
|
821
|
+
fundingSigner?: FundingSigner;
|
|
822
|
+
/**
|
|
823
|
+
* Relayed submissions only: poll the relayer-returned txHash until it is
|
|
824
|
+
* on-chain before returning (SDK-TXHASH-TRUST), surfacing a
|
|
825
|
+
* `TransactionTimeoutError` (with the hash) if it never lands.
|
|
826
|
+
*/
|
|
827
|
+
confirm?: boolean;
|
|
96
828
|
}
|
|
97
|
-
/**
|
|
98
|
-
interface
|
|
99
|
-
/**
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
|
|
829
|
+
/** Per-method scan cursors, so each adapter can resume where it left off. */
|
|
830
|
+
interface ScanCursor {
|
|
831
|
+
/** Pool announcement start index (next unread announcement) */
|
|
832
|
+
pool?: string;
|
|
833
|
+
/** Horizon paging token for the account method */
|
|
834
|
+
account?: string;
|
|
835
|
+
/** Reserved for the spp method */
|
|
836
|
+
spp?: string;
|
|
103
837
|
}
|
|
104
|
-
|
|
105
838
|
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* Developers don't need to understand the underlying protocol to use this.
|
|
110
|
-
*
|
|
111
|
-
* @example
|
|
112
|
-
* ```typescript
|
|
113
|
-
* const client = new StealthClient({ network: 'local', contractId: 'CXXX...' });
|
|
114
|
-
*
|
|
115
|
-
* // Generate keys
|
|
116
|
-
* const keys = StealthClient.keygen();
|
|
117
|
-
*
|
|
118
|
-
* // Send 100 XLM to a stealth address
|
|
119
|
-
* const receipt = await client.send(keys.metaAddress, 100, 'SXXX...');
|
|
120
|
-
*
|
|
121
|
-
* // Scan for received payments
|
|
122
|
-
* const payments = await client.scan(keys);
|
|
123
|
-
*
|
|
124
|
-
* // Withdraw
|
|
125
|
-
* await client.withdraw(payments[0].stealthAddress, 'GDEST...', {
|
|
126
|
-
* keys,
|
|
127
|
-
* feePayer: 'SXXX...',
|
|
128
|
-
* });
|
|
129
|
-
* ```
|
|
839
|
+
* Options controlling a cursor-aware scan. Also accepted by the balance path
|
|
840
|
+
* (`StealthClient.balance`/`balanceWithCursor`), whose account phase otherwise
|
|
841
|
+
* re-walks the entire Horizon history on every call.
|
|
130
842
|
*/
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
843
|
+
interface ScanOpts {
|
|
844
|
+
/** Restrict the scan to these methods (default: all enabled adapters) */
|
|
845
|
+
methods?: DeliveryMethod[];
|
|
846
|
+
/** Resume from a previously returned cursor */
|
|
847
|
+
cursor?: ScanCursor;
|
|
136
848
|
/**
|
|
137
|
-
*
|
|
138
|
-
*
|
|
849
|
+
* Account method with an indexer configured ({@link ClientConfig.indexerUrl})
|
|
850
|
+
* only: a COLD scan (no cursor) walks the full Horizon history from genesis
|
|
851
|
+
* instead of fast-starting at the indexer's first covered position. Set it
|
|
852
|
+
* to discover payments that predate the indexer's coverage. Without an
|
|
853
|
+
* indexer the Horizon walk is always exhaustive, so this has no effect.
|
|
139
854
|
*/
|
|
140
|
-
|
|
855
|
+
exhaustive?: boolean;
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Diagnostics for ONE slice of a segmented scan. The account method walks up
|
|
859
|
+
* to three segments per scan (Horizon pre-segment, indexer-covered span,
|
|
860
|
+
* Horizon tail) — or a single unbounded Horizon walk when no indexer applies —
|
|
861
|
+
* and reports each slice's throughput so operators can see where discovery
|
|
862
|
+
* work actually happened.
|
|
863
|
+
*/
|
|
864
|
+
interface ScanSegmentMeta {
|
|
865
|
+
/** Which feed served this segment's transactions. */
|
|
866
|
+
source: 'indexer' | 'horizon';
|
|
141
867
|
/**
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
868
|
+
* The segment's position in the scan: the bounded Horizon walk before
|
|
869
|
+
* indexer coverage (`'pre'`), the indexer-covered span (`'indexer'`), the
|
|
870
|
+
* always-run Horizon tail (`'tail'`), or the single unbounded walk of a
|
|
871
|
+
* scan with no usable indexer (`'full'`).
|
|
145
872
|
*/
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
873
|
+
role: 'pre' | 'indexer' | 'tail' | 'full';
|
|
874
|
+
/** Hash-memo txs that entered the matcher with a plausible 32-byte R. */
|
|
875
|
+
candidates: number;
|
|
876
|
+
/** Payments collected from this segment. */
|
|
877
|
+
matches: number;
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Per-method scan diagnostics. Produced by the account method today, and
|
|
881
|
+
* strictly observability: the numbers describe how a scan discovered its
|
|
882
|
+
* payments, never which payments it finds — the Horizon tail bounds
|
|
883
|
+
* correctness regardless of what happens to the indexer segment.
|
|
884
|
+
*/
|
|
885
|
+
interface MethodScanMeta {
|
|
886
|
+
/** True once at least one `/announcements` page was successfully consumed. */
|
|
887
|
+
indexerUsed: boolean;
|
|
149
888
|
/**
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
* deposits tokens into the pool contract, and records the announcement.
|
|
154
|
-
*
|
|
155
|
-
* Signing: by default the transaction is signed with `senderSecret` (a
|
|
156
|
-
* Stellar secret key). If `opts.signTransaction` is provided, signing is
|
|
157
|
-
* delegated to that external signer (e.g. a browser wallet) and the
|
|
158
|
-
* `senderSecret` argument is treated as the sender's public key instead.
|
|
159
|
-
*
|
|
160
|
-
* @param metaAddress - Recipient's meta-address (st:stellar:... format)
|
|
161
|
-
* @param amount - Amount in whole units (e.g. 100 = 100 XLM)
|
|
162
|
-
* @param senderSecret - Sender's secret key, or public key when using `opts.signTransaction`
|
|
163
|
-
* @param opts - Optional: asset to send, external signer
|
|
889
|
+
* Why a CONFIGURED indexer was skipped by the health guard (absent when no
|
|
890
|
+
* indexer is configured, or when it was used). `'stale'` means the indexer's
|
|
891
|
+
* self-reported lag exceeded `ClientConfig.indexerMaxLagSeconds`.
|
|
164
892
|
*/
|
|
165
|
-
|
|
893
|
+
indexerSkipReason?: 'unhealthy' | 'network_mismatch' | 'no_coverage' | 'stale' | 'unreachable';
|
|
166
894
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
* @param prepared - The prepared transaction to sign
|
|
171
|
-
* @param secretOrPublic - Secret key (local signing) or public key (external)
|
|
172
|
-
* @param publicKey - Signer's public key (G...)
|
|
173
|
-
* @param signer - Optional external signer; when present, no secret is used
|
|
895
|
+
* The indexer's self-reported lag when `/health` was read (`null` when the
|
|
896
|
+
* indexer did not report one). Absent when `/health` was never readable.
|
|
174
897
|
*/
|
|
175
|
-
|
|
898
|
+
indexerLagSeconds?: number | null;
|
|
176
899
|
/**
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
* @param keys - Your stealth keys (needs viewPrivKey + spendPubKey)
|
|
900
|
+
* The post-segment `/health` re-check verdict (present whenever the scan
|
|
901
|
+
* entered the indexer branch). Anything but `'ok'` means the segment's
|
|
902
|
+
* results and adopted positions were DISCARDED and the Horizon tail
|
|
903
|
+
* re-walked the whole span — a gap recorded while the segment was being
|
|
904
|
+
* paged must not advance the client cursor past a hole.
|
|
183
905
|
*/
|
|
184
|
-
|
|
906
|
+
postCheck?: 'ok' | 'unhealthy' | 'unreachable';
|
|
185
907
|
/**
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
908
|
+
* True when the final cursor came from indexer-supplied data, exceeded the
|
|
909
|
+
* chain head Horizon reports, and was clamped down to that head — a
|
|
910
|
+
* persisted cursor must never point past the chain, or one malicious feed
|
|
911
|
+
* response would blind every future scan.
|
|
912
|
+
*/
|
|
913
|
+
cursorClamped?: boolean;
|
|
914
|
+
/** The scan's segments, in the order they ran. */
|
|
915
|
+
segments: ScanSegmentMeta[];
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Result of a cursor-aware scan — and of `StealthClient.balanceWithCursor`,
|
|
919
|
+
* which returns the same shape with balance-view rows (claimed payments
|
|
920
|
+
* dropped, native rows reporting the live remaining balance).
|
|
921
|
+
*/
|
|
922
|
+
interface ScanResult {
|
|
923
|
+
/** Detected payments across all scanned methods */
|
|
924
|
+
payments: Payment[];
|
|
925
|
+
/** Updated cursor to persist and pass to the next scan */
|
|
926
|
+
cursor: ScanCursor;
|
|
927
|
+
/**
|
|
928
|
+
* Per-method scan diagnostics; populated by the account method today.
|
|
929
|
+
* Observability only — never affects which payments are found.
|
|
189
930
|
*/
|
|
190
|
-
|
|
931
|
+
meta?: Partial<Record<DeliveryMethod, MethodScanMeta>>;
|
|
932
|
+
}
|
|
933
|
+
/** Options for claiming a detected payment. */
|
|
934
|
+
interface ClaimOpts {
|
|
935
|
+
/** Stealth keys (need view + spend private keys) */
|
|
936
|
+
keys: StealthKeys;
|
|
191
937
|
/**
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
* Recovers the stealth private key, signs a withdraw message,
|
|
195
|
-
* and submits the transaction. Use `opts.relay` for privacy-preserving
|
|
196
|
-
* withdrawal via a relayer.
|
|
197
|
-
*
|
|
198
|
-
* @param stealthAddress - The stealth address to withdraw from
|
|
199
|
-
* @param destination - Destination Stellar address (G...)
|
|
200
|
-
* @param opts - Withdraw options (keys, feePayer, optional relay/asset/amount)
|
|
938
|
+
* Relay URL(s) for fee-bumped submission (privacy-preserving). A list is
|
|
939
|
+
* health-probed and routed like {@link ClientConfig.relayer}.
|
|
201
940
|
*/
|
|
202
|
-
|
|
941
|
+
relay?: string | string[];
|
|
942
|
+
/**
|
|
943
|
+
* For the account method: sweep the whole account via AccountMerge (default true).
|
|
944
|
+
* Set false to leave the stealth account open (Payment, keeping the base reserve).
|
|
945
|
+
* For the pool method this flag is ignored.
|
|
946
|
+
*/
|
|
947
|
+
merge?: boolean;
|
|
948
|
+
/** Secret key of an account paying the fee (pool method direct submission) */
|
|
949
|
+
feePayer?: string;
|
|
950
|
+
/** Asset to claim (pool method). Default: native XLM. Format: "CODE:ISSUER" */
|
|
951
|
+
asset?: string;
|
|
952
|
+
/**
|
|
953
|
+
* Amount to claim. Default: full balance. Honored by POOL claims (partial
|
|
954
|
+
* withdrawal) AND by account-method NATIVE claims with `merge: false`
|
|
955
|
+
* (partial payout that keeps the stealth account open). Passing it on an
|
|
956
|
+
* account-method native claim WITHOUT `merge: false`, or on an
|
|
957
|
+
* account-method token claim (always claimed in full), throws
|
|
958
|
+
* {@link ClaimAmountRequiresNoMergeError} instead of being silently ignored.
|
|
959
|
+
*/
|
|
960
|
+
amount?: number;
|
|
961
|
+
/**
|
|
962
|
+
* For the account-method token claim: use the relayer's sponsor-claim pair
|
|
963
|
+
* (prepare -> stealth-sign -> submit) instead of a self-funded stealth tx.
|
|
964
|
+
* Needed when the stealth account stub is missing / cannot pay reserves.
|
|
965
|
+
*/
|
|
966
|
+
sponsored?: boolean;
|
|
967
|
+
/**
|
|
968
|
+
* App funding account (G-address) to debit the relayer fee against when the
|
|
969
|
+
* relayer is credit-gated (`RELAYER_REQUIRE_CREDIT=1`). Threaded into the
|
|
970
|
+
* relay / sponsor-claim submit calls so a gated relayer does not respond 402
|
|
971
|
+
* `insufficient_credit`. Ignored by a non-gated relayer.
|
|
972
|
+
*/
|
|
973
|
+
fundingAccount?: string;
|
|
974
|
+
/**
|
|
975
|
+
* Signer proving control of {@link fundingAccount}: a credit-gated relayer
|
|
976
|
+
* requires a fresh challenge nonce signed by the funding account before it
|
|
977
|
+
* debits credit (proof-of-control; without it every gated relay /
|
|
978
|
+
* sponsor-claim submit is rejected 401 `missing_auth`). Omit for non-gated
|
|
979
|
+
* relayers.
|
|
980
|
+
*/
|
|
981
|
+
fundingSigner?: FundingSigner;
|
|
982
|
+
/**
|
|
983
|
+
* Relayed submissions only: poll the relayer-returned txHash until it is
|
|
984
|
+
* on-chain before returning (SDK-TXHASH-TRUST), surfacing a
|
|
985
|
+
* `TransactionTimeoutError` (with the hash) if it never lands.
|
|
986
|
+
*/
|
|
987
|
+
confirm?: boolean;
|
|
988
|
+
/**
|
|
989
|
+
* External signer for the FEE-PAYER leg (Freighter-style). When set on a pool
|
|
990
|
+
* claim, the caller passes the fee payer's PUBLIC key via
|
|
991
|
+
* {@link ClaimOpts.feePayerAddress} where a secret is normally expected (in
|
|
992
|
+
* {@link ClaimOpts.feePayer}) and the SDK delegates signing of the fee-paying
|
|
993
|
+
* transaction to this function instead of `Keypair.fromSecret`. The
|
|
994
|
+
* stealth-key withdrawal signature is unaffected — a wallet cannot hold the
|
|
995
|
+
* derived stealth scalar, so it always signs locally.
|
|
996
|
+
*/
|
|
997
|
+
signTransaction?: TransactionSigner;
|
|
998
|
+
/**
|
|
999
|
+
* The fee payer's G-address, required when {@link signTransaction} is set on a
|
|
1000
|
+
* pool claim (which needs a fee payer). The SDK never calls
|
|
1001
|
+
* `Keypair.fromSecret` on this value; it is handed to {@link signTransaction}
|
|
1002
|
+
* as the address to sign with. When {@link signTransaction} is set but this is
|
|
1003
|
+
* missing, a {@link FeePayerAddressRequiredError} is thrown.
|
|
1004
|
+
*/
|
|
1005
|
+
feePayerAddress?: string;
|
|
1006
|
+
}
|
|
1007
|
+
/** Receipt returned after a successful claim. */
|
|
1008
|
+
interface ClaimReceipt {
|
|
1009
|
+
/** Transaction hash */
|
|
1010
|
+
txHash: string;
|
|
1011
|
+
/** Amount claimed in whole units */
|
|
1012
|
+
amount: number;
|
|
1013
|
+
/** Which delivery method the claim used */
|
|
1014
|
+
method: DeliveryMethod;
|
|
1015
|
+
}
|
|
1016
|
+
/** Client configuration. */
|
|
1017
|
+
interface ClientConfig {
|
|
1018
|
+
/** Network to connect to (a key of the SDK's `NETWORKS` table) */
|
|
1019
|
+
network: NetworkName;
|
|
1020
|
+
/** Override the default contract ID for the stealth pool */
|
|
1021
|
+
contractId?: string;
|
|
1022
|
+
/** Override the Horizon REST endpoint (used by the account method) */
|
|
1023
|
+
horizonUrl?: string;
|
|
1024
|
+
/**
|
|
1025
|
+
* Optional announcement-indexer URL — an account-method DISCOVERY
|
|
1026
|
+
* accelerator. When set, `scan`/`balance` consume the indexer's
|
|
1027
|
+
* pre-extracted announcement feed (operations inlined, no per-tx Horizon
|
|
1028
|
+
* round-trip) instead of walking every Horizon transaction. Horizon remains
|
|
1029
|
+
* the source of truth: the scan verifies the indexer's /health coverage
|
|
1030
|
+
* first and falls back to the pure Horizon walk automatically when the
|
|
1031
|
+
* indexer is unreachable, unhealthy, or on the wrong network — and always
|
|
1032
|
+
* finishes with a Horizon tail so indexer lag cannot hide a payment.
|
|
1033
|
+
*/
|
|
1034
|
+
indexerUrl?: string;
|
|
1035
|
+
/**
|
|
1036
|
+
* Cap (seconds) on the indexer's self-reported `/health` lag before the
|
|
1037
|
+
* account scan skips it entirely. Default 21600 (6 hours) — deliberately
|
|
1038
|
+
* lenient: the Horizon tail already bounds correctness (a lagging indexer
|
|
1039
|
+
* can never hide a payment), and skipping a merely-slow indexer would force
|
|
1040
|
+
* cold scans back toward the full-history walk — so the cap exists only to
|
|
1041
|
+
* disqualify operationally abandoned indexers. `Number.POSITIVE_INFINITY`
|
|
1042
|
+
* disables the cap.
|
|
1043
|
+
*/
|
|
1044
|
+
indexerMaxLagSeconds?: number;
|
|
1045
|
+
/** Delivery methods to enable. Default: ['pool'] */
|
|
1046
|
+
methods?: DeliveryMethod[];
|
|
1047
|
+
/**
|
|
1048
|
+
* Default relayer(s) for fee-bumped submissions. A list enables BYO-relayer
|
|
1049
|
+
* discovery: candidates are health-probed in parallel and relayed calls
|
|
1050
|
+
* route to a healthy one (with failover on relayer faults) per
|
|
1051
|
+
* {@link relayerSelection}. A single string behaves exactly as before.
|
|
1052
|
+
*/
|
|
1053
|
+
relayer?: string | string[];
|
|
1054
|
+
/**
|
|
1055
|
+
* How a healthy relayer is picked from a multi-URL {@link relayer} list.
|
|
1056
|
+
* Default `'random'` — spreads users across the relayer set instead of
|
|
1057
|
+
* herding onto the first entry (anonymity-set preserving). Only meaningful
|
|
1058
|
+
* with more than one URL.
|
|
1059
|
+
*/
|
|
1060
|
+
relayerSelection?: RelayerSelection;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
/**
|
|
1064
|
+
* High-level client for stealth payments on Stellar.
|
|
1065
|
+
*
|
|
1066
|
+
* Wraps DKSAP cryptography behind pluggable delivery methods. Each method
|
|
1067
|
+
* (`'pool'`, `'account'`, and the reserved `'spp'`) is an adapter; the client
|
|
1068
|
+
* builds a registry from `config.methods` and routes send/scan/claim through it.
|
|
1069
|
+
*
|
|
1070
|
+
* @example
|
|
1071
|
+
* ```typescript
|
|
1072
|
+
* const client = new StealthClient({
|
|
1073
|
+
* network: 'testnet',
|
|
1074
|
+
* contractId: 'C...', // your deployed pool contract (required for 'pool')
|
|
1075
|
+
* methods: ['pool', 'account'],
|
|
1076
|
+
* });
|
|
1077
|
+
*
|
|
1078
|
+
* const keys = StealthClient.keygen();
|
|
1079
|
+
*
|
|
1080
|
+
* // A method is REQUIRED on every send — no implicit default.
|
|
1081
|
+
* await client.send(keys.metaAddress, 100, 'SXXX...', { method: 'auto' });
|
|
1082
|
+
*
|
|
1083
|
+
* const { payments } = await client.scanWithCursor(keys);
|
|
1084
|
+
* await client.claim(payments[0], 'GDEST...', { keys });
|
|
1085
|
+
* ```
|
|
1086
|
+
*/
|
|
1087
|
+
declare class StealthClient {
|
|
1088
|
+
private readonly contractId;
|
|
1089
|
+
private readonly networkPassphrase;
|
|
1090
|
+
private readonly server;
|
|
1091
|
+
private readonly enabledMethods;
|
|
1092
|
+
private readonly adapters;
|
|
1093
|
+
private readonly relayer?;
|
|
1094
|
+
private readonly relayerSelection?;
|
|
1095
|
+
constructor(config: ClientConfig);
|
|
1096
|
+
/**
|
|
1097
|
+
* Generate a new random stealth key pair.
|
|
1098
|
+
* No network connection needed.
|
|
1099
|
+
*/
|
|
1100
|
+
static keygen(): StealthKeys;
|
|
1101
|
+
/**
|
|
1102
|
+
* Generate stealth keys from a BIP-39 mnemonic.
|
|
1103
|
+
* Returns the mnemonic alongside the keys for backup.
|
|
1104
|
+
* No network connection needed.
|
|
1105
|
+
*/
|
|
1106
|
+
static fromMnemonic(mnemonic?: string): StealthKeys & {
|
|
1107
|
+
mnemonic: string;
|
|
1108
|
+
};
|
|
1109
|
+
/**
|
|
1110
|
+
* Resolve `'auto'` to a concrete method: native asset AND amount > 1 AND
|
|
1111
|
+
* 'account' enabled -> 'account'; otherwise 'pool'.
|
|
1112
|
+
*/
|
|
1113
|
+
private resolveMethod;
|
|
1114
|
+
private getAdapter;
|
|
1115
|
+
/**
|
|
1116
|
+
* Send tokens to a stealth address via the chosen delivery method.
|
|
1117
|
+
*
|
|
1118
|
+
* A method is REQUIRED on every call (`opts.method`). Pass `'auto'` to let the
|
|
1119
|
+
* client resolve one; there is deliberately no implicit default.
|
|
1120
|
+
*
|
|
1121
|
+
* @param metaAddress - Recipient's meta-address (shade:stellar:... format)
|
|
1122
|
+
* @param amount - Amount in whole units (e.g. 100 = 100 XLM)
|
|
1123
|
+
* @param senderSecret - Sender's Stellar secret key
|
|
1124
|
+
* @param opts - Delivery method (required) and optional asset
|
|
1125
|
+
* @throws {MethodRequiredError} If no method is provided.
|
|
1126
|
+
* @throws {MethodNotEnabledError} If the resolved method is not enabled.
|
|
1127
|
+
*/
|
|
1128
|
+
send(metaAddress: string, amount: number, senderSecret: string, opts: SendOpts): Promise<SendReceipt>;
|
|
1129
|
+
/**
|
|
1130
|
+
* Scan for stealth payments across every enabled delivery method.
|
|
1131
|
+
*
|
|
1132
|
+
* @param keys - Your stealth keys (needs viewPrivKey + spendPubKey)
|
|
1133
|
+
*/
|
|
1134
|
+
scan(keys: StealthKeys): Promise<Payment[]>;
|
|
1135
|
+
/**
|
|
1136
|
+
* Cursor-aware scan across enabled (or explicitly requested) methods. Returns
|
|
1137
|
+
* the merged payments plus an updated per-method cursor to persist and pass to
|
|
1138
|
+
* the next scan for incremental discovery.
|
|
1139
|
+
*
|
|
1140
|
+
* @param keys - Your stealth keys (needs viewPrivKey + spendPubKey)
|
|
1141
|
+
* @param opts - Optional method filter and resume cursor
|
|
1142
|
+
*/
|
|
1143
|
+
scanWithCursor(keys: StealthKeys, opts?: ScanOpts): Promise<ScanResult>;
|
|
1144
|
+
/**
|
|
1145
|
+
* Shared scan implementation. When `suppressClaimed` is set (the balance
|
|
1146
|
+
* path), the account adapter drops fully-swept/merged native accounts (live
|
|
1147
|
+
* balance 0) so a spent stealth account is not reported as spendable — while
|
|
1148
|
+
* the plain discovery scan keeps returning still-claimable rows.
|
|
1149
|
+
*/
|
|
1150
|
+
private scanInternal;
|
|
1151
|
+
/**
|
|
1152
|
+
* Get balances for all your stealth payments across enabled methods.
|
|
1153
|
+
*
|
|
1154
|
+
* @param keys - Your stealth keys (needs viewPrivKey + spendPubKey)
|
|
1155
|
+
* @param opts - Optional method filter and resume cursor (a cursor returned
|
|
1156
|
+
* by a previous {@link scanWithCursor}/{@link balanceWithCursor}). With no
|
|
1157
|
+
* opts the whole history is scanned, exactly as before. Passing the
|
|
1158
|
+
* persisted cursor turns the account phase from a full Horizon re-walk
|
|
1159
|
+
* into O(new transactions); use {@link balanceWithCursor} when you also
|
|
1160
|
+
* need the advanced cursor back to persist it.
|
|
1161
|
+
*/
|
|
1162
|
+
balance(keys: StealthKeys, opts?: ScanOpts): Promise<Balance[]>;
|
|
1163
|
+
/**
|
|
1164
|
+
* Cursor-aware balance check: like {@link balance}, but resumes the
|
|
1165
|
+
* underlying scan from `opts.cursor` and returns the full live payments
|
|
1166
|
+
* (ephemeral key, txHash, claimable-balance id) PLUS the advanced cursor so
|
|
1167
|
+
* callers can persist it for the next call — the exact counterpart of
|
|
1168
|
+
* {@link scanWithCursor} for the balance path.
|
|
1169
|
+
*
|
|
1170
|
+
* The rows are the balance view: claimed/fully-swept payments are dropped
|
|
1171
|
+
* and native rows report the LIVE remaining account balance (not the
|
|
1172
|
+
* original per-transaction amount). Callers that persist discovered
|
|
1173
|
+
* payments should merge these rows into their cache BEFORE persisting the
|
|
1174
|
+
* advanced cursor, since the next resumed scan will skip everything behind
|
|
1175
|
+
* it.
|
|
1176
|
+
*
|
|
1177
|
+
* @param keys - Your stealth keys (needs viewPrivKey + spendPubKey)
|
|
1178
|
+
* @param opts - Optional method filter and resume cursor
|
|
1179
|
+
*/
|
|
1180
|
+
balanceWithCursor(keys: StealthKeys, opts?: ScanOpts): Promise<ScanResult>;
|
|
1181
|
+
/**
|
|
1182
|
+
* Claim a detected payment to a destination, branching on the payment's
|
|
1183
|
+
* delivery method: `'pool'` uses the withdraw path, `'account'` sweeps or
|
|
1184
|
+
* partially pays out the stealth account.
|
|
1185
|
+
*
|
|
1186
|
+
* @param payment - A payment returned from {@link scan}/{@link scanWithCursor}.
|
|
1187
|
+
* @param destination - Destination Stellar G-address.
|
|
1188
|
+
* @param opts - Claim options (keys required; relay/merge/feePayer optional).
|
|
1189
|
+
*/
|
|
1190
|
+
claim(payment: Payment, destination: string, opts: ClaimOpts): Promise<ClaimReceipt>;
|
|
1191
|
+
/**
|
|
1192
|
+
* Withdraw tokens from the stealth pool.
|
|
1193
|
+
*
|
|
1194
|
+
* @deprecated Use {@link claim} with a pool payment instead. Retained for
|
|
1195
|
+
* backwards compatibility; behaves exactly like the original pool withdraw.
|
|
1196
|
+
*
|
|
1197
|
+
* @param stealthAddress - The stealth address to withdraw from
|
|
1198
|
+
* @param destination - Destination Stellar address (G...)
|
|
1199
|
+
* @param opts - Withdraw options (keys, feePayer, optional relay/asset/amount)
|
|
1200
|
+
*/
|
|
1201
|
+
withdraw(stealthAddress: string, destination: string, opts: WithdrawOpts): Promise<WithdrawReceipt>;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
/**
|
|
1205
|
+
* Crypto's raw (`Uint8Array`-based) meta-address, mirrored here.
|
|
1206
|
+
*
|
|
1207
|
+
* The SDK's own {@link StealthMetaAddress} is a `shade:stellar:` *string*; this
|
|
1208
|
+
* is the byte-level pair the crypto layer works with.
|
|
1209
|
+
*/
|
|
1210
|
+
interface RawStealthMetaAddress {
|
|
1211
|
+
/** 32-byte ed25519 public key for spending */
|
|
1212
|
+
spendPubKey: Uint8Array;
|
|
1213
|
+
/** 32-byte ed25519 public key for viewing */
|
|
1214
|
+
viewPubKey: Uint8Array;
|
|
1215
|
+
}
|
|
1216
|
+
/**
|
|
1217
|
+
* Crypto's raw (`Uint8Array`-based) stealth keys, exposed under a distinct name
|
|
1218
|
+
* so code that mixes both layers has one import site: convert with
|
|
1219
|
+
* {@link stealthKeysFromRaw} to get the SDK's hex-string {@link StealthKeys}.
|
|
1220
|
+
*/
|
|
1221
|
+
interface RawStealthKeys {
|
|
1222
|
+
/** 32-byte ed25519 private key for spending */
|
|
1223
|
+
spendPrivKey: Uint8Array;
|
|
1224
|
+
/** 32-byte ed25519 private key for viewing */
|
|
1225
|
+
viewPrivKey: Uint8Array;
|
|
1226
|
+
/** Derived meta-address containing public keys */
|
|
1227
|
+
metaAddress: RawStealthMetaAddress;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
/**
|
|
1231
|
+
* Convert crypto's raw stealth keys (Uint8Array scalars + meta-address object,
|
|
1232
|
+
* as returned by `generateMetaAddress`, `mnemonicToStealthKeys`, or
|
|
1233
|
+
* `deriveKeysFromSignature`) into the SDK's hex-string {@link StealthKeys}
|
|
1234
|
+
* shape used by every client/session API.
|
|
1235
|
+
*
|
|
1236
|
+
* The two packages deliberately reuse the name `StealthKeys` for different
|
|
1237
|
+
* shapes; import the raw one as `RawStealthKeys` (re-exported from the SDK
|
|
1238
|
+
* index) and convert at the boundary with this helper.
|
|
1239
|
+
*/
|
|
1240
|
+
declare function stealthKeysFromRaw(raw: RawStealthKeys): StealthKeys;
|
|
1241
|
+
/**
|
|
1242
|
+
* A wallet signer. Given the derivation message it returns an ed25519 signature
|
|
1243
|
+
* in one of the shapes real wallets produce: raw bytes, a hex/base64 string, or
|
|
1244
|
+
* Freighter's `{ signedMessage }` envelope.
|
|
1245
|
+
*/
|
|
1246
|
+
type WalletSigner = (message: string) => Promise<Uint8Array | string | {
|
|
1247
|
+
signedMessage: string | Uint8Array;
|
|
1248
|
+
}>;
|
|
1249
|
+
/**
|
|
1250
|
+
* The default key-derivation scope. This is DECOUPLED from any transport network
|
|
1251
|
+
* (e.g. testnet vs. local) so that a wallet re-derives the same stealth keys
|
|
1252
|
+
* regardless of which network a transaction is later submitted to. The CLI's
|
|
1253
|
+
* `--key-scope` flag defaults to this exact value so both tools line up.
|
|
1254
|
+
*/
|
|
1255
|
+
declare const DEFAULT_KEY_SCOPE = "stealth";
|
|
1256
|
+
/** The default application id used to scope derived keys. */
|
|
1257
|
+
declare const DEFAULT_APP_ID = "default";
|
|
1258
|
+
/** Options for {@link keysFromWalletSignature}. */
|
|
1259
|
+
interface WalletKeysOpts {
|
|
1260
|
+
/**
|
|
1261
|
+
* Key-derivation scope folded into the signed message (crypto's `network`
|
|
1262
|
+
* field). This MUST be decoupled from the transport network: use the same
|
|
1263
|
+
* value everywhere you derive from this wallet, or you will get different
|
|
1264
|
+
* (unrecoverable) keys. Defaults to {@link DEFAULT_KEY_SCOPE} (`'stealth'`),
|
|
1265
|
+
* matching the CLI's `--key-scope` default so both tools derive identical keys.
|
|
1266
|
+
*/
|
|
1267
|
+
keyScope?: string;
|
|
1268
|
+
/**
|
|
1269
|
+
* Application id to scope keys (folded into the signed message). Defaults to
|
|
1270
|
+
* {@link DEFAULT_APP_ID} (`'default'`), matching the CLI's `--app-id` default.
|
|
1271
|
+
*/
|
|
1272
|
+
appId?: string;
|
|
1273
|
+
/**
|
|
1274
|
+
* Sign the message TWICE and throw when the two signatures differ. This guards
|
|
1275
|
+
* against randomized/hardware signers that would otherwise derive different
|
|
1276
|
+
* (unrecoverable) keys on every call. Defaults to `true`; pass `false` only
|
|
1277
|
+
* for a signer you KNOW is deterministic (RFC 8032) to skip the extra signature.
|
|
1278
|
+
*/
|
|
1279
|
+
verifyDeterminism?: boolean;
|
|
1280
|
+
}
|
|
1281
|
+
/**
|
|
1282
|
+
* Derive stealth keys from a wallet's signature over the canonical derivation
|
|
1283
|
+
* message.
|
|
1284
|
+
*
|
|
1285
|
+
* Because RFC 8032 ed25519 signatures are deterministic, the same wallet
|
|
1286
|
+
* signing the same message always re-derives the same stealth keys — that is
|
|
1287
|
+
* what lets a user recover stealth keys from their wallet alone, with no extra
|
|
1288
|
+
* secret to store. Wallet compromise equals stealth-key compromise; this is the
|
|
1289
|
+
* accepted trade-off for keyless recovery.
|
|
1290
|
+
*
|
|
1291
|
+
* @param signer - Callback that signs the derivation message (see {@link WalletSigner}).
|
|
1292
|
+
* @param opts - Optional keyScope/appId scoping and determinism verification.
|
|
1293
|
+
* `keyScope` and `appId` MUST match across every tool that derives from this
|
|
1294
|
+
* wallet (they default to `'stealth'` / `'default'`, the same as the CLI).
|
|
1295
|
+
* @returns Hex-string {@link StealthKeys} in the same shape as `keygen()`.
|
|
1296
|
+
* @throws If the signature is not exactly 64 bytes, or (unless
|
|
1297
|
+
* `verifyDeterminism: false`) if two signatures over the same message differ.
|
|
1298
|
+
*
|
|
1299
|
+
* @example
|
|
1300
|
+
* ```typescript
|
|
1301
|
+
* // verifyDeterminism defaults to true; pass false only for a known-good signer.
|
|
1302
|
+
* const keys = await keysFromWalletSignature(
|
|
1303
|
+
* (msg) => freighter.signMessage(msg),
|
|
1304
|
+
* { appId: 'my-app' },
|
|
1305
|
+
* );
|
|
1306
|
+
* ```
|
|
1307
|
+
*/
|
|
1308
|
+
declare function keysFromWalletSignature(signer: WalletSigner, opts?: WalletKeysOpts): Promise<StealthKeys>;
|
|
1309
|
+
|
|
1310
|
+
/**
|
|
1311
|
+
* Base class for every error the SDK throws deliberately.
|
|
1312
|
+
*
|
|
1313
|
+
* Carries a stable, machine-readable {@link code} so applications can branch on
|
|
1314
|
+
* the error KIND without string-matching messages (which may be reworded) or
|
|
1315
|
+
* relying on `instanceof` (which breaks across duplicated package instances).
|
|
1316
|
+
* Subclasses keep their own `name` and human-readable message.
|
|
1317
|
+
*/
|
|
1318
|
+
declare class ShadeError extends Error {
|
|
1319
|
+
/**
|
|
1320
|
+
* Stable machine-readable identifier for this error kind (snake_case).
|
|
1321
|
+
* Guaranteed not to change across SDK versions, unlike the message text.
|
|
1322
|
+
*/
|
|
1323
|
+
readonly code: string;
|
|
1324
|
+
constructor(code: string, message: string);
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Thrown when `send()` is called without an explicit delivery method.
|
|
1328
|
+
* The SDK deliberately has NO implicit default: the app must choose a method
|
|
1329
|
+
* (or `'auto'`) on every send so the privacy trade-off is always a conscious one.
|
|
1330
|
+
*/
|
|
1331
|
+
declare class MethodRequiredError extends ShadeError {
|
|
1332
|
+
constructor();
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Thrown when a requested delivery method is not present in the client's
|
|
1336
|
+
* configured `methods` list.
|
|
1337
|
+
*/
|
|
1338
|
+
declare class MethodNotEnabledError extends ShadeError {
|
|
1339
|
+
constructor(method: DeliveryMethod);
|
|
1340
|
+
}
|
|
1341
|
+
/**
|
|
1342
|
+
* Thrown when a delivery method exists but cannot service the request
|
|
1343
|
+
* (e.g. a non-native asset over the account method, or the reserved spp method).
|
|
1344
|
+
*/
|
|
1345
|
+
declare class MethodNotAvailableError extends ShadeError {
|
|
1346
|
+
constructor(message: string);
|
|
1347
|
+
}
|
|
1348
|
+
/**
|
|
1349
|
+
* Thrown when an account-method XLM send is below the protocol minimum
|
|
1350
|
+
* (amount must be strictly greater than 1 XLM to fund a new stealth account).
|
|
1351
|
+
*/
|
|
1352
|
+
declare class MinimumAmountError extends ShadeError {
|
|
1353
|
+
constructor(amount: number);
|
|
1354
|
+
}
|
|
1355
|
+
/**
|
|
1356
|
+
* Thrown when a partial account claim requests more than the stealth account can
|
|
1357
|
+
* pay out while keeping its base reserve and covering the fee. The message names
|
|
1358
|
+
* the maximum claimable amount so the caller can retry within bounds.
|
|
1359
|
+
*/
|
|
1360
|
+
declare class ClaimAmountError extends ShadeError {
|
|
1361
|
+
/** The maximum amount that could be claimed for this account. */
|
|
1362
|
+
readonly max: number;
|
|
1363
|
+
constructor(requested: number, max: number);
|
|
1364
|
+
}
|
|
1365
|
+
/**
|
|
1366
|
+
* Thrown when `opts.amount` is passed to a claim that CANNOT honor it, instead
|
|
1367
|
+
* of silently moving a different amount (fund-safety):
|
|
1368
|
+
* - An account-method NATIVE claim with an effective merge (`merge` omitted or
|
|
1369
|
+
* `true`) sweeps the ENTIRE balance via AccountMerge — `amount` would be
|
|
1370
|
+
* ignored. Pass `merge: false` for a partial claim, or drop `amount` to sweep.
|
|
1371
|
+
* - An account-method TOKEN claim always claims the claimable balance in full;
|
|
1372
|
+
* `amount` is not supported there at all.
|
|
1373
|
+
*
|
|
1374
|
+
* Pool claims are unaffected: `amount` is honored as a partial withdrawal.
|
|
1375
|
+
*/
|
|
1376
|
+
declare class ClaimAmountRequiresNoMergeError extends ShadeError {
|
|
1377
|
+
constructor(kind?: 'native-merge' | 'token');
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Thrown when a send/claim amount is not a positive finite number, caught before
|
|
1381
|
+
* building the transaction so the caller gets an actionable SDK error rather than
|
|
1382
|
+
* an on-chain failure after the fee is burned.
|
|
1383
|
+
*/
|
|
1384
|
+
declare class InvalidAmountError extends ShadeError {
|
|
1385
|
+
constructor(amount: unknown);
|
|
1386
|
+
}
|
|
1387
|
+
/**
|
|
1388
|
+
* Thrown by the sponsored token-claim path when the relayer-prepared XDR does
|
|
1389
|
+
* NOT match the operation list the client re-derives from its own trusted inputs
|
|
1390
|
+
* (stealth address, asset, balanceId, destination, amount). A malicious relayer
|
|
1391
|
+
* could otherwise redirect the payout or append an AccountMerge to steal the
|
|
1392
|
+
* just-claimed token; the client refuses to sign such a transaction. The message
|
|
1393
|
+
* names the specific mismatch (e.g. a tampered payout destination or an extra
|
|
1394
|
+
* appended operation) so the failure is diagnosable.
|
|
1395
|
+
*/
|
|
1396
|
+
declare class SponsoredClaimMismatchError extends ShadeError {
|
|
1397
|
+
constructor(detail: string);
|
|
1398
|
+
}
|
|
1399
|
+
/**
|
|
1400
|
+
* Thrown by {@link StealthSession.unlock} when the supplied password fails to
|
|
1401
|
+
* decrypt the stored envelope. Surfaces as an AES-GCM authentication failure
|
|
1402
|
+
* (the tag does not verify), which is indistinguishable from tampering — either
|
|
1403
|
+
* way the key material must not be trusted.
|
|
1404
|
+
*/
|
|
1405
|
+
declare class WrongPasswordError extends ShadeError {
|
|
1406
|
+
constructor();
|
|
1407
|
+
}
|
|
1408
|
+
/**
|
|
1409
|
+
* Thrown by {@link StealthSession.unlock} when the public keys stored in the
|
|
1410
|
+
* clear on the envelope do NOT match the public keys re-derived from the
|
|
1411
|
+
* decrypted private scalars. The private material is protected by AES-GCM, but
|
|
1412
|
+
* the cleartext `spendPublicKey`/`viewPublicKey` are not — a storage-WRITE
|
|
1413
|
+
* attacker (no password) could otherwise substitute a wrong pubkey and silently
|
|
1414
|
+
* break scanning (denial of discovery). Re-deriving and asserting equality on
|
|
1415
|
+
* unlock turns that silent mis-scan into a loud, diagnosable failure.
|
|
1416
|
+
*/
|
|
1417
|
+
declare class SessionIntegrityError extends ShadeError {
|
|
1418
|
+
constructor(which: 'spend' | 'view');
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Thrown by the pool withdraw path when the resolved stealth address holds no
|
|
1422
|
+
* balance in the pool contract for the requested asset — there is nothing to
|
|
1423
|
+
* withdraw. Recoverable: the caller may have already withdrawn, or is querying
|
|
1424
|
+
* the wrong asset.
|
|
1425
|
+
*/
|
|
1426
|
+
declare class NoBalanceError extends ShadeError {
|
|
1427
|
+
constructor();
|
|
1428
|
+
}
|
|
1429
|
+
/**
|
|
1430
|
+
* Thrown by the pool withdraw path when no announcement matching the given
|
|
1431
|
+
* stealth address can be found for these keys — the payment is not ours or has
|
|
1432
|
+
* not yet been indexed.
|
|
1433
|
+
*/
|
|
1434
|
+
declare class AnnouncementNotFoundError extends ShadeError {
|
|
1435
|
+
constructor();
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* Thrown by the account claim path when the stealth account cannot be found on
|
|
1439
|
+
* Horizon — typically because the funding send has not yet confirmed.
|
|
1440
|
+
*/
|
|
1441
|
+
declare class StealthAccountNotFoundError extends ShadeError {
|
|
1442
|
+
constructor();
|
|
1443
|
+
}
|
|
1444
|
+
/**
|
|
1445
|
+
* Thrown by a token claim when the destination account does not (or cannot yet)
|
|
1446
|
+
* trust the asset being claimed. The message names the actionable fix — add the
|
|
1447
|
+
* trustline on the destination before claiming.
|
|
1448
|
+
*/
|
|
1449
|
+
declare class DestinationTrustlineError extends ShadeError {
|
|
1450
|
+
constructor(message: string);
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* Thrown by the pool withdraw path when a fee-payer secret is required (to pay
|
|
1454
|
+
* the Soroban invocation fee) but none was supplied.
|
|
1455
|
+
*/
|
|
1456
|
+
declare class FeePayerRequiredError extends ShadeError {
|
|
1457
|
+
constructor();
|
|
1458
|
+
}
|
|
1459
|
+
/**
|
|
1460
|
+
* Thrown by the pool claim path when an external {@link TransactionSigner} is
|
|
1461
|
+
* supplied (via `signTransaction`) but no `feePayerAddress` is given. A pool
|
|
1462
|
+
* claim needs a fee payer; with external signing the fee payer is identified by
|
|
1463
|
+
* its G-address, never a secret. Failing loudly here prevents the SDK from ever
|
|
1464
|
+
* calling `Keypair.fromSecret` on a public key.
|
|
1465
|
+
*/
|
|
1466
|
+
declare class FeePayerAddressRequiredError extends ShadeError {
|
|
1467
|
+
constructor();
|
|
1468
|
+
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Thrown by the pool withdraw path when the recipient's persistent Balance/Nonce
|
|
1471
|
+
* ledger entry has been archived (Soroban state expiration) and the automatic
|
|
1472
|
+
* RestoreFootprint transaction that must precede the withdraw could not be
|
|
1473
|
+
* completed. Withdrawing over an archived footprint fails on-chain even though
|
|
1474
|
+
* `get_balance`/`scan` still report the funds, so the SDK restores the entry
|
|
1475
|
+
* first; if that restore itself fails the funds are recoverable but the withdraw
|
|
1476
|
+
* cannot proceed until the entry is restored. The message carries the underlying
|
|
1477
|
+
* cause so the operator can diagnose (e.g. an unfunded fee payer or a relayer
|
|
1478
|
+
* rejection).
|
|
1479
|
+
*/
|
|
1480
|
+
declare class EntryArchivedRestoringError extends ShadeError {
|
|
1481
|
+
constructor(cause: string);
|
|
1482
|
+
}
|
|
1483
|
+
/**
|
|
1484
|
+
* Thrown when the RPC `sendTransaction` returns a non-terminal status that means
|
|
1485
|
+
* the transaction did NOT land — `TRY_AGAIN_LATER` (the node dropped it without
|
|
1486
|
+
* queueing) or any other non-`PENDING`/`SUCCESS` status. Nothing was submitted,
|
|
1487
|
+
* so the caller may safely retry with a fresh submission. Treating this as a
|
|
1488
|
+
* (retryable) failure prevents returning a success receipt for a tx that never
|
|
1489
|
+
* entered the ledger (SDK-01).
|
|
1490
|
+
*/
|
|
1491
|
+
declare class TransactionRetryableError extends ShadeError {
|
|
1492
|
+
/** Marks this error as safe to retry (the tx never entered the ledger). */
|
|
1493
|
+
readonly retryable: true;
|
|
1494
|
+
constructor(status: string);
|
|
1495
|
+
}
|
|
1496
|
+
/**
|
|
1497
|
+
* Thrown when confirmation polling gives up while the transaction is still
|
|
1498
|
+
* PENDING. The transaction was ACCEPTED by the RPC and MAY STILL LAND on-chain
|
|
1499
|
+
* after this error — blindly resubmitting could double-send the funds. The
|
|
1500
|
+
* error carries {@link txHash} so callers can keep polling that hash (e.g.
|
|
1501
|
+
* `getTransaction`) to a terminal status before deciding to retry;
|
|
1502
|
+
* `retryable` is `false` to distinguish it from
|
|
1503
|
+
* {@link TransactionRetryableError} in shared retry loops.
|
|
1504
|
+
*/
|
|
1505
|
+
declare class TransactionTimeoutError extends ShadeError {
|
|
1506
|
+
/**
|
|
1507
|
+
* NOT safe to blind-retry: unlike {@link TransactionRetryableError}, the
|
|
1508
|
+
* transaction may still be applied after this error is thrown.
|
|
1509
|
+
*/
|
|
1510
|
+
readonly retryable: false;
|
|
1511
|
+
/** Hash of the still-pending transaction — poll it before any resubmission. */
|
|
1512
|
+
readonly txHash: string;
|
|
1513
|
+
constructor(txHash: string);
|
|
1514
|
+
}
|
|
1515
|
+
/**
|
|
1516
|
+
* Thrown when a relayer HTTP endpoint responds non-2xx. Carries the HTTP
|
|
1517
|
+
* {@link status} and the relayer's own machine-readable {@link relayerCode}
|
|
1518
|
+
* (e.g. `insufficient_credit`, `missing_auth`) so callers — and the
|
|
1519
|
+
* `RelayerPool` failover — can branch on the KIND of failure: 5xx is a relayer
|
|
1520
|
+
* fault worth failing over, 4xx is this request's fault and would only repeat.
|
|
1521
|
+
*/
|
|
1522
|
+
declare class RelayerHttpError extends ShadeError {
|
|
1523
|
+
/** HTTP status the relayer responded with. */
|
|
1524
|
+
readonly status: number;
|
|
1525
|
+
/** The relayer's own `code` field from the error body, when present. */
|
|
1526
|
+
readonly relayerCode?: string;
|
|
1527
|
+
constructor(path: string, status: number, relayerCode?: string, detail?: string);
|
|
1528
|
+
}
|
|
1529
|
+
/**
|
|
1530
|
+
* Thrown when a relayer cannot be reached at the transport level (DNS failure,
|
|
1531
|
+
* refused connection, aborted fetch, invalid response body). Distinct from
|
|
1532
|
+
* {@link RelayerHttpError} so the `RelayerPool` can treat it as a candidate
|
|
1533
|
+
* fault (fail over) rather than a request fault.
|
|
1534
|
+
*/
|
|
1535
|
+
declare class RelayerNetworkError extends ShadeError {
|
|
1536
|
+
constructor(path: string, detail: string);
|
|
1537
|
+
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Thrown when an indexer HTTP endpoint responds non-2xx. Carries the HTTP
|
|
1540
|
+
* {@link status} and the indexer's own machine-readable {@link indexerCode}
|
|
1541
|
+
* from the `{ error, code }` body so callers can branch on the failure KIND.
|
|
1542
|
+
* The account scan treats it as "abandon the indexer segment" and falls back
|
|
1543
|
+
* to the Horizon walk — Horizon remains the source of truth.
|
|
1544
|
+
*/
|
|
1545
|
+
declare class IndexerHttpError extends ShadeError {
|
|
1546
|
+
/** HTTP status the indexer responded with. */
|
|
1547
|
+
readonly status: number;
|
|
1548
|
+
/** The indexer's own `code` field from the error body, when present. */
|
|
1549
|
+
readonly indexerCode?: string;
|
|
1550
|
+
constructor(path: string, status: number, indexerCode?: string, detail?: string);
|
|
1551
|
+
}
|
|
1552
|
+
/**
|
|
1553
|
+
* Thrown when an indexer cannot be reached at the transport level (DNS
|
|
1554
|
+
* failure, refused connection, request timeout, invalid response body).
|
|
1555
|
+
* Distinct from {@link IndexerHttpError} for parity with the relayer errors;
|
|
1556
|
+
* the account scan treats both the same way (fall back to Horizon).
|
|
1557
|
+
*/
|
|
1558
|
+
declare class IndexerNetworkError extends ShadeError {
|
|
1559
|
+
constructor(path: string, detail: string);
|
|
1560
|
+
}
|
|
1561
|
+
/**
|
|
1562
|
+
* Thrown by the `RelayerPool` when no candidate relayer is usable for this
|
|
1563
|
+
* call. {@link candidates} maps every probed URL to the reason it was rejected
|
|
1564
|
+
* (`unreachable: ...`, `timeout`, `http_<status>`, `status_not_ok`,
|
|
1565
|
+
* `network_mismatch (...)`, `balance_below_min (...)`,
|
|
1566
|
+
* `credit_gated_no_funding_auth`), so the failure is diagnosable per relayer
|
|
1567
|
+
* instead of a bare "nothing worked".
|
|
1568
|
+
*/
|
|
1569
|
+
declare class NoHealthyRelayerError extends ShadeError {
|
|
1570
|
+
/** Per-URL rejection reason for every probed candidate. */
|
|
1571
|
+
readonly candidates: Record<string, string>;
|
|
1572
|
+
constructor(candidates: Record<string, string>);
|
|
1573
|
+
}
|
|
1574
|
+
/**
|
|
1575
|
+
* Thrown by the {@link StealthClient} constructor when a pool-capable method is
|
|
1576
|
+
* enabled but no contract id was supplied (there are no built-in defaults —
|
|
1577
|
+
* pool deployments are per-operator). Failing here — rather than deep inside a
|
|
1578
|
+
* later Soroban call with an opaque error — makes the misconfiguration obvious:
|
|
1579
|
+
* pass `contractId` explicitly.
|
|
1580
|
+
*/
|
|
1581
|
+
declare class ContractIdRequiredError extends ShadeError {
|
|
1582
|
+
constructor(network: string);
|
|
1583
|
+
}
|
|
1584
|
+
/**
|
|
1585
|
+
* Thrown by `getNetworkConfig` (and therefore the {@link StealthClient}
|
|
1586
|
+
* constructor) when the requested network name is not in the `NETWORKS` table.
|
|
1587
|
+
* The type system already restricts `network` to the supported names; this is
|
|
1588
|
+
* the runtime guard for plain-JS callers and stale configs. The message lists
|
|
1589
|
+
* the currently supported networks, so it stays accurate as new networks
|
|
1590
|
+
* (e.g. `'public'` after the external audit) are added to the table.
|
|
1591
|
+
*/
|
|
1592
|
+
declare class UnsupportedNetworkError extends ShadeError {
|
|
1593
|
+
/** The unknown network name that was requested. */
|
|
1594
|
+
readonly network: string;
|
|
1595
|
+
/** The network names this SDK build supports. */
|
|
1596
|
+
readonly supported: string[];
|
|
1597
|
+
constructor(network: string, supported: string[]);
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
/**
|
|
1601
|
+
* Exact decimal <-> stroop conversions.
|
|
1602
|
+
*
|
|
1603
|
+
* A stroop is 1e-7 of a Stellar asset unit. Converting whole-unit amounts with
|
|
1604
|
+
* `Math.round(amount * 1e7)` or `Number(stroops) / 1e7` silently loses precision
|
|
1605
|
+
* once the magnitude exceeds `Number.MAX_SAFE_INTEGER` stroops (~9.007e8 XLM) —
|
|
1606
|
+
* a float can no longer represent every integer stroop, so large sends drift.
|
|
1607
|
+
* These helpers keep the value as a `bigint` count of stroops end to end and
|
|
1608
|
+
* only render a string at the display edge, so no amount ever round-trips
|
|
1609
|
+
* through a lossy float.
|
|
1610
|
+
*/
|
|
1611
|
+
/**
|
|
1612
|
+
* Parse a decimal amount string (whole units, e.g. `"100.5"`) into an exact
|
|
1613
|
+
* `bigint` count of stroops.
|
|
1614
|
+
*
|
|
1615
|
+
* @param amount - Decimal string with at most 7 fractional digits.
|
|
1616
|
+
* @throws {Error} If the input is not a non-negative decimal, or carries more
|
|
1617
|
+
* than 7 fractional digits (which cannot be represented in stroops).
|
|
1618
|
+
*/
|
|
1619
|
+
declare function parseStroops(amount: string): bigint;
|
|
1620
|
+
/**
|
|
1621
|
+
* Convert a `number` amount (whole units) into exact stroops, rejecting values
|
|
1622
|
+
* that cannot be represented exactly (non-finite, negative, or with sub-stroop
|
|
1623
|
+
* fractional precision). Prefer {@link parseStroops} when the amount originates
|
|
1624
|
+
* as a string; this exists for the numeric SDK surface.
|
|
1625
|
+
*
|
|
1626
|
+
* @param amount - Non-negative finite whole-unit amount.
|
|
1627
|
+
*/
|
|
1628
|
+
declare function numberToStroops(amount: number): bigint;
|
|
1629
|
+
/**
|
|
1630
|
+
* Render an exact `bigint` stroop count back to a fixed 7-decimal string
|
|
1631
|
+
* (e.g. `12345000000n` -> `"1234.5000000"`). Never goes through a float.
|
|
1632
|
+
*
|
|
1633
|
+
* @param stroops - Stroop count (non-negative).
|
|
1634
|
+
*/
|
|
1635
|
+
declare function formatStroops(stroops: bigint): string;
|
|
1636
|
+
|
|
1637
|
+
/**
|
|
1638
|
+
* Indexer `/health` response. Coverage is the OPEN interval
|
|
1639
|
+
* `(startCursor, cursor]` over Horizon's ascending transaction feed: the tx
|
|
1640
|
+
* whose paging_token equals `startCursor` is NOT covered (the scan fetches it
|
|
1641
|
+
* from Horizon), while everything after it up to `cursor` is.
|
|
1642
|
+
*/
|
|
1643
|
+
interface IndexerHealth {
|
|
1644
|
+
status: string;
|
|
1645
|
+
network?: string;
|
|
1646
|
+
/** Announcement store backend. */
|
|
1647
|
+
store?: string;
|
|
1648
|
+
/** Feed position fully covered by the indexer; `null` before first ingest. */
|
|
1649
|
+
cursor: string | null;
|
|
1650
|
+
/** First covered feed position (exclusive); `null` before first ingest. */
|
|
1651
|
+
startCursor: string | null;
|
|
1652
|
+
/** Close time of the last ingested ledger (ISO timestamp). */
|
|
1653
|
+
lastCloseTime?: string | null;
|
|
1654
|
+
/** Seconds the indexer lags the network head. */
|
|
1655
|
+
lagSeconds?: number | null;
|
|
1656
|
+
/** Total announcements held by the store. */
|
|
1657
|
+
announcements?: number;
|
|
1658
|
+
/**
|
|
1659
|
+
* Coverage holes (ledger ranges the indexer knows it skipped). A gapped
|
|
1660
|
+
* indexer reports `status: 'degraded'` and is therefore skipped by the
|
|
1661
|
+
* scan's health guard — listed here so operators can see WHAT is missing.
|
|
1662
|
+
*/
|
|
1663
|
+
gaps?: Array<{
|
|
1664
|
+
fromLedger: number;
|
|
1665
|
+
toLedger: number;
|
|
1666
|
+
detectedAt: string;
|
|
1667
|
+
}>;
|
|
1668
|
+
/** Ingest-loop diagnostics (shape owned by the indexer service). */
|
|
1669
|
+
ingest?: unknown;
|
|
1670
|
+
}
|
|
1671
|
+
/**
|
|
1672
|
+
* One announcement served by the indexer: a successful hash-memo transaction
|
|
1673
|
+
* with its operation records inlined VERBATIM (the same `HorizonOp` shapes
|
|
1674
|
+
* Horizon's `/transactions/{hash}/operations` returns), so consumers need no
|
|
1675
|
+
* per-tx Horizon round-trip.
|
|
1676
|
+
*/
|
|
1677
|
+
interface IndexerAnnouncement {
|
|
1678
|
+
hash: string;
|
|
1679
|
+
paging_token: string;
|
|
1680
|
+
/** base64 32-byte ephemeral key R (Horizon's MemoHash encoding). */
|
|
1681
|
+
memo: string;
|
|
1682
|
+
memo_type: string;
|
|
1683
|
+
successful: boolean;
|
|
1684
|
+
created_at?: string;
|
|
1685
|
+
/**
|
|
1686
|
+
* Transaction-level source account, when the indexer has it. Enables the
|
|
1687
|
+
* sponsor-precise claimable-balance binding without a per-tx Horizon
|
|
1688
|
+
* round-trip; older feeds omit it and the scan falls back to the
|
|
1689
|
+
* asset+amount binding.
|
|
1690
|
+
*/
|
|
1691
|
+
source_account?: string;
|
|
1692
|
+
/** Verbatim Horizon operation records for this transaction. */
|
|
1693
|
+
operations: HorizonOp[];
|
|
1694
|
+
}
|
|
1695
|
+
/**
|
|
1696
|
+
* Response page from `GET /announcements`. `records` ascend by paging_token
|
|
1697
|
+
* and are strictly greater than the request cursor. `cursor` is the position
|
|
1698
|
+
* to adopt directly for the next call: the last record's token on a full
|
|
1699
|
+
* page, otherwise max(request cursor, indexer global cursor).
|
|
1700
|
+
*/
|
|
1701
|
+
interface AnnouncementsPage {
|
|
1702
|
+
records: IndexerAnnouncement[];
|
|
1703
|
+
cursor: string;
|
|
1704
|
+
}
|
|
1705
|
+
/**
|
|
1706
|
+
* Thin HTTP client for the announcement indexer service — the account
|
|
1707
|
+
* method's discovery accelerator. Horizon remains the source of truth: every
|
|
1708
|
+
* failure here surfaces as a typed error the scan treats as "fall back to the
|
|
1709
|
+
* Horizon walk", never as a lost payment.
|
|
1710
|
+
*
|
|
1711
|
+
* Transport failures and timeouts throw {@link IndexerNetworkError}; non-2xx
|
|
1712
|
+
* responses throw {@link IndexerHttpError} carrying the HTTP status and the
|
|
1713
|
+
* indexer's `{ error, code }` body code.
|
|
1714
|
+
*
|
|
1715
|
+
* @example
|
|
1716
|
+
* ```typescript
|
|
1717
|
+
* const indexer = new IndexerClient('http://localhost:3100');
|
|
1718
|
+
* const { cursor } = await indexer.health();
|
|
1719
|
+
* ```
|
|
1720
|
+
*/
|
|
1721
|
+
declare class IndexerClient {
|
|
1722
|
+
private readonly baseUrl;
|
|
1723
|
+
private readonly fetchFn;
|
|
1724
|
+
private readonly timeoutMs;
|
|
1725
|
+
/**
|
|
1726
|
+
* @param baseUrl - Indexer root URL (no trailing slash required).
|
|
1727
|
+
* @param fetchFn - Injectable fetch (defaults to the global `fetch`).
|
|
1728
|
+
* @param opts - Optional per-request timeout in ms (default 10000).
|
|
1729
|
+
*/
|
|
1730
|
+
constructor(baseUrl: string, fetchFn?: FetchLike, opts?: {
|
|
1731
|
+
timeoutMs?: number;
|
|
1732
|
+
});
|
|
1733
|
+
private get;
|
|
1734
|
+
/**
|
|
1735
|
+
* Decode an indexer response. A non-JSON body on an error status (e.g. a
|
|
1736
|
+
* proxy's HTML 502 page) must not mask the status, so decode failures fall
|
|
1737
|
+
* back to an empty body there; a non-JSON body on a 2xx is a broken indexer
|
|
1738
|
+
* and surfaces as a transport error.
|
|
1739
|
+
*/
|
|
1740
|
+
private decode;
|
|
1741
|
+
/** Probe the indexer's health and coverage window. */
|
|
1742
|
+
health(): Promise<IndexerHealth>;
|
|
1743
|
+
/**
|
|
1744
|
+
* Fetch one page of announcements strictly after `cursor` (omit for the
|
|
1745
|
+
* start of coverage).
|
|
1746
|
+
*
|
|
1747
|
+
* @param cursor - Feed position to resume from (records returned are > it).
|
|
1748
|
+
* @param limit - Page size (the service caps it at 200).
|
|
1749
|
+
* @returns The page's records plus the cursor to adopt for the next call.
|
|
1750
|
+
*/
|
|
1751
|
+
getAnnouncements(cursor?: string, limit?: number): Promise<AnnouncementsPage>;
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
/**
|
|
1755
|
+
* A minimal key/value storage interface. `window.localStorage` satisfies it
|
|
1756
|
+
* directly; cookie / IndexedDB / React-Native wrappers are the app's job. All
|
|
1757
|
+
* methods may be sync or async so async backends work transparently.
|
|
1758
|
+
*/
|
|
1759
|
+
interface KVStorage {
|
|
1760
|
+
getItem(key: string): string | null | Promise<string | null>;
|
|
1761
|
+
setItem(key: string, value: string): void | Promise<void>;
|
|
1762
|
+
removeItem(key: string): void | Promise<void>;
|
|
1763
|
+
}
|
|
1764
|
+
/** Options for {@link StealthSession}. */
|
|
1765
|
+
interface StealthSessionOpts {
|
|
1766
|
+
/** The backing key/value store (e.g. `window.localStorage`). */
|
|
1767
|
+
storage: KVStorage;
|
|
1768
|
+
/** Storage-key namespace prefix. Default `'stealth'`. */
|
|
1769
|
+
namespace?: string;
|
|
1770
|
+
}
|
|
1771
|
+
/** Cached scan progress so returning users neither re-key nor rescan from zero. */
|
|
1772
|
+
interface ScanState {
|
|
1773
|
+
/** Per-method resume cursor. */
|
|
1774
|
+
cursor: ScanCursor;
|
|
1775
|
+
/** Discovered payments so far. */
|
|
1776
|
+
payments: Payment[];
|
|
1777
|
+
/** ISO timestamp of the last update. */
|
|
1778
|
+
updatedAt: string;
|
|
1779
|
+
}
|
|
1780
|
+
/**
|
|
1781
|
+
* Storage-agnostic, encrypted web session for stealth keys and scan state.
|
|
1782
|
+
*
|
|
1783
|
+
* Keys and scan history are encrypted with AES-256-GCM under a PBKDF2-SHA256
|
|
1784
|
+
* (600k iterations) key derived from the user's password, using only
|
|
1785
|
+
* `globalThis.crypto.subtle` — no new dependencies, works in browsers and Node
|
|
1786
|
+
* 18+. This is intentionally separate from the CLI keystore (which stays on
|
|
1787
|
+
* Node's scrypt).
|
|
1788
|
+
*
|
|
1789
|
+
* @example
|
|
1790
|
+
* ```typescript
|
|
1791
|
+
* const session = new StealthSession({ storage: window.localStorage });
|
|
1792
|
+
* await session.saveKeys(keys, password);
|
|
1793
|
+
* // ... later / new page load ...
|
|
1794
|
+
* await session.unlock(password);
|
|
1795
|
+
* const keys = session.keys;
|
|
1796
|
+
* ```
|
|
1797
|
+
*/
|
|
1798
|
+
declare class StealthSession {
|
|
1799
|
+
private readonly storage;
|
|
1800
|
+
private readonly namespace;
|
|
1801
|
+
private unlockedKeys;
|
|
1802
|
+
private password;
|
|
1803
|
+
constructor(opts: StealthSessionOpts);
|
|
1804
|
+
private get keysKey();
|
|
1805
|
+
private get scanKey();
|
|
1806
|
+
/**
|
|
1807
|
+
* Encrypt and persist the stealth keys under the given password. The public
|
|
1808
|
+
* keys are stored in the clear (they are shareable); only the private key
|
|
1809
|
+
* material is encrypted.
|
|
1810
|
+
*/
|
|
1811
|
+
saveKeys(keys: StealthKeys, password: string): Promise<void>;
|
|
1812
|
+
/**
|
|
1813
|
+
* Decrypt the stored keys into memory using the password.
|
|
1814
|
+
*
|
|
1815
|
+
* @throws {WrongPasswordError} If the password fails the AES-GCM auth check.
|
|
1816
|
+
* @throws If no keys are stored.
|
|
1817
|
+
*/
|
|
1818
|
+
unlock(password: string): Promise<StealthKeys>;
|
|
1819
|
+
/** The in-memory keys after {@link unlock}/{@link saveKeys}. */
|
|
1820
|
+
get keys(): StealthKeys;
|
|
1821
|
+
/** Wipe in-memory key material (storage is untouched). */
|
|
1822
|
+
lock(): void;
|
|
1823
|
+
/** Whether keys are present in storage (does not require unlock). */
|
|
1824
|
+
hasKeys(): Promise<boolean>;
|
|
1825
|
+
/** Remove all session data (keys + scan state) and lock in memory. */
|
|
1826
|
+
clear(): Promise<void>;
|
|
1827
|
+
/**
|
|
1828
|
+
* Load the encrypted scan-state cache. Requires an unlocked session (the same
|
|
1829
|
+
* derived key protects it, since payment history is private). Returns null
|
|
1830
|
+
* when nothing is cached.
|
|
1831
|
+
*/
|
|
1832
|
+
loadScanState(): Promise<ScanState | null>;
|
|
1833
|
+
/**
|
|
1834
|
+
* Encrypt and persist the scan-state cache under the session password.
|
|
1835
|
+
* Requires an unlocked session.
|
|
1836
|
+
*/
|
|
1837
|
+
saveScanState(state: ScanState): Promise<void>;
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
/**
|
|
1841
|
+
* How to sign a transaction leg for the restore flow — either the fee-payer
|
|
1842
|
+
* secret / external signer already used by the withdraw, expressed as a single
|
|
1843
|
+
* async callback. The helper stays agnostic to local-vs-Freighter signing.
|
|
1844
|
+
*/
|
|
1845
|
+
type RestoreSigner = (tx: Transaction) => Promise<Transaction>;
|
|
1846
|
+
/**
|
|
1847
|
+
* How to submit a signed transaction — either directly to the RPC or fee-bumped
|
|
1848
|
+
* through the relayer, mirroring whatever the withdraw itself uses. Returns the
|
|
1849
|
+
* submitted transaction hash.
|
|
1850
|
+
*/
|
|
1851
|
+
type RestoreSubmit = (tx: Transaction) => Promise<string>;
|
|
1852
|
+
/**
|
|
1853
|
+
* Optional progress reporter (the CLI wires this to a spinner/log line; the SDK
|
|
1854
|
+
* leaves it undefined).
|
|
1855
|
+
*/
|
|
1856
|
+
type RestoreNotify = (message: string) => void;
|
|
1857
|
+
/**
|
|
1858
|
+
* Rebuild the invocation transaction (e.g. the withdraw) from a freshly-fetched
|
|
1859
|
+
* source account. Used ONLY on the restore branch: after the RestoreFootprint
|
|
1860
|
+
* consumes the source account's next sequence number, the invocation must be
|
|
1861
|
+
* rebuilt on the new sequence — reusing the original pre-restore transaction
|
|
1862
|
+
* would submit at an already-consumed sequence (`txBAD_SEQ`). The factory must
|
|
1863
|
+
* produce the exact same operation/fee/timeout as the original build so only the
|
|
1864
|
+
* sequence differs.
|
|
1865
|
+
*/
|
|
1866
|
+
type RebuildInvocation = (account: Account) => Transaction;
|
|
1867
|
+
/**
|
|
1868
|
+
* Prepare a Soroban invocation transaction for submission, transparently
|
|
1869
|
+
* restoring an archived persistent footprint first when required.
|
|
1870
|
+
*
|
|
1871
|
+
* `server.prepareTransaction` only throws on a simulation ERROR — it never
|
|
1872
|
+
* consults `sim.restorePreamble`. When a recipient's persistent Balance/Nonce
|
|
1873
|
+
* entry has archived (Soroban state expiration), simulation returns
|
|
1874
|
+
* SUCCESS-with-restorePreamble; the bare prepare then assembles the withdraw
|
|
1875
|
+
* over the archived footprint, which submits but fails on-chain while
|
|
1876
|
+
* `get_balance`/`scan` still show the funds — an opaque brick.
|
|
1877
|
+
*
|
|
1878
|
+
* This helper reproduces `prepareTransaction`'s behavior for the normal path
|
|
1879
|
+
* (simulate → assemble the passed-in `tx`) but, when
|
|
1880
|
+
* {@link rpc.Api.isSimulationRestore} is true, first builds and submits a
|
|
1881
|
+
* RestoreFootprint transaction from `sim.restorePreamble`, waits for it to
|
|
1882
|
+
* succeed, then — because the restore consumes the fee-payer's next sequence
|
|
1883
|
+
* number — **re-fetches the source account and rebuilds the invocation on the
|
|
1884
|
+
* fresh sequence** via {@link RebuildInvocation} before re-simulating and
|
|
1885
|
+
* assembling. This avoids a `txBAD_SEQ` collision between the restore and the
|
|
1886
|
+
* withdraw. The non-archived path is behaviorally identical to the original
|
|
1887
|
+
* `prepareTransaction` call (the passed-in `tx` is assembled untouched).
|
|
1888
|
+
*
|
|
1889
|
+
* @param tx - The unsigned Soroban invocation transaction (e.g. the withdraw),
|
|
1890
|
+
* already built on the source account's current sequence. Used directly on the
|
|
1891
|
+
* non-archived path.
|
|
1892
|
+
* @param rebuildTx - Rebuilds the invocation from a freshly-fetched source
|
|
1893
|
+
* account; used only on the restore branch to obtain a non-colliding sequence.
|
|
1894
|
+
* @param server - The Soroban RPC server.
|
|
1895
|
+
* @param networkPassphrase - Network passphrase for building the restore tx.
|
|
1896
|
+
* @param signRestore - Signs the RestoreFootprint transaction (fee-payer leg).
|
|
1897
|
+
* @param submitRestore - Submits the signed RestoreFootprint transaction.
|
|
1898
|
+
* @param notify - Optional progress reporter.
|
|
1899
|
+
* @returns The assembled invocation transaction, ready to sign and submit.
|
|
1900
|
+
* @throws {EntryArchivedRestoringError} If the entry is archived and the
|
|
1901
|
+
* restore transaction could not be completed.
|
|
1902
|
+
*/
|
|
1903
|
+
declare function prepareWithRestore(tx: Transaction, rebuildTx: RebuildInvocation, server: rpc.Server, networkPassphrase: string, signRestore: RestoreSigner, submitRestore: RestoreSubmit, notify?: RestoreNotify): Promise<Transaction>;
|
|
1904
|
+
|
|
1905
|
+
/** Parameters passed to a delivery adapter's `send`. */
|
|
1906
|
+
interface AdapterSendParams {
|
|
1907
|
+
/** Recipient meta-address (shade:stellar:... or spend:view hex) */
|
|
1908
|
+
metaAddress: string;
|
|
1909
|
+
/** Amount in whole units */
|
|
1910
|
+
amount: number;
|
|
1911
|
+
/**
|
|
1912
|
+
* Sender's Stellar secret key — OR, when {@link signTransaction} is set, the
|
|
1913
|
+
* sender's PUBLIC G-address (the SDK never calls `Keypair.fromSecret` on it).
|
|
1914
|
+
*/
|
|
1915
|
+
senderSecret: string;
|
|
1916
|
+
/** Asset to send. Default: native XLM. Format: "CODE:ISSUER" */
|
|
1917
|
+
asset?: string;
|
|
1918
|
+
/**
|
|
1919
|
+
* Optional external signer for the sender leg (Freighter-style). When set,
|
|
1920
|
+
* {@link senderSecret} carries the sender's G-address and the adapter
|
|
1921
|
+
* delegates signing to this function instead of `Keypair.fromSecret`.
|
|
1922
|
+
*/
|
|
1923
|
+
signTransaction?: TransactionSigner;
|
|
1924
|
+
}
|
|
1925
|
+
/**
|
|
1926
|
+
* A pluggable delivery method. Each adapter fully owns one way of getting funds
|
|
1927
|
+
* to a stealth recipient: how to send, how to discover incoming payments, and
|
|
1928
|
+
* how to claim them. The client just wires adapters together per config.
|
|
1929
|
+
*/
|
|
1930
|
+
interface DeliveryAdapter {
|
|
1931
|
+
/** The delivery method this adapter implements. */
|
|
1932
|
+
readonly method: DeliveryMethod;
|
|
1933
|
+
/**
|
|
1934
|
+
* Send funds to a stealth recipient using this method.
|
|
1935
|
+
*
|
|
1936
|
+
* @param params - Recipient, amount, sender secret, and optional asset.
|
|
1937
|
+
* @returns The one-time stealth address and delivering transaction hash.
|
|
1938
|
+
*/
|
|
1939
|
+
send(params: AdapterSendParams): Promise<SendReceipt>;
|
|
1940
|
+
/**
|
|
1941
|
+
* Discover incoming payments for the given keys.
|
|
1942
|
+
*
|
|
1943
|
+
* @param keys - Recipient stealth keys (needs viewPrivKey + spendPubKey).
|
|
1944
|
+
* @param cursor - Opaque per-method resume cursor.
|
|
1945
|
+
* @param opts - `suppressClaimedNative` drops swept accounts (balance view);
|
|
1946
|
+
* `exhaustive` disables the account adapter's indexer fast cold start.
|
|
1947
|
+
* Adapters ignore flags that do not apply to their method.
|
|
1948
|
+
* @returns Detected payments, the next cursor to persist, and optional
|
|
1949
|
+
* per-scan diagnostics (observability only — see {@link MethodScanMeta}).
|
|
1950
|
+
*/
|
|
1951
|
+
scan(keys: StealthKeys, cursor?: string, opts?: {
|
|
1952
|
+
suppressClaimedNative?: boolean;
|
|
1953
|
+
exhaustive?: boolean;
|
|
1954
|
+
}): Promise<{
|
|
1955
|
+
payments: Payment[];
|
|
1956
|
+
cursor?: string;
|
|
1957
|
+
meta?: MethodScanMeta;
|
|
1958
|
+
}>;
|
|
1959
|
+
/**
|
|
1960
|
+
* Claim a previously discovered payment to a destination.
|
|
1961
|
+
*
|
|
1962
|
+
* @param payment - The payment to claim (from `scan`).
|
|
1963
|
+
* @param destination - Destination Stellar G-address.
|
|
1964
|
+
* @param opts - Claim options (keys, relay, merge, etc.).
|
|
1965
|
+
* @returns The claim transaction hash, amount, and method.
|
|
1966
|
+
*/
|
|
1967
|
+
claim(payment: Payment, destination: string, opts: ClaimOpts): Promise<ClaimReceipt>;
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
/**
|
|
1971
|
+
* The pool delivery adapter: deposit/scan/withdraw against the Soroban pool
|
|
1972
|
+
* contract. This is the original, private, multi-token path — the logic here is
|
|
1973
|
+
* the behavior previously inlined in `StealthClient`, extracted unchanged so the
|
|
1974
|
+
* client can treat it as one adapter among several.
|
|
1975
|
+
*/
|
|
1976
|
+
declare class PoolAdapter implements DeliveryAdapter {
|
|
1977
|
+
private readonly contractId;
|
|
1978
|
+
private readonly networkPassphrase;
|
|
1979
|
+
private readonly server;
|
|
1980
|
+
readonly method: "pool";
|
|
1981
|
+
private readonly relayerSelection?;
|
|
1982
|
+
constructor(contractId: string, networkPassphrase: string, server: StellarSdk.rpc.Server, opts?: {
|
|
1983
|
+
relayerSelection?: RelayerSelection;
|
|
1984
|
+
});
|
|
1985
|
+
/**
|
|
1986
|
+
* Derive a one-time stealth address and deposit into the pool contract,
|
|
1987
|
+
* recording the announcement atomically with the deposit.
|
|
1988
|
+
*/
|
|
1989
|
+
send(params: AdapterSendParams): Promise<SendReceipt>;
|
|
1990
|
+
/**
|
|
1991
|
+
* Scan pool announcements starting at the cursor (announcement index). The
|
|
1992
|
+
* cursor advances by the number of announcements returned; a cheap
|
|
1993
|
+
* `get_announcement_count` lets callers skip a full scan when nothing is new.
|
|
1994
|
+
*/
|
|
1995
|
+
scan(keys: StealthKeys, cursor?: string): Promise<{
|
|
1996
|
+
payments: Payment[];
|
|
1997
|
+
cursor?: string;
|
|
1998
|
+
}>;
|
|
1999
|
+
private matchAndPrice;
|
|
2000
|
+
/**
|
|
2001
|
+
* Claim (withdraw) a pool payment: recover the stealth key, sign a withdraw
|
|
2002
|
+
* message, and submit — directly or via a relayer fee-bump.
|
|
2003
|
+
*/
|
|
2004
|
+
claim(payment: Payment, destination: string, opts: ClaimOpts): Promise<ClaimReceipt>;
|
|
2005
|
+
/**
|
|
2006
|
+
* The original pool withdraw path, preserved for the client's `@deprecated`
|
|
2007
|
+
* `withdraw()` alias and reused by `claim()`.
|
|
2008
|
+
*/
|
|
2009
|
+
withdraw(stealthAddress: string, destination: string, opts: {
|
|
2010
|
+
keys: StealthKeys;
|
|
2011
|
+
feePayer: string;
|
|
2012
|
+
/** Relay URL(s); a list is health-probed and routed with failover. */
|
|
2013
|
+
relay?: string | string[];
|
|
2014
|
+
asset?: string;
|
|
2015
|
+
amount?: number;
|
|
2016
|
+
signTransaction?: TransactionSigner;
|
|
2017
|
+
feePayerAddress?: string;
|
|
2018
|
+
/** App funding account to debit against (credit-gated relayers). */
|
|
2019
|
+
fundingAccount?: string;
|
|
2020
|
+
/**
|
|
2021
|
+
* Signer proving control of `fundingAccount` — a credit-gated relayer
|
|
2022
|
+
* rejects `/relay` without a signed challenge (proof-of-control), so
|
|
2023
|
+
* `fundingAccount` alone is not enough to spend credit.
|
|
2024
|
+
*/
|
|
2025
|
+
fundingSigner?: FundingSigner;
|
|
2026
|
+
/**
|
|
2027
|
+
* Relayed submissions only: poll the relayer-returned txHash until it is
|
|
2028
|
+
* actually on-chain before returning (SDK-TXHASH-TRUST), surfacing a
|
|
2029
|
+
* `TransactionTimeoutError` (with the hash) if it never lands. Default
|
|
2030
|
+
* `false`: trust the relayer's response, exactly as before. The direct
|
|
2031
|
+
* (non-relay) path already confirms via `resolveSendResult` regardless.
|
|
2032
|
+
*/
|
|
2033
|
+
confirm?: boolean;
|
|
2034
|
+
}): Promise<{
|
|
2035
|
+
txHash: string;
|
|
2036
|
+
amount: number;
|
|
2037
|
+
}>;
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
/**
|
|
2041
|
+
* The account delivery adapter: a direct classic Stellar payment that
|
|
2042
|
+
* creates/pays a one-time stealth account, with the ephemeral key R carried in
|
|
2043
|
+
* a 32-byte MemoHash. No view tag is needed — the destination match against the
|
|
2044
|
+
* DKSAP-derived address IS the full verification.
|
|
2045
|
+
*
|
|
2046
|
+
* Native XLM lands via CreateAccount/Payment; tokens land as a
|
|
2047
|
+
* CreateClaimableBalance the recipient later claims (adding a trustline first).
|
|
2048
|
+
*/
|
|
2049
|
+
declare class AccountAdapter implements DeliveryAdapter {
|
|
2050
|
+
private readonly networkPassphrase;
|
|
2051
|
+
private readonly horizon;
|
|
2052
|
+
private readonly relayer?;
|
|
2053
|
+
readonly method: "account";
|
|
2054
|
+
private readonly rpcServer?;
|
|
2055
|
+
private readonly relayerSelection?;
|
|
2056
|
+
private readonly indexer?;
|
|
2057
|
+
private readonly indexerMaxLagSeconds;
|
|
2058
|
+
constructor(networkPassphrase: string, horizon: HorizonClient, relayer?: string | string[] | undefined, opts?: {
|
|
2059
|
+
rpcServer?: TransactionStatusSource;
|
|
2060
|
+
relayerSelection?: RelayerSelection;
|
|
2061
|
+
/** Optional discovery accelerator consumed by {@link scan}. */
|
|
2062
|
+
indexer?: IndexerClient;
|
|
2063
|
+
/**
|
|
2064
|
+
* Cap on the indexer's self-reported lag before the health guard skips
|
|
2065
|
+
* it (default {@link DEFAULT_INDEXER_MAX_LAG_SECONDS}; see the rationale
|
|
2066
|
+
* there). `Number.POSITIVE_INFINITY` disables the cap.
|
|
2067
|
+
*/
|
|
2068
|
+
indexerMaxLagSeconds?: number;
|
|
2069
|
+
});
|
|
2070
|
+
/**
|
|
2071
|
+
* Send funds directly to a one-time stealth account. Native XLM uses a plain
|
|
2072
|
+
* CreateAccount/Payment; a non-native asset uses CreateAccount (to open the
|
|
2073
|
+
* stealth account) plus a CreateClaimableBalance carrying the token. Amount
|
|
2074
|
+
* MUST be strictly greater than 1 XLM for native sends.
|
|
2075
|
+
*/
|
|
2076
|
+
send(params: AdapterSendParams): Promise<SendReceipt>;
|
|
2077
|
+
/** Native XLM send: CreateAccount (falls back to Payment on retry). */
|
|
2078
|
+
private sendNative;
|
|
2079
|
+
/**
|
|
2080
|
+
* Token send: one classic tx carrying two operations —
|
|
2081
|
+
* CreateAccount(stealth, 1.5001 XLM) to open the account with trustline
|
|
2082
|
+
* headroom, then CreateClaimableBalance(asset, amount, claimant: stealth,
|
|
2083
|
+
* unconditional). The 0.5 XLM claimable-balance reserve returns to the sender
|
|
2084
|
+
* when the recipient claims. The ephemeral R rides in the MemoHash exactly as
|
|
2085
|
+
* for native sends.
|
|
2086
|
+
*
|
|
2087
|
+
* Not idempotent on retry: unlike {@link sendNative} (which falls back to a
|
|
2088
|
+
* Payment on `op_already_exists`), a token send has no such fallback. Each
|
|
2089
|
+
* token send uses a fresh ephemeral — hence a fresh, never-before-created
|
|
2090
|
+
* stealth address — so a resubmit targets a new account rather than colliding,
|
|
2091
|
+
* making the retry path unnecessary here.
|
|
2092
|
+
*/
|
|
2093
|
+
private sendToken;
|
|
2094
|
+
/**
|
|
2095
|
+
* Discover incoming direct payments over Horizon's ascending transaction
|
|
2096
|
+
* feed. For each tx with a hash memo, the memo decodes to a candidate R;
|
|
2097
|
+
* deriving the stealth address from (viewPrivKey, spendPubKey, R) and
|
|
2098
|
+
* finding an operation whose destination equals that address confirms the
|
|
2099
|
+
* payment is ours. Three op shapes are matched:
|
|
2100
|
+
* - `create_account` / `payment` -> a native XLM send.
|
|
2101
|
+
* - `create_claimable_balance` with our address as a claimant -> a token send;
|
|
2102
|
+
* the claimable balance id is resolved via Horizon's claimable-balances API.
|
|
2103
|
+
*
|
|
2104
|
+
* With an indexer configured (and passing the health guard) the walk is
|
|
2105
|
+
* segmented: a bounded Horizon pre-segment covers anything before the
|
|
2106
|
+
* indexer's coverage window, the covered span consumes pre-extracted
|
|
2107
|
+
* announcements with inlined operations (no per-tx round-trip), and a
|
|
2108
|
+
* Horizon tail ALWAYS runs from the last adopted position so indexer lag —
|
|
2109
|
+
* or an indexer fault mid-segment — can never hide a payment. A cold scan
|
|
2110
|
+
* fast-starts at the indexer's first covered position; payments predating
|
|
2111
|
+
* that coverage need `exhaustive: true`. Without an indexer the behavior is
|
|
2112
|
+
* exactly the original unbounded Horizon walk.
|
|
2113
|
+
*
|
|
2114
|
+
* Every path also returns `meta` — per-segment candidate/match counts plus
|
|
2115
|
+
* the indexer guard's verdict. Strictly observability: the numbers describe
|
|
2116
|
+
* how the scan ran, never which payments it finds.
|
|
2117
|
+
*/
|
|
2118
|
+
scan(keys: StealthKeys, cursor?: string, opts?: {
|
|
2119
|
+
suppressClaimedNative?: boolean;
|
|
2120
|
+
exhaustive?: boolean;
|
|
2121
|
+
}): Promise<{
|
|
2122
|
+
payments: Payment[];
|
|
2123
|
+
cursor?: string;
|
|
2124
|
+
meta: MethodScanMeta;
|
|
2125
|
+
}>;
|
|
2126
|
+
/**
|
|
2127
|
+
* Probe the configured indexer and classify whether it is usable for THIS
|
|
2128
|
+
* scan: `status === 'ok'`, same network as this adapter, a non-null
|
|
2129
|
+
* coverage interval, and self-reported lag within the configured cap. On
|
|
2130
|
+
* any guard failure the scan stays on the pure Horizon walk (Horizon is the
|
|
2131
|
+
* source of truth); the returned reason feeds {@link MethodScanMeta} so the
|
|
2132
|
+
* otherwise-silent fallback is observable. A null/absent `lagSeconds` is
|
|
2133
|
+
* NOT stale — lag cannot be measured then, and correctness is tail-bounded
|
|
2134
|
+
* anyway.
|
|
2135
|
+
*/
|
|
2136
|
+
private indexerCoverage;
|
|
2137
|
+
/**
|
|
2138
|
+
* Ascending Horizon transaction walk shared by every scan segment,
|
|
2139
|
+
* processing candidates from `from` (exclusive, Horizon cursor semantics).
|
|
2140
|
+
* Unbounded when `stopAtToken` is undefined. When bounded, txs with
|
|
2141
|
+
* `BigInt(paging_token) <= BigInt(stopAtToken)` are processed — INCLUSIVE
|
|
2142
|
+
* of the boundary tx, matching the indexer's open coverage interval
|
|
2143
|
+
* (startCursor, cursor] — and the walk returns `stopAtToken` at the first
|
|
2144
|
+
* tx beyond it. Otherwise returns the last seen paging token (the scan
|
|
2145
|
+
* cursor), or `from` when nothing new was seen. `stats` accumulates the
|
|
2146
|
+
* segment's candidate/match counts (observability only).
|
|
2147
|
+
*/
|
|
2148
|
+
private walkHorizon;
|
|
2149
|
+
/**
|
|
2150
|
+
* Run one transaction through the candidate pipeline: verify the memo
|
|
2151
|
+
* shape, decode the ephemeral R, derive the receiver-side stealth address,
|
|
2152
|
+
* and — on an ownership match — collect the native and/or token payment
|
|
2153
|
+
* rows into `payments`.
|
|
2154
|
+
*
|
|
2155
|
+
* `ops` is either the inline operation records (indexer announcements carry
|
|
2156
|
+
* them verbatim — no per-tx round-trip) or a lazy fetch invoked only after
|
|
2157
|
+
* the candidate checks pass (the Horizon walk — preserving the pre-indexer
|
|
2158
|
+
* behavior of never fetching operations for non-candidate txs). The memo
|
|
2159
|
+
* checks run here even for indexer-served records, so a corrupt feed entry
|
|
2160
|
+
* degrades to "not a candidate" rather than a bogus derivation.
|
|
2161
|
+
*
|
|
2162
|
+
* `stats.candidates` counts every tx that passes the memo-shape checks —
|
|
2163
|
+
* BEFORE derivation, so Horizon- and indexer-served sources count
|
|
2164
|
+
* identically — and `stats.matches` counts every payment pushed.
|
|
2165
|
+
*/
|
|
2166
|
+
private collectCandidate;
|
|
2167
|
+
/**
|
|
2168
|
+
* Build the native income row for a matched create_account/payment leg, or
|
|
2169
|
+
* `null` to suppress it.
|
|
2170
|
+
*
|
|
2171
|
+
* Suppression is gated on whether the SAME tx also delivered a matching token
|
|
2172
|
+
* claimable balance (`hasTokenLeg`): the token path fronts a fixed reserve via
|
|
2173
|
+
* a create_account stub, which must NOT surface as a native payment — but this
|
|
2174
|
+
* is decided by the presence of the token leg, NOT by any magic starting
|
|
2175
|
+
* balance, so a genuine native send that merely happens to equal the reserve
|
|
2176
|
+
* constant is still discovered.
|
|
2177
|
+
*
|
|
2178
|
+
* On the balance path (`suppressClaimed`) the row reports the account's LIVE
|
|
2179
|
+
* native balance (what remains after any partial claim), and a fully-swept
|
|
2180
|
+
* (0) account is dropped. The discovery path reports the per-tx op amount so
|
|
2181
|
+
* two sends to the same address each surface independently.
|
|
2182
|
+
*/
|
|
2183
|
+
private buildNativePayment;
|
|
2184
|
+
/**
|
|
2185
|
+
* Build the token income row for a matched create_claimable_balance leg, or
|
|
2186
|
+
* `null` when no genuine, currently-claimable CB binds to it.
|
|
2187
|
+
*
|
|
2188
|
+
* The candidate CBs (Horizon lists ALL CBs the derived address can claim) are
|
|
2189
|
+
* bound to THIS specific op — not resolved by first-claimant — so an attacker
|
|
2190
|
+
* who creates their own CreateClaimableBalance naming the public stealth
|
|
2191
|
+
* address cannot mask/misattribute the real payment. Binding matches the CB
|
|
2192
|
+
* whose sponsor is the tx source AND whose asset+amount equal this op's, and
|
|
2193
|
+
* whose claim predicate for the derived address is currently satisfiable.
|
|
2194
|
+
*/
|
|
2195
|
+
private buildTokenPayment;
|
|
2196
|
+
/**
|
|
2197
|
+
* Select the live claimable balance that genuinely corresponds to this tx's
|
|
2198
|
+
* create_claimable_balance op. Filters candidates to those whose claimant is
|
|
2199
|
+
* the derived address with a currently-satisfiable predicate, then binds by
|
|
2200
|
+
* sponsor === tx source AND asset === op asset AND amount === op amount. Only
|
|
2201
|
+
* when that precise binding is ambiguous or the tx source is unknown does it
|
|
2202
|
+
* fall back to an asset+amount match — never a bare first-claimant pick, so an
|
|
2203
|
+
* attacker CB cannot shadow the real one.
|
|
2204
|
+
*/
|
|
2205
|
+
private bindClaimableBalance;
|
|
2206
|
+
/**
|
|
2207
|
+
* Claim a direct-account payment. Branches on the payment shape: a claimable
|
|
2208
|
+
* balance (token send) runs the trustline + claim recipe; a plain XLM send
|
|
2209
|
+
* sweeps or partially pays out the stealth account.
|
|
2210
|
+
*/
|
|
2211
|
+
claim(payment: Payment, destination: string, opts: ClaimOpts): Promise<ClaimReceipt>;
|
|
2212
|
+
/**
|
|
2213
|
+
* Claim a native XLM direct send. The stealth account is a real funded Stellar
|
|
2214
|
+
* account with a sequence number. Full sweep (default) uses AccountMerge; a
|
|
2215
|
+
* partial claim uses Payment. Signing uses the raw stealth scalar (verifies as
|
|
2216
|
+
* standard ed25519). Optionally fee-bumped via a relayer.
|
|
2217
|
+
*
|
|
2218
|
+
* Passing `opts.amount` without `merge: false` is rejected up front
|
|
2219
|
+
* ({@link ClaimAmountRequiresNoMergeError}): the default merge sweeps the
|
|
2220
|
+
* ENTIRE balance via AccountMerge, so silently ignoring `amount` would move
|
|
2221
|
+
* more funds than the caller asked for.
|
|
2222
|
+
*/
|
|
2223
|
+
private claimNative;
|
|
2224
|
+
/**
|
|
2225
|
+
* Claim a token direct send delivered as a claimable balance. Requires the
|
|
2226
|
+
* destination to already trust the asset (probed first with an actionable
|
|
2227
|
+
* error otherwise).
|
|
2228
|
+
*
|
|
2229
|
+
* Self-funded path (stealth account exists): ChangeTrust(asset) ->
|
|
2230
|
+
* ClaimClaimableBalance(id) -> optional full exit
|
|
2231
|
+
* [Payment(asset, destination) -> ChangeTrust(limit '0') ->
|
|
2232
|
+
* AccountMerge(destination)].
|
|
2233
|
+
*
|
|
2234
|
+
* Sponsored path (`opts.sponsored`, or account stub missing): delegate to the
|
|
2235
|
+
* relayer's sponsor-claim pair — prepare returns XDR, we attach the stealth
|
|
2236
|
+
* signature and submit.
|
|
2237
|
+
*/
|
|
2238
|
+
private claimToken;
|
|
2239
|
+
/**
|
|
2240
|
+
* Sponsored claim: relayer prepares the XDR (BeginSponsoring -> [CreateAccount]
|
|
2241
|
+
* -> ChangeTrust -> EndSponsoring -> ClaimClaimableBalance -> Payment to the
|
|
2242
|
+
* destination), we co-sign with the stealth key, relayer submits and pays the
|
|
2243
|
+
* fee. The claimed token is delivered to `destination` in the same tx — the
|
|
2244
|
+
* stealth account never needs its own fee balance. The receipt's amount is the
|
|
2245
|
+
* amount that reached the destination.
|
|
2246
|
+
*/
|
|
2247
|
+
private claimTokenSponsored;
|
|
2248
|
+
/**
|
|
2249
|
+
* Parse and verify a relayer-prepared sponsored-claim XDR against the client's
|
|
2250
|
+
* OWN trusted inputs BEFORE signing. This is the client-side security control:
|
|
2251
|
+
* the relayer-side `opsMatch` protects the relayer, not us, so a malicious
|
|
2252
|
+
* relayer could otherwise redirect the payout Payment or append an
|
|
2253
|
+
* AccountMerge to steal the just-claimed token.
|
|
2254
|
+
*
|
|
2255
|
+
* Verifies, throwing {@link SponsoredClaimMismatchError} on any mismatch:
|
|
2256
|
+
* - the tx is sourced by the relayer (never the stealth account);
|
|
2257
|
+
* - the tx carries no memo;
|
|
2258
|
+
* - every operation is one of the allowed sponsor-claim shapes, in the exact
|
|
2259
|
+
* order the relayer builds — BeginSponsoring(stealth) -> optional
|
|
2260
|
+
* CreateAccount(stealth, '0') -> ChangeTrust(asset, source stealth) ->
|
|
2261
|
+
* EndSponsoring(source stealth) -> ClaimClaimableBalance(balanceId, source
|
|
2262
|
+
* stealth) -> Payment(destination, asset, amount, source stealth);
|
|
2263
|
+
* - every value-moving op is sourced by the stealth account;
|
|
2264
|
+
* - the payout Payment's destination/asset/amount equal the caller's intent;
|
|
2265
|
+
* - no extra, missing, or reordered operations.
|
|
2266
|
+
*
|
|
2267
|
+
* Returns the parsed {@link Transaction} (ready to co-sign) on success.
|
|
2268
|
+
*/
|
|
2269
|
+
private verifySponsoredClaimXdr;
|
|
2270
|
+
/**
|
|
2271
|
+
* Resolve the EXACT 7-dp payout string for a token claim from the precise
|
|
2272
|
+
* stroop count, never the lossy `payment.amount` double. Prefers the exact
|
|
2273
|
+
* `payment.amountStroops` string; when it is absent, falls back to the 7-dp
|
|
2274
|
+
* `payment.amount` double with NO extra Horizon read (the amount is already in
|
|
2275
|
+
* hand from the scan). Above 2^53 stroops the double in `payment.amount`
|
|
2276
|
+
* cannot represent every stroop, so an exact stroop count should always be
|
|
2277
|
+
* threaded through when available (SDK-PREC-1).
|
|
2278
|
+
*/
|
|
2279
|
+
private resolveExactPayout;
|
|
2280
|
+
/** Recover the stealth scalar for signing from the payment's ephemeral R. */
|
|
2281
|
+
private recoverKey;
|
|
2282
|
+
/** Fail with an actionable error unless the destination trusts the asset. */
|
|
2283
|
+
private assertDestinationTrusts;
|
|
2284
|
+
/** Submit an XDR directly to Horizon, or via a relayer fee-bump when set. */
|
|
2285
|
+
private submit;
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
/**
|
|
2289
|
+
* Reserved adapter slot for Stellar Private Payments (SPP).
|
|
2290
|
+
*
|
|
2291
|
+
* SPP is the Nethermind ZK shielded-pool research prototype (testnet-only
|
|
2292
|
+
* today). This adapter intentionally implements the full {@link DeliveryAdapter}
|
|
2293
|
+
* surface but every method throws {@link MethodNotAvailableError}. The slot
|
|
2294
|
+
* exists so applications can opt into SPP later — by adding `'spp'` to
|
|
2295
|
+
* `ClientConfig.methods` — with ZERO API changes: the same `send`/`scan`/`claim`
|
|
2296
|
+
* calls simply start routing through a real SPP implementation once it lands.
|
|
2297
|
+
*/
|
|
2298
|
+
declare class SppAdapter implements DeliveryAdapter {
|
|
2299
|
+
readonly method: "spp";
|
|
2300
|
+
/**
|
|
2301
|
+
* @throws {MethodNotAvailableError} Always — SPP is not yet implemented. The
|
|
2302
|
+
* declared return type is {@link SendReceipt} (never actually returned) so the
|
|
2303
|
+
* adapter's surface matches the {@link DeliveryAdapter} interface exactly.
|
|
2304
|
+
*/
|
|
2305
|
+
send(_params: AdapterSendParams): Promise<SendReceipt>;
|
|
2306
|
+
/** @throws {MethodNotAvailableError} Always — SPP is not yet implemented. */
|
|
2307
|
+
scan(_keys: StealthKeys, _cursor?: string): Promise<{
|
|
2308
|
+
payments: Payment[];
|
|
2309
|
+
cursor?: string;
|
|
2310
|
+
}>;
|
|
2311
|
+
/** @throws {MethodNotAvailableError} Always — SPP is not yet implemented. */
|
|
2312
|
+
claim(_payment: Payment, _destination: string, _opts: ClaimOpts): Promise<ClaimReceipt>;
|
|
203
2313
|
}
|
|
204
2314
|
|
|
205
|
-
export { type Balance, type ClientConfig, type Payment, type SendOpts, type SendReceipt, StealthClient, type StealthKeys, type TransactionSigner, type WithdrawOpts, type WithdrawReceipt };
|
|
2315
|
+
export { AccountAdapter, type AdapterSendParams, AnnouncementNotFoundError, type AnnouncementsPage, type Balance, ClaimAmountError, ClaimAmountRequiresNoMergeError, type ClaimOpts, type ClaimReceipt, type ClientConfig, ContractIdRequiredError, type CreditChallenge, type CreditView, DEFAULT_APP_ID, DEFAULT_KEY_SCOPE, type DeliveryAdapter, type DeliveryMethod, DestinationTrustlineError, EntryArchivedRestoringError, FeePayerAddressRequiredError, FeePayerRequiredError, type FetchLike, type FundingSigner, type HorizonAccount, type HorizonClaimableBalance, type HorizonClaimant, HorizonClient, type HorizonOp, type HorizonPredicate, type HorizonTx, type IndexerAnnouncement, IndexerClient, type IndexerHealth, IndexerHttpError, IndexerNetworkError, InvalidAmountError, type KVStorage, MethodNotAvailableError, MethodNotEnabledError, MethodRequiredError, type MethodScanMeta, MinimumAmountError, NETWORKS, type NetworkConfig, type NetworkDefinition, type NetworkName, NoBalanceError, NoHealthyRelayerError, type Payment, PoolAdapter, type ProbeOutcome, type RawStealthKeys, type RawStealthMetaAddress, type RebuildInvocation, type RelayOpts, type RelayerCallCtx, RelayerClient, type RelayerHealth, RelayerHttpError, RelayerNetworkError, RelayerPool, type RelayerPoolOpts, type RelayerSelection, type RestoreNotify, type RestoreSigner, type RestoreSubmit, type ScanCursor, type ScanOpts, type ScanResult, type ScanSegmentMeta, type ScanState, type SendOpts, type SendReceipt, SessionIntegrityError, ShadeError, type SponsorClaimPrepareArgs, type SponsorClaimPrepared, type SponsorOpts, SponsoredClaimMismatchError, SppAdapter, StealthAccountNotFoundError, StealthClient, type StealthKeys, StealthSession, type StealthSessionOpts, TransactionRetryableError, type TransactionSigner, type TransactionStatusSource, TransactionTimeoutError, UnsupportedNetworkError, type WalletKeysOpts, type WalletSigner, type WithdrawOpts, type WithdrawReceipt, WrongPasswordError, buildWithdrawMessage, challengeMessage, createSimulationTx, formatStroops, getNetworkConfig, keysFromWalletSignature, labelForToken, networkNameForPassphrase, normalizeRelayList, numberToStroops, parseStroops, prepareWithRestore, resolveTokenAddress, simulateReadOnly, stealthKeysFromRaw, waitForTransaction };
|