diff --git a/.changeset/lovely-parrots-yawn.md b/.changeset/lovely-parrots-yawn.md new file mode 100644 index 0000000000..d5544e5cca --- /dev/null +++ b/.changeset/lovely-parrots-yawn.md @@ -0,0 +1,5 @@ +--- +"viem": patch +--- + +Support sign typed data signatures for coinbase smart wallets diff --git a/src/account-abstraction/accounts/implementations/toCoinbaseSmartAccount.ts b/src/account-abstraction/accounts/implementations/toCoinbaseSmartAccount.ts index 22551ee3f7..b0523e2cff 100644 --- a/src/account-abstraction/accounts/implementations/toCoinbaseSmartAccount.ts +++ b/src/account-abstraction/accounts/implementations/toCoinbaseSmartAccount.ts @@ -184,6 +184,18 @@ export async function toCoinbaseSmartAccount( }) if (owner.type === 'address') throw new Error('owner cannot sign') + // Try to sign with signTypedData first if the owner has this capability + const typedDataSignature = await signWithTypedData({ + hash, + owner, + chainId: client.chain!.id, + }) + if (typedDataSignature) { + return wrapSignature({ + ownerIndex, + signature: typedDataSignature, + }) + } const signature = await sign({ hash, owner }) return wrapSignature({ @@ -195,14 +207,26 @@ export async function toCoinbaseSmartAccount( async signMessage(parameters) { const { message } = parameters const address = await this.getAddress() + if (owner.type === 'address') throw new Error('owner cannot sign') + + // Try to sign with signTypedData first if the owner has this capability + const typedDataSignature = await signWithTypedData({ + hash: hashMessage(message), + owner, + chainId: client.chain!.id, + }) + if (typedDataSignature) { + return wrapSignature({ + ownerIndex, + signature: typedDataSignature, + }) + } const hash = toReplaySafeHash({ address, chainId: client.chain!.id, hash: hashMessage(message), }) - - if (owner.type === 'address') throw new Error('owner cannot sign') const signature = await sign({ hash, owner }) return wrapSignature({ @@ -216,6 +240,26 @@ export async function toCoinbaseSmartAccount( parameters as TypedDataDefinition const address = await this.getAddress() + if (owner.type === 'address') throw new Error('owner cannot sign') + + // Try to sign with signTypedData first if the owner has this capability + const typedDataSignature = await signWithTypedData({ + hash: hashTypedData({ + domain, + message, + primaryType, + types, + }), + owner, + chainId: client.chain!.id, + }) + if (typedDataSignature) { + return wrapSignature({ + ownerIndex, + signature: typedDataSignature, + }) + } + const hash = toReplaySafeHash({ address, chainId: client.chain!.id, @@ -226,8 +270,6 @@ export async function toCoinbaseSmartAccount( types, }), }) - - if (owner.type === 'address') throw new Error('owner cannot sign') const signature = await sign({ hash, owner }) return wrapSignature({ @@ -251,6 +293,18 @@ export async function toCoinbaseSmartAccount( }) if (owner.type === 'address') throw new Error('owner cannot sign') + // Try to sign with signTypedData first if the owner has this capability + const typedDataSignature = await signWithTypedData({ + hash, + owner, + chainId: client.chain!.id, + }) + if (typedDataSignature) { + return wrapSignature({ + ownerIndex, + signature: typedDataSignature, + }) + } const signature = await sign({ hash, owner }) return wrapSignature({ @@ -278,6 +332,63 @@ export async function toCoinbaseSmartAccount( // Utilities ///////////////////////////////////////////////////////////////////////////////////////////// +/** @internal */ +function getCoinbaseSmartWalletTypedData({ + address, + chainId, + hash, +}: { + address: Address + chainId: number + hash: Hash +}) { + return { + domain: { + chainId, + name: 'Coinbase Smart Wallet', + verifyingContract: address, + version: '1', + }, + types: { + CoinbaseSmartWalletMessage: [ + { + name: 'hash', + type: 'bytes32', + }, + ], + }, + primaryType: 'CoinbaseSmartWalletMessage' as const, + message: { + hash, + }, + } +} + +/** @internal */ +export async function signWithTypedData({ + hash, + owner, + chainId, +}: { + hash: Hash + owner: OneOf + chainId: number +}): Promise { + // Check if this is a non-WebAuthn account that supports signTypedData + if (owner.type !== 'webAuthn' && owner.signTypedData) { + const typedData = getCoinbaseSmartWalletTypedData({ + address: owner.address, + chainId, + hash, + }) + + return await owner.signTypedData(typedData) + } + + // Return undefined if we can't handle this type of signature + return undefined +} + /** @internal */ export async function sign({ hash, @@ -302,26 +413,9 @@ export function toReplaySafeHash({ chainId, hash, }: { address: Address; chainId: number; hash: Hash }) { - return hashTypedData({ - domain: { - chainId, - name: 'Coinbase Smart Wallet', - verifyingContract: address, - version: '1', - }, - types: { - CoinbaseSmartWalletMessage: [ - { - name: 'hash', - type: 'bytes32', - }, - ], - }, - primaryType: 'CoinbaseSmartWalletMessage', - message: { - hash, - }, - }) + return hashTypedData( + getCoinbaseSmartWalletTypedData({ address, chainId, hash }), + ) } /** @internal */