@lumenwipe/sdk 0.1.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -36,6 +36,20 @@ export declare interface AccountState {
36
36
  * numSubEntries on this account; populated via the Horizon adapter. */
37
37
  claimableBalances: ClaimableBalance[];
38
38
  subEntryMismatch: boolean;
39
+ /** Detected DeFi positions (Blend, Aquarius, Soroswap, Phoenix, FxDAO), resolved via OctoPos on
40
+ * mainnet or a direct contract read on testnet / mainnet degraded mode - architecture.md §7.1.
41
+ * Never absent: an unconfigured deployment or provider outage still returns a result, stamped
42
+ * with a null `timestamp` and reflected in `defiPositionsWarnings` rather than being omitted. */
43
+ defiPositions: DefiPositionsResult;
44
+ /** Plain-language notices from `assessDefiPositionsGate` (issue #147) - stale/unconfirmed data
45
+ * or an unrecognized position - reusing the exact same typed-blocker signal close/plan already
46
+ * gates on, so the analysis view and the plan never disagree about what's trustworthy. Empty
47
+ * when nothing needs flagging. */
48
+ defiPositionsWarnings: PlanBlocker[];
49
+ /** Soroban token balances held directly, best effort (see `SorobanTokensResult`). Absent on
50
+ * account reads produced before this field existed; a consumer treats absence as "nothing
51
+ * known", never as "nothing held". */
52
+ sorobanTokens?: SorobanTokensResult;
39
53
  }
40
54
 
41
55
  export declare interface AccountThresholds {
@@ -44,16 +58,134 @@ export declare interface AccountThresholds {
44
58
  high: number;
45
59
  }
46
60
 
47
- export declare type ApiErrorBody = StructuredApiError | PlainApiError;
61
+ /**
62
+ * A live, non-zero SEP-41 allowance the account has granted a spender on one token
63
+ * (architecture.md §12). The amount always comes from a live `allowance(from, spender)` read -
64
+ * `approve` neither decrements on spend nor is the only way the amount could have changed since
65
+ * it was set, so the event that discovered the pair is never trusted for the amount itself.
66
+ */
67
+ export declare interface Allowance {
68
+ /** The token contract, `C...`. */
69
+ token: string;
70
+ /** From the token's own `symbol()`, null when it does not answer. */
71
+ tokenSymbol: string | null;
72
+ /** From the token's own `decimals()`, null when it does not answer. */
73
+ tokenDecimals: number | null;
74
+ /** The approved spender. Almost always a contract (`C...`) - a DeFi protocol - but SEP-41's
75
+ * `approve(from, spender, ...)` types `spender` as `Address`, so a plain account (`G...`) is a
76
+ * legitimate, if unusual, spender too. */
77
+ spender: string;
78
+ /** The spender's protocol, when its address resolves to a known DeFi contract registry entry
79
+ * on this network. Null does not mean unsafe - most legitimate spenders (a specific vault, an
80
+ * integrator's own contract) are simply not in the registry. */
81
+ spenderProtocol: DefiProtocol | null;
82
+ /** Base units, integer string, as `allowance(from, spender)` reports it live on the ledger. */
83
+ amount: string;
84
+ /** The ledger this allowance expires at, from the most recent `approve` event that discovered
85
+ * this pair. Null when the amount is confirmed live but no `approve` event for this exact pair
86
+ * was found within the event scan's retention window (e.g. approved long enough ago that the
87
+ * event has aged out) - the amount is still ground truth, the expiration simply is not
88
+ * observable from this discovery path, since SEP-41's `allowance()` returns only the amount. */
89
+ expirationLedger: number | null;
90
+ /** Every source that proposed this pair. */
91
+ sources: AllowanceSource[];
92
+ }
93
+
94
+ /** How one candidate source fared, so the interface can say exactly what was consulted. */
95
+ export declare interface AllowanceCoverage {
96
+ source: AllowanceSource;
97
+ status: "ok" | "failed" | "skipped";
98
+ detail?: string;
99
+ }
100
+
101
+ /** Where an allowance candidate (token, spender) pair came from before its live amount was
102
+ * confirmed on the ledger. */
103
+ export declare type AllowanceSource = "events" | "registry";
104
+
105
+ /**
106
+ * Every live allowance the account has granted, across every token it has approved anything on.
107
+ * Soroban has no on-chain index of an account's approvals, so this is a best-effort union of
108
+ * candidate (token, spender) pairs - recent `approve` events naming this account as `from`, and
109
+ * the known DeFi contract registry's spenders crossed with a small curated token list - each
110
+ * confirmed by reading `allowance(from, spender)` on the ledger. A zero allowance, live or never
111
+ * granted, is not reported: only what is actually outstanding right now.
112
+ */
113
+ export declare interface AllowancesResult {
114
+ allowances: Allowance[];
115
+ coverage: AllowanceCoverage[];
116
+ /** The ledger range the event scan covered, or null when it ran out of budget before starting. */
117
+ eventsScanned: {
118
+ fromLedger: number;
119
+ toLedger: number;
120
+ } | null;
121
+ /** Plain-language warnings: a source that failed, candidates dropped by the cap. */
122
+ warnings: PlanBlocker[];
123
+ }
124
+
125
+ export declare type ApiErrorBody = StructuredApiError;
126
+
127
+ /** An `ErrorCode` the API can return, or `"upstream_error"` for a non-JSON failure from a proxy
128
+ * or CDN in front of it. The open `string` arm keeps a newer API's code from breaking a build. */
129
+ export declare type ApiErrorCode = ErrorCode | "upstream_error" | (string & {});
130
+
131
+ export declare interface AquariusLpPosition extends DefiPositionBase {
132
+ protocol: "aquarius";
133
+ positionType: "lp";
134
+ shareAmount: string;
135
+ /** Reported by OctoPos alongside the LP position rather than as its own position. */
136
+ claimableAquaAmount?: string;
137
+ /** The pool's tokens in the pool's own order - the order `withdraw` pays them out and
138
+ * `min_amounts` is given in. Read from the pool when detection can. */
139
+ tokens?: string[];
140
+ /** The pool's LP share token: a separate contract in Aquarius, where the account's shares live. */
141
+ shareToken?: string;
142
+ /** Which pool code the pool runs, from the registry. */
143
+ poolType?: AquariusPoolType;
144
+ }
145
+
146
+ /** Aquarius deploys three pool codes; only the share-based ones can be exited by shares. */
147
+ export declare type AquariusPoolType = "constant_product" | "stable" | "concentrated";
48
148
 
49
149
  /**
50
- * What happens to a non-XLM balance before its trustline is removed.
150
+ * What happens to a non-XLM balance before the account is merged.
51
151
  *
52
152
  * `convert` swaps it to XLM and `issuer` sends it back to be burned - both end the close with the
53
153
  * position destroyed. `transfer` keeps the asset, as the asset, by paying it to another account
54
- * that already holds the trustline.
154
+ * (one that already holds the trustline, for a classic asset). `leave` applies only to a Soroban
155
+ * token balance, which sits in the token contract's storage and does not stop a merge: the user
156
+ * acknowledges that it stays with this address, recoverable only by funding the same key again.
157
+ * A classic trustline can never be left - the merge fails while it holds a balance.
55
158
  */
56
- export declare type AssetDisposition = "convert" | "issuer" | "transfer";
159
+ export declare type AssetDisposition = "convert" | "issuer" | "transfer" | "leave";
160
+
161
+ export declare interface BatchPlanResponse {
162
+ results: BatchPlanResult[];
163
+ }
164
+
165
+ /** One address's plan within a batch call - always present, even when the account could not be
166
+ * read or planned safely (that failure surfaces as a `blocked` plan with its own blocker,
167
+ * never as a missing entry). */
168
+ export declare interface BatchPlanResult {
169
+ address: string;
170
+ plan: PlanResponse;
171
+ }
172
+
173
+ export declare interface BlendBorrowPosition extends DefiPositionBase {
174
+ protocol: "blend";
175
+ positionType: "borrow";
176
+ assetAddress: string;
177
+ dTokenAmount: string;
178
+ healthFactor?: string;
179
+ }
180
+
181
+ export declare interface BlendSupplyPosition extends DefiPositionBase {
182
+ protocol: "blend";
183
+ positionType: "supply";
184
+ assetAddress: string;
185
+ bTokenAmount: string;
186
+ /** True when this supply is also posted as backstop collateral. */
187
+ isBackstop?: boolean;
188
+ }
57
189
 
58
190
  export declare interface BuildPlanResult {
59
191
  steps: PlannedStep[];
@@ -127,7 +259,14 @@ export declare interface CloseEngineDeps {
127
259
  /** Called after a transaction confirms, with the transaction and its hash. */
128
260
  onConfirmed?: (tx: CloseTransaction, hash: string) => void;
129
261
  onProgress?: (message: string) => void;
130
- /** Safety bound so a misbehaving API can never spin the loop forever. */
262
+ /**
263
+ * Safety bound so a misbehaving API can never spin the loop forever. This is what covers
264
+ * #59's mediator-cleanup concern: the API is stateless and re-reads live state every round,
265
+ * so `remaining.requiresAnotherCall` for that flow reflects "cleanup still sees something to
266
+ * remove," not a bounded countdown - if an asset keeps getting re-funded between rounds (or
267
+ * any other close path never converges for whatever reason), this stops it after `maxRounds`
268
+ * with a clear thrown error instead of looping indefinitely.
269
+ */
131
270
  maxRounds?: number;
132
271
  }
133
272
 
@@ -147,6 +286,24 @@ export declare interface CloseTransaction {
147
286
  sourceSequence: string;
148
287
  validUntilLedger: number;
149
288
  covers: StepType[];
289
+ /**
290
+ * Which of the covered steps this transaction is for, by the same identity a plan step carries
291
+ * (`PlannedStep.affectedContract` for an exit, `affectedAsset` for an asset or token). `covers`
292
+ * names step TYPES, and a close can hold several steps of one type - three DeFi exits, two
293
+ * token transfers - so a client marking by type alone marked them all done off one transaction
294
+ * and stamped them all with its hash. The permanent receipt then listed one exit where three
295
+ * had run. Absent when the transaction covers every step of the types it names, which is the
296
+ * only shape an older client ever saw.
297
+ */
298
+ coversTargets?: string[];
299
+ /**
300
+ * True when the account cannot pay this transaction's own fee without dropping below its
301
+ * reserve (architecture.md §8.1): the API built it with its own fee at zero, and the client
302
+ * must route the signed envelope through `POST /:network/fee-bump/sponsor` before submitting
303
+ * it, instead of submitting it directly. Absent (not `false`) on every ordinary transaction -
304
+ * an older client reading this field sees nothing here and simply submits as it always has.
305
+ */
306
+ needsSponsoredFee?: true;
150
307
  intent: TxIntent;
151
308
  }
152
309
 
@@ -171,6 +328,12 @@ export declare interface ConversionPath {
171
328
  destMin: string;
172
329
  }
173
330
 
331
+ export declare interface DailyActivity {
332
+ /** UTC day, YYYY-MM-DD. */
333
+ date: string;
334
+ accountsClosed: number;
335
+ }
336
+
174
337
  export declare interface DataEntry {
175
338
  key: string;
176
339
  value: string;
@@ -181,6 +344,20 @@ export declare interface DecisionAnswer {
181
344
  choice: string;
182
345
  params?: {
183
346
  maxSlippageBps?: number;
347
+ /**
348
+ * Required by `convert_to_xlm` on a Soroban token: the least XLM, in stroops, the user was
349
+ * shown the swap would deliver (the plan's `quote.minAmountOut`). The build refuses a route
350
+ * that no longer clears it (`quote_drifted`) instead of swapping at a rate nobody agreed to,
351
+ * and the browser holds the built swap's own minimum to at least this figure.
352
+ */
353
+ minAmountOut?: string;
354
+ /**
355
+ * Pinned alongside `minAmountOut`: which provider's quote the user was shown and accepted.
356
+ * The build re-quotes and builds through exactly this provider, never a different one that
357
+ * might look better right now but that the user never saw priced (architecture.md, this
358
+ * feature's spec §5.2).
359
+ */
360
+ provider?: "soroswap" | "xbull";
184
361
  /**
185
362
  * Required by the `transfer_to_account` choice: the `G...` address the balance is paid to.
186
363
  *
@@ -210,16 +387,155 @@ export declare interface DecisionPoint {
210
387
  required: boolean;
211
388
  }
212
389
 
390
+ /** Keyed by asset/contract address. Built from OctoPos's queryKeys.tokenInfos. */
391
+ export declare interface DefiEnrichmentEntry {
392
+ symbol: string;
393
+ decimals: number;
394
+ usdPrice: string | null;
395
+ priceSource: string | null;
396
+ }
397
+
398
+ export declare type DefiPosition = BlendSupplyPosition | BlendBorrowPosition | AquariusLpPosition | SoroswapLpPosition | PhoenixLpPosition | PhoenixStakePosition | FxdaoCdpPosition;
399
+
400
+ declare interface DefiPositionBase {
401
+ protocol: DefiProtocol;
402
+ /** Pool, market, or vault contract this position lives in. */
403
+ contractAddress: string;
404
+ /** Human-readable view of the position, when the analysis could resolve one. */
405
+ display?: DefiPositionDisplay;
406
+ /** Left as an optional hook: the wasmHash -\> protocol-version contract registry
407
+ * architecture.md describes does not exist in code yet (a later contribution). */
408
+ wasmHash?: string;
409
+ usdValue: string | null;
410
+ }
411
+
412
+ /**
413
+ * What a person should read about a position, in the protocol's own terms, resolved by the API
414
+ * from a live read at analysis time (issue #197). Presentation only: every field is nullable, a
415
+ * failed read leaves the whole object absent, and nothing here feeds the plan, the gate, or an
416
+ * exit - those re-read the ledger themselves.
417
+ */
418
+ export declare interface DefiPositionDisplay {
419
+ /** The pool, pair, or vault by name (the protocol's own metadata or the registry label). */
420
+ pool: string | null;
421
+ /** Symbol of the position's asset; for an LP position the pair, e.g. "XLM/USDC". */
422
+ asset: string | null;
423
+ /** Underlying amount in the asset's own units as a decimal string - not shares or bTokens. */
424
+ amount: string | null;
425
+ /** The part of `amount` posted as collateral (lending protocols), decimal string; null when
426
+ * the protocol has no such distinction. */
427
+ collateralAmount: string | null;
428
+ /** Current yield as a percentage with two decimals ("3.99"): earned on a supply, paid on debt. */
429
+ yieldPct: string | null;
430
+ yieldKind: "earned" | "paid" | null;
431
+ /** One protocol-specific clause the line ends with, e.g. what an LP share is worth in its two
432
+ * tokens ("9.95 XLM + 19.90 USDC"). Null when there is nothing to add. */
433
+ detail?: string | null;
434
+ }
435
+
436
+ /**
437
+ * The normalized DeFi position model every downstream consumer (exit adapters, plan preview)
438
+ * depends on - no OctoPos-specific field names beyond this boundary (architecture.md §7.1).
439
+ */
440
+ export declare interface DefiPositionsResult {
441
+ address: string;
442
+ network: Network;
443
+ positions: DefiPosition[];
444
+ unrecognizedPositions: UnrecognizedDefiPosition[];
445
+ enrichment: Record<string, DefiEnrichmentEntry>;
446
+ /** OctoPos's own freshness/provenance signal ("snapshot" | "empty" | "cache" | "not-tracked").
447
+ * Passed through verbatim rather than architecture.md's described (but not actually shipped)
448
+ * partial_result/attribution_confidence fields - see issue #147 for the gating that consumes
449
+ * this. */
450
+ source: string;
451
+ timestamp: string | null;
452
+ queryKeys: DefiQueryKeys;
453
+ }
454
+
455
+ /**
456
+ * Lowercased from OctoPos's own SUPPLY/BORROW/LP/STAKE tags. FxDAO's COLLATERAL+BORROW pair is
457
+ * one vault - one exit unit per architecture.md §9.7 - so it collapses to a single "cdp" position
458
+ * carrying both legs rather than two positions. WALLET positions are out of scope: classic
459
+ * balances already come from AccountState.trustlines, and Soroban token balance handling is a
460
+ * separate Phase-2 deliverable, not this epic.
461
+ */
462
+ export declare type DefiPositionType = "supply" | "borrow" | "lp" | "cdp" | "stake";
463
+
464
+ /**
465
+ * Protocols LumenWipe knows how to act on: most are DeFi positions it knows how to exit
466
+ * (architecture.md §9's table); `xbull` is the one exception, a swap router with no position to
467
+ * exit, registered here only so it can share the contract registry's protocol-keyed lookups.
468
+ * OctoPos itself indexes more than this (e.g. "untangled-vault", "stellar-wallet") - those are
469
+ * dropped by the adapter rather than represented here, since the type system should not claim
470
+ * LumenWipe can act on a protocol it has no exit path for.
471
+ */
472
+ export declare type DefiProtocol = "blend" | "aquarius" | "soroswap" | "phoenix" | "fxdao" | "xbull";
473
+
474
+ export declare interface DefiQueryKeys {
475
+ rpcEndpoints: DefiRpcEndpoint[];
476
+ rpcPolicy: DefiRpcPolicy;
477
+ slices: Partial<Record<DefiProtocol, DefiQueryKeysSlice>>;
478
+ }
479
+
480
+ /**
481
+ * Ready-made ledger keys plus pool/vault metadata for one protocol, letting a caller read
482
+ * positions directly over RPC without OctoPos storing anything server-side (architecture.md
483
+ * §7.1). Passed through typed rather than discarded, since issue #148's RPC fallback needs
484
+ * exactly this and would otherwise have to refetch it.
485
+ */
486
+ export declare interface DefiQueryKeysSlice {
487
+ protocol: string;
488
+ ledgerKeys: string[];
489
+ poolAddresses: string[];
490
+ }
491
+
492
+ /** A public Soroban RPC endpoint OctoPos suggests for a direct getLedgerEntries read. */
493
+ export declare interface DefiRpcEndpoint {
494
+ url: string;
495
+ health: string;
496
+ avgLatencyMs: number;
497
+ }
498
+
499
+ export declare interface DefiRpcPolicy {
500
+ maxKeysPerCall: number;
501
+ recommendedConcurrency: number;
502
+ backoffOn429Ms: number[];
503
+ timeoutMs: number;
504
+ }
505
+
213
506
  export declare type DemolishPhase = "IDLE" | "ANALYZING" | "PREFLIGHT_COMPLETE" | "SIGNER_SETUP" | "STEP_EXECUTING" | "STEP_CONFIRMED" | "STEP_FAILED" | "COMPLETE" | "ABORTED";
214
507
 
508
+ /**
509
+ * Every code the API can put in `error.code`. A code is a contract: it is added here first and
510
+ * never renamed or removed without a deprecation window. The API's runtime list
511
+ * (apps/api/src/common/error-codes.ts) is checked against this union at compile time.
512
+ */
513
+ export declare type ErrorCode = "account_not_found" | "account_read_failed" | "account_too_large" | "admin_api_not_configured" | "allowances_read_failed" | "api_key_not_found" | "aquarius_trustline_missing" | "backstop_emissions_unclaimed" | "backstop_token_unconvertible" | "backstop_withdrawal_cooling_down" | "backstop_withdrawal_not_queued" | "bad_request" | "blend_emissions_trustline_missing" | "blend_repay_asset_balance_unknown" | "blend_repay_asset_missing" | "confirmation_timeout" | "conversion_floor_missing" | "conversion_provider_unrecognized" | "defi_exit_blocked" | "defi_exit_unsupported" | "defi_position_unrecognized" | "defi_positions_blocked" | "defi_positions_stale" | "defi_positions_unavailable" | "destination_not_acknowledged" | "destination_read_failed" | "fee_bump_exceeds_cap" | "fee_bump_not_configured" | "forbidden" | "forward_amount_exceeds_balance" | "inner_fee_not_zero" | "integrator_api_not_configured" | "internal_error" | "invalid_address" | "invalid_addresses" | "invalid_body" | "invalid_challenge" | "invalid_decisions" | "invalid_destination" | "invalid_memo" | "invalid_network" | "invalid_owner" | "invalid_rate_limit" | "invalid_request" | "invalid_signature" | "invalid_signed_xdr" | "invalid_source" | "invalid_spender" | "invalid_token" | "invalid_tokens" | "invalid_transaction_xdr" | "invalid_tx_hash" | "key_limit_reached" | "mediator_key_misconfiguration" | "mediator_not_configured" | "memo_required" | "merge_destination_unusable" | "merged_account_not_found" | "missing_parameters" | "missing_transaction" | "needs_decisions" | "not_found" | "operation_not_sponsorable" | "owner_not_found" | "path_lookup_failed" | "payload_too_large" | "phoenix_trustline_missing" | "plan_failed" | "provider_response_unusable" | "quote_drifted" | "rate_limited" | "registry_expired" | "request_failed" | "revoke_build_failed" | "revoke_needs_restore" | "revoke_simulation_failed" | "revoke_unsafe" | "service_unavailable" | "signer_normalization_unsafe" | "soroban_token_conversion_failed" | "soroban_token_conversion_unavailable" | "soroban_token_conversion_unsafe" | "soroban_token_needs_restore" | "soroban_token_route_lost" | "soroban_token_transfer_failed" | "soroban_token_transfer_unsafe" | "soroban_token_unreadable" | "soroswap_trustline_missing" | "source_sequence_too_far" | "stats_unavailable" | "submit_failed" | "submit_rejected" | "too_many_addresses" | "transaction_structure_not_allowed" | "transactions_failed" | "transfer_destination_missing" | "transfer_destination_unusable" | "trustline_cannot_be_left" | "trustline_deauthorized_with_balance" | "tx_not_verified" | "unauthorized" | "unprocessable_entity" | "unsupported_media_type" | "unsupported_memo_type" | "vault_undercollateralized" | "withdraw_before_repay";
514
+
515
+ /** One transaction the close is expected to take. */
215
516
  export declare interface ExecutionTxBreakdown {
517
+ /** Position among the transactions, from 0, in the order they are built and submitted. */
216
518
  order: number;
519
+ /** The plan step types this transaction carries operations for. */
217
520
  covers: StepType[];
218
521
  reason?: "op_batch" | "defi_dependency";
219
522
  }
220
523
 
524
+ /** Response from `POST /:network/fee-bump/sponsor`. */
525
+ export declare interface FeeBumpSponsorResponse {
526
+ transaction: string;
527
+ }
528
+
221
529
  export declare type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
222
530
 
531
+ export declare interface FxdaoCdpPosition extends DefiPositionBase {
532
+ protocol: "fxdao";
533
+ positionType: "cdp";
534
+ denomination: string;
535
+ collateralAmount: string;
536
+ debtAmount: string;
537
+ }
538
+
223
539
  /** Response from `GET /health`. */
224
540
  export declare interface HealthResponse {
225
541
  status: string;
@@ -233,16 +549,6 @@ export declare class InsufficientSignatureWeightError extends Error {
233
549
  constructor(pending: PendingRound);
234
550
  }
235
551
 
236
- /**
237
- * One operation in a decoded close transaction.
238
- *
239
- * `source` is the account the operation acts as - the operation's own source account when it
240
- * carries one, otherwise the transaction's. It is what lets verification assert relationships
241
- * *between* operations rather than trusting each in isolation: in a mediated close the forward
242
- * payment must be sent by the very account the merge just paid into, which is what makes the
243
- * intermediary a conduit rather than a destination. Without it the only available check is
244
- * pinning the intermediary's address, which every consumer would then have to be told.
245
- */
246
552
  export declare type IntentOperation = IntentOperationBody & {
247
553
  source: string;
248
554
  };
@@ -293,15 +599,57 @@ export declare type IntentOperationBody = {
293
599
  type: "revoke_sponsorship";
294
600
  entryKind: "account" | "trustline" | "offer" | "data_entry" | "signer";
295
601
  owner: string;
602
+ } | {
603
+ type: "invoke_host_function";
604
+ contract: string;
605
+ function: string;
606
+ /** The decoded arguments rendered for a human, in order. */
607
+ args: string[];
608
+ /** Every Stellar account (G...) named anywhere in the arguments or in the authorization
609
+ * tree the signature would satisfy. A verifier insists these are all the closing account. */
610
+ accountsReferenced: string[];
611
+ /** Every contract (C...) named the same way, including nested calls the signature would
612
+ * authorize. A verifier insists these are contracts the account is known to deal with. */
613
+ contractsReferenced: string[];
614
+ /** Address forms that cannot be pinned to anything (muxed accounts, claimable balances,
615
+ * liquidity pools). A verifier refuses any. */
616
+ unsupportedAddressCount: number;
617
+ /** True when the signature would authorize more than the account's own plain contract
618
+ * calls: another party's credentials, or a contract creation, in the auth entries. */
619
+ authorizesBeyondSelf: boolean;
620
+ /** How deep the authorization tree nests: 0 when every entry is a single plain call with
621
+ * no sub-invocations, 1 when a call authorizes one level of nested calls, and so on. A
622
+ * token transfer must be 0 - the token may not make the account authorize anything else. */
623
+ authDepth: number;
624
+ /** Every non-root call in the authorization tree - what the root call (a pool, router, or
625
+ * backstop) would itself be authorized to do on the account's behalf, one level down. A
626
+ * DeFi exit's top-level contract/function pinning says nothing about these: a hostile
627
+ * build could keep a legitimate top-level call while hiding an unrelated transfer here. */
628
+ subInvocations: SubInvocationCall[];
296
629
  } | {
297
630
  type: "unknown";
298
631
  };
299
632
 
633
+ /** Narrows an unknown error to a `LumenWipeApiError` carrying the given `code`. */
634
+ export declare function isApiErrorCode<C extends ApiErrorCode>(error: unknown, code: C): error is LumenWipeApiError & {
635
+ code: C;
636
+ };
637
+
638
+ /** Thrown when the caller's `signal` aborts a request. Distinct from a timeout. */
639
+ export declare class LumenWipeAbortError extends Error {
640
+ readonly reason?: unknown | undefined;
641
+ constructor(reason?: unknown | undefined);
642
+ }
643
+
300
644
  /** Thrown when the API responds with a non-2xx status. `body` is the parsed error payload. */
301
645
  export declare class LumenWipeApiError extends Error {
302
646
  readonly status: number;
303
647
  readonly body: unknown;
304
- constructor(status: number, body: unknown);
648
+ readonly code: ApiErrorCode;
649
+ readonly details: unknown;
650
+ readonly retryAfterMs: number | undefined;
651
+ readonly requestId: string | undefined;
652
+ constructor(status: number, body: unknown, headers?: Pick<Headers, "get">);
305
653
  }
306
654
 
307
655
  /**
@@ -312,25 +660,42 @@ export declare class LumenWipeApiError extends Error {
312
660
  export declare class LumenWipeClient {
313
661
  private readonly http;
314
662
  private readonly defaultNetwork;
663
+ private readonly log;
664
+ private warnedDefaultNetwork;
315
665
  constructor(options: LumenWipeClientOptions);
316
- health(): Promise<HealthResponse>;
317
- getAccount(address: string, network?: Network): Promise<AccountState>;
666
+ private resolveNetwork;
667
+ health(options?: RequestOptions): Promise<HealthResponse>;
668
+ getAccount(address: string, network?: Network, options?: RequestOptions): Promise<AccountState>;
318
669
  getPaths(params: {
319
670
  fromAsset: string;
320
671
  amount: string;
321
- }, network?: Network): Promise<PathResponse>;
322
- closePlan(body: ClosePlanRequest, network?: Network): Promise<PlanResponse>;
672
+ }, network?: Network, options?: RequestOptions): Promise<PathResponse>;
673
+ closePlan(body: ClosePlanRequest, network?: Network, options?: RequestOptions): Promise<PlanResponse>;
323
674
  /**
324
675
  * Builds the next unsigned transaction(s) for a close. A close can span several
325
- * transactions (a fused close, or separate claim / cleanup / mediator-merge steps),
676
+ * transactions (a single transaction, or separate claim / cleanup / mediator-merge steps),
326
677
  * so the response's `remaining.requiresAnotherCall` says whether more follow: sign and
327
678
  * submit the returned transactions in `order`, wait for confirmation, then call this
328
679
  * again until `requiresAnotherCall` is false.
329
680
  */
330
- closeTransactions(body: CloseTransactionsRequest, network?: Network): Promise<TransactionsResponse>;
331
- submit(signedXdr: string, network?: Network): Promise<SubmitResponse>;
332
- mediatorCheck(address: string, network?: Network): Promise<MediatorCheckResult>;
333
- mediatorSign(transaction: string, network?: Network): Promise<MediatorSignResponse>;
681
+ closeTransactions(body: CloseTransactionsRequest, network?: Network, options?: RequestOptions): Promise<TransactionsResponse>;
682
+ submit(signedXdr: string, network?: Network, options?: RequestOptions): Promise<SubmitResponse>;
683
+ mediatorCheck(address: string, network?: Network, options?: RequestOptions): Promise<MediatorCheckResult>;
684
+ mediatorSign(transaction: string, network?: Network, options?: RequestOptions): Promise<MediatorSignResponse>;
685
+ /** Wraps a wind-down transaction (its own fee already zero) in a signed CAP-15 fee-bump
686
+ * envelope for an account that cannot pay its own fee (architecture.md §8.1). The caller
687
+ * submits the returned XDR through `submit()`, exactly like any other close transaction. */
688
+ feeBumpSponsor(transaction: string, network?: Network, options?: RequestOptions): Promise<FeeBumpSponsorResponse>;
689
+ /** Every live SEP-41 allowance the account has granted (architecture.md §12). Independent of
690
+ * closing an account - a standalone security utility. */
691
+ getAllowances(address: string, network?: Network, options?: RequestOptions): Promise<AllowancesResult>;
692
+ /** Builds the unsigned `approve(owner, spender, 0, 0)` transaction that revokes one allowance.
693
+ * The caller signs and submits it through `submit()`, like any other transaction here. */
694
+ revokeAllowance(params: {
695
+ owner: string;
696
+ token: string;
697
+ spender: string;
698
+ }, network?: Network, options?: RequestOptions): Promise<RevokeAllowanceResponse>;
334
699
  }
335
700
 
336
701
  export declare interface LumenWipeClientOptions {
@@ -338,12 +703,16 @@ export declare interface LumenWipeClientOptions {
338
703
  baseUrl: string;
339
704
  /** Integrator API key, sent as `Authorization: Bearer <apiKey>`. */
340
705
  apiKey: string;
341
- /** Default network for calls that omit it. Defaults to `"testnet"`. */
706
+ /** Default network for calls that omit it. Defaults to `"testnet"`, with a one-time notice. */
342
707
  network?: Network;
343
708
  /** Custom fetch (for environments without a global `fetch`, or for testing). */
344
709
  fetch?: FetchLike;
345
710
  /** Per-request timeout in milliseconds. Defaults to 30000. `0`/`Infinity` disables it. */
346
711
  timeout?: number;
712
+ /** Opt-in retries. Off by default; never applied to `submit` or `mediatorSign`. */
713
+ retry?: RetryOptions;
714
+ /** Receives one-time notices (plaintext `baseUrl`, defaulted network). Defaults to `console.warn`. */
715
+ logger?: (message: string) => void;
347
716
  }
348
717
 
349
718
  /** Thrown when a request exceeds the configured timeout. */
@@ -360,6 +729,8 @@ export declare interface MediatorCheckResult {
360
729
  exchangeName: string | null;
361
730
  /** Whether the server can actually perform the mediator flow (secret configured). */
362
731
  available: boolean;
732
+ /** Whether the exchange registry is inside its verification window; false means exchange closes are refused. */
733
+ registryFresh: boolean;
363
734
  }
364
735
 
365
736
  /** Response from `POST /:network/mediator/sign`. */
@@ -367,6 +738,16 @@ export declare interface MediatorSignResponse {
367
738
  transaction: string;
368
739
  }
369
740
 
741
+ /** One verified, counted close transaction. */
742
+ export declare interface MergeRecord {
743
+ txHash: string;
744
+ network: Network;
745
+ accountsClosed: number;
746
+ xlmStroops: string;
747
+ /** Ledger close time of the transaction, ISO 8601. */
748
+ timestamp: string;
749
+ }
750
+
370
751
  export declare type Network = "mainnet" | "testnet";
371
752
 
372
753
  export declare interface OpenOffer {
@@ -395,10 +776,18 @@ export declare interface PendingRound {
395
776
  requiresAnotherCall: boolean;
396
777
  }
397
778
 
398
- /** @deprecated The API emits one envelope (`StructuredApiError`). Kept only so a consumer
399
- * pinned to an older deployment still type-checks during a rollout; remove once none remain. */
400
- export declare interface PlainApiError {
401
- error: string;
779
+ export declare interface PhoenixLpPosition extends DefiPositionBase {
780
+ protocol: "phoenix";
781
+ positionType: "lp";
782
+ shareAmount: string;
783
+ }
784
+
785
+ export declare interface PhoenixStakePosition extends DefiPositionBase {
786
+ protocol: "phoenix";
787
+ positionType: "stake";
788
+ stakedAmount: string;
789
+ /** Unix seconds; unbonding requires the original stake's timestamp (architecture.md §9.6). */
790
+ stakedAtEpoch: string;
402
791
  }
403
792
 
404
793
  export declare interface PlanBlocker {
@@ -420,7 +809,15 @@ export declare interface PlannedStep {
420
809
  status: StepStatus;
421
810
  txHash: string | null;
422
811
  error: string | null;
812
+ /** What the user's own account paid for this step, once confirmed - exactly "0" when a
813
+ * dedicated sponsor account covered the fee-bump instead. Otherwise the plan-time estimate
814
+ * copied forward: the close-engine's confirmation callback carries only a tx hash, not the
815
+ * fee Horizon actually charged, so this cannot correct for surge pricing. Absent until the
816
+ * step is confirmed. */
817
+ actualFeeLumens?: string;
423
818
  affectedAsset?: string;
819
+ /** The pool, pair, or vault an EXIT_POSITIONS step leaves. */
820
+ affectedContract?: string;
424
821
  fallbackToIssuer?: boolean;
425
822
  }
426
823
 
@@ -438,8 +835,19 @@ export declare interface PlanResponse {
438
835
  feeStroops: string;
439
836
  freedReserveXlm: string;
440
837
  };
838
+ /**
839
+ * The transactions this close is expected to take, counted with the splitter the builder
840
+ * packs them with: Soroban token moves, the claim round, the classic close split at the
841
+ * per-transaction operation cap, and, for an exchange destination, the mediator transfer.
842
+ *
843
+ * `estimatedTransactionCount` is `null`, with `transactions` empty, when it cannot be known
844
+ * yet: the plan has a DeFi exit (its transaction count depends on live debt), a held token has
845
+ * no decision, or no destination was given. Never read it as a number without checking, and
846
+ * never show an unknown count as a figure. It is an estimate from the account as planned; the
847
+ * close re-reads live state each round.
848
+ */
441
849
  execution: {
442
- estimatedTransactionCount: number;
850
+ estimatedTransactionCount: number | null;
443
851
  transactions: ExecutionTxBreakdown[];
444
852
  };
445
853
  }
@@ -455,6 +863,28 @@ export declare interface QuoteInfo {
455
863
  expiresAtLedger: number;
456
864
  }
457
865
 
866
+ /** Response from `POST /v1/:network/stats/merges`. `counted` is false for an already-counted hash. */
867
+ export declare interface RecordMergeResponse {
868
+ counted: boolean;
869
+ }
870
+
871
+ export declare interface RequestOptions {
872
+ /** Aborts the request. Rejects with `LumenWipeAbortError`, distinct from a timeout. */
873
+ signal?: AbortSignal;
874
+ }
875
+
876
+ export declare interface RetryOptions {
877
+ /** Total attempts including the first. Values below 2 disable retrying. */
878
+ attempts: number;
879
+ /** Base delay for exponential backoff when the response has no `Retry-After`. */
880
+ baseDelayMs: number;
881
+ }
882
+
883
+ /** Response from `POST /:network/allowances/revoke`. */
884
+ export declare interface RevokeAllowanceResponse {
885
+ transaction: string;
886
+ }
887
+
458
888
  /**
459
889
  * Runs the full multi-round close. Each round: fetch a batch of unsigned transactions,
460
890
  * verify every one before signing, sign it, and submit only once its accumulated signing
@@ -465,6 +895,60 @@ export declare interface QuoteInfo {
465
895
  */
466
896
  export declare function runClose(deps: CloseEngineDeps, resume?: PendingRound): Promise<void>;
467
897
 
898
+ /** A SEP-41 token balance the account holds in a contract's storage - not a classic trustline. */
899
+ export declare interface SorobanTokenBalance {
900
+ /** The token contract, `C...`. */
901
+ contract: string;
902
+ /** Base units, integer string, as `balance(account)` reports it on the ledger. "0" only for a
903
+ * token the account does not hold yet but a detected position's exit will pay out. */
904
+ balance: string;
905
+ /** From the contract's own `symbol()` / `decimals()`, null when it does not answer them. */
906
+ symbol: string | null;
907
+ decimals: number | null;
908
+ /** Every source that proposed this contract; the balance itself always comes from the ledger. */
909
+ sources: SorobanTokenSource[];
910
+ }
911
+
912
+ /** How one candidate source fared, so the interface can say exactly what was consulted. */
913
+ export declare interface SorobanTokenCoverage {
914
+ source: SorobanTokenSource;
915
+ status: "ok" | "failed" | "skipped";
916
+ detail?: string;
917
+ }
918
+
919
+ /** Where a Soroban token candidate came from before its balance was confirmed on the ledger. */
920
+ export declare type SorobanTokenSource = "explorer" | "positions" | "list" | "events" | "manual";
921
+
922
+ /**
923
+ * Soroban token balances the account holds directly. Soroban has no per-account token index, so
924
+ * this is a best-effort union of candidate sources - an explorer, the tokens named by detected
925
+ * positions, bundled lists, recent on-chain `transfer`/`mint` events, and contracts the user added
926
+ * by hand - each confirmed by reading `balance(account)` on the ledger. What could not be read is
927
+ * listed, never dropped; classic assets' contracts are excluded (their balance is the trustline).
928
+ */
929
+ export declare interface SorobanTokensResult {
930
+ tokens: SorobanTokenBalance[];
931
+ /** Candidate contracts whose balance could not be read (a hostile, broken, or archived token). */
932
+ unreadable: string[];
933
+ coverage: SorobanTokenCoverage[];
934
+ /** The ledger range the event scan covered, or null when it ran out of budget before starting. */
935
+ eventsScanned: {
936
+ fromLedger: number;
937
+ toLedger: number;
938
+ } | null;
939
+ /** Plain-language warnings for the plan: a source that failed, contracts that could not be read. */
940
+ warnings: PlanBlocker[];
941
+ }
942
+
943
+ export declare interface SoroswapLpPosition extends DefiPositionBase {
944
+ protocol: "soroswap";
945
+ positionType: "lp";
946
+ shareAmount: string;
947
+ /** The pair's two token contracts (token_0, token_1), read from the pair when detection can;
948
+ * what a withdrawal receives and the exit's arguments name. */
949
+ tokens?: [string, string];
950
+ }
951
+
468
952
  /**
469
953
  * A ledger entry this account currently sponsors, on another account (or itself, for
470
954
  * "account" - a fully-sponsored account creation). Mirrors the ledger-key kinds
@@ -495,19 +979,54 @@ export declare type SponsoredEntry = {
495
979
  balanceId: string;
496
980
  };
497
981
 
982
+ /** Response from `GET /v1/:network/stats/feed`. `daily` is oldest first, one entry per day. */
983
+ export declare interface StatsFeed {
984
+ totals: StatsTotals;
985
+ recent: MergeRecord[];
986
+ daily: DailyActivity[];
987
+ }
988
+
989
+ /** Response from `GET /v1/:network/stats`. Stroops are decimal strings: the total can exceed 2^53. */
990
+ export declare interface StatsTotals {
991
+ network: Network;
992
+ accountsClosed: number;
993
+ xlmRecoveredStroops: string;
994
+ }
995
+
498
996
  export declare type StepStatus = "pending" | "signing" | "submitted" | "confirmed" | "failed" | "skipped";
499
997
 
500
- export declare type StepType = "NORMALIZE_SIGNERS" | "REVOKE_SPONSORSHIP" | "REMOVE_DATA_ENTRIES" | "CANCEL_OFFERS" | "ADD_TRUSTLINE_FOR_CLAIM" | "CLAIM_BALANCES" | "HANDLE_ASSETS" | "REMOVE_TRUSTLINES" | "CLOSE_ACCOUNT" | "MERGE";
998
+ export declare type StepType = "NORMALIZE_SIGNERS" | "REVOKE_SPONSORSHIP" | "REMOVE_DATA_ENTRIES" | "CANCEL_OFFERS" | "ADD_TRUSTLINE_FOR_CLAIM" | "CLAIM_BALANCES" | "EXIT_POSITIONS" | "HANDLE_ASSETS" | "REMOVE_TRUSTLINES" | "CLOSE_ACCOUNT" | "MERGE";
501
999
 
502
- /** Structured error body used by the v1 (`/v1/...`) endpoints. */
1000
+ /** The single error envelope every API error response carries. */
503
1001
  export declare interface StructuredApiError {
504
1002
  error: {
505
1003
  code: string;
506
1004
  message: string;
507
1005
  details?: unknown;
1006
+ requestId?: string;
508
1007
  };
509
1008
  }
510
1009
 
1010
+ /**
1011
+ * One operation in a decoded close transaction.
1012
+ *
1013
+ * `source` is the account the operation acts as - the operation's own source account when it
1014
+ * carries one, otherwise the transaction's. It is what lets verification assert relationships
1015
+ * *between* operations rather than trusting each in isolation: in a mediated close the forward
1016
+ * payment must be sent by the very account the merge just paid into, which is what makes the
1017
+ * intermediary a conduit rather than a destination. Without it the only available check is
1018
+ * pinning the intermediary's address, which every consumer would then have to be told.
1019
+ */
1020
+ /** One non-root call in a Soroban authorization tree: what a DeFi exit's own contract (a pool,
1021
+ * router, or backstop) would itself be authorized to call on the account's behalf. */
1022
+ export declare interface SubInvocationCall {
1023
+ contract: string;
1024
+ function: string;
1025
+ /** The decoded arguments rendered for a human, in order - same rendering as the top-level
1026
+ * invocation's own `args`. */
1027
+ args: string[];
1028
+ }
1029
+
511
1030
  /** Body for `POST /v1/:network/submit`. */
512
1031
  export declare interface SubmitRequest {
513
1032
  signedXdr: string;
@@ -574,4 +1093,17 @@ export declare interface TxIntent {
574
1093
  operations: IntentOperation[];
575
1094
  }
576
1095
 
1096
+ /**
1097
+ * A position tagged with a supported protocol whose shape the adapter could not validate. Kept
1098
+ * distinct from "no position" so a caller (issue #147's gating) can surface it rather than treat
1099
+ * a parse failure as an account with nothing to unwind - the "no silent skips" invariant applied
1100
+ * to a provider field OctoPos does not formally type.
1101
+ */
1102
+ export declare interface UnrecognizedDefiPosition {
1103
+ protocol: DefiProtocol;
1104
+ /** OctoPos's raw type tag (e.g. "SUPPLY"), kept for diagnostics even though it didn't parse. */
1105
+ rawType: string;
1106
+ reason: string;
1107
+ }
1108
+
577
1109
  export { }