Skip to content

Instantly share code, notes, and snippets.

@sarvagnakadiya
Created January 27, 2025 08:04
Show Gist options
  • Save sarvagnakadiya/b0d8b12c1c31f88354c69e490f188b11 to your computer and use it in GitHub Desktop.
Save sarvagnakadiya/b0d8b12c1c31f88354c69e490f188b11 to your computer and use it in GitHub Desktop.
campaign.tsx file with metaTransaction
"use client";
import { Button } from "@/components/ui/Button";
import { signOut } from "next-auth/react";
import { useCallback, useState } from "react";
import { useRouter } from "next/navigation";
import { ethers } from "ethers";
import CampaignsNativeGaslessClaim from "@/lib/abi/CampaignsNativeGaslessClaim.json";
import {
useAccount,
useChainId,
useSendTransaction,
useSwitchChain,
} from "wagmi";
import { base } from "viem/chains";
// interface MetaTxTypeData {
// nonce: bigint;
// userAddress: string;
// contractAddress: string;
// chainId: number;
// domainName: string;
// domainVersion: string;
// functionSignature: string;
// }
// Add this utility function at the top level
const serverLog = async (message: string, data?: any) => {
try {
await fetch("/api/log", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ message, data }),
});
} catch (error) {
// Fallback to client console in case of fetch error
console.error("Failed to send log to server:", error);
}
};
// Add this utility function at the top level
const getEIP712TypedData = ({
campaignManager,
campaignId,
claimer,
contractAddress,
chainId,
domainName,
domainVersion,
}: {
campaignManager: string;
campaignId: number;
claimer: string;
contractAddress: string;
chainId: number;
domainName: string;
domainVersion: string;
}) => {
const domain = {
name: domainName,
version: domainVersion,
chainId,
verifyingContract: contractAddress,
};
const types = {
Claim: [
{ name: "campaignManager", type: "address" },
{ name: "campaignId", type: "uint256" },
{ name: "claimer", type: "address" },
],
};
const value = {
campaignManager,
campaignId,
claimer,
};
return { domain, types, value };
};
const Campaigns_generateClaimSignature = async (
campaigns: `0x${string}`,
campaignManager: string,
campaignId: number,
claimer: string,
chainId: number,
trustedAddress: ethers.Wallet
) => {
const { domain, types, value } = getEIP712TypedData({
campaignManager,
campaignId,
claimer,
contractAddress: campaigns,
chainId: chainId,
domainName: "CampaignsNativeGaslessClaim",
domainVersion: "1.0",
});
const rawSignature: string = await trustedAddress.signTypedData(
domain,
types,
value
);
return ethers.Signature.from(rawSignature);
};
export default function Campaign() {
const { address, isConnected } = useAccount();
const {
sendTransaction,
data,
error: sendTxError,
isError: isSendTxError,
isPending: isSendTxPending,
} = useSendTransaction();
const [txHash, setTxHash] = useState<string | null>(null);
const router = useRouter();
const chainId = useChainId();
const {
switchChain,
error: switchChainError,
isError: isSwitchChainError,
isPending: isSwitchChainPending,
} = useSwitchChain();
const handleSwitchChainIfNeeded = useCallback(async () => {
await serverLog("current chainId", { chainId });
if (chainId !== base.id) {
await serverLog("Switching chain inside if", { chainId });
try {
await serverLog("Switching chain inside try", { chainId });
switchChain({ chainId: base.id });
} catch (error) {
console.error("Error switching chain:", error);
return false;
}
}
return true;
}, [chainId, switchChain]);
const handleSwitchChainId = useCallback(async () => {
await serverLog("currentchainId", chainId);
try {
switchChain({ chainId: base.id });
await serverLog("chain switched");
} catch (error) {
await serverLog("Error switching to baseTestnet:", error);
}
}, [switchChain]);
const handleSend = useCallback(() => {
sendTransaction({
to: "0x542FfB7d78D78F957895891B6798B3d60e979b64",
value: BigInt(1),
});
}, [sendTransaction]);
const handleClaimCampaign = useCallback(async () => {
const campaignManager = "0xEA380ddC224497dfFe5871737E12136d3968af15";
const campaignId = 0;
const referrer = "0x0000000000000000000000000000000000000000";
await serverLog("Claim Campaign Data", {
campaignManager,
campaignId,
referrer,
});
const provider = new ethers.JsonRpcProvider(
"https://base-mainnet.g.alchemy.com/v2/9-2O3J1H0d0Z-xDdDwZHHCBM2mwzVMwT"
);
const campaigns_cobj = new ethers.Contract(
"0x542FfB7d78D78F957895891B6798B3d60e979b64",
CampaignsNativeGaslessClaim,
provider
);
if (campaigns_cobj && provider) {
await serverLog("provider & campaigns done");
}
const v = 27;
const r =
"0x29e3ca3a9d56c30a40d7b0ed455da27123b61e194605560a41b30dbc45973c8e";
const s =
"0x41c7ef79a97eb68a6dde6e3b2834ed6eb4c5321547b23781714c8992007c5a3e";
await serverLog("Claim Campaign Data", {
campaignManager,
campaignId,
r,
s,
v,
referrer,
});
const data = campaigns_cobj.interface.encodeFunctionData("claim", [
campaignManager,
campaignId,
r,
s,
v,
referrer,
]) as `0x${string}`;
await serverLog("Claim Campaign Data", {
campaignManager,
campaignId,
r,
s,
v,
referrer,
});
sendTransaction(
{
to: campaigns_cobj.target as `0x${string}`,
data: data,
value: BigInt(150000000000000),
},
{
onSuccess: (hash) => {
console.log("Claim transaction hash:", hash);
serverLog("Claim transaction hash", { hash });
},
}
);
}, [sendTransaction, address]);
// typeHash: 0x23d10def3caacba2e4042e0c75d44a42d2558aabcf5ce951d0642a8032e1e653
// const getNativeMetaTxTypeData = ({
// nonce,
// userAddress,
// contractAddress,
// chainId,
// domainName,
// domainVersion,
// functionSignature,
// }: MetaTxTypeData) => {
// return {
// domain: {
// name: domainName,
// version: domainVersion,
// chainId: chainId,
// verifyingContract: contractAddress,
// },
// types: {
// MetaTransaction: [
// { name: "nonce", type: "uint256" },
// { name: "from", type: "address" },
// { name: "functionSignature", type: "bytes" },
// ],
// },
// value: {
// nonce: parseInt(String(nonce)),
// from: userAddress,
// functionSignature: functionSignature,
// },
// };
// };
// const handleCreateCampaign = async () => {
// try {
// // -------- getting relayer signer --------
// const RELAYER_PRIVATE_KEY = process.env.NEXT_PUBLIC_RELAYER_PRIVATE_KEY;
// const CREATOR_PRIVATE_KEY = process.env.NEXT_PUBLIC_CREATOR_PRIVATE_KEY;
// const provider = new ethers.JsonRpcProvider(
// process.env.NEXT_PUBLIC_RPC_URL
// );
// if (!RELAYER_PRIVATE_KEY) {
// throw new Error("Relayer private key is not defined");
// }
// if (!CREATOR_PRIVATE_KEY) {
// throw new Error("creator private key is not defined");
// }
// const relayerSigner = new ethers.Wallet(RELAYER_PRIVATE_KEY, provider);
// // 0x6479ff62F767d67c255a61d5c2DcBF4f0Cc45d02
// const creatorSigner = new ethers.Wallet(CREATOR_PRIVATE_KEY, provider);
// await serverLog("Relayer address", { address: relayerSigner.address });
// await serverLog("Provider & signer done----");
// // Campaign parameters
// const args = {
// _tokenAddress: "0x2246A41B6efB730A3845012EF8eBE6bc1D367A79",
// _maxClaims: BigInt(100),
// _amountPerClaim: BigInt(10000000),
// _isGasless: BigInt(1),
// _maxSponsoredClaims: BigInt(50),
// };
// await serverLog("Args", args);
// // Get contract instance (you'll need to add your contract ABI and address)
// const campaignsNativeGaslessClaim = new ethers.Contract(
// "0x2A942c4216857ec55216F28e82B8de7dc33FFba1",
// CampaignsNativeGaslessClaim,
// provider
// );
// const campaignsNativeGaslessClaim2 = new ethers.Contract(
// "0x2A942c4216857ec55216F28e82B8de7dc33FFba1",
// CampaignsNativeGaslessClaim,
// relayerSigner
// );
// await serverLog("CampaignsNativeGaslessClaim contract instance done----");
// const nonce = await campaignsNativeGaslessClaim.getNonce(
// creatorSigner.address
// );
// await serverLog("Nonce", nonce);
// const network = await provider.getNetwork();
// await serverLog("Network", network);
// await serverLog("ChainId", chainId);
// await serverLog("Nonce & chainId done----");
// const functionSignature =
// campaignsNativeGaslessClaim.interface.encodeFunctionData(
// "createCampaign",
// [
// args._tokenAddress,
// args._maxClaims,
// args._amountPerClaim,
// args._isGasless,
// args._maxSponsoredClaims,
// ]
// );
// await serverLog("FunctionSignature", functionSignature);
// const { domain, types, value } = getNativeMetaTxTypeData({
// nonce,
// userAddress: creatorSigner.address,
// contractAddress: campaignsNativeGaslessClaim.target as string,
// chainId: 84532,
// domainName: "CampaignsNativeGaslessClaim",
// domainVersion: "1.0",
// functionSignature,
// });
// await serverLog("Address", relayerSigner.address);
// await serverLog("creatorAddress", creatorSigner.address);
// await serverLog("MetaTxTypeData done----");
// // Sign the typed data
// const signature = await creatorSigner.signTypedData(domain, types, value);
// await serverLog("Signature", signature);
// const splitSignature = ethers.Signature.from(signature);
// await serverLog("splitSignature", splitSignature);
// // Recover and verify the signer's address
// const recoveredAddress = ethers.verifyTypedData(
// domain,
// types,
// value,
// signature
// );
// await serverLog("Recovered address", recoveredAddress);
// // Verify the recovered address matches the creator's address
// if (
// recoveredAddress.toLowerCase() !== creatorSigner.address.toLowerCase()
// ) {
// throw new Error("Signature verification failed");
// }
// await serverLog("Signature verification passed");
// // Call executeMetaTransaction
// const tx = await campaignsNativeGaslessClaim2.executeMetaTransaction(
// relayerSigner.address, // creatorAddress (user's address)
// functionSignature,
// splitSignature.r,
// splitSignature.s,
// splitSignature.v
// );
// await serverLog("Transaction sent", { hash: tx.hash });
// // Wait for transaction confirmation
// const receipt = await tx.wait();
// await serverLog("Transaction confirmed", { receipt });
// } catch (error) {
// await serverLog("Error creating campaign", { error: error });
// console.error("Error creating campaign:", error);
// }
// };
const handleApproveToken = useCallback(async () => {
const switched = await handleSwitchChainIfNeeded();
if (!switched) return;
const tokenContractAddress = "0xBA8E964439E782D940979aC0100139415D0504ce"; // Replace with your token contract address
const campaignContractAddress =
"0x2A942c4216857ec55216F28e82B8de7dc33FFba1";
const abi = [
"function approve(address spender, uint256 amount) external returns (bool)",
];
const iface = new ethers.Interface(abi);
const data = iface.encodeFunctionData("approve", [
campaignContractAddress, // spender
ethers.MaxUint256, // amount (approve maximum)
]) as unknown as `0x${string}`;
await serverLog("Approve Token Data", {
tokenContractAddress,
campaignContractAddress,
data,
});
sendTransaction(
{
to: tokenContractAddress,
data: data as `0x${string}`,
},
{
onSuccess: (hash) => {
console.log("Approval transaction hash:", hash);
serverLog("Approval transaction hash", { hash });
},
}
);
}, [sendTransaction, handleSwitchChainIfNeeded]);
const handleCreateCampaign = useCallback(async () => {
const switched = await handleSwitchChainIfNeeded();
if (!switched) return;
const contractAddress = "0x2A942c4216857ec55216F28e82B8de7dc33FFba1";
const abi = [
"function createCampaign(address _tokenAddress, uint256 _maxClaims, uint256 _amountPerClaim, uint8 _isGasless, uint256 _maxSponsoredClaims) external payable returns (uint256 _campaignId)",
];
const iface = new ethers.Interface(abi);
const data = iface.encodeFunctionData("createCampaign", [
"0xBA8E964439E782D940979aC0100139415D0504ce", // _tokenAddress
BigInt(100), // _maxClaims
BigInt(1000000000000000000), // _amountPerClaim
0, // _isGasless
BigInt(0), // _maxSponsoredClaims
]) as unknown as `0x${string}`;
await serverLog("Create Campaign Data", { contractAddress, data });
sendTransaction(
{
to: contractAddress,
data: data as `0x${string}`,
},
{
onSuccess: (hash) => {
setTxHash(hash);
serverLog("Create Campaign transaction hash", { hash });
},
}
);
}, [sendTransaction, handleSwitchChainIfNeeded]);
const handleSignOut = useCallback(async () => {
try {
await signOut({ redirect: false });
router.push("/");
} catch (error) {
await serverLog("Error signing out", { error: error });
console.error("Error signing out", error);
}
}, [router]);
return (
<div>
<h1>Campaign Page</h1>
<p>Welcome to the campaign page!</p>
{chainId && (
<div className="my-2 text-xs">
Chain ID: <pre className="inline">{chainId}</pre>
</div>
)}
<Button onClick={handleSwitchChainId}>Switch to Base Testnet</Button>
<Button onClick={handleApproveToken}>Approve Token</Button>
<Button onClick={handleCreateCampaign}>Create Campaign</Button>
<Button onClick={handleClaimCampaign}>Claim Campaign</Button>
<Button onClick={handleSignOut}>Sign Out</Button>
<Button onClick={handleSend}>Send ETH</Button>
</div>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment