Skip to content

Commit

Permalink
Merge pull request #17 from a-singh09/blockchain-integration
Browse files Browse the repository at this point in the history
[Feat] Added blockchain integration in the Create Stablecoin form
  • Loading branch information
ceilican authored Jan 11, 2025
2 parents 079cfe1 + aaaad55 commit fedc5e3
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 104 deletions.
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
NEXT_PUBLIC_WEBSITE_URL=
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=
NEXT_PUBLIC_ALCHEMY_API_KEY=
NEXT_PUBLIC_NETWORK=
NEXT_PUBLIC_FACTORY_ADDRESS_FOUNDRY=
NEXT_PUBLIC_FACTORY_ADDRESS_SEPOLIA=
NEXT_PUBLIC_FACTORY_ADDRESS_ETHEREUM=
3 changes: 2 additions & 1 deletion .github/workflows/nextjs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ jobs:
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID }}
NEXT_PUBLIC_ALCHEMY_API_KEY: ${{ secrets.NEXT_PUBLIC_ALCHEMY_API_KEY }}
NEXT_PUBLIC_WEBSITE_URL: ${{ secrets.NEXT_PUBLIC_WEBSITE_URL }}
NEXT_PUBLIC_NETWORK: ${{ secrets.NEXT_PUBLIC_NETWORK }}
NEXT_PUBLIC_FACTORY_ADDRESS_SEPOLIA: ${{ secrets.NEXT_PUBLIC_FACTORY_ADDRESS_SEPOLIA }}
NEXT_PUBLIC_FACTORY_ADDRESS_ETHEREUM: ${{ secrets.NEXT_PUBLIC_FACTORY_ADDRESS_ETHEREUM }}
steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down
165 changes: 147 additions & 18 deletions src/app/(root)/create/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,145 @@
import React from "react";
"use client";

import React, { useState } from "react";
import FormCard from "@/components/form-card";
import { Chains } from "@/lib/chains";
import { CardFooter } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { isAddress, getAddress } from "viem";
import { getFactoryAddress } from '@/lib/chains';
import { writeContract, waitForTransactionReceipt } from "@wagmi/core";
import { config } from "@/wagmi/config";
import GluonTokenFactory from "@/out/GluonTokenFactory.sol/GluonTokenFactory.json";
import { useChainId } from 'wagmi';

const { abi } = GluonTokenFactory;

export default function Dashboard() {
const chainId = useChainId();
const [allFormData, setAllFormData] = useState<{ [key: string]: any }>({});
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);

const updateFormData = (section: string, data: any) => {
setAllFormData((prev) => ({
...prev,
[section]: data,
}));
};

const validateAddress = (address: string | undefined): boolean => {
if (!address) return false;
return isAddress(address);
};

const handleSubmit = async () => {
// Combine all form sections
const formData = {
...allFormData.erc20,
...allFormData.stable,
...allFormData.reserve,
...allFormData.treasury,
...allFormData.ratios,
...allFormData.fees,
};

console.log("Submitted Form Data:", formData);

try {
setIsSubmitting(true);

console.log("Raw form data:", allFormData);

if (!formData) {
throw new Error("Kindly fill all the fields");
}

if (!validateAddress(formData?.token_address_existing_erc_20_token)) {
throw new Error("Invalid token address format");
}

if (!validateAddress(formData?.treasury_address)) {
throw new Error("Invalid treasury address format");
}

// Prepare contract parameters with default values
const params = {
tokenAddress: formData.token_address_existing_erc_20_token,
neutronName: formData.name || "",
neutronSymbol: formData.symbol || "",
protonName: formData.name || "",
protonSymbol: formData.symbol || "",
treasury: formData.treasury_address,
initialTreasuryFee: formData.initial_treasury_fee || "0",
treasuryRevenueTarget: formData.treasury_revenue_target || "0",
criticalRatio: formData.critical_ratio || "0",
targetRatio: formData.target_ratio || "0",
feeFission: formData.fee_for_fission || "0",
feeFusion: formData.fee_for_fusion || "0",
decayRate: formData.decay_rate || "0",
vaultFee: formData.reserve_fee || "0",
vaultCreatorFee: formData.vault_creator_fee || "0",
stableOrderFee: formData.dev_fee || "0",
};

const denominator = "1000000";

const factoryAddress = getFactoryAddress(chainId);
if (!factoryAddress) {
throw new Error('Factory not deployed on this chain');
}

const hash = await writeContract(config, {
abi,
address: factoryAddress as `0x${string}`,
functionName: "createGluonReactor",
args: [
params.tokenAddress,
params.neutronName,
params.neutronSymbol,
params.protonName,
params.protonSymbol,
parseInt(denominator),
params.treasury,
parseInt(params.initialTreasuryFee),
parseInt(params.treasuryRevenueTarget),
parseInt(params.criticalRatio),
parseInt(params.targetRatio),
parseInt(params.feeFission),
parseInt(params.feeFusion),
parseInt(params.decayRate),
parseInt(params.vaultFee),
parseInt(params.vaultCreatorFee),
parseInt(params.stableOrderFee),
],
});

const receipt = await waitForTransactionReceipt(config, { hash });

const deployedAddress = receipt.logs[0]?.topics[1];
if (!deployedAddress) {
console.log("Failed to get deployed address");
}

const formattedAddress = getAddress(`0x${deployedAddress?.slice(26)}`);

console.log("Transaction Hash:", hash);
console.log("Receipt: ", receipt);
console.log("Deployed Contract Address:", formattedAddress);

} catch (error) {
console.error("Error:", error);
} finally {
setIsSubmitting(false);
}
};

return (
<div className="flex min-h-screen w-full flex-col">
<main className="flex min-h-[calc(100vh_-_theme(spacing.16))] flex-1 flex-col gap-4 bg-muted/40 p-4 md:gap-8 md:p-10">
<div className="mx-auto grid w-full max-w-6xl gap-2">
<h1 className="text-3xl font-semibold">Create New Deployment</h1>
<h3 className="text-pretty text-base ml-1 text-zinc-400">Fill this form to create new Deployment</h3>
<h3 className="text-pretty text-base ml-1 text-zinc-400">
Fill this form to create new Deployment
</h3>
</div>
<div className="mx-auto w-full max-w-6xl items-start gap-6">
<div className="grid gap-6">
Expand All @@ -20,28 +151,19 @@ export default function Dashboard() {
{ placeholder: "Symbol" },
{ placeholder: "Token Address (existing ERC-20 token)" },
]}
dropdowns={[
{
placeholder: "Select Chains",
options: Chains.map(chain => ({ value: chain.value, label: chain.label })),
},
]}
onDataChange={(data) => updateFormData("erc20", data)}
/>
<FormCard
title="Stable Tokens"
description="Details about stable tokens (neutrons) to be minted"
inputs={[
{ placeholder: "Name" },
{ placeholder: "Symbol" },
]}
inputs={[{ placeholder: "Name" }, { placeholder: "Symbol" }]}
onDataChange={(data) => updateFormData("stable", data)}
/>
<FormCard
title="Reserve Tokens"
description="Details about reserve tokens (protons) to be minted"
inputs={[
{ placeholder: "Name" },
{ placeholder: "Symbol" },
]}
inputs={[{ placeholder: "Name" }, { placeholder: "Symbol" }]}
onDataChange={(data) => updateFormData("reserve", data)}
/>
<FormCard
title="Treasury Details"
Expand All @@ -51,6 +173,7 @@ export default function Dashboard() {
{ placeholder: "Initial Treasury Fee" },
{ placeholder: "Treasury Revenue Target" },
]}
onDataChange={(data) => updateFormData("treasury", data)}
/>
<FormCard
title="Ratios and Fees"
Expand All @@ -62,6 +185,7 @@ export default function Dashboard() {
{ placeholder: "Fee for Fusion" },
{ placeholder: "Decay Rate" },
]}
onDataChange={(data) => updateFormData("ratios", data)}
/>
<FormCard
title="Additional Fees"
Expand All @@ -71,8 +195,13 @@ export default function Dashboard() {
{ placeholder: "Vault Creator Fee" },
{ placeholder: "Dev Fee" },
]}
footerButtonText="Create Stablecoin"
onDataChange={(data) => updateFormData("fees", data)}
/>
<CardFooter className="px-2 py-1">
<Button onClick={handleSubmit}>
{isSubmitting ? "Creating..." : "Create Stablecoin"}
</Button>
</CardFooter>
</div>
</div>
</main>
Expand Down
80 changes: 34 additions & 46 deletions src/components/form-card.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,43 @@
"use client";

import React, { useState } from "react";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Button } from "@/components/ui/button";

interface FormCardProps {
title: string;
description: string;
inputs: { placeholder: string }[];
dropdowns?: { placeholder: string; options: { value: string; label: string }[] }[];
footerButtonText?: string;
onDataChange: (data: any) => void;
}

const FormCard: React.FC<FormCardProps> = ({ title, description, inputs, dropdowns, footerButtonText }) => {
const FormCard: React.FC<FormCardProps> = ({
title,
description,
inputs,
onDataChange,
}) => {
const [formData, setFormData] = useState<any>({});
const [selectedChains, setSelectedChains] = useState<string[]>([]);

const handleChainSelect = (value: string) => {
setSelectedChains((prevSelectedChains) =>
prevSelectedChains.includes(value)
? prevSelectedChains.filter((chain) => chain !== value)
: [...prevSelectedChains, value]
);
const sanitizeInputName = (placeholder: string): string => {
return placeholder
.toLowerCase()
.replace(/[\s()-]+/g, "_") // Replace spaces, parentheses, and hyphens with underscore
.replace(/_+/g, "_") // Replace multiple underscores with single underscore
.replace(/^_|_$/g, ""); // Remove leading/trailing underscores
};

const getSelectedChainNames = () => {
const chainNames = dropdowns?.flatMap((dropdown) =>
dropdown.options.filter((option) => selectedChains.includes(option.value)).map((option) => option.label)
);
return chainNames?.join(", ") || "";
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newFormData = { ...formData, [e.target.name]: e.target.value };
setFormData(newFormData);
onDataChange(newFormData);
};

return (
Expand All @@ -40,38 +48,18 @@ const FormCard: React.FC<FormCardProps> = ({ title, description, inputs, dropdow
</CardHeader>
<CardContent>
{inputs.map((input, index) => (
<Input key={index} placeholder={input.placeholder} className="my-2" />
<Input
key={index}
name={sanitizeInputName(input.placeholder)}
placeholder={input.placeholder}
className="my-2"
onChange={handleInputChange}
value={formData[sanitizeInputName(input.placeholder)] || ""}
/>
))}
{dropdowns &&
dropdowns.map((dropdown, index) => (
<>
<div key={index} className="my-2">
<Select onValueChange={handleChainSelect}>
<SelectTrigger>
<SelectValue placeholder={dropdown.placeholder} />
</SelectTrigger>
<SelectContent>
{dropdown.options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="mt-4 text-sm">
<strong>Selected Chains:</strong> {getSelectedChainNames()}
</div>
</>
))}
</CardContent>
{footerButtonText && (
<CardFooter className="border-t px-6 py-4">
<Button>{footerButtonText}</Button>
</CardFooter>
)}
</Card>
);
};

export default FormCard;
export default FormCard;
49 changes: 44 additions & 5 deletions src/lib/chains.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,45 @@
export const Chains: Chain[] = [
{ value: "1", label: "Ethereum" },
{ value: "56", label: "Binance Smart Chain" },
{ value: "137", label: "Polygon" },
{ value: "2001", label: "Milkomeda" },
export interface Chain {
value: string;
label: string;
factoryAddress: `0x${string}`;
}

const isDevelopment = process.env.NODE_ENV === "development";

const productionChains = [
{
value: "1",
label: "Ethereum",
factoryAddress:
(process.env.NEXT_PUBLIC_FACTORY_ADDRESS_ETHEREUM as `0x${string}`) ||
undefined,
},
{
value: "11155111",
label: "Sepolia",
factoryAddress:
(process.env.NEXT_PUBLIC_FACTORY_ADDRESS_SEPOLIA as `0x${string}`) ||
undefined,
},
];

const developmentChains = [
{
value: "31337",
label: "Foundry",
factoryAddress:
(process.env.NEXT_PUBLIC_FACTORY_ADDRESS_FOUNDRY as `0x${string}`) ||
undefined,
},
];

export const Chains: Chain[] = isDevelopment
? developmentChains
: productionChains;

export const getFactoryAddress = (
chainId: number,
): `0x${string}` | undefined => {
return Chains.find((chain) => Number(chain.value) === chainId)
?.factoryAddress;
};
Loading

0 comments on commit fedc5e3

Please sign in to comment.