-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
220 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
/// This file is a viem implementation of the useDecodedSafeMessage hook from: | ||
/// https://github.com/safe-global/safe-wallet-web | ||
import { type SafeInfo } from "@safe-global/safe-gateway-typescript-sdk"; | ||
import { gte } from "semver"; | ||
import { | ||
Address, | ||
fromHex, | ||
Hash, | ||
hashMessage, | ||
hashTypedData, | ||
isHex, | ||
TypedDataDomain, | ||
} from "viem"; | ||
|
||
interface TypedDataTypes { | ||
name: string; | ||
type: string; | ||
} | ||
type TypedMessageTypes = { | ||
[key: string]: TypedDataTypes[]; | ||
}; | ||
|
||
export type EIP712TypedData = { | ||
domain: TypedDataDomain; | ||
types: TypedMessageTypes; | ||
message: Record<string, unknown>; | ||
primaryType: string; | ||
}; | ||
|
||
export type MinimalSafeInfo = Pick<SafeInfo, "address" | "version" | "chainId">; | ||
|
||
/* | ||
* From v1.3.0, EIP-1271 support was moved to the CompatibilityFallbackHandler. | ||
* Also 1.3.0 introduces the chainId in the domain part of the SafeMessage | ||
*/ | ||
const EIP1271_FALLBACK_HANDLER_SUPPORTED_SAFE_VERSION = "1.3.0"; | ||
|
||
const generateSafeMessageMessage = ( | ||
message: string | EIP712TypedData | ||
): string => { | ||
return typeof message === "string" | ||
? hashMessage(message) | ||
: hashTypedData(message); | ||
}; | ||
|
||
/** | ||
* Generates `SafeMessage` typed data for EIP-712 | ||
* https://github.com/safe-global/safe-contracts/blob/main/contracts/handler/CompatibilityFallbackHandler.sol#L12 | ||
* @param safe Safe which will sign the message | ||
* @param message Message to sign | ||
* @returns `SafeMessage` types for signing | ||
*/ | ||
const generateSafeMessageTypedData = ( | ||
{ version, chainId, address }: MinimalSafeInfo, | ||
message: string | EIP712TypedData | ||
): EIP712TypedData => { | ||
if (!version) { | ||
throw Error("Cannot create SafeMessage without version information"); | ||
} | ||
const isHandledByFallbackHandler = gte( | ||
version, | ||
EIP1271_FALLBACK_HANDLER_SUPPORTED_SAFE_VERSION | ||
); | ||
const verifyingContract = address.value as Address; | ||
return { | ||
domain: isHandledByFallbackHandler | ||
? { | ||
chainId: Number(BigInt(chainId)), | ||
verifyingContract, | ||
} | ||
: { verifyingContract }, | ||
types: { | ||
SafeMessage: [{ name: "message", type: "bytes" }], | ||
}, | ||
message: { | ||
message: generateSafeMessageMessage(message), | ||
}, | ||
primaryType: "SafeMessage", | ||
}; | ||
}; | ||
|
||
const generateSafeMessageHash = ( | ||
safe: MinimalSafeInfo, | ||
message: string | EIP712TypedData | ||
): Hash => { | ||
const typedData = generateSafeMessageTypedData(safe, message); | ||
return hashTypedData(typedData); | ||
}; | ||
|
||
/** | ||
* If message is a hex value and is Utf8 encoded string we decode it, else we return the raw message | ||
* @param {string} message raw input message | ||
* @returns {string} | ||
*/ | ||
const getDecodedMessage = (message: string): string => { | ||
if (isHex(message)) { | ||
try { | ||
return fromHex(message, "string"); | ||
} catch (e) { | ||
// the hex string is not UTF8 encoding so return the raw message. | ||
} | ||
} | ||
|
||
return message; | ||
}; | ||
|
||
/** | ||
* Returns the decoded message, the hash of the `message` and the hash of the `safeMessage`. | ||
* The `safeMessageMessage` is the value inside the SafeMessage and the `safeMessageHash` gets signed if the connected wallet does not support `eth_signTypedData`. | ||
* | ||
* @param message message as string, UTF-8 encoded hex string or EIP-712 Typed Data | ||
* @param safe SafeInfo of the opened Safe | ||
* @returns `{ | ||
* decodedMessage, | ||
* safeMessageMessage, | ||
* safeMessageHash | ||
* }` | ||
*/ | ||
export function decodedSafeMessage( | ||
message: string | EIP712TypedData, | ||
safe: MinimalSafeInfo | ||
): { | ||
decodedMessage: string | EIP712TypedData; | ||
safeMessageMessage: string; | ||
safeMessageHash: Hash; | ||
} { | ||
const decodedMessage = | ||
typeof message === "string" ? getDecodedMessage(message) : message; | ||
|
||
return { | ||
decodedMessage, | ||
safeMessageMessage: generateSafeMessageMessage(decodedMessage), | ||
safeMessageHash: generateSafeMessageHash(safe, decodedMessage), | ||
}; | ||
} | ||
|
||
// const isEIP712TypedData = (obj: any): obj is EIP712TypedData => { | ||
// return ( | ||
// typeof obj === "object" && | ||
// obj != null && | ||
// "domain" in obj && | ||
// "types" in obj && | ||
// "message" in obj | ||
// ); | ||
// }; | ||
|
||
// export const isBlindSigningPayload = (obj: EIP712TypedData | string): boolean => | ||
// !isEIP712TypedData(obj) && isHash(obj); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { zeroAddress } from "viem"; | ||
|
||
import { decodedSafeMessage } from "../../src/lib/safe-message"; | ||
|
||
describe("Multisend", () => { | ||
const plainMessage = `Welcome to OpenSea! | ||
Click to sign in and accept the OpenSea Terms of Service (https://opensea.io/tos) and Privacy Policy (https://opensea.io/privacy). | ||
This request will not trigger a blockchain transaction or cost any gas fees. | ||
Wallet address: | ||
0xdcf56f5a8cc380f63b6396dbddd0ae9fa605beee | ||
Nonce: | ||
2a29a96e-c741-4500-9de3-03a865ff05db`; | ||
const safeInfo = { | ||
address: { | ||
value: "0xDcf56F5a8Cc380f63b6396Dbddd0aE9fa605BeeE", | ||
}, | ||
chainId: "11155111", | ||
version: "1.4.1+L2", | ||
}; | ||
it("decodeSafeMessage", () => { | ||
expect(decodedSafeMessage(plainMessage, safeInfo)).toStrictEqual({ | ||
decodedMessage: plainMessage, | ||
safeMessageMessage: | ||
"0xc90ef7cffa3b5b1422e6c49ca7a5d7c1e9f514db067ec9bad52db13e83cbbb7c", | ||
safeMessageHash: | ||
"0x19dbea8af895c61831f2830ebba00d6160e4527398ec1d88553a8f0b8318959d", | ||
}); | ||
// Lower Safe Version. | ||
expect( | ||
decodedSafeMessage(plainMessage, { ...safeInfo, version: "1.2.1" }) | ||
).toStrictEqual({ | ||
decodedMessage: plainMessage, | ||
safeMessageMessage: | ||
"0xc90ef7cffa3b5b1422e6c49ca7a5d7c1e9f514db067ec9bad52db13e83cbbb7c", | ||
safeMessageHash: | ||
"0x58daaab88459f40802201741918791f85cb81a435328168ee6a1eaa735442809", | ||
}); | ||
}); | ||
|
||
it("decodeSafeMessage", () => { | ||
const versionlessSafeInfo = { | ||
address: { value: zeroAddress }, | ||
chainId: "1", | ||
version: null, | ||
}; | ||
expect(() => decodedSafeMessage(plainMessage, versionlessSafeInfo)).toThrow( | ||
"Cannot create SafeMessage without version information" | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters