> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rhinestone.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom signing

> Inspect intent messages and coordinate signatures across custom owner implementations.

Use `getTransactionMessages` to inspect the ordered signing requests for a prepared transaction. Requests can carry EIP-712, personal-sign, EIP-7702, or WebAuthn payloads. Let the SDK create the proofs and account-specific envelopes.

## Inspect the messages

```ts theme={null}
const prepared = await rhinestoneAccount.prepareTransaction(transaction);
const requests = rhinestoneAccount.getTransactionMessages(prepared);

for (const request of requests) {
  console.log(request.purpose, request.account, request.authority);
  console.log(request.scope, request.validity, request.payload);
}
```

Use these requests to render an approval screen, audit the requested operation, or send metadata to an external coordinator. In the signed result, `proofs[i]` answers `quote.signingRequests[i]`. Never sort, filter, or deduplicate that vector, even when two payloads look identical. Pass `{ intentId }` as the second argument to inspect a quote other than `prepared.quotes.best`.

<Warning>
  Do not sign these messages independently and construct a `submitTransaction`
  payload by hand. Raw EIP-712 signatures do not include every validator and
  account-specific envelope assembled by the SDK.
</Warning>

## Provide a custom signer

The accounts configured in `evm.owners` are viem `Account` or `WebAuthnAccount` objects. Implement their signing methods with your own HSM, MPC service, custody provider, or remote approval flow. The SDK calls those methods while preserving the account's required encoding.

For the normal single-process flow, `signTransaction` automatically collects required EIP-7702 delegations as well as account proofs:

```ts theme={null}
const signed = await rhinestoneAccount.signTransaction(prepared);
const result = await rhinestoneAccount.submitTransaction(signed);
await rhinestoneAccount.waitForExecution(result);
```

## Solana remote signers

A managed Solana entry takes one viem secp256k1 `Account` or `WebAuthnAccount` under `solana.owner`. For an existing ECDSA role, adapt your remote signer with `toAccount` and forward `signMessage` unchanged:

```ts theme={null}
import { toAccount } from 'viem/accounts'

const remoteOwner = toAccount({
  address: remoteSigner.address,
  signMessage: (parameters) => remoteSigner.signMessage(parameters),
  async signTypedData() {
    throw new Error('Typed-data signing is not enabled by this adapter')
  },
  async signTransaction() {
    throw new Error('Raw transaction signing is not enabled by this adapter')
  },
})
const solanaAccount = await rhinestone.createAccount({
  solana: { swig, owner: { type: 'ecdsa', account: remoteOwner } },
})
```

Here `remoteSigner` is your authenticated viem-compatible signing adapter, `rhinestone` is the shared SDK instance, and `swig` is saved state with that key already authorized. Authorize every remote request. Preserve viem message semantics, including raw bytes and Ethereum personal-sign framing; do not hash or prefix the SDK message twice. Let the SDK encode the final Solana proof.

Spending through an existing role needs the account address and `signMessage`. Creating a Swig additionally needs the corresponding 65-byte, `0x04`-prefixed uncompressed secp256k1 `publicKey` exposed on the configured account; the SDK verifies it derives the account address. An address-only remote adapter cannot deploy a new root. Authority enrollment helpers also need the actual public key, not an EVM address. Obtain it from the signer provider without exporting the private key. A provider's Ed25519 Solana wallet is not a substitute.

For passkeys, reuse the [shared credential setup and restoration](/wallets/custom-signer/account-setup/native-signers/passkeys). Use normal `signTransaction` for supported Solana flows; independent owner contribution assembly remains EVM-only. See [Solana support](/wallets/custom-signer/solana-support) and [account deployment](/transactions/relaying/account-deployment).

## Collect owner approvals independently

ECDSA, passkey, and multi-factor smart-account owners can approve the same prepared transaction in separate processes. Each process must receive the same complete prepared transaction and sign the same quote. These snippets continue an existing managed EVM account setup with `ownerA` and `ownerB` in its configured owner set:

```ts theme={null}
const signatureA = await rhinestoneAccount.signTransaction(prepared, {
  owner: ownerA,
  intentId: prepared.quotes.best.intentId,
});

const signatureB = await rhinestoneAccount.signTransaction(prepared, {
  owner: ownerB,
  intentId: prepared.quotes.best.intentId,
});

const signed = await rhinestoneAccount.assembleTransaction(prepared, [
  signatureA,
  signatureB,
]);
const result = await rhinestoneAccount.submitTransaction(signed);
```

For a multi-factor account, also pass the factor's `validatorId` with each owner. `assembleTransaction` orders owners for encoding but rejects duplicate contributions, unknown owners, mixed intent IDs, and signatures below the configured threshold.

Keep returned owner contributions opaque. Pass them back unchanged; do not reconstruct their fields or cast them to a hand-written transport schema. When transporting prepared artifacts and contributions, use a serialization format that preserves `bigint` values and the complete SDK objects.

### Collect delegations separately

For independent assembly that also needs EIP-7702 delegation proofs, replace the assembly step above with:

```ts theme={null}
const proofs = await rhinestoneAccount.signAuthorizations(prepared, {
  intentId: prepared.quotes.best.intentId,
});
const signed = await rhinestoneAccount.assembleTransaction(
  prepared,
  [signatureA, signatureB],
  { proofs },
);
```

These indexed contributions bind `intentId`, `requestSetId`, and `requestIndex` to a proof. Preserve them unchanged. Assembly checks that they belong to the selected quote's request set and that every required slot is covered. Do not append them manually to a proof vector. Normal signing does not need this separate step.

## Constraints

* Keep the complete prepared transaction, including `request`, unchanged across signers. Preparing again can select a different quote or request set. Earlier SDK artifact formats are rejected; there is no automatic migration or re-quote.
* Submit before the selected quote expires. Re-prepare and collect new signatures after expiry.
* Keep the SDK's local owner and threshold configuration synchronized with on-chain changes.
* Independent assembly is limited to supported managed EVM owner validators and exposed authorization slots. Target-execution requests are not a blanket prohibition: supported EIP-712 slots can be assembled. EOA account mode, session keys, K1/ERC-7739, and Solana independent owner assembly are not supported. Use the normal signing flow where supported; if assembly raises `IndependentSigningNotSupportedError`, do not bypass it.

See the generated references for [`getTransactionMessages`](/wallets/custom-signer/sdk-reference/account/transactions/get-transaction-messages), [`signTransaction`](/wallets/custom-signer/sdk-reference/account/transactions/sign-transaction), and [`assembleTransaction`](/wallets/custom-signer/sdk-reference/account/transactions/assemble-transaction).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.