Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/lovely-parrots-yawn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"viem": patch
---

Support sign typed data signatures for coinbase smart wallets
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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({
Expand All @@ -216,6 +240,26 @@ export async function toCoinbaseSmartAccount(
parameters as TypedDataDefinition<TypedData, string>
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,
Expand All @@ -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({
Expand All @@ -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({
Expand Down Expand Up @@ -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<LocalAccount | WebAuthnAccount>
chainId: number
}): Promise<Hex | undefined> {
// 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,
Expand All @@ -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 */
Expand Down
Loading