Title: fix: keep EIP-1271 contract signatures when rebuilding a queued tx (GS021)
Resolves: #ISSUE. #3673 appears to have the same underlying cause.
In a Safe with a threshold of 2 or more, Safe{Wallet} can’t simulate or execute a queued transaction when a confirmation the Safe has to check is an EIP-1271 contract signature. The failure is GS021 and “Cannot estimate”. I reproduced it with a passkey signer (SafeWebAuthnSignerProxy). #3673 reports it for a nested Safe owner.
toSafeSignature(signer, signature) is new, in packages/utils/src/utils/safeTransaction.ts. It turns a confirmation as the Transaction Service returns it into an EthSafeSignature:
new EthSafeSignature(signer, data, true), a contract signature carrying only its data.new EthSafeSignature(signer, signature). Its bytes are unchanged, as today.buildSignatureBytes then places contract signatures’ data after all the static parts and computes their offsets, the same way it does when protocol-kit signs with a contract owner.createExistingTx (web) and addSignaturesToTx (mobile) now use the helper instead of the inline plain-signature object.Unit tests (yarn workspace @safe-global/utils test src/utils/__tests__/safeTransaction.test.ts, 11 tests):
The unit tests check encoding only. The fixtures come from a separate fork experiment:
createExistingTx does today revert with GS021.ExecutionSuccess is emitted, the recipient’s balance increases by the transferred amount, and the SafeMultiSigTransaction event logs exactly the bytes used as fixtures.Manual:
QueuedTxSimulation) and speeding it up (SpeedUpModal), both of which use createExistingTx.addSignaturesToTx. It is reached from execution, relay, WalletConnect, Ledger signing and execution, and the transaction checks. This PR updates that helper. Mobile behavior has not been verified on a device.packages/utils gains one export, used by web and mobile.flowchart LR
S[Confirmation from the Transaction Service] -->|EOA, eth_sign, v = 1| P[EthSafeSignature, plain]
S -->|v = 0: owner, offset, 00, length, data| C[EthSafeSignature, contract, data only]
P --> B[buildSignatureBytes]
C --> B
B -->|static parts, then dynamic parts at offsets past them| E[correctly encoded signatures]
S -. before: every confirmation plain .-> X[offset stays 65, inside the static region when threshold ≥ 2: GS021]
Drafted with AI assistance; I reviewed the changes. Testing and limitations are documented above.
With the submission of this Pull Request, I confirm that I have read and agree to the terms of the Contributor License Agreement.