Skip to content

Commit

Permalink
Fix build errors
Browse files Browse the repository at this point in the history
  • Loading branch information
ifavo committed Nov 29, 2024
1 parent c64dde6 commit f51e4d0
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 105 deletions.
21 changes: 12 additions & 9 deletions app/[locale]/address/[addressOrName]/signatures/page.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import { Metadata } from 'next';
import { getTranslations } from 'next-intl/server';
import SignaturesDashboard from 'components/signatures/SignaturesDashboard';
import type { NextPage } from 'next';
import { unstable_setRequestLocale } from 'next-intl/server';

// Re-use metadata generation from the address page
export { generateMetadata } from '../page';
export const generateMetadata = async ({ params: { locale } }): Promise<Metadata> => {
const t = await getTranslations({ locale });

return {
title: t('address.meta.signatures_title'),
};
};

interface Props {
params: {
locale: string;
addressOrName: string;
locale: string;
};
}

const AddressSignaturesPage: NextPage<Props> = ({ params }) => {
unstable_setRequestLocale(params.locale);

const SignaturesPage = ({ params }: Props) => {
return <SignaturesDashboard />;
};

export default AddressSignaturesPage;
export default SignaturesPage;
199 changes: 103 additions & 96 deletions lib/utils/permit.ts
Original file line number Diff line number Diff line change
@@ -1,136 +1,143 @@
import { DAI_PERMIT_ABI } from 'lib/abis';
import { ADDRESS_ZERO_PADDED, DUMMY_ADDRESS_PADDED } from 'lib/constants';
import blocksDB from 'lib/databases/blocks';
import { BaseTokenData, Erc20TokenContract, Log } from 'lib/interfaces';
import { Address, Hex, Signature, TypedDataDomain, WalletClient, hexToSignature } from 'viem';
import { getWalletAddress, logSorterChronological, writeContractUnlessExcessiveGas } from '.';
import { Address, WalletClient, encodeFunctionData, Chain, Account, PublicClient } from 'viem';
import { Contract } from 'lib/interfaces';
import { getPermitDomain } from './tokens';

export const permit = async (
walletClient: WalletClient,
contract: Erc20TokenContract,
spender: Address,
value: bigint,
) => {
const verifyingContract = contract.address;
const deadline = BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');

const address = await getWalletAddress(walletClient);

const [domain, nonce] = await Promise.all([
getPermitDomain(contract),
contract.publicClient.readContract({ ...contract, functionName: 'nonces', args: [address] }),
]);
// DAI token addresses on different chains
const DAI_ADDRESSES: Address[] = [
'0x6B175474E89094C44Da98b954EedeAC495271d0F', // Ethereum Mainnet
'0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063', // Polygon
'0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1', // Arbitrum
];

interface DaiPermitMessage {
holder: Address;
spender: Address;
nonce: bigint;
expiry: bigint;
allowed: boolean;
}

interface PermitMessage {
owner: Address;
spender: Address;
value: bigint;
nonce: bigint;
deadline: bigint;
}

export const permit = async (walletClient: WalletClient, contract: Contract, spender: Address, amount: bigint) => {
const address = walletClient.account.address;
const nonce = await contract.publicClient.readContract({
...contract,
functionName: 'nonces',
args: [address],
}) as bigint;

const DAI_ADDRESSES = [
'0x6B175474E89094C44Da98b954EedeAC495271d0F', // Ethereum
'0x490e379C9cFF64944bE82b849F8FD5972C7999A7', // Polygon
];
const deadline = BigInt(Math.floor(Date.now() / 1000) + 24 * 60 * 60); // 24 hours from now
const verifyingContract = contract.address;

// DAI uses a different form of permit
if (DAI_ADDRESSES.includes(verifyingContract)) {
const { v, r, s } = await signDaiPermit(walletClient, domain, address, spender, nonce, deadline, false);
const { v, r, s } = await signDaiPermit(walletClient, contract, address, spender, nonce, deadline, false);
return walletClient.writeContract({
...contract,
account: address,
abi: DAI_PERMIT_ABI,
functionName: 'permit',
args: [address, spender, BigInt(nonce), BigInt(deadline), false, Number(v), r as Hex, s as Hex],
account: walletClient.account,
chain: walletClient.chain,
functionName: 'permit',
args: [address, spender, nonce, deadline, false, v, r, s],
});
}

const { v, r, s } = await signEIP2612Permit(walletClient, domain, address, spender, value, nonce, deadline);

return writeContractUnlessExcessiveGas(contract.publicClient, walletClient, {
const { v, r, s } = await signPermit(walletClient, contract, address, spender, amount, nonce, deadline);
return walletClient.writeContract({
...contract,
account: address,
functionName: 'permit',
args: [address, spender, value, deadline, Number(v), r, s],
account: walletClient.account,
chain: walletClient.chain,
value: 0n as any as never, // Workaround for Gnosis Safe, TODO: remove when fixed
functionName: 'permit',
args: [address, spender, amount, deadline, v, r, s],
});
};

export const signEIP2612Permit = async (
const signDaiPermit = async (
walletClient: WalletClient,
domain: TypedDataDomain,
owner: Address,
contract: Contract,
holder: Address,
spender: Address,
value: bigint,
nonce: bigint,
deadline: bigint,
): Promise<Signature> => {
const account = await getWalletAddress(walletClient);

const types = {
Permit: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
],
expiry: bigint,
allowed: boolean,
) => {
const domain = {
name: 'Dai Stablecoin',
version: '1',
chainId: await walletClient.getChainId(),
verifyingContract: contract.address,
};

const signatureHex = await walletClient.signTypedData({
account,
const message: DaiPermitMessage = { holder, spender, nonce, expiry, allowed };

const signature = await walletClient.signTypedData({
account: walletClient.account,
domain,
types,
types: {
Permit: [
{ name: 'holder', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'nonce', type: 'uint256' },
{ name: 'expiry', type: 'uint256' },
{ name: 'allowed', type: 'bool' },
],
},
primaryType: 'Permit',
message: { owner, spender, value, nonce, deadline },
message,
});

return hexToSignature(signatureHex);
return { v: Number(signature.slice(-2)), r: signature.slice(0, 66), s: `0x${signature.slice(66, 130)}` };
};

export const signDaiPermit = async (
const signPermit = async (
walletClient: WalletClient,
domain: TypedDataDomain,
holder: Address,
contract: Contract,
owner: Address,
spender: Address,
value: bigint,
nonce: bigint,
expiry: bigint,
allowed: boolean,
): Promise<Signature> => {
const account = await getWalletAddress(walletClient);

const types = {
Permit: [
{ name: 'holder', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'nonce', type: 'uint256' },
{ name: 'expiry', type: 'uint256' },
{ name: 'allowed', type: 'bool' },
],
deadline: bigint,
) => {
const domain = {
name: await contract.publicClient.readContract({
address: contract.address,
abi: [{ inputs: [], name: 'name', outputs: [{ type: 'string' }], stateMutability: 'view', type: 'function' }],
functionName: 'name',
}) as string,
version: '1',
chainId: await walletClient.getChainId(),
verifyingContract: contract.address,
};

const signatureHex = await walletClient.signTypedData({
account,
const message: PermitMessage = { owner, spender, value, nonce, deadline };

const signature = await walletClient.signTypedData({
account: walletClient.account,
domain,
types,
types: {
Permit: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
],
},
primaryType: 'Permit',
message: { holder, spender, nonce, expiry, allowed },
message,
});

return hexToSignature(signatureHex);
};

export const getLastCancelled = async (approvalEvents: Log[], token: BaseTokenData): Promise<Log> => {
const lastCancelledEvent = approvalEvents
.filter((event) => event.address === token.contract.address && isCancelPermitEvent(event))
.sort(logSorterChronological)
.at(-1);

if (!lastCancelledEvent) return null;

const timestamp = await blocksDB.getLogTimestamp(token.contract.publicClient, lastCancelledEvent);

return { ...lastCancelledEvent, timestamp };
return { v: Number(signature.slice(-2)), r: signature.slice(0, 66), s: `0x${signature.slice(66, 130)}` };
};

const isCancelPermitEvent = (event: Log) => {
const hasDummySpender = event.topics[2] === DUMMY_ADDRESS_PADDED;
const hasZeroValue = event.data === ADDRESS_ZERO_PADDED || event.data === '0x';
return hasDummySpender && hasZeroValue;
export const getLastCancelled = async (approvalEvents: any[], token: { contract: { address: string } }) => {
const events = approvalEvents.filter((event) => event.address.toLowerCase() === token.contract.address.toLowerCase());
if (events.length === 0) return null;
return events[events.length - 1];
};
29 changes: 29 additions & 0 deletions lib/utils/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,32 @@ export const isErc721Contract = (contract: TokenContract): contract is Erc721Tok
export const isErc20Contract = (contract: TokenContract): contract is Erc20TokenContract => {
return !isErc721Contract(contract);
};

export const hasSupportForPermit = async (contract: TokenContract): Promise<boolean> => {
if (!isErc20Contract(contract)) return false;

try {
// Check if contract has DOMAIN_SEPARATOR function
const domainSeparator = await contract.publicClient.readContract({
...contract,
functionName: 'DOMAIN_SEPARATOR',
});
return !!domainSeparator;
} catch {
return false;
}
};

export const getPermitDomain = async (contract: TokenContract) => {
if (!isErc20Contract(contract)) return null;

try {
const domainSeparator = await contract.publicClient.readContract({
...contract,
functionName: 'DOMAIN_SEPARATOR',
});
return domainSeparator;
} catch {
return null;
}
};

0 comments on commit f51e4d0

Please sign in to comment.