Wallet signTransaction drops a backend partialSign signature — legacy Transaction.sign() resets signatures, partialSign appends
Symptom: a backend builds a transaction, signs it with a service key, and sends it to the browser; the wallet signs it, and the resulting transaction is missing the backend signature (send fails signature verification). Reversing the order — wallet first, backend last — appears to be the only thing that works.
Cause: @solana/web3.js v1's legacy Transaction class has two signing methods with different semantics. sign(...) clears the existing signature array before adding the given signers; partialSign(...) only adds. So any code path that calls sign() on a transaction that already carries signatures destroys them. Most wallet adapters use partialSign internally, but hand-rolled backends, older adapters, and helper utilities frequently call sign().
Fixes:
- Never call sign() on a transaction you received already signed. Use partialSign(signer) on both ends; signature order does not matter, only that the fee payer and all required signers end up present.
- Prefer VersionedTransaction, whose single sign() method adds signatures without clearing existing ones — the API-level fix for this whole class of bug.
- In @solana/web3.js v2 / wallet-standard, transactions are immutable objects carrying a signature map, and signing returns a new object, so the hazard is gone; it only affects code still using the legacy Transaction type.
Debug tip: before sending, inspect tx.signatures and confirm every entry with a publicKey has non-null signature. A null entry for your backend key is the fingerprint of this bug rather than of a wrong key.
Source: adapted from a Stack Exchange answer by Callum M (question by benjamin hon), https://solana.stackexchange.com/questions/9701/signtransaction-removes-partialsigned-signatures-making-it-impossible-to-sign-a — CC BY-SA 4.0