@b3dotfun/anyspend-sdk
Version:
React Hooks and UI Components for AnySpend by B3
185 lines (160 loc) • 5.34 kB
text/typescript
import { chainIdToPublicClient, getMulticall3Address, isEvmChain } from "@b3dotfun/anyspend-sdk/utils/chain";
import { isNativeToken } from "@b3dotfun/anyspend-sdk/utils/token";
import { useQuery } from "@tanstack/react-query";
import { Address, Hex } from "viem";
import { ABI_USDC_BASE } from "../abis/abi-usdc-base";
export type GetPermitDataParams = {
chainId: number;
tokenAddress: Hex;
ownerAddress: Hex | undefined;
amount: bigint;
};
// EIP-5267 ABI for eip712Domain function
const EIP5267_ABI = [
{
inputs: [],
name: "eip712Domain",
outputs: [
{ name: "fields", type: "bytes1" },
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" },
{ name: "salt", type: "bytes32" }
],
stateMutability: "view",
type: "function"
}
] as const;
export async function getPermitData(p: GetPermitDataParams) {
console.log("Start getting permit data...");
if (!isEvmChain(p.chainId) || isNativeToken(p.tokenAddress) || !p.ownerAddress) {
return {
canPermit: false,
data: null
};
}
const publicClient = chainIdToPublicClient(p.chainId);
const balance = await publicClient.readContract({
address: p.tokenAddress,
abi: ABI_USDC_BASE,
functionName: "balanceOf",
args: [p.ownerAddress]
});
if (balance < p.amount) {
return {
canPermit: false,
data: null
};
}
// Domain parameters
let name: string;
let version: string = "1"; // Default version for EIP-2612 (used by Uniswap V2 reference implementation)
// Try to get domain information using EIP-5267 (eip712Domain function)
try {
const domainData = await publicClient.readContract({
address: p.tokenAddress,
abi: EIP5267_ABI,
functionName: "eip712Domain"
});
console.log("Found EIP-5267 eip712Domain function");
name = domainData[1]; // name is the second return value
version = domainData[2]; // version is the third return value
console.log("Contract domain data from EIP-5267:");
console.log("- name:", name);
console.log("- version:", version);
console.log("- chainId:", domainData[3]);
console.log("- verifyingContract:", domainData[4]);
} catch (error) {
console.log("[EIP-5267] eip712Domain function not found, falling back to individual calls");
// Fallback: get name from contract
name = await publicClient.readContract({
address: p.tokenAddress,
abi: ABI_USDC_BASE,
functionName: "name"
});
// Try to get version from contract
try {
version = await publicClient.readContract({
address: p.tokenAddress,
abi: ABI_USDC_BASE,
functionName: "version"
});
} catch (error) {
console.log(`Function "version" not found, using default version "1" (standard for EIP-2612)`);
}
}
// Fetch the PERMIT_TYPEHASH - important for ensuring our structure matches the contract
const permitTypeHash = await publicClient
.readContract({
address: p.tokenAddress,
abi: ABI_USDC_BASE,
functionName: "PERMIT_TYPEHASH"
})
.catch(() => {
console.log("PERMIT_TYPEHASH not directly accessible, using standard EIP-2612 value");
return "0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9" as `0x${string}`;
});
// Fetch the DOMAIN_SEPARATOR - critical for signature validation
const contractDomainSeparator = await publicClient.readContract({
address: p.tokenAddress,
abi: ABI_USDC_BASE,
functionName: "DOMAIN_SEPARATOR"
});
console.log("Contract name:", name);
console.log("Contract version:", version);
console.log("Contract PERMIT_TYPEHASH:", permitTypeHash);
console.log("Contract DOMAIN_SEPARATOR:", contractDomainSeparator);
// Domain data for EIP-712 signature
const domain = {
name,
version,
chainId: p.chainId,
verifyingContract: p.tokenAddress
};
// EIP-2612 Permit type definition
// This must match the structure used to compute the PERMIT_TYPEHASH in the contract
const PermitType = [
{ name: "owner", type: "address" },
{ name: "spender", type: "address" },
{ name: "value", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" }
];
const multicall3 = getMulticall3Address(p.chainId);
const nonce = await publicClient.readContract({
address: p.tokenAddress,
abi: ABI_USDC_BASE,
functionName: "nonces",
args: [p.ownerAddress]
});
const deadlineInSeconds = BigInt(Math.floor(Date.now() / 1000) + 60 * 60); // 60 minutes
// Permit data to sign
const messageToSign = {
owner: p.ownerAddress,
spender: multicall3 as Address,
value: p.amount,
nonce,
deadline: deadlineInSeconds
};
return {
canPermit: true,
data: {
domain,
types: { Permit: PermitType },
messageToSign
}
};
}
export function usePermitData(p: GetPermitDataParams) {
const { data, isLoading, error, refetch } = useQuery({
queryKey: ["usePermitData", p.chainId, p.tokenAddress],
queryFn: () => getPermitData(p)
});
return {
permitData: data,
isCheckingPermit: isLoading,
checkPermitError: error,
recheckPermit: refetch
};
}