# External signing

Source: https://docs.settlemint.com/docs/concepts/custody-and-signing/external-signing
Sign an EVM write with a key the platform never holds. A transaction preparation binds the write, records your submission and links it to one request.

External signing lets the holder of a custody account sign a write on an EVM network with a key the platform never holds, and still have the platform record that write in its [transaction lifecycle](https://docs.settlemint.com/docs/concepts/settlement/transaction-lifecycle). It works through a transaction preparation, a record that binds one write, its signer and its network for ten minutes. A retry of the same prepare or finalise call records one write, and a new preparation is a new write.

The preparation covers custody accounts linked through Privy, Dynamic or a browser wallet, the three providers that leave signing to the holder. The key sits with the holder's wallet or wallet provider. The alternative is the regular API route, where the platform asks a managed custody provider to sign for the account.

## Keeping keys in your own HSM or custody system [#keeping-keys-in-your-own-hsm-or-custody-system]

Choose one of two setups.

- **Connect it as a managed custody provider.** The platform connects to IBM Digital Asset Haven, DFNS, Fireblocks, Luna HSM, Ripple custody and Utila as managed providers, and its own platform key store is managed too. The key stays in the HSM or the provider's system, the provider's policy or quorum decides whether to sign, and the platform asks for each signature. This is managed signing, and the preparation routes on this page refuse these accounts. The [custody provider](https://docs.settlemint.com/docs/concepts/custody-and-signing/custody-provider) page describes the providers, and the [custody approval](https://docs.settlemint.com/docs/concepts/custody-and-signing/custody-approval) page describes how their policy holds a signature.
- **Hold the key as a self-custody account and sign through preparations.** The account's EVM address is linked to a self-custody provider configuration. You sign each prepared write yourself, and the platform checks the result against the preparation. An HSM the platform has no managed connector for can take this route when a wallet that speaks EIP-1193 fronts its EVM key and is linked as a browser wallet. That wallet signs the challenge that links the address, then signs each prepared write.

On the second route the platform never receives the signed transaction bytes. For a plain transaction, your wallet or service sends the transaction to the network and then reports the sender, the nonce, which is the sending address's sequence number, and the transaction hash. The platform checks what you report against the frozen write and follows the nonce on the network. It does not check which device produced the signature, so the evidence that a key sits in an HSM comes from your own key management.

Linking the address starts with `POST /api/v2/users/me/custody-account-control-preparations`, which returns a challenge for one enabled self-custody provider configuration and one EVM address. [Choose investor custody](https://docs.settlemint.com/docs/api-reference/guides/choose-investor-custody) walks through the linking calls.

## Signing setups the preparation accepts [#signing-setups-the-preparation-accepts]

The preparation records its signing method in `signingMethod`. Two values are possible on these routes, and the user-operation method has two ways to reach the network.

| Setup | `signingMethod` | Executor, the address that sends the write | Who signs | Who sends it to the network | Request state at finalise |
| --- | --- | --- | --- | --- | --- |
| Browser wallet transaction | `eip-1193-transaction` | The custody account's own address | The holder's wallet | The holder's wallet | `BROADCASTING` |
| Owner-signed user operation | `eip-4337-user-operation` | The participant's smart wallet | The holder, as the smart wallet's owner | The platform | `QUEUED`, then execution starts |
| Provider-submitted user operation | `eip-4337-user-operation` | The participant's smart wallet | The holder | The holder's wallet provider | `BROADCASTING` |

A [smart wallet](https://docs.settlemint.com/docs/concepts/custody-and-signing/smart-wallet) is a contract account that acts through user operations, signed instructions an EntryPoint contract validates and executes. [User operations](https://docs.settlemint.com/docs/concepts/custody-and-signing/user-operations) covers the bundler that sends them.

The platform picks the method when you prepare. You ask for `executionMode` `prefer-aa`, which uses the smart wallet, through account abstraction, when it can, or `direct`. With `prefer-aa` the preparation is an owner-signed user operation, and the smart wallet is the executor, when two conditions hold: the provider supports account abstraction, and the participant has a smart wallet on that network whose owner is the custody account's address. In every other case, and with `direct`, the preparation is a browser wallet transaction from the custody account's own address. The user-operation path needs a smart wallet with a single owner key. A smart wallet that uses weighted signers is refused with a binding conflict.

The preparation routes refuse managed signing. A custody account whose provider holds the key gets `DALP-14032` on the prepare call, and a finalise call that carries a managed authorisation gets `DALP-14033`. For that account, send the write through its regular API route.

## Who can prepare and sign [#who-can-prepare-and-sign]

The three preparation routes accept a signed-in session or an API key, and they require no organisation role. The custody account decides eligibility. It must be active, belong to the participant the request acts as, and hold an eligible organisation context on an enabled EVM network. Its provider configuration must serve that network, and its executor wallet must not be archived. A request that fails any of these gets `DALP-14032`.

A preparation belongs to the user and organisation that created it. Any other user or organisation that reads or advances it gets `DALP-14031`, the same answer as for a preparation that does not exist.

## What a preparation binds [#what-a-preparation-binds]

The prepare call fixes these facts and stores a digest over them: the calling user, the participant, the organisation, the custody account, its provider configuration, the network, the executor and the source address, the operation's kind and its digest, the summary, the signing method and the expiry. Each later claim, attachment and finalise call is checked against the stored facts it touches.

Send an `Idempotency-Key` header on the prepare call. The key belongs to the calling user. A repeat with the same key and the same binding returns the stored preparation. The same key with a different binding is refused with `DALP-14033`. Without the header, each call creates a new preparation.

The preparation expires ten minutes after it is created. A preparation that nobody has claimed cannot be claimed or finalised after that time.

## Steps from prepare to transaction request [#steps-from-prepare-to-transaction-request]

**Step 1 of 6**

### Prepare the write [#prepare-the-write]

Call `POST /api/v2/transaction-preparations` with `custodyAccountId`, `executionMode`, a `summary` and the `operation`: its `kind`, `contractAddress`, `callData` and an optional `value`. The response is the preparation in state `prepared`. It names the `signingMethod`, the `executor`, the `expiresAt` time and, for a user operation, `preparedUserOperation` with the `userOperationHash` and the `previewData` of the operation.

For a user operation the platform builds a preview before you sign. When the organisation's gas payer on that network is Paymaster-paid, the preview carries the paymaster's sponsorship, so the smart wallet pays no gas. Otherwise a smart wallet that cannot cover the gas and the native currency the operation sends gets `DALP-14034`, with `smartWalletAddress`, `heldWei` and the missing `shortfallWei`. A smart wallet can hold one open owner-signed operation per network. A second one is refused with `DALP-14033` until the first finishes or expires.

**Step 2 of 6**

### Read the preparation [#read-the-preparation]

`GET /api/v2/transaction-preparations/{preparationId}` returns the bound write and its progress: the signing method, the expiry, the submission claim, the attached handle or artifact and, once finalised, `transactionRequestId`.

**Step 3 of 6**

### Claim the submission before anything is sent [#claim-the-submission-before-anything-is-sent]

Call `POST /api/v2/transaction-preparations/{preparationId}/transitions` with `action` set to `claim`. A claim reserves one submission attempt. Repeating a claim with the same `claimKey` succeeds, so choose a key you can regenerate.

- **Browser wallet transaction.** The claim has `submissionKind` `eip-1193`, a `claimKey`, and the `sender`, `chainId`, `nonce`, `to`, `value` and `data` you are about to send. They must match the prepared sender, network, destination, value and call data. The claim reserves that sender, network and nonce against every other preparation. The body also requires a `startBlock`, but the platform ignores its value and records the network's current block as the start of its search for the transaction.
- **Provider-submitted user operation.** The claim has `submissionKind` `provider-handle` and a `claimKey`. It applies only to a user-operation preparation, and the key cannot belong to another preparation under the same provider configuration.
- **Owner-signed user operation sent by the platform.** Make no claim. Go to finalise.

**Step 4 of 6**

### Sign the write, and send it from your wallet [#sign-the-write-and-send-it-from-your-wallet]

For a browser wallet transaction, the wallet signs and sends the claimed transaction. Once it is sent, the nonce is used and the claim can no longer be abandoned.

For a user operation, sign the `userOperationHash` the preparation returned. The platform recovers the signer from that hash with no prefix added. A personal-message signature adds a prefix, so it does not recover to the account. A typed-data signature works when its hash equals `userOperationHash`.

On the provider path, record the provider's handle with `action` `attach` and an `attachment` of `claimKey` and `providerHandle`. The handle must name the active claim. A repeat of the same handle succeeds, and a different handle is refused with `DALP-14240`.

**Step 5 of 6**

### Finalise [#finalise]

Finalising cannot be undone: a consumed preparation stays linked to its transaction request. It links the preparation to a request and does not prove the chain executed the call, so follow the request to a terminal status.

Call the transitions route with `action` `finalize` and an `artifact` whose `kind` matches the prepared signing method. The artifact also carries a `_tag`: `Eip1193TransactionAuthorization` for a browser wallet transaction and `Eip4337UserOperationAuthorization` for a user operation.

- **Browser wallet transaction.** The artifact carries `sender`, `chainId`, `nonce` and `transactionHash`, which is `null` when the wallet returned no hash. It must match the persisted claim.
- **Owner-signed user operation.** The artifact carries `userOperationHash` and `signature`. The platform checks that the hash is the prepared one, recomputes it from the operation's own fields, requires a canonical signature and verifies that it recovers to the custody account's address. The request enters the queue and the platform starts execution.
- **Provider-submitted user operation.** The same artifact, finalised against the claim that holds the handle. The platform checks the hash against the prepared one. It does not recompute the hash or verify the signature on this path, because the provider submitted the operation. The EntryPoint verifies the signature when it executes the operation, so a bad signature fails on chain.

The response is the preparation in state `consumed`, with `transactionRequestId`. A repeat with the same artifact returns the same request, and a different artifact is refused with `DALP-14033`.

**Step 6 of 6**

### Follow the transaction request [#follow-the-transaction-request]

Read `GET /api/v2/transaction-requests/{transactionId}`, or its `/stream`, until `status` is `COMPLETED`, `FAILED`, `DEAD_LETTER` or `CANCELLED`. Keep reading a `DEAD_LETTER` request, one the platform stopped retrying, while its `mayStillLand` is `true`, because its call can still land. A write that your wallet already sent enters as `BROADCASTING`, and confirmation tracking follows a request that carries a transaction hash. An owner-signed user operation enters as `QUEUED` and runs the platform's execution from there.

To release a provider claim that has no handle, call the transitions route with `action` `abandon`. The preparation returns to `prepared`. Claim again with a new key, because the preparation refuses every key it has abandoned. A browser wallet claim cannot be abandoned, because its nonce may already have reached the network. A claim that holds a provider handle cannot be abandoned either.

## Preparation states [#preparation-states]

[Figure] A preparation moves through four states and ends consumed or expired. Four states: prepared, submission-claimed, consumed and expired. A claim moves prepared to submission-claimed, and an abandon moves a provider claim back to prepared. Finalise moves submission-claimed to consumed. Finalising an owner-signed user operation moves prepared straight to consumed, and the platform submits it. Ten minutes passing moves an unclaimed preparation to expired. For an expired browser wallet claim, reconciliation moves it to consumed when the wallet's nonce moved past the claim, and to expired when the nonce is still open. Labels: Claim; Abandon; Finalise; Nonce moved; Finalise, platform submits; Ten minutes pass; Nonce still open; prepared; bound for ten minutes; submission-claimed; one attempt reserved; consumed; linked to one request; expired; prepare again; a call you make; time passing or platform reconciliation.

Your calls move a preparation through `prepared`, `submission-claimed` and `consumed`, and time or reconciliation moves it to `expired` or `consumed`.

A preparation has four states, and the read returns the current one in `state`.

- `prepared`: the write is bound and nothing is claimed. A claim moves it to `submission-claimed`. Finalising an owner-signed user operation moves it straight to `consumed`, and the platform submits the operation.
- `submission-claimed`: one submission attempt is reserved. Finalise moves it to `consumed`. An abandon moves a provider claim without a handle back to `prepared`.
- `consumed`: the preparation is permanently linked to its transaction request.
- `expired`: the write can no longer be claimed or finalised, and no transaction request exists for it. Prepare the write again.

An unclaimed preparation expires ten minutes after it is created. A claim or finalise call after that time is refused with `DALP-12012`. The platform records the `expired` state when it refuses a claim or a finalise. Until then, the read can still show `prepared` with an `expiresAt` time in the past.

A claimed provider submission does not expire on its own. Finalise it, or abandon it while it has no handle.

## When a browser wallet claim outlives its preparation [#when-a-browser-wallet-claim-outlives-its-preparation]

A browser wallet claim whose ten minutes have passed is checked by the platform's background reconciliation, which reads the sender's nonce on the network.

- When the pending nonce equals the reserved nonce and the confirmed nonce has not passed it, nothing was sent. The preparation expires and the claim key is retired.
- When the pending nonce has moved past the reservation but the confirmed nonce has not, the wallet sent something that the network has not mined. The platform consumes the preparation and records a `BROADCASTING` request with the claimed sender, network, nonce and start block, and no transaction hash. It starts no new execution.
- When the confirmed nonce has moved past the reservation, the platform looks for the transaction that used the nonce in a finalised block. When that transaction has the claimed destination, value and call data, the platform consumes the preparation and records a `BROADCASTING` request with that transaction hash, so confirmation tracking follows it. When it finds no such transaction, or the transaction carries another call, it leaves the preparation as it is.
- A nonce slot that is blocked, or that the network does not answer for, leaves the preparation as it is.

A request recorded without a transaction hash shows only that the wallet sent some transaction at that nonce. It does not prove that the intended call succeeded, and confirmation tracking skips a request without a transaction hash. Check the network for the wallet's transaction at that nonce before you send anything again.

## Writes that need more than one step [#writes-that-need-more-than-one-step]

Some Console and Investor portal flows need several writes in order, such as an approval followed by a purchase. The public prepare route prepares one operation. A platform flow prepares the sequence itself. For a smart wallet it prepares one user operation that carries the extra calls, with the final write last. For a plain wallet it prepares one linked preparation per step, and the read names the earlier step a preparation waits on.

A linked step can be claimed only after the earlier step's transaction request is `COMPLETED`. Until then, its claim is refused as pending, even when the earlier step is only claimed. When the earlier request failed, was cancelled, or was dead-lettered with `mayStillLand` reading `false`, the claim is refused as failed and the whole sequence must be prepared again. While a dead-lettered earlier request may still land, the claim stays refused as pending. A step whose own time ran out, or whose earlier step expired, is refused as expired.

## Refusals and what the caller does [#refusals-and-what-the-caller-does]

When the preview of an owner-signed write reverts, the call answers `422` with the contract's own error, such as `DALP-1009` for a compliance refusal, or `DALP-0447` with `revertData` when the catalogue does not know the revert. No preparation is stored. A token write that a browser smart wallet signs answers the same way, with the contract's error instead of a generic queue refusal. A preview that the platform refuses without revert bytes stays a binding conflict, `DALP-14033`.

| Kind | Code | HTTP | What the platform observed | What the caller does |
| --- | --- | --- | --- | --- |
| Expired | `DALP-12012` | 410 | The ten minutes ran out before an unclaimed preparation was claimed or finalised, or an earlier step of its sequence expired. | Prepare the write again. Nothing was recorded as a transaction request. |
| Claim conflict | `DALP-14240` | 409 | The preparation already holds a different claim, the claim key was abandoned, the sender and nonce belong to another preparation, the claimed transaction is not the prepared one, the artifact has no matching claim, or the provider handle differs. | Read the preparation. Resume the claim you hold, or wait until the other preparation that holds the nonce is sent or expires. |
| Binding conflict | `DALP-14033` | 409 | The call does not match how the preparation is bound: another signing method or executor, a managed authorisation, a signature that does not recover to the account, a reused idempotency key with another binding, a smart wallet with weighted signers, or a second open owner-signed operation on the same smart wallet. | Send the claim, attachment or artifact that matches `signingMethod` and `executor`. Use a new idempotency key for a different write. |
| Preimage mismatch | `DALP-14241` | 409 | The user operation hash in the artifact is not the prepared one. | Sign the `userOperationHash` the preparation returned. |
| Pending | `DALP-14429` | 409, retryable | An earlier step of the sequence has no completed transaction request. | Wait for the earlier step to complete, then claim again. |
| Failed | `DALP-14430` | 409 | An earlier step of the sequence failed, was cancelled, or was dead-lettered and cannot land. | Prepare the sequence again. This step can never be claimed. |
| Not found | `DALP-14031` | 404 | No preparation with this id belongs to the calling user and organisation. | Use the id the prepare call returned to you. |
| Ineligible account | `DALP-14032` | 422 | The custody account is managed, inactive, held by another participant, not eligible on this network, or its executor wallet is archived. | Prepare with a self-custody account you hold on that network. |
| Smart wallet gas | `DALP-14034` | 422 | The smart wallet cannot cover the operation's gas and the native currency it sends, and the paymaster does not pay for it. `data` names `smartWalletAddress`, `heldWei` and `shortfallWei`. | Send at least `shortfallWei` to the smart wallet, then prepare again. |

The [Platform API error reference](https://docs.settlemint.com/docs/api-reference/errors/platform-api-error-reference#domain-rule-errors) lists each code with its full text.

## In the Console and the Investor portal [#in-the-console-and-the-investor-portal]

The Console's token transfer sheet and the Investor portal use the browser wallet path for a custody account linked to a browser wallet. Both need writable browser storage and the browser's lock API. They store each submission attempt, so a reload resumes a matching recorded attempt and does not send it again. When a submission stops, the Console names the reason with messages such as these:

- "The transaction expired before the wallet sent it, so the platform can no longer record it. Start the transaction again."
- "Another session holds the wallet submission for this transaction. Finish it there."
- "Another transaction from this wallet holds its next nonce until it is sent or expires. Finish that transaction, or start this one again in ten minutes."
- "The wallet may have sent the transaction, but the platform could not record it. Check the wallet's activity before sending anything again."

## Terms that sound similar [#terms-that-sound-similar]

Three other pages use a similar term for something different.

- The [custody approval](https://docs.settlemint.com/docs/concepts/custody-and-signing/custody-approval) page describes an external custody provider or HSM whose policy decides whether to release a signature the platform asked for. That is managed signing, which these routes refuse.
- [Multisig](https://docs.settlemint.com/docs/concepts/custody-and-signing/multisig) describes an external seat: a signer address on a weighted smart wallet with no platform identity behind it. A weighted smart wallet cannot use the owner-signed path on this page.
- [Single sign-on](https://docs.settlemint.com/docs/deployment/sso) describes external sign-in to the Console through an OpenID Connect identity provider. It decides who signs in, and it signs no transaction.

## You should see [#you-should-see]

- After you prepare a write and finalise it, reading the preparation shows `state` as `consumed` and `transactionRequestId` set. Keep the `preparationId` to read it back.
- The transaction request reaches a terminal `status`.
- For a browser wallet transaction, the request's `transactionHash` matches the hash the wallet returned.

## Related pages [#related-pages]

Read [custody provider](https://docs.settlemint.com/docs/concepts/custody-and-signing/custody-provider) for the managed and self-custody providers, [smart wallet](https://docs.settlemint.com/docs/concepts/custody-and-signing/smart-wallet) for the account an owner-signed operation spends from, [Embed a Privy wallet](https://docs.settlemint.com/docs/api-reference/guides/embed-a-privy-wallet) for a worked user-operation example, and [Transaction lifecycle](https://docs.settlemint.com/docs/concepts/settlement/transaction-lifecycle) for the path a request follows once it is recorded.
