SDK
Guides
Migrate to v7

Migrate to v7

Overview

This release adds support for Safe v1.5.0 smart contracts across the SDK suite (types-kit, protocol-kit, api-kit, and sdk-starter-kit). It includes breaking changes that require attention when upgrading.

Breaking Changes

1. DEFAULT_SAFE_VERSION changed from 1.3.0 to 1.4.1

Affected package: protocol-kit

The default Safe version used when no explicit version is provided has changed from 1.3.0 to 1.4.1. This affects Safe creation, deployment, and address prediction.

Before:


_10
// Implicitly deployed a Safe v1.3.0
_10
const protocolKit = await Safe.init({
_10
provider,
_10
signer,
_10
predictedSafe: {
_10
safeAccountConfig: { owners, threshold }
_10
}
_10
})

After:


_10
// Now implicitly deploys a Safe v1.4.1
_10
const protocolKit = await Safe.init({
_10
provider,
_10
signer,
_10
predictedSafe: {
_10
safeAccountConfig: { owners, threshold }
_10
}
_10
})

⚠️ Warning: Predicted Safe addresses will differ if you were relying on the previous default. Make sure to pin the version explicitly in production environments.

How to migrate:

If your application depends on deploying Safe v1.3.0 contracts, explicitly set the version:


_10
const protocolKit = await Safe.init({
_10
provider,
_10
signer,
_10
predictedSafe: {
_10
safeAccountConfig: { owners, threshold },
_10
safeDeploymentConfig: {
_10
safeVersion: '1.3.0'
_10
}
_10
}
_10
})

2. encodeTransactionData removed from the Safe contract in v1.5.0

Affected packages: protocol-kit, types-kit

In Safe v1.5.0, the encodeTransactionData function has been removed from the Safe contract and relocated to the CompatibilityFallbackHandler.

Before (≤ v1.4.1):


_13
// Called directly on the Safe contract
_13
const data = await safeContract.encodeTransactionData(
_13
to,
_13
value,
_13
data,
_13
operation,
_13
safeTxGas,
_13
baseGas,
_13
gasPrice,
_13
gasToken,
_13
refundReceiver,
_13
nonce
_13
)

After (v1.5.0):

Calling encodeTransactionData on a Safe v1.5.0 contract will revert on-chain.

How to migrate:

For Safe v1.5.0, use the CompatibilityFallbackHandler contract instead:


_19
import { getCompatibilityFallbackHandlerContract } from '@safe-global/protocol-kit'
_19
_19
const fallbackHandler = await getCompatibilityFallbackHandlerContract({
_19
safeProvider,
_19
safeVersion: '1.5.0'
_19
})
_19
_19
const data = await fallbackHandler.encodeTransactionData(
_19
to,
_19
value,
_19
data,
_19
operation,
_19
safeTxGas,
_19
baseGas,
_19
gasPrice,
_19
gasToken,
_19
refundReceiver,
_19
nonce
_19
)

If you need to support multiple Safe versions, add a version check:


_10
import { semverSatisfies } from '@safe-global/protocol-kit'
_10
_10
if (semverSatisfies(safeVersion, '>=1.5.0')) {
_10
// Use CompatibilityFallbackHandler
_10
} else {
_10
// Use Safe contract directly
_10
}

3. Legacy isValidSignature(bytes, bytes) removed in v1.5.0

Affected package: protocol-kit

The CompatibilityFallbackHandler in Safe v1.5.0 no longer exposes the legacy isValidSignature(bytes, bytes) overload. Only the modern EIP-1271 overload isValidSignature(bytes32, bytes) is supported.

Before (≤ v1.4.1):

The SDK called both overloads in parallel during signature validation:

  • isValidSignature(bytes32, bytes): EIP-1271 standard
  • isValidSignature(bytes, bytes): Legacy overload

After (v1.5.0):

Only the modern overload is called. The legacy call would revert or return invalid data against a v1.5.0 Safe.

How to migrate:

If you are calling isValidSignature directly, ensure you use the bytes32 overload:


_10
// ✅ Correct for all versions
_10
const result = await safeContract.isValidSignature(messageHash, signature)
_10
// where messageHash is bytes32
_10
_10
// ❌ Will fail on v1.5.0
_10
const result = await safeContract.isValidSignature(messageBytes, signature)
_10
// where messageBytes is raw bytes

Note: The SDK handles this internally. If you rely on the SDK's isValidSignature method in the Safe class, no changes are needed, the SDK now conditionally skips the legacy call for v1.5.0+.

4. SafeVersion type extended with '1.5.0'

Affected package: types-kit

The SafeVersion union type now includes '1.5.0' as a valid value.

Before:


_10
type SafeVersion = '1.0.0' | '1.1.1' | '1.2.0' | '1.3.0' | '1.4.1'

After:


_10
type SafeVersion = '1.0.0' | '1.1.1' | '1.2.0' | '1.3.0' | '1.4.1' | '1.5.0'

How to migrate:

If your code performs exhaustive checks on SafeVersion, add a case for '1.5.0':


_22
// Before — will cause a TypeScript compilation error
_22
switch (version) {
_22
case '1.0.0': // ...
_22
case '1.1.1': // ...
_22
case '1.2.0': // ...
_22
case '1.3.0': // ...
_22
case '1.4.1': // ...
_22
default:
_22
const \_exhaustive: never = version // ❌ TS error
_22
}
_22
_22
// After — add the new version
_22
switch (version) {
_22
case '1.0.0': // ...
_22
case '1.1.1': // ...
_22
case '1.2.0': // ...
_22
case '1.3.0': // ...
_22
case '1.4.1': // ...
_22
case '1.5.0': // ...
_22
default:
_22
const \_exhaustive: never = version // ✅
_22
}

5. Passkey Verifier Changes

The FCLP256Verifier contract used as the default P256 signature verifier for passkey signers has been deprecated. This release removes the silent default and migrates to DaimoP256Verifier as the new recommended verifier.

As part of this change, the passkey API has been cleaned up to make the verifier address explicit and required at setup time. This prevents a class of bugs where existing passkeys could silently resolve to a wrong signer address if the default ever changed.

PasskeyArgType now requires verifierAddress

The optional customVerifierAddress?: string field has been replaced by a required verifierAddress: string.


_13
// ❌ Before
_13
const passkey: PasskeyArgType = {
_13
rawId: '...',
_13
coordinates: { x: '...', y: '...' },
_13
customVerifierAddress: '0x...' // optional, SDK filled in a default if omitted
_13
}
_13
_13
// ✅ After
_13
const passkey: PasskeyArgType = {
_13
rawId: '...',
_13
coordinates: { x: '...', y: '...' },
_13
verifierAddress: '0x...' // required, no default
_13
}

New type ExtractedPasskeyData

A new type has been introduced to represent the raw output of credential extraction, just rawId and coordinates. PasskeyArgType now extends this type.


_11
// ExtractedPasskeyData — what you get from a WebAuthn credential alone
_11
type ExtractedPasskeyData = {
_11
rawId: string
_11
coordinates: PasskeyCoordinates
_11
}
_11
_11
// PasskeyArgType — what you need to use the passkey as a Safe signer
_11
type PasskeyArgType = ExtractedPasskeyData & {
_11
verifierAddress: string
_11
getFn?: GetPasskeyCredentialFn
_11
}

Safe.createPasskeySigner() return type changed

This method now returns ExtractedPasskeyData instead of PasskeyArgType. You must combine the result with a verifierAddress before passing it to Safe.init().


_10
// ❌ Before — result was directly usable as a signer
_10
const signer = await Safe.createPasskeySigner(credential)
_10
// signer was PasskeyArgType (verifier defaulted silently)
_10
_10
// ✅ After — must add verifierAddress explicitly
_10
const extracted = await Safe.createPasskeySigner(credential)
_10
const signer: PasskeyArgType = {
_10
...extracted,
_10
verifierAddress: getP256VerifierAddress(chainId)
_10
}

getDefaultFCLP256VerifierAddress() removed

This internal function has been replaced by the new public export getP256VerifierAddress(chainId), which returns the DaimoP256Verifier address for a given chain.


_10
// ❌ Before (internal, not recommended for direct use)
_10
import { getDefaultFCLP256VerifierAddress } from '@safe-global/protocol-kit'
_10
const address = getDefaultFCLP256VerifierAddress(chainId)
_10
_10
// ✅ After
_10
import { getP256VerifierAddress } from '@safe-global/protocol-kit'
_10
const address = getP256VerifierAddress(chainId)

chainId parameter removed from createPasskeyClient and isSharedSigner

Both functions no longer accept a chainId argument, as it was only used internally to resolve the default verifier.

We changed the default verifier from FCLP256Verifier to DaimoP256Verifier. If you were relying on the old default, you must now explicitly provide the new verifier address when constructing your passkey signer. You can use @safe-global/safe-deployments (opens in a new tab) v2 to resolve the correct address for your chain, or hard-code it if you prefer.


_12
// ❌ Before
_12
await createPasskeyClient(
_12
passkey,
_12
contract,
_12
provider,
_12
safeAddress,
_12
owners,
_12
chainId
_12
)
_12
_12
// ✅ After
_12
await createPasskeyClient(passkey, contract, provider, safeAddress, owners)

Full Migration Example

Setting up a new passkey signer


_26
import Safe, {
_26
getP256VerifierAddress,
_26
type PasskeyArgType
_26
} from '@safe-global/protocol-kit'
_26
_26
// 1. Create a WebAuthn credential (for example, via navigator.credentials.create)
_26
const credential = await navigator.credentials.create({ publicKey: { ... } })
_26
_26
// 2. Extract the passkey data from the credential
_26
const extracted = await Safe.createPasskeySigner(credential)
_26
_26
// 3. Get the recommended verifier address for your chain
_26
const verifierAddress = getP256VerifierAddress(chainId)
_26
_26
// 4. Build the full PasskeyArgType
_26
const passkeySigner: PasskeyArgType = {
_26
...extracted,
_26
verifierAddress
_26
}
_26
_26
// 5. Use it to initialize a Safe
_26
const protocolKit = await Safe.init({
_26
provider,
_26
signer: passkeySigner,
_26
safeAddress: '0x...'
_26
})

Using a custom verifier

If your deployment uses a custom P256 verifier (for example, for testing or a non-standard chain), pass its address directly:


_10
const passkeySigner: PasskeyArgType = {
_10
...extracted,
_10
verifierAddress: '0xYourCustomVerifierAddress'
_10
}

Reconnecting an existing passkey (stored credentials)

If you store passkey data and reload it across sessions, make sure to persist and restore verifierAddress:


_17
// When saving
_17
localStorage.setItem(
_17
'passkey',
_17
JSON.stringify({
_17
rawId: passkeySigner.rawId,
_17
coordinates: passkeySigner.coordinates,
_17
verifierAddress: passkeySigner.verifierAddress // ← persist this!
_17
})
_17
)
_17
_17
// When loading
_17
const stored = JSON.parse(localStorage.getItem('passkey'))
_17
const passkeySigner: PasskeyArgType = {
_17
rawId: stored.rawId,
_17
coordinates: stored.coordinates,
_17
verifierAddress: stored.verifierAddress // ← restore it
_17
}

⚠️ Important: The verifierAddress is baked into the Safe's on-chain state at passkey setup time. Always restore the original verifier address that was used when the passkey owner was added to the Safe. Using a different address will cause the signer resolution to fail silently.

New Features (non-breaking)

ExtensibleFallbackHandler contract

A new ExtensibleFallbackHandler contract is available for Safe v1.5.0 with read and write methods:


_16
import { getExtensibleFallbackHandlerContract } from '@safe-global/protocol-kit'
_16
_16
const handler = await getExtensibleFallbackHandlerContract({
_16
safeProvider,
_16
safeVersion: '1.5.0'
_16
})
_16
_16
// Read methods
_16
await handler.domainVerifiers(safe, domainSeparator)
_16
await handler.safeMethods(safe, selector)
_16
await handler.safeInterfaces(safe, interfaceId)
_16
_16
// Write methods (executed as Safe transactions)
_16
await handler.setSafeMethod(selector, newMethod)
_16
await handler.setDomainVerifier(domainSeparator, verifier)
_16
await handler.setSupportedInterface(interfaceId, supported)

checkNSignatures with executor parameter

Safe v1.5.0 adds a new overload of checkNSignatures and checkSignatures that accepts an explicit executor address:


_10
// Original (all versions)
_10
await safeContract.checkNSignatures(dataHash, signatures, requiredSignatures)
_10
_10
// New overload (v1.5.0 only)
_10
await safeContract.checkNSignaturesWithExecutor(
_10
executor,
_10
dataHash,
_10
signatures,
_10
requiredSignatures
_10
)

Module Guard support

Safe v1.5.0 introduces module guards. New methods are available in the Safe class:


_10
// Enable a module guard
_10
const tx = await protocolKit.createEnableModuleGuardTx(moduleGuardAddress)
_10
_10
// Disable the module guard
_10
const tx = await protocolKit.createDisableModuleGuardTx()

Note: Module guard functionality is only available for Safe v1.5.0+. Calling these methods on earlier versions will throw an error.

Deprecations

zkSync EraVM support in predictSafeAddress

The zkSync EraVM-specific logic in predictSafeAddress has been deprecated for Safe v1.5.0. If you are predicting Safe addresses on zkSync with v1.5.0 contracts, be aware that this path is no longer supported.

Quick Reference

ChangeAction RequiredPackages
Default version → 1.4.1Pin safeVersion explicitly if you need 1.3.0protocol-kit
encodeTransactionData movedUse CompatibilityFallbackHandler for v1.5.0protocol-kit, types-kit
Legacy isValidSignature removedUse bytes32 overload only for v1.5.0protocol-kit
SafeVersion type extendedAdd '1.5.0' case to exhaustive switchestypes-kit
PasskeyArgType.customVerifierAddress removedUse verifierAddress (required) insteadprotocol-kit
PasskeyArgType.verifierAddress addedProvide verifierAddress when constructing passkey argumentsprotocol-kit
ExtractedPasskeyData type addedUse new type { rawId, coordinates } where applicableprotocol-kit
Safe.createPasskeySigner() return type changedExpect ExtractedPasskeyData instead of PasskeyArgTypeprotocol-kit
getDefaultFCLP256VerifierAddress() removedUse getP256VerifierAddress(chainId) insteadprotocol-kit
getP256VerifierAddress(chainId) addedNew public export to resolve the P256 verifier addressprotocol-kit
createPasskeyClient signature changedRemove chainId argument from call sitesprotocol-kit
isSharedSigner signature changedRemove chainId argument from call sitesprotocol-kit

Was this page helpful?