diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs
index 864e6e2f..b93a7715 100644
--- a/ethers-core/src/utils/mod.rs
+++ b/ethers-core/src/utils/mod.rs
@@ -121,6 +121,53 @@ pub fn get_contract_address(sender: impl Into
, nonce: impl Into)
/// [EIP1014](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1014.md)
///
/// keccak256( 0xff ++ senderAddress ++ salt ++ keccak256(init_code))[12..]
+///
+/// # Example
+///
+/// Calculate the address of a UniswapV3 pool.
+///
+/// ```
+/// use ethers_core::{
+/// abi,
+/// abi::Token,
+/// types::{Address, Bytes, U256},
+/// utils::{get_create2_address, keccak256},
+/// };
+///
+/// // We substitute some arbitrary short init code for brevity. The real
+/// // pool init code can be found under "Contract Creation Code" on etherscan:
+/// // https://etherscan.io/address/0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640/advanced#code
+/// let UNISWAP_V3_POOL_INIT_CODE = Bytes::from(hex::decode("610160604052").unwrap());
+/// let factory: Address = "0x1F98431c8aD98523631AE4a59f267346ea31F984"
+/// .parse()
+/// .unwrap();
+/// let token0: Address = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
+/// .parse()
+/// .unwrap();
+/// let token1: Address = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
+/// .parse()
+/// .unwrap();
+/// let fee = 500;
+///
+/// let input = abi::encode(&vec![
+/// Token::Address(token0),
+/// Token::Address(token1),
+/// Token::Uint(U256::from(fee)),
+/// ]);
+///
+/// // keccak256(abi.encode(token0, token1, fee))
+/// let salt = keccak256(&input);
+/// let pool_address = get_create2_address(factory, salt.to_vec(), UNISWAP_V3_POOL_INIT_CODE);
+///
+/// // Actual USDC/ETH pool address (created with proper init code):
+/// // 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640
+/// assert_eq!(
+/// pool_address,
+/// "0x43953f76983c3ee678bb7a23b4e9eb813d6508b4"
+/// .parse()
+/// .unwrap()
+/// );
+/// ```
pub fn get_create2_address(
from: impl Into,
salt: impl Into,
diff --git a/examples/permit_hash.rs b/examples/permit_hash.rs
new file mode 100644
index 00000000..db6c4cc0
--- /dev/null
+++ b/examples/permit_hash.rs
@@ -0,0 +1,94 @@
+use ethers::{
+ abi,
+ abi::Token,
+ prelude::U256,
+ types::Address,
+ utils,
+ utils::{keccak256, parse_ether},
+};
+
+const UNISWAP_V2_USDC_ETH_PAIR: &'static str = "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc";
+
+// Generate the EIP712 permit hash to sign for a Uniswap V2 pair.
+// https://eips.ethereum.org/EIPS/eip-712
+// https://eips.ethereum.org/EIPS/eip-2612
+fn main() {
+ // Test data
+ let owner: Address = "0x617072Cb2a1897192A9d301AC53fC541d35c4d9D"
+ .parse()
+ .unwrap();
+ let spender: Address = "0x2819c144D5946404C0516B6f817a960dB37D4929"
+ .parse()
+ .unwrap();
+ let value = parse_ether(10).unwrap();
+ let nonce = U256::from(1);
+ let deadline = U256::from(3133728498 as u32);
+ let verifying_contract: Address = UNISWAP_V2_USDC_ETH_PAIR.parse().unwrap();
+ let name = "Uniswap V2";
+ let version = "1";
+ let chainid = 1;
+
+ // Typehash for the permit() function
+ let permit_typehash = utils::keccak256(
+ "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)",
+ );
+ // Typehash for the struct used to generate the domain separator
+ let domain_typehash = keccak256(
+ "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)",
+ );
+
+ // Corresponds to solidity's abi.encode()
+ let domain_separator_input = abi::encode(&vec![
+ Token::Uint(U256::from(domain_typehash)),
+ Token::Uint(U256::from(keccak256(&name))),
+ Token::Uint(U256::from(keccak256(&version))),
+ Token::Uint(U256::from(chainid)),
+ Token::Address(verifying_contract),
+ ]);
+
+ // Corresponds to the following solidity:
+ // DOMAIN_SEPARATOR = keccak256(
+ // abi.encode(
+ // keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
+ // keccak256(bytes(name)),
+ // keccak256(bytes('1')),
+ // chainId,
+ // address(this)
+ // )
+ // );
+ let domain_separator = keccak256(&domain_separator_input);
+
+ // Corresponds to solidity's abi.encode()
+ let struct_input = abi::encode(&vec![
+ Token::Uint(U256::from(permit_typehash)),
+ Token::Address(owner),
+ Token::Address(spender),
+ Token::Uint(value),
+ Token::Uint(nonce),
+ Token::Uint(deadline),
+ ]);
+ let struct_hash = keccak256(&struct_input);
+
+ // Corresponds to solidity's abi.encodePacked()
+ let digest_input = [
+ &[0x19, 0x01],
+ domain_separator.as_ref(),
+ struct_hash.as_ref(),
+ ]
+ .concat();
+
+ // Matches the following solidity:
+ // bytes32 digest = keccak256(
+ // abi.encodePacked(
+ // '\x19\x01',
+ // DOMAIN_SEPARATOR,
+ // keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
+ // )
+ // );
+ let permit_hash = keccak256(&digest_input);
+
+ assert_eq!(
+ hex::encode(permit_hash),
+ "7b90248477de48c0b971e0af8951a55974733455191480e1e117c86cc2a6cd03"
+ );
+}