From 20f3d06bb6128daf75d82234e163271d0327dd47 Mon Sep 17 00:00:00 2001 From: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> Date: Thu, 20 Aug 2026 11:05:53 +0800 Subject: [PATCH] chore: update stellar docs --- .gitignore | 1 - packages/stellar-wallet-snap/README.md | 5 +- .../docs/misc/synchronization/accounts.md | 95 ++++++++++++++++ .../docs/misc/synchronization/assets.md | 65 +++++++++++ .../misc/synchronization/synchronization.md | 88 +++++++++++++++ .../docs/misc/synchronization/transaction.md | 106 ++++++++++++++++++ .../transaction/send-classic-trustline.md | 61 ++++++++++ .../docs/misc/transaction/send-native.md | 78 +++++++++++++ .../docs/misc/transaction/send-sep41.md | 64 +++++++++++ .../docs/misc/transaction/send-swap.md | 78 +++++++++++++ .../misc/transaction/submit-sequence-retry.md | 80 +++++++++++++ .../docs/use-cases/README.md | 60 ++++++++++ .../docs/use-cases/assets/assets.md | 33 ++++++ .../client-request/changeTrustOpt.md | 100 +++++++++++++++++ .../use-cases/client-request/computeFee.md | 68 +++++++++++ .../use-cases/client-request/confirmSend.md | 96 ++++++++++++++++ .../client-request/onAddressInput.md | 47 ++++++++ .../use-cases/client-request/onAmountInput.md | 77 +++++++++++++ .../client-request/signAndSendTransaction.md | 81 +++++++++++++ .../docs/use-cases/cron-job/cronjob.md | 44 ++++++++ .../cron-job/refreshConfirmationContext.md | 86 ++++++++++++++ .../docs/use-cases/cron-job/syncAccounts.md | 47 ++++++++ .../docs/use-cases/cron-job/syncAssets.md | 40 +++++++ .../use-cases/cron-job/trackTransaction.md | 72 ++++++++++++ .../docs/use-cases/keyring/keyring.md | 50 +++++++++ .../docs/use-cases/keyring/signAuthEntry.md | 58 ++++++++++ .../docs/use-cases/keyring/signMessage.md | 73 ++++++++++++ .../docs/use-cases/keyring/signTransaction.md | 67 +++++++++++ .../docs/use-cases/user-input/userInput.md | 29 +++++ 29 files changed, 1845 insertions(+), 4 deletions(-) create mode 100644 packages/stellar-wallet-snap/docs/misc/synchronization/accounts.md create mode 100644 packages/stellar-wallet-snap/docs/misc/synchronization/assets.md create mode 100644 packages/stellar-wallet-snap/docs/misc/synchronization/synchronization.md create mode 100644 packages/stellar-wallet-snap/docs/misc/synchronization/transaction.md create mode 100644 packages/stellar-wallet-snap/docs/misc/transaction/send-classic-trustline.md create mode 100644 packages/stellar-wallet-snap/docs/misc/transaction/send-native.md create mode 100644 packages/stellar-wallet-snap/docs/misc/transaction/send-sep41.md create mode 100644 packages/stellar-wallet-snap/docs/misc/transaction/send-swap.md create mode 100644 packages/stellar-wallet-snap/docs/misc/transaction/submit-sequence-retry.md create mode 100644 packages/stellar-wallet-snap/docs/use-cases/README.md create mode 100644 packages/stellar-wallet-snap/docs/use-cases/assets/assets.md create mode 100644 packages/stellar-wallet-snap/docs/use-cases/client-request/changeTrustOpt.md create mode 100644 packages/stellar-wallet-snap/docs/use-cases/client-request/computeFee.md create mode 100644 packages/stellar-wallet-snap/docs/use-cases/client-request/confirmSend.md create mode 100644 packages/stellar-wallet-snap/docs/use-cases/client-request/onAddressInput.md create mode 100644 packages/stellar-wallet-snap/docs/use-cases/client-request/onAmountInput.md create mode 100644 packages/stellar-wallet-snap/docs/use-cases/client-request/signAndSendTransaction.md create mode 100644 packages/stellar-wallet-snap/docs/use-cases/cron-job/cronjob.md create mode 100644 packages/stellar-wallet-snap/docs/use-cases/cron-job/refreshConfirmationContext.md create mode 100644 packages/stellar-wallet-snap/docs/use-cases/cron-job/syncAccounts.md create mode 100644 packages/stellar-wallet-snap/docs/use-cases/cron-job/syncAssets.md create mode 100644 packages/stellar-wallet-snap/docs/use-cases/cron-job/trackTransaction.md create mode 100644 packages/stellar-wallet-snap/docs/use-cases/keyring/keyring.md create mode 100644 packages/stellar-wallet-snap/docs/use-cases/keyring/signAuthEntry.md create mode 100644 packages/stellar-wallet-snap/docs/use-cases/keyring/signMessage.md create mode 100644 packages/stellar-wallet-snap/docs/use-cases/keyring/signTransaction.md create mode 100644 packages/stellar-wallet-snap/docs/use-cases/user-input/userInput.md diff --git a/.gitignore b/.gitignore index ca324d052..da2292f97 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,6 @@ examples/*/dist examples/*/docs packages/*/coverage packages/*/dist -packages/*/docs scripts/coverage # yarn v3 (w/o zero-install) diff --git a/packages/stellar-wallet-snap/README.md b/packages/stellar-wallet-snap/README.md index 18831bb3c..55bea1acf 100644 --- a/packages/stellar-wallet-snap/README.md +++ b/packages/stellar-wallet-snap/README.md @@ -53,9 +53,8 @@ High-level layout of `packages/snap` (nested implementation folders like `servic ## Use cases -End-to-end flows (handler → services → UI) live under [`docs/use-cases/`](./docs/use-cases/). -Background synchronization overview: [`docs/misc/synchronization/overview.md`](./docs/misc/synchronization/overview.md). -Shared transaction build / validate / send: [`docs/misc/transaction/`](./docs/misc/transaction/README.md). +End-to-end flows (handler → services → UI) live under [`docs/use-cases/`](./docs/use-cases/README.md). +Background synchronization overview: [`docs/misc/synchronization/synchronization.md`](./docs/misc/synchronization/synchronization.md). ## API examples diff --git a/packages/stellar-wallet-snap/docs/misc/synchronization/accounts.md b/packages/stellar-wallet-snap/docs/misc/synchronization/accounts.md new file mode 100644 index 000000000..dbad7efff --- /dev/null +++ b/packages/stellar-wallet-snap/docs/misc/synchronization/accounts.md @@ -0,0 +1,95 @@ +# Synchronization: accounts + +On-chain account snapshots (balances, trustlines, SEP-41 tokens) for **activated** keyring accounts. + +| | | +| ---------------- | ---------------------------------------------------------------------------------------------------------------- | +| **Service** | `[OnChainAccountSynchronizeService](../../../src/services/on-chain-account/OnChainAccountSynchronizeService.ts)` | +| **Orchestrator** | `[SynchronizeService](../../../src/services/sync/SynchronizeService.ts)` | +| **Snap state** | `[OnChainAccountRepository](../../../src/services/on-chain-account/OnChainAccountRepository.ts)` | +| **Overview** | [synchronization.md](./synchronization.md) | + +## Participants + +| Component | Path | Role | +| ---------------------------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| `SynchronizeService` | `services/sync` | Load activated pairs + SEP-41 catalog; run account + tx sync | +| `OnChainAccountSynchronizeService` | `services/on-chain-account` | Merge snapshots, persist, emit | +| `OnChainAccountService` | `services/on-chain-account` | Resolve live on-chain account | +| `NetworkService` | `services/network` | [Horizon](https://developers.stellar.org/docs/data/apis/horizon/api-reference/retrieve-an-account) account + SEP-41 balance reads | +| `SyncAccountsHandler` | `handlers/cronjob` | Cron / scheduled entry | + +## Request / response + +Triggered via `SynchronizeService.synchronize` (see [syncAccounts.md](../../use-cases/cron-job/syncAccounts.md)). Emits keyring events after snap state is persisted: + +- `AccountBalancesUpdated` +- `AccountAssetListUpdated` + +SEP-41 token balances are **not** from Horizon — they use Soroban RPC `balance(Address)` simulation. + +## Step-by-step + +1. `SynchronizeService` loads **activated** account pairs from [Horizon](https://developers.stellar.org/docs/data/apis/horizon/api-reference/retrieve-an-account); unfunded / not-yet-activated accounts are skipped. +2. Load SEP-41 asset metadata for the scope (shared with transaction sync on the same run). +3. `OnChainAccountSynchronizeService.synchronize` — batch-fetch SEP-41 token balances (best effort). +4. Load previous snap-state snapshots as merge baseline. +5. Per activated account — apply SEP-41 balances, then **merge** persisted gaps (classic tombstones + SEP-41 backfill). +6. **Compute deltas** — `#computeKeyringSyncDeltas` compares pre-sync snapshot vs merged on-chain view (visibility transitions → balance / asset-list payloads). +7. Persist snapshots atomically, then emit `AccountBalancesUpdated` and `AccountAssetListUpdated`. + +Account sync and transaction sync run **in parallel** when both are enabled on the same `synchronize` call. + +## Tombstones, merge, and deltas + +Merge and deltas are two linked steps: + +1. `#mergePersistedEntriesIntoOnChainAccount` — fill gaps so the in-memory on-chain view is complete before diffing. +2. `#computeKeyringSyncDeltas` — compare **persisted snapshot** vs **merged on-chain view** for visibility transitions: + +- newly visible → `added` (+ balance) +- no longer visible → `removed` (+ balance `0`) +- already not visible → omit (avoid flooding zeros) + +## Sequence + +```mermaid +sequenceDiagram + participant Cron as SyncAccountsHandler + participant Sync as SynchronizeService + participant OnChain as OnChainAccountSynchronizeService + participant Network as NetworkService + participant State as OnChainAccountRepository + participant MM as MetaMask controller + + Cron->>Sync: synchronize(accounts, scope) + Sync->>Network: resolve activated pairs (GET /accounts/:id) + Sync->>OnChain: synchronize(pairs, scope, sep41Assets) + OnChain->>Network: SEP-41 balance simulation + OnChain->>State: load last snapshots + loop per activated account + OnChain->>OnChain: apply SEP-41 balances + OnChain->>OnChain: merge (classic tombstones + SEP-41 backfill) + OnChain->>OnChain: computeKeyringSyncDeltas + end + OnChain->>State: saveMany + OnChain->>MM: AccountBalancesUpdated (delta balances) + OnChain->>MM: AccountAssetListUpdated (delta added/removed) +``` + +## Data source + +| Data | Source | +| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| Native + classic trustline balances | **Live on-chain** via [Horizon](https://developers.stellar.org/docs/data/apis/horizon/api-reference/retrieve-an-account) `GET /accounts/:account_id` | +| SEP-41 token balances | **Live on-chain** (Soroban simulation) | +| Persisted snapshot | **Snap state** (`OnChainAccountRepository`) | +| Keyring-facing balances / asset list | Emitted from latest snapshot (can be slightly stale until next sync) | + +## Related + +- [Horizon — Accounts](https://developers.stellar.org/docs/data/apis/horizon/api-reference/resources/accounts) +- [Horizon — Retrieve an Account](https://developers.stellar.org/docs/data/apis/horizon/api-reference/retrieve-an-account) +- [syncAccounts.md](../../use-cases/cron-job/syncAccounts.md) — cron entry and params +- [keyring.md](../../use-cases/keyring/keyring.md) — `listAccountAssets` / `getAccountBalances` read snap snapshots +- [transaction.md](./transaction.md) — transaction sync on the same run diff --git a/packages/stellar-wallet-snap/docs/misc/synchronization/assets.md b/packages/stellar-wallet-snap/docs/misc/synchronization/assets.md new file mode 100644 index 000000000..88f2e4eee --- /dev/null +++ b/packages/stellar-wallet-snap/docs/misc/synchronization/assets.md @@ -0,0 +1,65 @@ +# Synchronization: assets + +Asset metadata catalog refresh from the token API. + +| | | +| ---------------- | -------------------------------------------------------------------------------------------------- | +| **Service** | [`AssetMetadataService.synchronize`](../../../src/services/asset-metadata/AssetMetadataService.ts) | +| **Orchestrator** | [`SynchronizeService.synchronizeAssets`](../../../src/services/sync/SynchronizeService.ts) | +| **Snap state** | [`AssetMetadataRepository`](../../../src/services/asset-metadata/AssetMetadataRepository.ts) | +| **Overview** | [synchronization.md](./synchronization.md) | + +## Participants + +| Component | Path | Role | +| ---------------------- | ----------------------------------- | ----------------------------------- | +| `SyncAssetsHandler` | `handlers/cronjob` | Cron entry | +| `SynchronizeService` | `services/sync` | `synchronizeAssets(scope)` delegate | +| `AssetMetadataService` | `services/asset-metadata` | Fetch + persist catalog | +| `TokenApiClient` | `services/asset-metadata/token-api` | Token API | + +## Request / response + +Triggered by the `synchronizeAssets` cron (see [syncAssets.md](../../use-cases/cron-job/syncAssets.md)). Always uses **mainnet** scope — asset metadata is only available there, regardless of the user's selected network. + +Wire format: [SIP-29 Snap Assets API](https://metamask.github.io/SIPs/SIPS/sip-29) (lookup handlers read from the persisted catalog). + +## Step-by-step + +1. `SyncAssetsHandler` cron fires (always **mainnet** scope). +2. `SynchronizeService.synchronizeAssets(scope)` delegates to `AssetMetadataService.synchronize`. +3. Fetch full token list from the token API. +4. Persist catalog via `AssetMetadataRepository.saveMany`. +5. Failures are logged / tracked; the cron does not fail the whole Snap lifecycle. + +During account / transaction sync, `SynchronizeService` also preloads SEP-41 metadata via `fetchSep41AssetsOrSyncOnce` so transaction mapping and balance reads have catalog data without waiting for the assets cron. + +## Sequence + +```mermaid +sequenceDiagram + participant Cron as SyncAssetsHandler + participant Sync as SynchronizeService + participant Meta as AssetMetadataService + participant API as TokenApiClient + participant State as AssetMetadataRepository + + Cron->>Sync: synchronizeAssets(mainnet) + Sync->>Meta: synchronize(mainnet) + Meta->>API: fetch token list + API-->>Meta: tokens metadata + Meta->>State: saveMany +``` + +## Data source + +| Data | Source | +| ----------------------------------------- | ------------------------------------------------ | +| Asset catalog (symbol, decimals, icon, …) | **Token API** → persisted in **snap state** | +| On-demand lookup (`onAssetsLookup`) | Snap state catalog (fetch + persist missing ids) | + +## Related + +- [syncAssets.md](../../use-cases/cron-job/syncAssets.md) — cron entry +- [assets.md](../../use-cases/assets/assets.md) — `onAssets*` handlers +- [transaction.md](./transaction.md) — SEP-41 metadata used during tx mapping diff --git a/packages/stellar-wallet-snap/docs/misc/synchronization/synchronization.md b/packages/stellar-wallet-snap/docs/misc/synchronization/synchronization.md new file mode 100644 index 000000000..07fed3643 --- /dev/null +++ b/packages/stellar-wallet-snap/docs/misc/synchronization/synchronization.md @@ -0,0 +1,88 @@ +# Synchronization + +How background sync wires accounts, transactions, and asset catalog together. + +| | | +| ---------------- | -------------------------------------------------------------------------------- | +| **Orchestrator** | [`SynchronizeService`](../../../src/services/sync/SynchronizeService.ts) | +| **Accounts** | [accounts.md](./accounts.md) — on-chain snapshots (balances, trustlines, SEP-41) | +| **Transactions** | [transaction.md](./transaction.md) — Horizon history, mapping, pending reconcile | +| **Assets** | [assets.md](./assets.md) — token metadata catalog | + +## Participants + +| Component | Path | Role | +| ------------------------- | ------------------ | ------------------------------------------------------ | +| `CronjobHandler` | `handlers/cronjob` | Gate — skip if inactive / locked | +| `SyncAccountsHandler` | `handlers/cronjob` | Cron / scheduled entry for accounts + txs | +| `SyncAssetsHandler` | `handlers/cronjob` | Cron entry for asset catalog | +| `TrackTransactionHandler` | `handlers/cronjob` | Poll until terminal → then synchronize | +| `SynchronizeService` | `services/sync` | Hub — mutex, parallel account + tx sync, asset catalog | + +## Step-by-step + +### `synchronizeAccounts` run + +1. Load **activated** account pairs (skip unfunded). +2. Preload SEP-41 metadata for the scope. +3. Run **[accounts](./accounts.md)** and **[transactions](./transaction.md)** sync **in parallel**. +4. Per-task failures are logged; they do not fail the whole run. + +## Sequence + +```mermaid +sequenceDiagram + participant Sync as SynchronizeService + participant Assets as assets sync + participant Acc as accounts sync + participant Tx as transaction sync + + Sync->>Assets: preload SEP-41 metadata + Assets-->>Sync: sep41Assets + par + Sync->>Acc: synchronize + and + Sync->>Tx: synchronize + end +``` + +Component detail: [accounts](./accounts.md) · [transactions](./transaction.md) · [assets](./assets.md) + +## Skip synchronization / delay synchronization + +`SynchronizeService` uses a mutex so only **one exclusive sync** runs at a time: + +| Overlapping request | Behavior | +| ------------------------------------------ | ------------------------------------------------------------------------------------------------- | +| Same accounts already in the current run | **Skip** (common during onboarding) | +| Other accounts (e.g. after account switch) | **Delay** — schedule `synchronizeAccounts` background event (~2s) instead of waiting on the mutex | + +This avoids Snap request timeouts when syncs overlap. + +```mermaid +sequenceDiagram + participant A as Sync request A + participant B as Sync request B + participant Sync as SynchronizeService + participant Cron as delayed synchronizeAccounts + + A->>Sync: synchronize(accounts A) + Note over Sync: mutex held + B->>Sync: synchronize(accounts B) + alt B accounts already in A's run + Sync-->>B: skip + else B has other accounts + Sync->>Cron: schedule (~2s) + Note over Sync: mutex released later + Cron->>Sync: synchronize(accounts B) + end +``` + +## Related + +| Use case | Doc | +| ------------------- | ------------------------------------------------------------------- | +| Cron gate | [cronjob.md](../../use-cases/cron-job/cronjob.md) | +| Sync accounts entry | [syncAccounts.md](../../use-cases/cron-job/syncAccounts.md) | +| Sync assets entry | [syncAssets.md](../../use-cases/cron-job/syncAssets.md) | +| Track submitted tx | [trackTransaction.md](../../use-cases/cron-job/trackTransaction.md) | diff --git a/packages/stellar-wallet-snap/docs/misc/synchronization/transaction.md b/packages/stellar-wallet-snap/docs/misc/synchronization/transaction.md new file mode 100644 index 000000000..45868f302 --- /dev/null +++ b/packages/stellar-wallet-snap/docs/misc/synchronization/transaction.md @@ -0,0 +1,106 @@ +# Synchronization: transactions + +Maps [Horizon](https://developers.stellar.org/docs/data/apis/horizon/api-reference/resources/transactions) history to keyring transactions and reconciles snap-state pending txs. + +| | | +| -------------- | ----------------------------------------------------------------------------------------------------- | +| **Service** | [`TransactionSynchronizeService`](../../../src/services/transaction/TransactionSynchronizeService.ts) | +| **Mapper** | [`TransactionMapper`](../../../src/services/transaction/TransactionMapper.ts) | +| **Snap state** | [`TransactionRepository`](../../../src/services/transaction/TransactionRepository.ts) | +| **Overview** | [synchronization.md](./synchronization.md) | + +## Participants + +| Component | Path | Role | +| ------------------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| `TransactionSynchronizeService` | `services/transaction` | Scan, map, reconcile, emit | +| `TransactionMapper` | `services/transaction` | On-chain tx → keyring tx | +| `KeyringTransactionBuilder` | `services/transaction` | Build keyring tx shapes | +| `TransactionRepository` | `services/transaction` | Pending txs + scan cursors | +| `NetworkService` | `services/network` | [Horizon](https://developers.stellar.org/docs/data/apis/horizon/api-reference/resources/transactions) fetch by account / hash | + +## Step-by-step + +1. **Create context** — load pending txs from snap state, last-scan cursors, all snap-managed accounts on scope (for SEP-41 receive), SEP-41 metadata map. +2. **Scan** — paginated [account transactions](https://developers.stellar.org/docs/data/apis/horizon/api-reference/get-transactions-by-account-id) per **activated** account; map each tx; apply SEP-41 synthetic receive when eligible. +3. **Reconcile pending** — for remaining pending hashes, [fetch by hash](https://developers.stellar.org/docs/data/apis/horizon/api-reference/retrieve-a-transaction); map when found; increment reconcile attempt on 404. +4. **Save & emit** — emit `AccountTransactionsUpdated`, then persist mapped txs + remaining pending + updated cursors. + +First scan for an account uses **DESC** (newest first). Incremental scans use **ASC** from the saved cursor. + +## Sequence + +```mermaid +sequenceDiagram + participant Sync as TransactionSynchronizeService + participant Horizon as Horizon API + participant Mapper as TransactionMapper + participant State as TransactionRepository + participant MM as MetaMask controller + + Sync->>State: load pending + cursors + loop per activated account + Sync->>Horizon: GET /accounts/:id/transactions + Horizon-->>Sync: on-chain txs + Sync->>Mapper: mapTransactionSafe + Mapper-->>Sync: keyring tx (or skip) + end + loop remaining pending + Sync->>Horizon: GET /transactions/:hash + alt found + Sync->>Mapper: map with pending state + else 404 + Sync->>State: increment reconcileAttemptCount + end + end + Sync->>MM: AccountTransactionsUpdated + Sync->>State: save pending + cursors +``` + +## Transaction mapping + +### How we decide what type of activity it is + +| Activity type | When we map it as this | Extra mapping conditions | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Send | I am the sender, and the transaction only contains payment or create-account operations. | Send takes priority over swap. If there are multiple operations, we only show the first payment or create-account (recipient, asset, amount). | +| Token send (SEP-41) | I am the sender, and it is a supported SEP-41 token we recognize. | Unsupported tokens may fall back to unknown. | +| Swap | I am the sender, the transaction matches our swap pattern, and it includes a path payment that credits back to my own account. | Self-swap is not a receive. If there are multiple path payment operations, we only use the first one for from/to assets and amounts. | +| Receive | At least one operation credits my account (payment, account creation, or swap). | Self-send / self-swap are not receives. Failed receives are hidden. Dust spam is hidden (very small incoming native XLM <= 0.001 from someone else). If multiple assets are credited, we show the first unique asset only (amounts are not summed). | +| Token trust (add) | I am the sender, and every operation is adding trust for a token. | If there are multiple change-trust operations, we only show the first token. | +| Token trust (remove) | I am the sender, and every operation is removing trust for a token. | If there are multiple change-trust operations, we only show the first token. | +| Unknown | The transaction does not match any rule above, or mapping fails. | We still show it as activity rather than hiding it. | + +Notes: +General rule for multi-operation transactions: + +one on-chain transaction = one history entry. + +We do not split multiple sends, swaps, or receives in the same transaction into separate rows. + +### Fee handling (Only for transactions that made from MetaMask) + +| Activity type | Fee while pending | When settled (confirmed / failed) | +| -------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------- | +| Send | No fee shown yet. | We read the actual network fee from the settled transaction and show it in XLM. | +| Token trust (add / remove) | No fee shown yet. | Same as send - actual network fee in XLM is shown after settlement. | +| Swap | Estimated fee from the signed transaction. | Fee is replaced with the actual network fee from the settled transaction. | +| Bridge send | Estimated fee from the signed transaction. | Fee is replaced with the actual network fee from the settled transaction. | + +### Swap amounts (Only for transactions that made from MetaMask) + +| Activity type | Amounts while pending | When settled (confirmed / failed) | +| ----------------------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| Swap | Estimated amounts from the signed transaction, not the final executed amounts yet. | We re-read actual executed amounts from the on-chain result and update the from and to legs. | +| Contract-based swap | Amounts shown as `0` - final amounts not known yet. | We try to re-map from on-chain data. If that fails, we keep the pending amounts (still `0`) as best effort. | +| Cross-chain bridge send | No from/to amounts in the snap. | Handled from transaction history when available. | + +## Related + +- [Horizon — Transactions](https://developers.stellar.org/docs/data/apis/horizon/api-reference/resources/transactions) +- [Horizon — Account's Transactions](https://developers.stellar.org/docs/data/apis/horizon/api-reference/get-transactions-by-account-id) +- [Horizon — Retrieve a Transaction](https://developers.stellar.org/docs/data/apis/horizon/api-reference/retrieve-a-transaction) +- [Horizon — Pagination](https://developers.stellar.org/docs/data/apis/horizon/api-reference/structure/pagination/page-arguments) +- [syncAccounts.md](../../use-cases/cron-job/syncAccounts.md) — cron entry +- [trackTransaction.md](../../use-cases/cron-job/trackTransaction.md) — post-submit poll +- [keyring.md](../../use-cases/keyring/keyring.md) — `listAccountTransactions` = snap pending only diff --git a/packages/stellar-wallet-snap/docs/misc/transaction/send-classic-trustline.md b/packages/stellar-wallet-snap/docs/misc/transaction/send-classic-trustline.md new file mode 100644 index 000000000..0bd1afed2 --- /dev/null +++ b/packages/stellar-wallet-snap/docs/misc/transaction/send-classic-trustline.md @@ -0,0 +1,61 @@ +# Send classic trustline asset + +Payment of a **classic issued asset** (CAIP-19 classic, non-native). Destination must already hold / be able to receive the asset — the Snap does **not** create the destination account for non-native assets. + +| | | +| ----------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| **Service** | `TransactionService.createValidatedSendTransaction` → `#createValidatedClassicAssetTransfer` | +| **Builder** | `TransactionBuilder.transfer` → `#send` (`Operation.payment`) | +| **Client** | [`onAmountInput`](../../use-cases/client-request/onAmountInput.md), [`confirmSend`](../../use-cases/client-request/confirmSend.md) | +| **Submit** | [Submit & bad-sequence retry](./submit-sequence-retry.md) | + +## Build + +1. Load destination account from the network. +2. If destination is **not** activated → `AccountNotActivatedException` (classic non-native cannot use `createAccount`). +3. Fetch base inclusion fee. +4. `TransactionBuilder.transfer` with `isActivated: true`: + - Normalize amount to human-readable Stellar units (`normalizeAmount`). + - `#send` → single `Operation.payment` with `caip19ToStellarAsset(assetId)`. +5. Source sequence / account id from `OnChainAccount`. + +Trustline existence / limits on sender and receiver are enforced in **validate**, not by adding a `changeTrust` op on this path (opt-in/out is [`changeTrustOpt`](../../use-cases/client-request/changeTrustOpt.md)). + +### Cache + +**Send / submit (`confirmSend`) does not use cache** (`useCache: false`). Destination load is always fresh so the envelope is safe to sign. + +**Preflight only (`onAmountInput`)** passes `useCache: true` when building the validated send for amount checks. See [onAmountInput cache note](../../use-cases/client-request/onAmountInput.md#note-cache-usage). + +## Validate + +Local checks against the sender (and destination when known): + +- Sender has enough of the classic asset to send. +- Sender and destination trustlines allow the transfer (limit / authorization). +- Sender can cover the network fee in XLM. + +## Send + +1. `Wallet.signTransaction`. +2. `TransactionService.sendTransaction` — classic envelopes **can** use one automatic `txBadSeq` rebuild + re-sign when the tx source is this wallet account. See [submit-sequence-retry](./submit-sequence-retry.md). + +## Flow + +```mermaid +sequenceDiagram + participant Handler + participant Tx as TransactionService + participant Builder as TransactionBuilder + participant Sim as TransactionSimulator + participant Wallet + + Handler->>Tx: createValidatedSendTransaction (classic) + Tx->>Tx: load destination (must be activated) + Tx->>Builder: transfer → payment + Builder-->>Tx: unsigned payment + Tx->>Sim: validate (balance, trustline, fee) + Note over Handler: confirmSend only ↓ + Handler->>Wallet: signTransaction + Handler->>Tx: sendTransaction +``` diff --git a/packages/stellar-wallet-snap/docs/misc/transaction/send-native.md b/packages/stellar-wallet-snap/docs/misc/transaction/send-native.md new file mode 100644 index 000000000..f141343ef --- /dev/null +++ b/packages/stellar-wallet-snap/docs/misc/transaction/send-native.md @@ -0,0 +1,78 @@ +# Send native XLM + +Native asset (slip44) send has two use cases, chosen by whether the destination is already funded on-chain. + +| | | +| ----------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| **Service** | `TransactionService.createValidatedSendTransaction` → `#createValidatedClassicAssetTransfer` | +| **Builder** | `TransactionBuilder.transfer` → `#send` or `#createAccount` | +| **Client** | [`onAmountInput`](../../use-cases/client-request/onAmountInput.md), [`confirmSend`](../../use-cases/client-request/confirmSend.md) | +| **Submit** | [Submit & bad-sequence retry](./submit-sequence-retry.md) | + +Only **native / slip44** can fund a new account. Classic / SEP-41 sends to an unactivated destination fail earlier (`AccountNotActivatedException` / `InvalidAssetForCreateAccountException`). + +## Cache + +**Send / submit (`confirmSend`) does not use cache** (`useCache: false`). Destination load is always fresh so the envelope is safe to sign. + +**Preflight only (`onAmountInput`)** passes `useCache: true` when building the validated send for amount checks. See [onAmountInput cache note](../../use-cases/client-request/onAmountInput.md#note-cache-usage). + +--- + +## Use case A — destination is activated + +Send XLM to an account that already exists on the network. + +### Build + +1. Load destination → activated (`destinationAccount !== null`). +2. Fetch base inclusion fee. +3. `TransactionBuilder.transfer` → `#send` → single `Operation.payment` (native asset). +4. Amount is normalized to human-readable Stellar units; sequence from sender `OnChainAccount`. + +### Validate + +Checks spendable native balance (after reserves) and that the sender can cover the payment plus network fee. + +### Send + +Sign → `sendTransaction` ([submit-sequence-retry](./submit-sequence-retry.md)). + +--- + +## Use case B — destination is not activated + +Fund a new Stellar account by sending native XLM. The Snap builds a **`createAccount`** op instead of `payment`. + +### Build + +1. Load destination → not activated (`destinationAccount === null`). +2. Fetch base inclusion fee. +3. `TransactionBuilder.transfer` → `#createAccount` → `Operation.createAccount({ destination, startingBalance })`. +4. **`startingBalance` = the send amount** (same value the user entered / confirmed). There is no separate “funding” field. + +### Validate + +- **`startingBalance` must be ≥ 1 XLM** (minimum when not sponsoring). +- Sender must have enough spendable native to cover that starting balance plus fee. + +### Send + +Sign → `sendTransaction` ([submit-sequence-retry](./submit-sequence-retry.md)). + +--- + +## Flow + +```mermaid +flowchart TD + Start[Native send] --> Dest{Destination activated?} + + Dest -->|yes — use case A| A1[Build: payment] + A1 --> A2[Validate: spendable XLM + fee] + A2 --> Sign[sign + send] + + Dest -->|no — use case B| B1[Build: createAccount
startingBalance = send amount] + B1 --> B2[Validate: startingBalance ≥ 1 XLM
+ spendable cover] + B2 --> Sign +``` diff --git a/packages/stellar-wallet-snap/docs/misc/transaction/send-sep41.md b/packages/stellar-wallet-snap/docs/misc/transaction/send-sep41.md new file mode 100644 index 000000000..f00418f46 --- /dev/null +++ b/packages/stellar-wallet-snap/docs/misc/transaction/send-sep41.md @@ -0,0 +1,64 @@ +# Send SEP-41 + +Soroban contract token transfer (`SEP-41` CAIP-19 asset → `invokeHostFunction` calling `transfer`). + +| | | +| ----------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| **Service** | `TransactionService.createValidatedSendTransaction` → `#createValidatedSep41Transfer` | +| **Builder** | `TransactionBuilder.sep41Transfer` | +| **Client** | [`onAmountInput`](../../use-cases/client-request/onAmountInput.md), [`confirmSend`](../../use-cases/client-request/confirmSend.md) | +| **Submit** | [Submit & bad-sequence retry](./submit-sequence-retry.md) | + +## Build + +1. Destination must already be **activated**; otherwise `AccountNotActivatedException` (no `createAccount` for SEP-41). +2. Fetch base inclusion fee. +3. `TransactionBuilder.sep41Transfer`: + - Parse contract id from CAIP-19 asset reference. + - Build one `invokeHostFunction` op: `transfer(from, to, amount)` with amount in token **smallest units** (i128) — no classic decimal normalize. + - Source account + sequence come from the resolved `OnChainAccount`. +4. If the asset row is missing from the on-chain snapshot (`getRawAsset`), fetch SEP-41 balances from the network and attach a local balance. +5. Fail early if local balance < amount (`InsufficientBalanceException`). +6. Network simulation attaches Soroban resource fee / footprint (`sorobanData`). + +### Cache + +**Send / submit paths (`confirmSend`) never use cache** (`useCache: false`). Destination load and SEP-41 simulation are always fresh so the envelope is safe to sign. + +**Preflight only (`onAmountInput`)** passes `useCache: true` so repeated amount checks stay responsive (SEP-41 sim keyed by asset + sender + recipient + scope, not amount). That result must not be signed. See [onAmountInput cache note](../../use-cases/client-request/onAmountInput.md#note-cache-usage). + +## Validate + +Local checks after network simulation has attached the Soroban resource fee: + +- Sender has enough of the SEP-41 token balance. +- Sender can cover inclusion + resource fees in XLM. +- Envelope is a single contract `transfer` invoke with a consistent source/sender. + +## Send + +1. `Wallet.signTransaction`. +2. `TransactionService.sendTransaction` — see [submit-sequence-retry](./submit-sequence-retry.md). +3. Sequence-only rebuild does **not** safely preserve Soroban `sorobanData`; on `txBadSeq` for invoke envelopes the caller should re-simulate / rebuild rather than relying on a blind sequence bump. + +## Flow + +```mermaid +sequenceDiagram + participant Handler + participant Tx as TransactionService + participant Builder as TransactionBuilder + participant Net as NetworkService + participant Sim as TransactionSimulator + participant Wallet + + Handler->>Tx: createValidatedSendTransaction (SEP-41) + Tx->>Builder: sep41Transfer + Builder-->>Tx: unsigned invokeHostFunction + Tx->>Net: simulate (fresh for confirmSend) + Net-->>Tx: assembled envelope (+ resource fee) + Tx->>Sim: validate (balance + fees) + Note over Handler: confirmSend only ↓ + Handler->>Wallet: signTransaction + Handler->>Tx: sendTransaction +``` diff --git a/packages/stellar-wallet-snap/docs/misc/transaction/send-swap.md b/packages/stellar-wallet-snap/docs/misc/transaction/send-swap.md new file mode 100644 index 000000000..b7eda37a6 --- /dev/null +++ b/packages/stellar-wallet-snap/docs/misc/transaction/send-swap.md @@ -0,0 +1,78 @@ +# Send swap / bridge (from XDR) + +Swap and bridge envelopes are **not** built by `TransactionBuilder` inside the Snap. MetaMask CrossChain API supplies Base64 XDR; the Snap decodes, checks the accepted op shape, validates, then (for submit) signs and sends. + +| | | +| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Service** | `TransactionService.createValidatedSwapTransaction` | +| **Decode** | `Transaction.fromXdr` | +| **Shape gate** | `SwapTransactionXdrStruct` ([`api/xdr.ts`](../../../src/api/xdr.ts)) | +| **Client** | [`computeFee`](../../use-cases/client-request/computeFee.md), [`signAndSendTransaction`](../../use-cases/client-request/signAndSendTransaction.md) | +| **Submit** | [Submit & bad-sequence retry](./submit-sequence-retry.md) | + +## Accepted operation patterns from XDR + +`SwapTransactionXdrStruct` only checks **operation kind + order** (not balances / memos). Downstream service + simulator do the rest. + +| Ops (in order) | Meaning | +| -------------------------------------- | ---------------------------------------------------------------------------------------- | +| `[invokeHostFunction]` | Soroban swap (single contract invoke) | +| `[payment]` | Bridge deposit (single payment to deposit account) **or** swap without a separate fee op | +| `[pathPayment*]` | Classic swap without a trailing fee payment | +| `[pathPayment*, payment]` | Classic swap + route fee payment | +| `[changeTrust, pathPayment*]` | Trustline setup then swap (no fee op) | +| `[payment, payment]` | Bridge: deposit payment + fee wallet payment | +| `[changeTrust, pathPayment*, payment]` | Trustline + classic swap + fee payment | + +`pathPayment*` = `pathPaymentStrictSend` or `pathPaymentStrictReceive`. + +Anything else → rejected at the JSON-RPC struct (`Unsupported swap transaction operation shape`). + +## Build / decode + +1. Client passes the **same** XDR used for `computeFee` into `signAndSendTransaction`. +2. `Transaction.fromXdr({ xdr, scope })`. +3. `computingFee(transaction)`: + - If envelope has `invokeHostFunction` → `NetworkService.simulateTransaction` (fresh Soroban assemble / resource fee). + - Else → keep fee already on the envelope (Bridge / classic quote trusted). +4. Preload participating accounts from the network (skipped for invoke-only envelopes). + +## Validate + +Local checks on the decoded envelope (balances, trustlines, fees) for whichever ops are present — e.g. path-payment amounts, trailing fee payment, leading change-trust, or Soroban invoke after simulation. + +## Send + +Only `signAndSendTransaction`: + +1. `Wallet.signTransaction` (user consent is **client-side** — Snap shows no confirmation). +2. `sendTransaction` — see [submit-sequence-retry](./submit-sequence-retry.md). +3. Persist pending keyring tx as `Swap` (same-chain) or `BridgeSend` (cross-chain) from `options.sourceAssetId` / `destAssetId`. + +## Flow + +```mermaid +sequenceDiagram + participant Client + participant Fee as ComputeFeeHandler + participant Sign as SignAndSendTransactionHandler + participant Tx as TransactionService + participant Net as NetworkService + participant Wallet + + Client->>Fee: computeFee { xdr, scope } + Fee->>Tx: createValidatedSwapTransaction + Tx->>Tx: fromXdr + computingFee + opt Soroban invoke + Tx->>Net: simulateTransaction + end + Tx->>Tx: validate (balances / trustlines / fees) + Fee-->>Client: fee entries + + Note over Client: User approves in client UI + Client->>Sign: signAndSendTransaction { same xdr, scope, options } + Sign->>Tx: createValidatedSwapTransaction + Sign->>Wallet: signTransaction + Sign->>Tx: sendTransaction + Sign-->>Client: { transactionId } +``` diff --git a/packages/stellar-wallet-snap/docs/misc/transaction/submit-sequence-retry.md b/packages/stellar-wallet-snap/docs/misc/transaction/submit-sequence-retry.md new file mode 100644 index 000000000..e2fb74c7d --- /dev/null +++ b/packages/stellar-wallet-snap/docs/misc/transaction/submit-sequence-retry.md @@ -0,0 +1,80 @@ +# Submit & bad-sequence retry (`txBadSeq`) + +Shared on-chain **submit** path used after any flow has built (or decoded), validated, and signed an envelope. This doc is only about submission and recovering when the account **sequence is wrong or too old**. + +Suggested name vs “general on-chain send”: prefer **submit / sequence retry** — it is not a payment-type chooser; it is the last mile of every successful submit. + +| | | +| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Service** | `TransactionService.sendTransaction` | +| **Network** | `NetworkService.send` | +| **Rebuild** | `TransactionBuilder.rebuildTxnWithNewSeq` | +| **Used by** | [`confirmSend`](../../use-cases/client-request/confirmSend.md), [`changeTrustOpt`](../../use-cases/client-request/changeTrustOpt.md), [`signAndSendTransaction`](../../use-cases/client-request/signAndSendTransaction.md), … | + +## Happy path + +```text +assert scope matches envelope + → NetworkService.send(signed tx) + → optional poll for terminal SUCCESS + → return transaction hash +``` + +## When sequence is stale (`txBadSeq`) + +Stellar rejects the submit with `txBadSeq` when the envelope’s sequence does not match the source account’s current sequence (concurrent txs, race after a long confirmation dialog, etc.). + +`NetworkService.send` maps that RPC error to `TransactionRetryableException`. + +`TransactionService.sendTransaction` then: + +1. Checks the envelope **source** equals the resolved `onChainAccount.accountId` (this wallet consumes the sequence). + - If source is **another** account → rethrow (cannot bump someone else’s sequence). +2. Reloads the account from the network (`getAccount`) for a fresh `sequenceNumber`. +3. `rebuildTxnWithNewSeq` — clone ops / fee / timebounds onto a new envelope with the new sequence. +4. `wallet.signTransaction` again. +5. Submit **once** more. + +Only **one** automatic retry. + +## Soroban / `invokeHostFunction` caveat + +Sequence-only rebuild copies classic-style fields and operations; it does **not** correctly preserve assembled Soroban `sorobanData`. JSDoc on `sendTransaction` states that for invoke envelopes, `txBadSeq` should not be treated as a safe auto-retry — the caller should **re-simulate / re-assemble** (e.g. fresh quote or fresh SEP-41 sim) instead of relying on a blind sequence bump. + +## Flow + +```mermaid +sequenceDiagram + participant Handler + participant Tx as TransactionService + participant Net as NetworkService + participant Builder as TransactionBuilder + participant Wallet + + Handler->>Wallet: signTransaction (initial) + Handler->>Tx: sendTransaction + Tx->>Net: send + alt SUCCESS + Net-->>Tx: hash + Tx-->>Handler: transactionId + else txBadSeq (retryable) + Net-->>Tx: TransactionRetryableException + alt source === wallet account + Tx->>Net: getAccount (fresh sequence) + Tx->>Builder: rebuildTxnWithNewSeq + Tx->>Wallet: signTransaction + Tx->>Net: send (retry once) + Net-->>Tx: hash + Tx-->>Handler: transactionId + else source is another account + Tx-->>Handler: rethrow + end + end +``` + +## After submit (callers) + +Handlers typically then: + +- `savePendingKeyringTransactionSafe` +- `TrackTransactionHandler.scheduleBackgroundEvent` diff --git a/packages/stellar-wallet-snap/docs/use-cases/README.md b/packages/stellar-wallet-snap/docs/use-cases/README.md new file mode 100644 index 000000000..ad094cda4 --- /dev/null +++ b/packages/stellar-wallet-snap/docs/use-cases/README.md @@ -0,0 +1,60 @@ +# Use cases + +High-level flows for the Stellar Wallet Snap. Each doc focuses on **handlers**, **services**, and **UI** involved in a use case — utility modules are omitted. + +## Client request (`onClientRequest`) + +| Use case | Entry method | Doc | +| ----------------------------------- | ------------------------ | ----------------------------------------------------------------------- | +| Validate send destination | `onAddressInput` | [onAddressInput.md](./client-request/onAddressInput.md) | +| Preflight send amount | `onAmountInput` | [onAmountInput.md](./client-request/onAmountInput.md) | +| Confirm & submit send | `confirmSend` | [confirmSend.md](./client-request/confirmSend.md) | +| Quote swap / bridge fee | `computeFee` | [computeFee.md](./client-request/computeFee.md) | +| Sign & submit swap / bridge | `signAndSendTransaction` | [signAndSendTransaction.md](./client-request/signAndSendTransaction.md) | +| Change trustline (opt-in / opt-out) | `changeTrustOpt` | [changeTrustOpt.md](./client-request/changeTrustOpt.md) | + +## Cronjob (`onCronjob`) + +All methods are no-ops when MetaMask is inactive or the wallet is **locked** — see [cronjob.md](./cron-job/cronjob.md). + +| Use case | Entry method | Doc | +| --------------------------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------- | +| Cron gate (locked / inactive) | `CronjobHandler` | [cronjob.md](./cron-job/cronjob.md) | +| Sync overview (accounts + txs + assets) | `SynchronizeService` | [synchronization.md](../misc/synchronization/synchronization.md) | +| Track submitted transaction | `trackTransaction` | [trackTransaction.md](./cron-job/trackTransaction.md) · [transaction sync](./../misc/synchronization/transaction.md) | +| Sync selected / listed accounts | `synchronizeAccounts` | [syncAccounts.md](./cron-job/syncAccounts.md) · [accounts sync](./../misc/synchronization/accounts.md) | +| Sync asset metadata catalog | `synchronizeAssets` | [syncAssets.md](./cron-job/syncAssets.md) · [assets sync](./../misc/synchronization/assets.md) | +| Refresh open confirmation | `refreshConfirmationContext` | [refreshConfirmationContext.md](./cron-job/refreshConfirmationContext.md) | + +## Keyring (`onKeyringRequest`) + +Account management is summarized in one place; SEP-43 signing methods have their own docs. + +| Use case | Entry method | Doc | +| ---------------------------------------- | ----------------- | -------------------------------------------------- | +| Keyring overview (accounts, balances, …) | `KeyringHandler` | [keyring.md](./keyring/keyring.md) | +| Sign transaction (no broadcast) | `signTransaction` | [signTransaction.md](./keyring/signTransaction.md) | +| Sign message | `signMessage` | [signMessage.md](./keyring/signMessage.md) | +| Sign Soroban auth entry | `signAuthEntry` | [signAuthEntry.md](./keyring/signAuthEntry.md) | + +## Assets (`endowment:assets`) + +| Use case | Entry method | Doc | +| ----------------------------------- | --------------- | ------------------------------- | +| Assets overview (lookup, prices, …) | `AssetsHandler` | [assets.md](./assets/assets.md) | + +## User input (`onUserInput`) + +| Use case | Entry method | Doc | +| ---------------------------- | ------------------ | ----------------------------------------- | +| Interactive UI event routing | `UserInputHandler` | [userInput.md](./user-input/userInput.md) | + +## Synchronization + +Background sync flow and components: [synchronization.md](../misc/synchronization/synchronization.md). + +| Component | Doc | +| -------------------------------- | -------------------------------------------------------- | +| Accounts (balances / trustlines) | [accounts.md](../misc/synchronization/accounts.md) | +| Transactions (history / pending) | [transaction.md](../misc/synchronization/transaction.md) | +| Asset catalog | [assets.md](../misc/synchronization/assets.md) | diff --git a/packages/stellar-wallet-snap/docs/use-cases/assets/assets.md b/packages/stellar-wallet-snap/docs/use-cases/assets/assets.md new file mode 100644 index 000000000..1ba144705 --- /dev/null +++ b/packages/stellar-wallet-snap/docs/use-cases/assets/assets.md @@ -0,0 +1,33 @@ +# Use case: assets (`AssetsHandler`) + +Asset metadata and price entry points via Snap Assets API handlers → `AssetsHandler`. + +| | | +| ---------- | ----------------------------------------------------------------------------------------------------------- | +| **Entry** | `onAssetsLookup` / `onAssetsConversion` / `onAssetsMarketData` / `onAssetHistoricalPrice` → `AssetsHandler` | +| **Source** | [`handlers/asset/assets.ts`](../../../src/handlers/asset/assets.ts) | + +## Participants + +| Component | Path | Role | +| ---------------------- | ------------------------- | ------------------------------------------- | +| `AssetsHandler` | `handlers/asset` | Assets API surface | +| `AssetMetadataService` | `services/asset-metadata` | Resolve CAIP-19 asset metadata | +| `PriceService` | `services/price` | Conversions, market data, historical prices | + +## Request / response + +Asset methods follow the MetaMask **Snap Assets API** ([SIP-29](https://metamask.github.io/SIPs/SIPS/sip-29)): + +- [Entry points](https://docs.metamask.io/snaps/reference/entry-points/) (`onAssetsLookup`, `onAssetsConversion`, `onAssetsMarketData`, `onAssetHistoricalPrice`) + +This Snap implements those handlers in `index.ts` and delegates to `AssetsHandler`. Lookup is restricted to Stellar asset ids. + +## Methods + +| Method | What it does | Data source | +| ------------------------ | --------------------------------------------------- | -------------------------------------------------------------------------------- | +| `onAssetsLookup` | Metadata for requested CAIP-19 asset ids | **Snap state** catalog via `AssetMetadataService` (fetch + persist when missing) | +| `onAssetsConversion` | Conversion rates for requested asset pairs | **Price API** via `PriceService` (cached) | +| `onAssetsMarketData` | Market data for requested assets | **Price API** via `PriceService` (cached) | +| `onAssetHistoricalPrice` | Historical price intervals for a `from` → `to` pair | **Price API** via `PriceService` (cached) | diff --git a/packages/stellar-wallet-snap/docs/use-cases/client-request/changeTrustOpt.md b/packages/stellar-wallet-snap/docs/use-cases/client-request/changeTrustOpt.md new file mode 100644 index 000000000..f43277fe8 --- /dev/null +++ b/packages/stellar-wallet-snap/docs/use-cases/client-request/changeTrustOpt.md @@ -0,0 +1,100 @@ +# Use case: `changeTrustOpt` + +Add or remove a classic Stellar trustline for an asset on a managed account. + +| | | +| ----------- | --------------------------------------------------------------------------------------------------- | +| **Entry** | `onClientRequest` → `ClientRequestHandler` → `ChangeTrustOptHandler` | +| **Method** | `changeTrustOpt` (`ClientRequestMethod.ChangeTrustOpt`) | +| **Actions** | `add` (opt-in) · `delete` (opt-out, limit forced to `0`) | +| **Source** | [`handlers/clientRequest/changeTrustOpt.ts`](../../../src/handlers/clientRequest/changeTrustOpt.ts) | + +## Request / response (shape) + +**Request params** + +- `accountId` — keyring account UUID +- `assetId` — CAIP-19 classic asset +- `scope` — CAIP-2 chain ID +- `action` — `"add"` `"delete"` +- `limit` — optional; only for `"add"` (nonzero Stellar amount) + +**Response** + +- `{ status: true, transactionId }` — built, confirmed, signed, and submitted +- `{ status: true }` — opt-in already satisfied (trustline exists with limit > 0), or became redundant while the dialog was open +- `{ status: false }` — account not activated (funding prompt shown; not an RPC error) + +User rejection of the confirmation dialog throws `UserRejectedRequestError`. + +## Participants + +| Component | Path | Role in this flow | +| ----------------------------------- | --------------------------- | -------------------------------------------------------------------- | +| `ClientRequestHandler` | `handlers/clientRequest` | Routes `changeTrustOpt` to the handler | +| `ChangeTrustOptHandler` | `handlers/clientRequest` | Orchestrates the use case | +| `AccountResolver` | `handlers/` | Loads keyring account + wallet + **live** on-chain account (network) | +| `AccountService` | `services/account` | Keyring account lookup (via resolver) | +| `WalletService` / `Wallet` | `services/wallet` | Signing key material + `signTransaction` | +| `OnChainAccountService` | `services/on-chain-account` | Fetch fresh on-chain balances / trustlines | +| `AssetMetadataService` | `services/asset-metadata` | Resolve symbol / metadata for UI | +| `TransactionService` | `services/transaction` | Build + validate change-trust tx; submit; save pending keyring tx | +| `NetworkService` | `services/network` | Base fee (via `TransactionService`) | +| `ConfirmationUXController` | `ui/confirmation` | Opt-in / opt-out confirmation dialog | +| `TransactionScanService` | `services/transaction-scan` | Security scan while dialog is open | +| `RefreshConfirmationContextHandler` | `handlers/cronjob` | Re-validate tx / fees while dialog is open | +| `TrackTransactionHandler` | `handlers/cronjob` | Schedule background status tracking after submit | + +## Step-by-step + +1. **Route** — `onClientRequest` dispatches to `ChangeTrustOptHandler`. +2. **Resolve** — `AccountResolver` loads keyring account, wallet, and activated on-chain account from the **live network**. Unfunded accounts show the activation prompt and return `{ status: false }`. +3. **Short-circuit** — If `add` and a trustline with limit > 0 already exists → `{ status: true }`. If `delete` and no trustline → `TrustlineNotFoundException`. +4. **Build** — Resolve asset metadata; `TransactionService.createValidatedChangeTrustTransaction` builds a change-trust op (`delete` forces limit `"0"`). +5. **Confirm** — `ConfirmationUXController` shows opt-in or opt-out UI (fee, security scan, local re-validation cron while open). +6. **Refresh** — After confirm, account is resolved again from the live network; fee must not exceed what the user approved; redundant opt-in returns `{ status: true }` without submit. +7. **Sign & send** — `Wallet.signTransaction` → `TransactionService.sendTransaction`. +8. **Post-submit** — Persist pending keyring tx (`ChangeTrustOptIn` / `ChangeTrustOptOut`) and schedule `TrackTransactionHandler`. + +## Sequence (happy path) + +```mermaid +sequenceDiagram + participant Client + participant Handler as ChangeTrustOptHandler + participant Resolver as AccountResolver + participant Meta as AssetMetadataService + participant Tx as TransactionService + participant UI as ConfirmationUXController + participant Refresh as RefreshConfirmationContextHandler + participant Scan as TransactionScanService + participant Wallet + participant Track as TrackTransactionHandler + + Client->>Handler: changeTrustOpt (add | delete) + Handler->>Resolver: resolve activated account (live on-chain) + Resolver-->>Handler: account, wallet, onChainAccount + Handler->>Handler: trustline needed? + Handler->>Meta: resolve(assetId) + Handler->>Tx: createValidatedChangeTrustTransaction + Tx-->>Handler: Transaction + Handler->>UI: renderConfirmationDialog + UI->>Refresh: schedule background refresh + + loop While dialog open (until Client confirms / rejects) + Refresh->>Resolver: resolve on-chain account (live balances / trustlines) + Refresh->>Tx: rebuild & re-validate change-trust tx + Refresh->>Scan: security scan (refreshed envelope) + Refresh->>UI: update confirmation context + end + + Client-->>UI: confirm + UI-->>Handler: confirmed + Handler->>Resolver: resolve again (live on-chain) + Handler->>Tx: createValidatedChangeTrustTransaction + Handler->>Wallet: signTransaction + Handler->>Tx: sendTransaction + Handler->>Tx: savePendingKeyringTransactionSafe + Handler->>Track: scheduleBackgroundEvent + Handler-->>Client: { status: true, transactionId } +``` diff --git a/packages/stellar-wallet-snap/docs/use-cases/client-request/computeFee.md b/packages/stellar-wallet-snap/docs/use-cases/client-request/computeFee.md new file mode 100644 index 000000000..69a2ed9c1 --- /dev/null +++ b/packages/stellar-wallet-snap/docs/use-cases/client-request/computeFee.md @@ -0,0 +1,68 @@ +# Use case: `computeFee` + +Quotes fees for a swap / bridge envelope from MetaMask CrossChain API (same validation path as `signAndSendTransaction`). + +| | | +| ------------------------ | ------------------------------------------------------------------------------------------- | +| **Entry** | `onClientRequest` → `ClientRequestHandler` → `ComputeFeeHandler` | +| **Method** | `computeFee` (`ClientRequestMethod.ComputeFee`) | +| **Source** | [`handlers/clientRequest/computeFee.ts`](../../../src/handlers/clientRequest/computeFee.ts) | +| **Transaction pipeline** | [Swap / bridge from XDR](../../misc/transaction/send-swap.md) | + +## Client workflow + +1. After the user selects a quote, obtain the unsigned XDR from MetaMask CrossChain API. +2. Call **`computeFee`** with that XDR and `scope` so the user can review fees. +3. After approval in the client UI, call **`signAndSendTransaction`** with the **same** `transaction` XDR and `scope`. + +## Request / response (shape) + +**Request params** + +- `accountId` — keyring account UUID +- `scope` — CAIP-2 chain ID +- `transaction` — Base64-encoded swap / bridge XDR +- `options` — optional (`visible`, `type`, `feeLimit`) + +**Response** + +- Array of fee entries: `[{ type: FeeType.Base, asset: { unit, type, amount, fungible } }]` +- On insufficient native balance / fee coverage, still returns a fee entry using the **required** amount (so the client can surface the shortfall) + +No confirmation dialog; nothing is signed or submitted. + +## Participants + +| Component | Path | Role in this flow | +| ----------------------- | --------------------------- | -------------------------------------------------------------------- | +| `ClientRequestHandler` | `handlers/clientRequest` | Routes `computeFee` to the handler | +| `ComputeFeeHandler` | `handlers/clientRequest` | Orchestrates fee quoting | +| `AccountResolver` | `handlers/` | Loads keyring account + wallet + **live** on-chain account (network) | +| `AccountService` | `services/account` | Keyring account lookup (via resolver) | +| `OnChainAccountService` | `services/on-chain-account` | Balances for validation / simulation | +| `TransactionService` | `services/transaction` | Decode, validate, simulate swap XDR; read `totalFee` | +| `NetworkService` | `services/network` | Fees / simulation network reads (via `TransactionService`) | + +## Step-by-step + +1. **Route** — `onClientRequest` dispatches to `ComputeFeeHandler`. +2. **Resolve** — `AccountResolver` loads keyring account, wallet, and activated on-chain account from the **live network**. +3. **Validate** — `TransactionService.createValidatedSwapTransaction` (same path as sign-and-send, including Soroban simulation when needed). +4. **Return** — Base fee as display amount in native XLM; map balance/fee shortfalls to a required-amount fee entry. + +## Sequence (happy path) + +```mermaid +sequenceDiagram + participant Client + participant Handler as ComputeFeeHandler + participant Resolver as AccountResolver + participant Tx as TransactionService + + Client->>Handler: computeFee { accountId, scope, transaction } + Handler->>Resolver: resolve activated account (live on-chain) + Resolver-->>Handler: account, wallet, onChainAccount + Handler->>Tx: createValidatedSwapTransaction + Tx-->>Handler: Transaction (totalFee) + Handler-->>Client: [{ type: Base, asset: XLM fee }] +``` diff --git a/packages/stellar-wallet-snap/docs/use-cases/client-request/confirmSend.md b/packages/stellar-wallet-snap/docs/use-cases/client-request/confirmSend.md new file mode 100644 index 000000000..26a1010a0 --- /dev/null +++ b/packages/stellar-wallet-snap/docs/use-cases/client-request/confirmSend.md @@ -0,0 +1,96 @@ +# Use case: `confirmSend` + +Confirms and submits a send for Unified Non-EVM Send (live on-chain data at build and submit time). + +| | | +| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Entry** | `onClientRequest` → `ClientRequestHandler` → `ConfirmSendHandler` | +| **Method** | `confirmSend` (`ClientRequestMethod.ConfirmSend`) | +| **Source** | [`handlers/clientRequest/confirmSend.ts`](../../../src/handlers/clientRequest/confirmSend.ts) | +| **Transaction pipeline** | [SEP-41](../../misc/transaction/send-sep41.md) · [classic](../../misc/transaction/send-classic-trustline.md) · [native](../../misc/transaction/send-native.md) | + +## Request / response (shape) + +**Request params** + +- `fromAccountId` — keyring account UUID (coerced to `accountId` internally) +- `toAddress` — Stellar destination +- `assetId` — CAIP-19 classic / SEP-41 / slip44 (`scope` derived from `assetId`) +- `amount` — human-readable amount string + +**Response** + +- `{ valid: true, errors: [], transactionId }` — confirmed, signed, and submitted +- `{ valid: false, errors: [{ code }] }` — `Invalid` · `InsufficientBalance` · `InsufficientBalanceToCoverFee` + +User rejection of the confirmation dialog throws `UserRejectedRequestError`. Unactivated accounts return `{ valid: false, errors: [{ code: "Invalid" }] }` (no activation prompt). + +## Participants + +| Component | Path | Role in this flow | +| ----------------------------------- | --------------------------- | ---------------------------------------------------------- | +| `ClientRequestHandler` | `handlers/clientRequest` | Routes `confirmSend` to the handler | +| `ConfirmSendHandler` | `handlers/clientRequest` | Orchestrates the use case | +| `AccountResolver` | `handlers/` | Loads keyring account + wallet + **live** on-chain account | +| `AccountService` | `services/account` | Keyring account lookup (via resolver) | +| `WalletService` / `Wallet` | `services/wallet` | Signing key material + `signTransaction` | +| `OnChainAccountService` | `services/on-chain-account` | Fresh balances / sequence | +| `AssetMetadataService` | `services/asset-metadata` | Decimals, symbol, metadata for UI | +| `TransactionService` | `services/transaction` | Build + validate send; submit; save pending keyring tx | +| `NetworkService` | `services/network` | Base fee / network reads (via `TransactionService`) | +| `ConfirmationUXController` | `ui/confirmation` | Send confirmation dialog | +| `TransactionScanService` | `services/transaction-scan` | Security scan while dialog is open | +| `RefreshConfirmationContextHandler` | `handlers/cronjob` | Refresh balances, tx, and scan while dialog is open | +| `TrackTransactionHandler` | `handlers/cronjob` | Schedule background status tracking after submit | + +## Step-by-step + +1. **Route** — `onClientRequest` dispatches to `ConfirmSendHandler`. +2. **Resolve** — `AccountResolver` loads keyring account, wallet, and activated on-chain account from the **live network**. +3. **Build** — Resolve asset metadata; convert amount; `TransactionService.createValidatedSendTransaction`. +4. **Confirm** — `ConfirmationUXController` shows send UI (fee, estimated changes, security scan, local re-validation cron while open). +5. **Refresh** — After confirm, account is resolved again from the live network; fee must not exceed what the user approved. +6. **Sign & send** — `Wallet.signTransaction` → `TransactionService.sendTransaction`. +7. **Post-submit** — Persist pending keyring tx (`Send`) and schedule `TrackTransactionHandler` for sender + destination. + +## Sequence (happy path) + +```mermaid +sequenceDiagram + participant Client + participant Handler as ConfirmSendHandler + participant Resolver as AccountResolver + participant Meta as AssetMetadataService + participant Tx as TransactionService + participant UI as ConfirmationUXController + participant Refresh as RefreshConfirmationContextHandler + participant Scan as TransactionScanService + participant Wallet + participant Track as TrackTransactionHandler + + Client->>Handler: confirmSend { fromAccountId, toAddress, assetId, amount } + Handler->>Resolver: resolve activated account (live on-chain) + Resolver-->>Handler: account, wallet, onChainAccount + Handler->>Meta: resolve(assetId) + Handler->>Tx: createValidatedSendTransaction + Tx-->>Handler: Transaction + Handler->>UI: renderConfirmationDialog + UI->>Refresh: schedule background refresh + + loop While dialog open (until Client confirms / rejects) + Refresh->>Resolver: resolve on-chain account (live balances) + Refresh->>Tx: rebuild & re-validate send tx + Refresh->>Scan: security scan (refreshed envelope) + Refresh->>UI: update confirmation context + end + + Client-->>UI: confirm + UI-->>Handler: confirmed + Handler->>Resolver: resolve again (live on-chain) + Handler->>Tx: createValidatedSendTransaction + Handler->>Wallet: signTransaction + Handler->>Tx: sendTransaction + Handler->>Tx: savePendingKeyringTransactionSafe + Handler->>Track: scheduleBackgroundEvent + Handler-->>Client: { valid: true, errors: [], transactionId } +``` diff --git a/packages/stellar-wallet-snap/docs/use-cases/client-request/onAddressInput.md b/packages/stellar-wallet-snap/docs/use-cases/client-request/onAddressInput.md new file mode 100644 index 000000000..8b55cf952 --- /dev/null +++ b/packages/stellar-wallet-snap/docs/use-cases/client-request/onAddressInput.md @@ -0,0 +1,47 @@ +# Use case: `onAddressInput` + +Validates a Stellar destination address for Unified Non-EVM Send (as the user types). + +| | | +| ---------- | --------------------------------------------------------------------------------------------------- | +| **Entry** | `onClientRequest` → `ClientRequestHandler` → `OnAddressInputHandler` | +| **Method** | `onAddressInput` (`ClientRequestMethod.OnAddressInput`) | +| **Source** | [`handlers/clientRequest/onAddressInput.ts`](../../../src/handlers/clientRequest/onAddressInput.ts) | + +## Request / response (shape) + +**Request params** + +- `value` — Stellar address string to validate + +**Response** + +- `{ valid: true, errors: [] }` — address is a valid Stellar address +- `{ valid: false, errors: [{ code: "Invalid" }] }` — validation failed + +No account resolution, confirmation UI, or network calls. + +## Participants + +| Component | Path | Role in this flow | +| ----------------------- | ------------------------ | -------------------------------------- | +| `ClientRequestHandler` | `handlers/clientRequest` | Routes `onAddressInput` to the handler | +| `OnAddressInputHandler` | `handlers/clientRequest` | Validates params via Superstruct | + +## Step-by-step + +1. **Route** — `onClientRequest` dispatches to `OnAddressInputHandler`. +2. **Validate** — Request is checked against `OnAddressInputJsonRpcRequestStruct` (`value` must be a Stellar address). +3. **Return** — `{ valid: true }` on success, or `{ valid: false, errors: [{ code: "Invalid" }] }` when params fail. + +## Sequence (happy path) + +```mermaid +sequenceDiagram + participant Client + participant Handler as OnAddressInputHandler + + Client->>Handler: onAddressInput { value } + Handler->>Handler: validate Stellar address + Handler-->>Client: { valid: true, errors: [] } +``` diff --git a/packages/stellar-wallet-snap/docs/use-cases/client-request/onAmountInput.md b/packages/stellar-wallet-snap/docs/use-cases/client-request/onAmountInput.md new file mode 100644 index 000000000..eb3a757cc --- /dev/null +++ b/packages/stellar-wallet-snap/docs/use-cases/client-request/onAmountInput.md @@ -0,0 +1,77 @@ +# Use case: `onAmountInput` + +Preflight-validates a send amount while the user types (balance and fee checks only; nothing is signed or submitted). + +| | | +| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Entry** | `onClientRequest` → `ClientRequestHandler` → `OnAmountInputHandler` | +| **Method** | `onAmountInput` (`ClientRequestMethod.OnAmountInput`) | +| **Source** | [`handlers/clientRequest/onAmountInput.ts`](../../../src/handlers/clientRequest/onAmountInput.ts) | +| **Transaction pipeline** | [SEP-41](../../misc/transaction/send-sep41.md) · [classic](../../misc/transaction/send-classic-trustline.md) · [native](../../misc/transaction/send-native.md) | + +## Request / response (shape) + +**Request params** + +- `accountId` — keyring account UUID +- `assetId` — CAIP-19 classic / SEP-41 / slip44 asset (`scope` is derived from `assetId`) +- `value` — positive amount string (human-readable units) +- `to` — optional Stellar destination; omitted → self-transfer validation + +**Response** + +- `{ valid: true, errors: [] }` — amount can fund a send (incl. fee) +- `{ valid: false, errors: [{ code }] }` — `Invalid` · `InsufficientBalance` · `InsufficientBalanceToCoverFee` + +Unactivated accounts return `{ valid: false, errors: [{ code: "Invalid" }] }` (no activation prompt). + +## Participants + +| Component | Path | Role in this flow | +| ----------------------- | --------------------------- | --------------------------------------------------------- | +| `ClientRequestHandler` | `handlers/clientRequest` | Routes `onAmountInput` to the handler | +| `OnAmountInputHandler` | `handlers/clientRequest` | Orchestrates preflight validation | +| `AccountResolver` | `handlers/` | Loads account + wallet + on-chain snapshot from **state** | +| `AccountService` | `services/account` | Keyring account lookup (via resolver) | +| `OnChainAccountService` | `services/on-chain-account` | Persisted on-chain balances (via resolver) | +| `AssetMetadataService` | `services/asset-metadata` | Decimals for amount conversion | +| `TransactionService` | `services/transaction` | Build + validate send tx (`useCache: true`) | + +## Step-by-step + +1. **Route** — `onClientRequest` dispatches to `OnAmountInputHandler`. +2. **Resolve** — `AccountResolver` loads keyring account, wallet, and on-chain account from snap state. +3. **Convert** — Resolve asset metadata; convert `value` to smallest units; reject if sub-unit decimals remain. +4. **Preflight** — `TransactionService.createValidatedSendTransaction` with cached network reads (destination defaults to sender). +5. **Return** — Structured validation result; expected balance/fee failures are returned as error codes (not thrown). + +## Note: cache usage + +`onAmountInput` is the **only** client send path that passes `useCache: true`. Real send/submit (`confirmSend`) always uses `useCache: false` (fresh destination load + fresh SEP-41 simulation). + +For **SEP-41**, that preflight cache matters most: + +- Fee estimation needs an on-chain **simulation**. Without caching, every keystroke would hit RPC. +- Simulation is reused and keyed by **asset, sender, recipient, and scope** — **not** by amount. Cached XDR may carry a **stale amount or sequence** — never sign it. +- Balance is still checked **locally** before simulation, so insufficient funds fail fast. + +Classic / native preflight still uses `useCache: true` for destination-account / network reads so typing stays responsive; submit paths do not. + +## Sequence (happy path) + +```mermaid +sequenceDiagram + participant Client + participant Handler as OnAmountInputHandler + participant Resolver as AccountResolver + participant Meta as AssetMetadataService + participant Tx as TransactionService + + Client->>Handler: onAmountInput { accountId, assetId, value, to? } + Handler->>Resolver: resolve from keyring state + Resolver-->>Handler: account, wallet, onChainAccount + Handler->>Meta: resolve(assetId) + Handler->>Tx: createValidatedSendTransaction (useCache) + Tx-->>Handler: ok + Handler-->>Client: { valid: true, errors: [] } +``` diff --git a/packages/stellar-wallet-snap/docs/use-cases/client-request/signAndSendTransaction.md b/packages/stellar-wallet-snap/docs/use-cases/client-request/signAndSendTransaction.md new file mode 100644 index 000000000..e7abcd6e3 --- /dev/null +++ b/packages/stellar-wallet-snap/docs/use-cases/client-request/signAndSendTransaction.md @@ -0,0 +1,81 @@ +# Use case: `signAndSendTransaction` + +Signs and submits a swap / bridge envelope previously quoted via `computeFee`. **No Snap confirmation dialog** — the caller must obtain user consent first. + +| | | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------- | +| **Entry** | `onClientRequest` → `ClientRequestHandler` → `SignAndSendTransactionHandler` | +| **Method** | `signAndSendTransaction` (`ClientRequestMethod.SignAndSendTransaction`) | +| **Source** | [`handlers/clientRequest/signAndSendTransaction.ts`](../../../src/handlers/clientRequest/signAndSendTransaction.ts) | +| **Transaction pipeline** | [Swap / bridge from XDR](../../misc/transaction/send-swap.md) | + +## Client workflow + +1. Quote fees with **`computeFee`** using the CrossChain API XDR. +2. Obtain explicit user approval in the **client** UI. +3. Call **`signAndSendTransaction`** with the **same** `transaction` XDR, `scope`, and swap asset options. + +## Request / response (shape) + +**Request params** + +- `accountId` — keyring account UUID +- `scope` — CAIP-2 chain ID +- `transaction` — Base64-encoded swap / bridge XDR (same as `computeFee`) +- `options.sourceAssetId` — Stellar source asset (CAIP-19 / slip44) +- `options.destAssetId` — destination asset (CAIP-19; may be another chain for bridges) +- `options.visible` / `options.type` — optional + +**Response** + +- `{ transactionId }` — submitted transaction hash + +## Security note + +This handler does **not** show a Snap confirmation. The client is responsible for displaying details and obtaining approval before calling. Signing without caller-side consent is a critical vulnerability. + +## Participants + +| Component | Path | Role in this flow | +| ------------------------------- | --------------------------- | -------------------------------------------------------------------- | +| `ClientRequestHandler` | `handlers/clientRequest` | Routes `signAndSendTransaction` to the handler | +| `SignAndSendTransactionHandler` | `handlers/clientRequest` | Orchestrates validate → sign → submit | +| `AccountResolver` | `handlers/` | Loads keyring account + wallet + **live** on-chain account (network) | +| `AccountService` | `services/account` | Keyring account lookup (via resolver) | +| `WalletService` / `Wallet` | `services/wallet` | `signTransaction` | +| `OnChainAccountService` | `services/on-chain-account` | Balances / sequence for validation | +| `AssetMetadataService` | `services/asset-metadata` | Same-chain swap asset labels for pending tx | +| `TransactionService` | `services/transaction` | Validate swap XDR; submit; save pending keyring tx | +| `TrackTransactionHandler` | `handlers/cronjob` | Schedule background status tracking after submit | + +## Step-by-step + +1. **Route** — `onClientRequest` dispatches to `SignAndSendTransactionHandler`. +2. **Resolve** — `AccountResolver` loads keyring account, wallet, and activated on-chain account from the **live network**. +3. **Validate** — `TransactionService.createValidatedSwapTransaction` on the XDR. +4. **Sign & send** — `Wallet.signTransaction` → `TransactionService.sendTransaction`. +5. **Post-submit** — Persist pending keyring tx (`Swap` for same-chain, `BridgeSend` for cross-chain) and schedule `TrackTransactionHandler`. + +## Sequence (happy path) + +```mermaid +sequenceDiagram + participant Client + participant Handler as SignAndSendTransactionHandler + participant Resolver as AccountResolver + participant Tx as TransactionService + participant Wallet + participant Track as TrackTransactionHandler + + Note over Client: User already approved in client UI + Client->>Handler: signAndSendTransaction { accountId, scope, transaction, options } + Handler->>Resolver: resolve activated account (live on-chain) + Resolver-->>Handler: account, wallet, onChainAccount + Handler->>Tx: createValidatedSwapTransaction + Tx-->>Handler: Transaction + Handler->>Wallet: signTransaction + Handler->>Tx: sendTransaction + Handler->>Tx: savePendingKeyringTransactionSafe (Swap | BridgeSend) + Handler->>Track: scheduleBackgroundEvent + Handler-->>Client: { transactionId } +``` diff --git a/packages/stellar-wallet-snap/docs/use-cases/cron-job/cronjob.md b/packages/stellar-wallet-snap/docs/use-cases/cron-job/cronjob.md new file mode 100644 index 000000000..2cdf221c7 --- /dev/null +++ b/packages/stellar-wallet-snap/docs/use-cases/cron-job/cronjob.md @@ -0,0 +1,44 @@ +# Use case: cronjob gate (`CronjobHandler`) + +All background events enter through `onCronjob` → `CronjobHandler`, which **skips work when MetaMask is inactive or the wallet is locked**. + +| | | +| ---------- | ------------------------------------------------------------------------- | +| **Entry** | `onCronjob` → `CronjobHandler` | +| **Source** | [`handlers/cronjob/cronjob.ts`](../../../src/handlers/cronjob/cronjob.ts) | + +## Behavior + +1. Call `getClientStatus()` → `{ active, locked }`. +2. If **`!active` or `locked`** → return immediately (no method dispatch, no side effects). +3. Otherwise validate `request.method` against `BackgroundEventMethod` and route to the matching handler. + +This gate applies to **every** cron method (`trackTransaction`, `synchronizeAccounts`, `synchronizeAssets`, `refreshConfirmationContext`). Individual handlers do not re-check lock state. + +## Cronjob handlers + +| Cronjob handlers | Doc | +| ---------------------------- | ---------------------------------------------------------------- | +| `trackTransaction` | [trackTransaction.md](./trackTransaction.md) | +| `synchronizeAccounts` | [syncAccounts.md](./syncAccounts.md) | +| `synchronizeAssets` | [syncAssets.md](./syncAssets.md) | +| `refreshConfirmationContext` | [refreshConfirmationContext.md](./refreshConfirmationContext.md) | + +## Sequence + +```mermaid +sequenceDiagram + participant Snap as onCronjob + participant Cron as CronjobHandler + participant Status as getClientStatus + participant Method as Cronjob handler + + Snap->>Cron: background event request + Cron->>Status: active? locked? + alt not active or locked + Status-->>Cron: skip + Cron-->>Snap: return (no-op) + else unlocked and active + Cron->>Method: handle(request) + end +``` diff --git a/packages/stellar-wallet-snap/docs/use-cases/cron-job/refreshConfirmationContext.md b/packages/stellar-wallet-snap/docs/use-cases/cron-job/refreshConfirmationContext.md new file mode 100644 index 000000000..92a66ad30 --- /dev/null +++ b/packages/stellar-wallet-snap/docs/use-cases/cron-job/refreshConfirmationContext.md @@ -0,0 +1,86 @@ +# Use case: `refreshConfirmationContext` + +While a confirmation dialog is open, periodically refresh prices, security scan, and/or rebuild the pending transaction against live on-chain state. + +| | | +| ---------- | ----------------------------------------------------------------------------------------------------------- | +| **Entry** | `onCronjob` → `CronjobHandler` → `RefreshConfirmationContextHandler` | +| **Method** | `refreshConfirmationContext` (`BackgroundEventMethod.RefreshConfirmationContext`) | +| **Source** | [`handlers/cronjob/refreshConfirmationContext/`](../../../src/handlers/cronjob/refreshConfirmationContext/) | +| **Gate** | Skipped when wallet locked / inactive — see [cronjob.md](./cronjob.md) | + +Scheduled by `ConfirmationUXController` when a dialog opens with pricing, security scanning, and/or local simulation enabled (e.g. [`confirmSend`](../client-request/confirmSend.md), [`changeTrustOpt`](../client-request/changeTrustOpt.md)). + +## Request params + +- `interfaceId` — Snap UI interface id +- `interfaceKey` — which confirmation view +- `scope` — CAIP-2 chain ID +- `refresherKeys` — which slices to run this cycle: `Prices` · `Scan` · `Transaction` + +## Participants + +| Component | Path | Role | +| ----------------------------------- | ------------------------------------------- | ----------------------------------------------------- | +| `RefreshConfirmationContextHandler` | `handlers/cronjob` | Orchestrate refreshers, re-render, reschedule | +| `ConfirmationPriceRefresher` | `handlers/cronjob/.../priceRefresher` | Spot prices via `PriceService` | +| `ConfirmationScanRefresher` | `handlers/cronjob/.../scanRefresher` | Blockaid / security scan via `TransactionScanService` | +| `ConfirmationTransactionRefresher` | `handlers/cronjob/.../transactionRefresher` | Rebuild + re-validate pending tx (live account) | +| `ConfirmationUXController` | `ui/confirmation` | Apply patched context to the open dialog | + +## Refreshers + +### Prices + +Fetches / updates token spot prices shown on the confirmation (fee asset, send asset, etc.). Requests reschedule while pricing is still needed and not in a terminal error state. + +### Security scan + +Runs (or refreshes) the remote security scan on the current transaction envelope in context. Uses the **latest** envelope when the transaction refresher has already patched it this cycle. + +### Transaction rebuild + +Runs **first** when enabled: + +1. Resolve live on-chain account. +2. Rebuild the pending send / change-trust envelope (fresh fee, sequence, time bounds). +3. Re-validate locally; update fee / validation status in context. +4. Write the rebuilt XDR into the security-scan request so the scan refresher does not scan a stale snapshot. + +## Step-by-step (one cycle) + +1. Resolve enabled refreshers from `refresherKeys`. +2. Load interface context; if the dialog was dismissed → stop (no reschedule). +3. Run **transaction** refresher alone (if selected), merge its patch. +4. Run **prices** and **scan** in parallel on the updated context. +5. Merge patches → `ConfirmationUXController.updateConfirmation`. +6. If any refresher asks to **reschedule** → schedule the next `refreshConfirmationContext` event. + +## Sequence + +```mermaid +sequenceDiagram + participant Cron as RefreshConfirmationContextHandler + participant TxR as Transaction refresher + participant Price as Price refresher + participant Scan as Scan refresher + participant UI as ConfirmationUXController + + Cron->>Cron: load interface context + alt dialog gone + Cron-->>Cron: stop + else still open + opt Transaction in refresherKeys + Cron->>TxR: rebuild + validate (live) + TxR-->>Cron: patch (xdr, fee, status) + end + par + Cron->>Price: refresh spot prices + Cron->>Scan: security scan (latest xdr) + end + Cron->>UI: updateConfirmation + opt any reschedule + Cron->>Cron: schedule next cycle + end + end +``` diff --git a/packages/stellar-wallet-snap/docs/use-cases/cron-job/syncAccounts.md b/packages/stellar-wallet-snap/docs/use-cases/cron-job/syncAccounts.md new file mode 100644 index 000000000..93d3aa874 --- /dev/null +++ b/packages/stellar-wallet-snap/docs/use-cases/cron-job/syncAccounts.md @@ -0,0 +1,47 @@ +# Use case: `synchronizeAccounts` + +Periodically (or on demand) refresh on-chain account state and transaction history for keyring accounts. + +| | | +| ------------------- | ----------------------------------------------------------------------------------- | +| **Entry** | `onCronjob` → `CronjobHandler` → `SyncAccountsHandler` | +| **Method** | `synchronizeAccounts` (`BackgroundEventMethod.SynchronizeAccounts`) | +| **Source** | [`handlers/cronjob/syncAccounts.ts`](../../../src/handlers/cronjob/syncAccounts.ts) | +| **Synchronization** | [synchronization](../../misc/synchronization/synchronization.md) | +| **Gate** | Skipped when wallet locked / inactive — see [cronjob.md](./cronjob.md) | + +Also schedulable via `SyncAccountsHandler.scheduleBackgroundEvent` (e.g. after account changes). Declarative cron may omit params → treat as **selected** accounts. + +## Request params + +- `accountIds` — optional + - omitted / `'selected'` → all **selected** keyring accounts + - UUID array → those account ids only + +Scope comes from `AppConfig.selectedNetwork`. + +## Participants + +| Component | Path | Role | +| --------------------- | ------------------ | ---------------------------------------------------------- | +| `SyncAccountsHandler` | `handlers/cronjob` | Resolve which accounts to sync | +| `AccountService` | `services/account` | `getAllSelected` / `findByIds` | +| `SynchronizeService` | `services/sync` | Sync balances, trustlines, transactions for those accounts | + +## Step-by-step + +1. Resolve account list (`selected` vs explicit ids). +2. `SynchronizeService.synchronize(accounts, { scope })` — updates on-chain snapshots and related history for the configured network. + +## Sequence + +```mermaid +sequenceDiagram + participant Cron as SyncAccountsHandler + participant Acc as AccountService + participant Sync as SynchronizeService + + Cron->>Acc: getAllSelected() or findByIds(...) + Acc-->>Cron: accounts + Cron->>Sync: synchronize(accounts, selectedNetwork) +``` diff --git a/packages/stellar-wallet-snap/docs/use-cases/cron-job/syncAssets.md b/packages/stellar-wallet-snap/docs/use-cases/cron-job/syncAssets.md new file mode 100644 index 000000000..9f6be3b65 --- /dev/null +++ b/packages/stellar-wallet-snap/docs/use-cases/cron-job/syncAssets.md @@ -0,0 +1,40 @@ +# Use case: `synchronizeAssets` + +Refresh the Snap’s asset metadata catalog from the token API (mainnet only). + +| | | +| ------------------- | ------------------------------------------------------------------------------- | +| **Entry** | `onCronjob` → `CronjobHandler` → `SyncAssetsHandler` | +| **Method** | `synchronizeAssets` (`BackgroundEventMethod.SynchronizeAssets`) | +| **Source** | [`handlers/cronjob/syncAssets.ts`](../../../src/handlers/cronjob/syncAssets.ts) | +| **Synchronization** | [synchronization](../../misc/synchronization/synchronization.md) | +| **Gate** | Skipped when wallet locked / inactive — see [cronjob.md](./cronjob.md) | + +Declarative cron in `snap.manifest.json` (no params required). + +## Participants + +| Component | Path | Role | +| ---------------------- | ------------------------- | ---------------------------- | +| `SyncAssetsHandler` | `handlers/cronjob` | Cron entry | +| `SynchronizeService` | `services/sync` | `synchronizeAssets(scope)` | +| `AssetMetadataService` | `services/asset-metadata` | Pull / persist asset catalog | + +## Step-by-step + +1. Always use **mainnet** scope (`KnownCaip2ChainId.Mainnet`) — asset metadata is only available there, regardless of the user’s selected network. +2. `SynchronizeService.synchronizeAssets` → `AssetMetadataService.synchronize`. +3. Failures are logged / tracked; the cron does not throw through to fail the whole Snap lifecycle aggressively. + +## Sequence + +```mermaid +sequenceDiagram + participant Cron as SyncAssetsHandler + participant Sync as SynchronizeService + participant Meta as AssetMetadataService + + Cron->>Sync: synchronizeAssets(mainnet) + Sync->>Meta: synchronize(mainnet) + Meta-->>Sync: catalog updated +``` diff --git a/packages/stellar-wallet-snap/docs/use-cases/cron-job/trackTransaction.md b/packages/stellar-wallet-snap/docs/use-cases/cron-job/trackTransaction.md new file mode 100644 index 000000000..2706bb10b --- /dev/null +++ b/packages/stellar-wallet-snap/docs/use-cases/cron-job/trackTransaction.md @@ -0,0 +1,72 @@ +# Use case: `trackTransaction` + +After a tx is submitted, keep polling Horizon until it shows up, then refresh the affected accounts’ balances / history. + +| | | +| ------------------- | ------------------------------------------------------------------------------------------- | +| **Entry** | `onCronjob` → `CronjobHandler` → `TrackTransactionHandler` | +| **Method** | `trackTransaction` (`BackgroundEventMethod.TrackTransaction`) | +| **Source** | [`handlers/cronjob/trackTransaction.ts`](../../../src/handlers/cronjob/trackTransaction.ts) | +| **Synchronization** | [synchronization](../../misc/synchronization/synchronization.md) | +| **Gate** | Skipped when wallet locked / inactive — see [cronjob.md](./cronjob.md) | + +Scheduled right after submit by [`confirmSend`](../client-request/confirmSend.md), [`changeTrustOpt`](../client-request/changeTrustOpt.md), [`signAndSendTransaction`](../client-request/signAndSendTransaction.md). + +## Request params + +- `txId` — Stellar transaction hash +- `scope` — CAIP-2 chain ID +- `accountIdsOrAddresses` — `[senderAccountUuid]` or `[senderAccountUuid, receiverAddress]` +- `attempt` — optional reschedule counter (default `0`) + +## What Horizon returns + +| Outcome | Meaning | +| ------------------- | ------------------------------------------------- | +| **404 / not found** | Not indexed yet (or unknown hash) | +| **Found + success** | On-chain succeeded → keyring status **Confirmed** | +| **Found + fail** | On-chain failed → keyring status **Failed** | + +Both found outcomes are **terminal** — settlement is done; sync accounts. + +## Participants + +| Component | Path | Role | +| ------------------------- | ------------------ | --------------------------------------------------- | +| `TrackTransactionHandler` | `handlers/cronjob` | Poll Horizon, reschedule, sync on settle | +| `NetworkService` | `services/network` | `getTransaction` from Horizon | +| `AccountService` | `services/account` | Resolve sender / optional receiver keyring accounts | +| `SynchronizeService` | `services/sync` | Refresh balances / history after settle | + +## Step-by-step + +1. Ask Horizon for `txId`. +2. **404 / not found** or **network error**: + - If `attempt < trackTransactionMaxReschedules` → reschedule another poll (~2s, `attempt++`). + - If **max attempts reached** → **skip** (stop tracking this tx; no further reschedule). Periodic `synchronizeAccounts` may still pick it up later. +3. **Found (confirmed or failed)** → load sender (and receiver if it is in the keyring) → `SynchronizeService.synchronize`. +4. Any other unexpected error → log / track; skip further tracking for this run. + +## Sequence + +```mermaid +sequenceDiagram + participant Cron as TrackTransactionHandler + participant Net as NetworkService + participant Acc as AccountService + participant Sync as SynchronizeService + + Cron->>Net: getTransaction(txId, scope) + alt 404 / network error + Net-->>Cron: not found / network error + alt attempt < max + Cron->>Cron: reschedule poll (attempt++) + else attempt >= max + Cron->>Cron: Skip — stop tracking + end + else completed + Net-->>Cron: transaction + Cron->>Acc: find sender (+ optional receiver) + Cron->>Sync: synchronize(accounts) + end +``` diff --git a/packages/stellar-wallet-snap/docs/use-cases/keyring/keyring.md b/packages/stellar-wallet-snap/docs/use-cases/keyring/keyring.md new file mode 100644 index 000000000..a2bbd1abd --- /dev/null +++ b/packages/stellar-wallet-snap/docs/use-cases/keyring/keyring.md @@ -0,0 +1,50 @@ +# Use case: keyring (`KeyringHandler`) + +Account management and SEP-43 signing entry points via `onKeyringRequest` → `KeyringHandler`. + +| | | +| ---------- | ------------------------------------------------------------------------- | +| **Entry** | `onKeyringRequest` → `KeyringHandler` | +| **Source** | [`handlers/keyring/keyring.ts`](../../../src/handlers/keyring/keyring.ts) | + +## Participants + +| Component | Path | Role | +| ----------------------- | --------------------------- | ----------------------------------------------------------------------- | +| `KeyringHandler` | `handlers/keyring` | Keyring API surface + routing | +| `AccountService` | `services/account` | Persist / derive / select accounts (snap state) | +| `OnChainAccountService` | `services/on-chain-account` | Snap-state snapshots for balances/assets; live activation for discovery | +| `TransactionService` | `services/transaction` | Local pending keyring txs for `listAccountTransactions` | +| `SyncAccountsHandler` | `handlers/cronjob` | Scheduled after selection changes to refresh on-chain snapshots | + +## Request / response + +Account-management methods follow the MetaMask **Keyring API** (request method names `keyring_*`, params, and return types): + +- [Account Management API](https://docs.metamask.io/snaps/reference/keyring-api/account-management/) +- [`@metamask/keyring-api` docs](https://metamask.github.io/keyring-api/latest/) + +This Snap implements the `Keyring` interface; calls arrive via `wallet_invokeKeyring` / `onKeyringRequest` and are dispatched with `handleKeyringRequest`. + +`submitRequest` uses **SEP-43** request/response shapes — see [SEP-43](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0043.md). + +## Methods + +Requests are origin-checked, then dispatched to the methods below. + +| Method | What it does | Data source | +| ------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------- | +| `listAccounts` | List all Stellar keyring accounts | **Snap state** (`AccountService`) | +| `getAccount` | Get one account by id | **Snap state** | +| `createAccount` | Derive / create one account; emit `AccountCreated` for new accounts (rollback delete if emit fails) | **Snap state** (HD derive + persist) | +| `createAccounts` | Batch BIP-44 create (single index or range); Snap keyring v2 path — no `AccountCreated` events | **Snap state** (HD derive + persist) | +| `deleteAccount` | Emit `AccountDeleted`, then remove the account | **Snap state** | +| `setSelectedAccounts` | Validate ids exist, then schedule `synchronizeAccounts` for those accounts | | +| `listAccountAssets` | Visible CAIP asset ids; if no snapshot yet, returns native slip44 only | **Snap state** on-chain snapshot (background-synced; not a live Horizon read) | +| `getAccountBalances` | Balances for requested assets; missing / inactive snapshot → native `0` if asked | **Snap state** on-chain snapshot (same as above; can be slightly stale within the sync window) | +| `listAccountTransactions` | Paginated keyring transactions for the account | **Snap state** (pending / local txs via `TransactionService` — **not** Horizon history) | +| `discoverAccounts` | Derive BIP-44 address for index; return it only if activated on any requested scope | Derive locally; activation check is **live on-chain** (`NetworkService.getAccount`) | +| `resolveAccountAddress` | Given an address, return CAIP-10 if this snap owns it; else `null` (MetaMask may fall back) | **Snap state** (keyring account lookup by address) | +| `filterAccountChains` | Not implemented | Throws `MethodNotSupportedError` | +| `updateAccount` | Not implemented | Throws `MethodNotSupportedError` | +| `submitRequest` | [signTransaction.md](./signTransaction.md) · [signMessage.md](./signMessage.md) · [signAuthEntry.md](./signAuthEntry.md) | | diff --git a/packages/stellar-wallet-snap/docs/use-cases/keyring/signAuthEntry.md b/packages/stellar-wallet-snap/docs/use-cases/keyring/signAuthEntry.md new file mode 100644 index 000000000..572c6d232 --- /dev/null +++ b/packages/stellar-wallet-snap/docs/use-cases/keyring/signAuthEntry.md @@ -0,0 +1,58 @@ +# Use case: `signAuthEntry` + +SEP-43: confirm and sign a Soroban authorization preimage (`HashIdPreimage` / `envelopeTypeSorobanAuthorization`). + +| | | +| ------------ | ------------------------------------------------------------------------------------- | +| **Entry** | `onKeyringRequest` → `KeyringHandler.submitRequest` → `SignAuthEntryHandler` | +| **Method** | `signAuthEntry` (`MultichainMethod.SignAuthEntry`) | +| **Source** | [`handlers/keyring/signAuthEntry.ts`](../../../src/handlers/keyring/signAuthEntry.ts) | +| **Overview** | [keyring.md](./keyring.md) | + +## Participants + +| Component | Path | Role | +| -------------------------- | ------------------ | ----------------------------- | +| `SignAuthEntryHandler` | `handlers/keyring` | Decode, confirm, sign | +| `AccountResolver` | `handlers/` | Load keyring account + wallet | +| `Wallet` | `services/wallet` | `signAuthEntry` | +| `ConfirmationUXController` | `ui/confirmation` | Sign-auth-entry dialog | + +## Request / response + +Wire format follows **SEP-43** `signAuthEntry` (via keyring `submitRequest`): + +- [SEP-43](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0043.md) +- Keyring transport: [Account Management API](https://docs.metamask.io/snaps/reference/keyring-api/account-management/) (`keyring_submitRequest`) + +Local validators: [`handlers/keyring/api.ts`](../../../src/handlers/keyring/api.ts) (`SignAuthEntryRequestStruct` / `SignAuthEntryResponseStruct`). + +The dapp supplies a base64 `HashIdPreimage`. The Snap decodes it for the confirmation UI (contract, function, args, nested invocations, nonce, expiry ledger), then on approve signs `sha256(preimage)` with ed25519. Network id is already inside the preimage (mainnet-only validation at the struct layer). + +## Step-by-step + +1. Resolve account + wallet. +2. Decode preimage into a readable auth summary for the UI. +3. Show confirmation. +4. On approve → `wallet.signAuthEntry` → return signature. +5. On reject → SEP-43 error envelope. + +## Sequence + +```mermaid +sequenceDiagram + participant Dapp + participant Handler as SignAuthEntryHandler + participant UI as ConfirmationUXController + participant Wallet + + Dapp->>Handler: signAuthEntry (SEP-43 via keyring submitRequest) + Handler->>Handler: decode Soroban auth preimage + Handler->>UI: confirmation dialog + alt user rejects + Handler-->>Dapp: SEP-43 error envelope + else user confirms + Handler->>Wallet: signAuthEntry + Handler-->>Dapp: SEP-43 success (signedAuthEntry, signerAddress) + end +``` diff --git a/packages/stellar-wallet-snap/docs/use-cases/keyring/signMessage.md b/packages/stellar-wallet-snap/docs/use-cases/keyring/signMessage.md new file mode 100644 index 000000000..30e15c155 --- /dev/null +++ b/packages/stellar-wallet-snap/docs/use-cases/keyring/signMessage.md @@ -0,0 +1,73 @@ +# Use case: `signMessage` + +SEP-43: confirm and sign an arbitrary UTF-8 message. + +| | | +| ------------ | --------------------------------------------------------------------------------- | +| **Entry** | `onKeyringRequest` → `KeyringHandler.submitRequest` → `SignMessageHandler` | +| **Method** | `signMessage` (`MultichainMethod.SignMessage`) | +| **Source** | [`handlers/keyring/signMessage.ts`](../../../src/handlers/keyring/signMessage.ts) | +| **Overview** | [keyring.md](./keyring.md) | + +## Participants + +| Component | Path | Role | +| -------------------------- | ------------------ | ----------------------------- | +| `SignMessageHandler` | `handlers/keyring` | Confirm + sign | +| `AccountResolver` | `handlers/` | Load keyring account + wallet | +| `Wallet` | `services/wallet` | `signMessage` | +| `ConfirmationUXController` | `ui/confirmation` | Sign-message dialog | + +## Request / response + +Wire format follows **SEP-43** `signMessage` (via keyring `submitRequest`): + +- [SEP-43](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0043.md) — keyring method + params/response envelope +- Keyring transport: [Account Management API](https://docs.metamask.io/snaps/reference/keyring-api/account-management/) (`keyring_submitRequest`) + +Local validators: [`handlers/keyring/api.ts`](../../../src/handlers/keyring/api.ts) (`SignMessageRequestStruct` / `SignMessageResponseStruct`). + +This snap accepts **UTF-8 text only** for `message` (non-empty). Base64-encoded binary payloads are not auto-detected. + +## How signing works + +Signing follows the **Stellar Signed Message** protocol ([SEP-0053](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0053.md)), implemented in [`Wallet.signMessage`](../../../src/services/wallet/Wallet.ts): + +1. **Interpret `message`** — always as UTF-8 text (string → UTF-8 bytes). +2. **Build the payload** — prepend the fixed prefix `Stellar Signed Message:\n`, then append the message bytes. +3. **Hash** — SHA-256 over that byte sequence. +4. **Sign** — Ed25519 sign the digest with the account’s keypair. +5. **Return** — base64-encoded signature as `signedMessage` (SEP-43 response). + +The confirmation UI shows the UTF-8 `message` string as given — the same content that gets signed. + +**References** + +| Spec | Role | +| ----------------------------------------------------------------------------------------- | ------------------------------------------------------------ | +| [SEP-43](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0043.md) | Keyring `signMessage` request/response wire format | +| [SEP-0053](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0053.md) | Stellar Signed Message prefix, hash, and signature algorithm | + +## Step-by-step + +1. Resolve keyring account + wallet for the signer. +2. Show confirmation with the UTF-8 message text. +3. On approve → `Wallet.signMessage` (SEP-0053, UTF-8) → return `signedMessage` + `signerAddress`. +4. On reject → SEP-43 error envelope. + +```mermaid +sequenceDiagram + participant Dapp + participant Handler as SignMessageHandler + participant UI as ConfirmationUXController + participant Wallet + + Dapp->>Handler: signMessage (SEP-43 via keyring submitRequest) + Handler->>UI: confirmation dialog (UTF-8 message) + alt user rejects + Handler-->>Dapp: SEP-43 error envelope + else user confirms + Handler->>Wallet: signMessage (SEP-0053, UTF-8) + Handler-->>Dapp: SEP-43 success (signedMessage, signerAddress) + end +``` diff --git a/packages/stellar-wallet-snap/docs/use-cases/keyring/signTransaction.md b/packages/stellar-wallet-snap/docs/use-cases/keyring/signTransaction.md new file mode 100644 index 000000000..61802589d --- /dev/null +++ b/packages/stellar-wallet-snap/docs/use-cases/keyring/signTransaction.md @@ -0,0 +1,67 @@ +# Use case: `signTransaction` + +SEP-43: show a confirmation, then return a **signed** transaction XDR to the dapp. The Snap does **not** change the envelope and does **not** broadcast it. + +| | | +| ------------ | ----------------------------------------------------------------------------------------- | +| **Entry** | `onKeyringRequest` → `KeyringHandler.submitRequest` → `SignTransactionHandler` | +| **Method** | `signTransaction` (`MultichainMethod.SignTransaction`) | +| **Source** | [`handlers/keyring/signTransaction.ts`](../../../src/handlers/keyring/signTransaction.ts) | +| **Overview** | [keyring.md](./keyring.md) | + +## Participants + +| Component | Path | Role | +| -------------------------- | --------------------------- | ------------------------------------------------------ | +| `SignTransactionHandler` | `handlers/keyring` | Decode, confirm, sign | +| `AccountResolver` | `handlers/` | Load keyring account + wallet | +| `Wallet` | `services/wallet` | `signTransaction` | +| `ConfirmationUXController` | `ui/confirmation` | Sign-transaction dialog | +| `TransactionScanService` | `services/transaction-scan` | Security scan + remote simulation while dialog is open | + +## Request / response + +Wire format follows **SEP-43** `signTransaction` (and MetaMask keyring `submitRequest` wrapping): + +- [SEP-43](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0043.md) +- Keyring transport: [Account Management API](https://docs.metamask.io/snaps/reference/keyring-api/account-management/) (`keyring_submitRequest`) + +Local Superstruct validators live in [`handlers/keyring/api.ts`](../../../src/handlers/keyring/api.ts) (`SignTransactionRequestStruct` / `SignTransactionResponseStruct`). + +## Important notes + +- **No modification** — the Snap does not rebuild, re-fee, or re-simulate the transaction for signing. It decodes the dapp-supplied XDR, checks scope, and signs as-is. Balance / op-level validation is the caller’s responsibility. +- **No broadcast** — after signing, the Snap returns the SEP-43 success fields (`signedTxXdr`, `signerAddress`). Submitting to the network is entirely the dapp’s job (unlike [`confirmSend`](../client-request/confirmSend.md) / [`signAndSendTransaction`](../client-request/signAndSendTransaction.md)). +- Fee on the envelope is trusted as provided by the dapp; security scan / remote simulation may still surface issues in the confirmation UI. +- Failures / user reject are returned in the SEP-43 `error` envelope (does not throw to the dapp). + +## Step-by-step + +1. Resolve account + wallet for the signer. +2. `Transaction.fromXdr` + assert scope matches. +3. Show confirmation (readable ops, fee, prices, security scan / remote simulation). +4. On approve → `wallet.signTransaction` → return signed XDR. +5. On reject → user-rejected path mapped to SEP-43 error response. + +## Sequence + +```mermaid +sequenceDiagram + participant Dapp + participant Handler as SignTransactionHandler + participant UI as ConfirmationUXController + participant Wallet + + Dapp->>Handler: signTransaction (SEP-43 via keyring submitRequest) + Handler->>Handler: fromXdr (no rebuild / no balance validate) + Handler->>UI: confirmation dialog + alt user rejects + UI-->>Handler: rejected + Handler-->>Dapp: SEP-43 error envelope + else user confirms + UI-->>Handler: confirmed + Handler->>Wallet: signTransaction + Handler-->>Dapp: SEP-43 success (signedTxXdr, signerAddress) + Note over Dapp: Dapp broadcasts if needed + end +``` diff --git a/packages/stellar-wallet-snap/docs/use-cases/user-input/userInput.md b/packages/stellar-wallet-snap/docs/use-cases/user-input/userInput.md new file mode 100644 index 000000000..39b03c6a8 --- /dev/null +++ b/packages/stellar-wallet-snap/docs/use-cases/user-input/userInput.md @@ -0,0 +1,29 @@ +# Use case: user input (`UserInputHandler`) + +Interactive UI events via `onUserInput` → `UserInputHandler`. + +| | | +| ---------- | ----------------------------------------------------------------------------------- | +| **Entry** | `onUserInput` → `UserInputHandler.handle` | +| **Source** | [`handlers/user-input/userInput.ts`](../../../src/handlers/user-input/userInput.ts) | + +## Participants + +| Component | Path | Role | +| ------------------------ | -------------------------------- | --------------------------------------------------------------- | +| `UserInputHandler` | `handlers/user-input` | Route UI events by `event.name` | +| Confirmation view events | `ui/confirmation/views/*/events` | Confirm / cancel (and related) handlers that resolve the dialog | + +## Request / response + +Wire format follows the MetaMask **interactive UI** entry point: + +- [Entry points — `onUserInput`](https://docs.metamask.io/snaps/reference/entry-points/#onuserinput) + +MetaMask calls `onUserInput` with `{ id, event, context }` when the user interacts with a Snap interface. This Snap ignores events without a `name`, then dispatches to a handler map built from confirmation views. + +## Methods + +| Method | What it does | Data source | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------ | ----------- | +| `onUserInput` | Route named UI events to confirmation handlers (sign message / tx / auth entry, change trust, send, malicious acknowledgement) | |