Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/stellar-wallet-snap/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Show pre-submit send and change-trust validation failures in the confirmation dialog, then return structured error codes (send) or rethrow (change-trust) ([#220](https://github.com/MetaMask/internal-snaps/pull/220))
- Banner copy uses `confirmation.txnError.*` locale keys
- `onAmountInput` now always validates a self-transfer so destination errors surface in `confirmSend` ([#220](https://github.com/MetaMask/internal-snaps/pull/220))
- `createValidatedSendTransaction` now throws `InvalidAssetForCreateAccountException` instead of `AccountNotActivatedException` when sending a non-native asset to an unfunded destination ([#185](https://github.com/MetaMask/internal-snaps/pull/185))

## [0.1.0]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ Add or remove a classic Stellar trustline for an asset on a managed account.
- `{ 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`.
Pre-submit validation failures (missing trustline, non-zero opt-out balance, reserve, fee) are shown in the change-trust confirmation dialog first (no fee or price estimates). After the dialog closes, the handler rethrows the validation error. Failures after the user confirms rethrow without a second dialog.

User rejection of a valid confirmation dialog throws `UserRejectedRequestError`.

## Participants

Expand All @@ -49,12 +51,13 @@ User rejection of the confirmation dialog throws `UserRejectedRequestError`.

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`.
3. **Short-circuit** — If `add` and a trustline with limit > 0 already exists → `{ status: true }`. If `delete` and no trustline, the opt-out confirmation shows the error, then `TrustlineNotFoundException` is rethrown.
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`.
5. **Pre-submit validation errors** — Missing trustline, non-zero opt-out balance, reserve, and fee failures are shown in the change-trust confirmation (no fee or price estimates). After the dialog closes, the handler rethrows.
6. **Confirm** — `ConfirmationUXController` shows opt-in or opt-out UI (fee, security scan, local re-validation cron while open).
7. **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. Validation failures here rethrow without a second dialog.
8. **Sign & send** — `Wallet.signTransaction` → `TransactionService.sendTransaction`.
9. **Post-submit** — Persist pending keyring tx (`ChangeTrustOptIn` / `ChangeTrustOptOut`) and schedule `TrackTransactionHandler`.

## Sequence (happy path)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ Confirms and submits a send for Unified Non-EVM Send (live on-chain data at buil
- `{ 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).
Pre-submit validation failures (balance, memo, trustline, create-account, expired transaction, non-native send to an unfunded destination) are shown in the send confirmation dialog first (no fee or price estimates). After the dialog closes, the handler returns the error codes above. Failures after the user confirms return those same codes without a second dialog.

User rejection of a valid confirmation dialog throws `UserRejectedRequestError`. Unactivated sender accounts show the account activation prompt and rethrow `AccountNotActivatedException`.

## Participants

Expand All @@ -48,10 +50,11 @@ User rejection of the confirmation dialog throws `UserRejectedRequestError`. Una
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.
4. **Pre-submit validation errors** — Balance, memo, trustline, and create-account failures are shown in the send confirmation (no fee or price estimates). After the dialog closes, the handler returns `{ valid: false, errors: [{ code }] }`.
5. **Confirm** — `ConfirmationUXController` shows send UI (fee, estimated changes, 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. Validation failures here return error codes without a second dialog.
7. **Sign & send** — `Wallet.signTransaction` → `TransactionService.sendTransaction`.
8. **Post-submit** — Persist pending keyring tx (`Send`) and schedule `TrackTransactionHandler` for sender + destination.

## Sequence (happy path)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Preflight-validates a send amount while the user types (balance and fee checks o
- `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
- `to` — optional Stellar destination; ignored for preflight (always a self-transfer so destination errors surface in `confirmSend`)

**Response**

Expand All @@ -42,7 +42,7 @@ Unactivated accounts return `{ valid: false, errors: [{ code: "Invalid" }] }` (n
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).
4. **Preflight** — `TransactionService.createValidatedSendTransaction` with cached network reads (destination is always the sender).
5. **Return** — Structured validation result; expected balance/fee failures are returned as error codes (not thrown).

## Note: cache usage
Expand Down
44 changes: 40 additions & 4 deletions packages/stellar-wallet-snap/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,47 @@
"confirmation.simulationErrorSubtitle": {
"message": "{reason}"
},
"confirmation.transactionInvalidTitle": {
"message": "Transaction is no longer valid"
"confirmation.txnError.requiresMemo": {
"message": "This account requires a memo. Sends to it are not supported."
},
"confirmation.transactionInvalidSubtitle": {
"message": "It may have expired or your account balance changed. Close this request and try again."
"confirmation.txnError.invalidCreateAccountAmount": {
"message": "Update Send Amount. A new account requires 1 XLM minimum."
},
"confirmation.txnError.invalidCreateAccountAsset": {
"message": "Update Send Asset. New accounts must first be funded by 1 XLM."
},
"confirmation.txnError.trustlineNotAuthorized": {
"message": "The destination is not authorized to receive this asset."
},
"confirmation.txnError.trustlineNotFound": {
"message": "The destination has not activated a trustline for this asset."
},
"confirmation.txnError.trustlineNotFoundOnAccount": {
"message": "This account has not activated a trustline for this asset."
},
"confirmation.txnError.trustlineExceedLimit": {
"message": "This payment would exceed the destination's trustline limit."
},
"confirmation.txnError.insufficientBalance": {
"message": "Insufficient balance for this transaction."
},
"confirmation.txnError.insufficientBalanceToCoverFee": {
"message": "Insufficient balance to cover the network fee."
},
"confirmation.txnError.generic": {
"message": "This transaction cannot be completed. Close this request and try again."
},
"confirmation.txnError.trustlineNonZeroBalance": {
"message": "Send or swap the remaining balance before removing this trustline."
},
"confirmation.txnError.updateTrustlineLimit": {
"message": "The new trustline limit cannot be below the current balance."
},
"confirmation.txnError.insufficientBalanceToCoverBaseReserve": {
"message": "Insufficient balance to cover the account reserve."
},
"confirmation.txnError.expired": {
"message": "This transaction has expired. Close this request and try again."
},
"confirmation.validationScanErrorTitle": {
"message": "Security check unavailable"
Expand Down
44 changes: 40 additions & 4 deletions packages/stellar-wallet-snap/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,47 @@
"confirmation.simulationErrorSubtitle": {
"message": "{reason}"
},
"confirmation.transactionInvalidTitle": {
"message": "Transaction is no longer valid"
"confirmation.txnError.requiresMemo": {
"message": "This account requires a memo. Sends to it are not supported."
},
"confirmation.transactionInvalidSubtitle": {
"message": "It may have expired or your account balance changed. Close this request and try again."
"confirmation.txnError.invalidCreateAccountAmount": {
"message": "Update Send Amount. A new account requires 1 XLM minimum."
},
"confirmation.txnError.invalidCreateAccountAsset": {
"message": "Update Send Asset. New accounts must first be funded by 1 XLM."
},
"confirmation.txnError.trustlineNotAuthorized": {
"message": "The destination is not authorized to receive this asset."
},
"confirmation.txnError.trustlineNotFound": {
"message": "The destination has not activated a trustline for this asset."
},
"confirmation.txnError.trustlineNotFoundOnAccount": {
"message": "This account has not activated a trustline for this asset."
},
"confirmation.txnError.trustlineExceedLimit": {
"message": "This payment would exceed the destination's trustline limit."
},
"confirmation.txnError.insufficientBalance": {
"message": "Insufficient balance for this transaction."
},
"confirmation.txnError.insufficientBalanceToCoverFee": {
"message": "Insufficient balance to cover the network fee."
},
"confirmation.txnError.generic": {
"message": "This transaction cannot be completed. Close this request and try again."
},
"confirmation.txnError.trustlineNonZeroBalance": {
"message": "Send or swap the remaining balance before removing this trustline."
},
"confirmation.txnError.updateTrustlineLimit": {
"message": "The new trustline limit cannot be below the current balance."
},
"confirmation.txnError.insufficientBalanceToCoverBaseReserve": {
"message": "Insufficient balance to cover the account reserve."
},
"confirmation.txnError.expired": {
"message": "This transaction has expired. Close this request and try again."
},
"confirmation.validationScanErrorTitle": {
"message": "Security check unavailable"
Expand Down
44 changes: 40 additions & 4 deletions packages/stellar-wallet-snap/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,47 @@
"confirmation.simulationErrorSubtitle": {
"message": "{reason}"
},
"confirmation.transactionInvalidTitle": {
"message": "Transaction is no longer valid"
"confirmation.txnError.requiresMemo": {
"message": "This account requires a memo. Sends to it are not supported."
},
"confirmation.transactionInvalidSubtitle": {
"message": "It may have expired or your account balance changed. Close this request and try again."
"confirmation.txnError.invalidCreateAccountAmount": {
"message": "Update Send Amount. A new account requires 1 XLM minimum."
},
"confirmation.txnError.invalidCreateAccountAsset": {
"message": "Update Send Asset. New accounts must first be funded by 1 XLM."
},
"confirmation.txnError.trustlineNotAuthorized": {
"message": "The destination is not authorized to receive this asset."
},
"confirmation.txnError.trustlineNotFound": {
"message": "The destination has not activated a trustline for this asset."
},
"confirmation.txnError.trustlineNotFoundOnAccount": {
"message": "This account has not activated a trustline for this asset."
},
"confirmation.txnError.trustlineExceedLimit": {
"message": "This payment would exceed the destination's trustline limit."
},
"confirmation.txnError.insufficientBalance": {
"message": "Insufficient balance for this transaction."
},
"confirmation.txnError.insufficientBalanceToCoverFee": {
"message": "Insufficient balance to cover the network fee."
},
"confirmation.txnError.generic": {
"message": "This transaction cannot be completed. Close this request and try again."
},
"confirmation.txnError.trustlineNonZeroBalance": {
"message": "Send or swap the remaining balance before removing this trustline."
},
"confirmation.txnError.updateTrustlineLimit": {
"message": "The new trustline limit cannot be below the current balance."
},
"confirmation.txnError.insufficientBalanceToCoverBaseReserve": {
"message": "Insufficient balance to cover the account reserve."
},
"confirmation.txnError.expired": {
"message": "This transaction has expired. Close this request and try again."
},
"confirmation.validationScanErrorTitle": {
"message": "Security check unavailable"
Expand Down
2 changes: 1 addition & 1 deletion packages/stellar-wallet-snap/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/internal-snaps.git"
},
"source": {
"shasum": "XmHWNIVVLlhvKq+iksu3PyMYqx95Zj7lQAB/s1cOFlk=",
"shasum": "jUXDatd/u/35x3ftDRxgrJH2bTn7lfuSxJk5dGFbqgc=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
Loading