From 1b275fcfb6e487fc54f700c8ce78b7f66eb6e5f9 Mon Sep 17 00:00:00 2001
From: Spencer Schoeneman <78219375+Spencer-Sch@users.noreply.github.com>
Date: Fri, 8 Dec 2023 10:19:08 -0600
Subject: [PATCH 08/10] Payments Page - Admin Dash (#17)
* comment out incomplete isAdmin code
* add temp 'toggle isAdmin' button to header
* rename 'Transactions' to 'Payments'
* update payment data sctructure and display
* changed z-index on filter dropdown - updated filter options - NOTE: filter/search not fully functional
* remove 'billing' from admin sidebar
---
.../dash-wind/containers/Header.tsx | 10 +-
.../{transactions => payments}/index.tsx | 58 +++--
.../dash-wind/features/user/Login.tsx | 63 ++---
.../{Transactions.tsx => Payments.tsx} | 6 +-
.../components/dash-wind/routes/index.ts | 101 ++++----
.../components/dash-wind/routes/sidebar.ts | 47 ++--
.../components/dash-wind/utils/dummyData.ts | 245 +++++++++++++-----
.../dapp/{transactions.tsx => payments.tsx} | 4 +-
8 files changed, 351 insertions(+), 183 deletions(-)
rename packages/nextjs/components/dash-wind/features/{transactions => payments}/index.tsx (70%)
rename packages/nextjs/components/dash-wind/pages/protected/{Transactions.tsx => Payments.tsx} (68%)
rename packages/nextjs/pages/dapp/{transactions.tsx => payments.tsx} (81%)
diff --git a/packages/nextjs/components/dash-wind/containers/Header.tsx b/packages/nextjs/components/dash-wind/containers/Header.tsx
index d80d64f..20d32ac 100644
--- a/packages/nextjs/components/dash-wind/containers/Header.tsx
+++ b/packages/nextjs/components/dash-wind/containers/Header.tsx
@@ -9,7 +9,7 @@ import Bars3Icon from "@heroicons/react/24/outline/Bars3Icon";
import BellIcon from "@heroicons/react/24/outline/BellIcon";
import MoonIcon from "@heroicons/react/24/outline/MoonIcon";
import SunIcon from "@heroicons/react/24/outline/SunIcon";
-import { setIsConnected } from "~~/auth/authSlice";
+import { setIsAdmin, setIsConnected } from "~~/auth/authSlice";
import { web3auth } from "~~/auth/web3auth";
// import UserIcon from "@heroicons/react/24/outline/UserIcon";
import { MyState, useMyDispatch, useMySelector } from "~~/components/dash-wind/app/store";
@@ -18,6 +18,7 @@ import { Address } from "~~/components/web-3-crew/Address";
function Header() {
const dispatch = useMyDispatch();
const { noOfNotifications, pageTitle } = useMySelector((state: MyState) => state.header);
+ const { isAdmin } = useMySelector((state: MyState) => state.auth);
const [currentTheme, setCurrentTheme] = useState(
typeof window !== "undefined" ? localStorage.getItem("theme") : null,
);
@@ -46,6 +47,10 @@ function Header() {
router.push("/login");
}
+ function toggleIsAdmin() {
+ dispatch(setIsAdmin({ isAdmin: !isAdmin }));
+ }
+
return (
<>
@@ -58,6 +63,9 @@ function Header() {
+
{/* Multiple theme selection, uncomment this if you want to enable multiple themes selection,
also includes corporate and retro themes in tailwind.config file */}
diff --git a/packages/nextjs/components/dash-wind/features/transactions/index.tsx b/packages/nextjs/components/dash-wind/features/payments/index.tsx
similarity index 70%
rename from packages/nextjs/components/dash-wind/features/transactions/index.tsx
rename to packages/nextjs/components/dash-wind/features/payments/index.tsx
index 955e780..9a60353 100644
--- a/packages/nextjs/components/dash-wind/features/transactions/index.tsx
+++ b/packages/nextjs/components/dash-wind/features/payments/index.tsx
@@ -3,9 +3,9 @@ import { useCallback, useEffect, useState } from "react";
// import Image from "next/image";
import TitleCard from "../../components/Cards/TitleCard";
import SearchBar from "../../components/Input/SearchBar";
-import { RECENT_TRANSACTIONS } from "../../utils/dummyData";
+import { RECENT_PAYMENTS } from "../../utils/dummyData";
// import { showNotification } from "../common/headerSlice";
-import moment from "moment";
+// import moment from "moment";
// import { MyState, useMyDispatch, useMySelector } from "~~/components/dash-wind/app/store";
import FunnelIcon from "@heroicons/react/24/outline/FunnelIcon";
import XMarkIcon from "@heroicons/react/24/outline/XMarkIcon";
@@ -19,7 +19,7 @@ interface TopSideButtonsProps {
const TopSideButtons = ({ removeFilter, applyFilter, applySearch }: TopSideButtonsProps) => {
const [filterParam, setFilterParam] = useState("");
const [searchText, setSearchText] = useState("");
- const locationFilters = ["Paris", "London", "Canada", "Peru", "Tokyo"];
+ const statusFilters = ["paid", "pending"];
const showFiltersAndApply = (params: string) => {
applyFilter(params);
@@ -54,11 +54,11 @@ const TopSideButtons = ({ removeFilter, applyFilter, applySearch }: TopSideButto
Filter
-
- {locationFilters.map((l, k) => {
+
+ {statusFilters.map((s, k) => {
return (
-
- showFiltersAndApply(l)}>{l}
+ showFiltersAndApply(s)}>{s}
);
})}
@@ -72,32 +72,38 @@ const TopSideButtons = ({ removeFilter, applyFilter, applySearch }: TopSideButto
);
};
-function Transactions() {
- const [trans, setTrans] = useState(RECENT_TRANSACTIONS);
+function Payments() {
+ const [payments, setPayments] = useState(RECENT_PAYMENTS);
+
+ const getPaymentStatus = (status: string) => {
+ if (status === "Paid") return {status}
;
+ if (status === "Pending") return {status}
;
+ else return {status}
;
+ };
const removeFilter = () => {
- setTrans(RECENT_TRANSACTIONS);
+ setPayments(RECENT_PAYMENTS);
};
const applyFilter = (params: string) => {
- const filteredTransactions = RECENT_TRANSACTIONS.filter(t => {
- return t.location == params;
+ const filteredPayments = RECENT_PAYMENTS.filter(p => {
+ return p.status.toLowerCase() == params.toLowerCase();
});
- setTrans(filteredTransactions);
+ setPayments(filteredPayments);
};
// Search according to name
const applySearch = (value: string) => {
- const filteredTransactions = RECENT_TRANSACTIONS.filter(t => {
- return t.email.toLowerCase().includes(value.toLowerCase()) || t.email.toLowerCase().includes(value.toLowerCase());
+ const filteredPayments = RECENT_PAYMENTS.filter(p => {
+ return p.name.toLowerCase().includes(value.toLowerCase()) || p.name.toLowerCase().includes(value.toLowerCase());
});
- setTrans(filteredTransactions);
+ setPayments(filteredPayments);
};
return (
<>
@@ -109,14 +115,16 @@ function Transactions() {
Name |
- Email Id |
- Location |
+ To Wallet |
+ Invoice No |
+ Invoice Generated On |
Amount |
- Transaction Date |
+ Status |
+ Invoice Paid On |
- {trans.map((l, k) => {
+ {payments.map((l, k) => {
return (
@@ -131,10 +139,12 @@ function Transactions() {
|
- {l.email} |
- {l.location} |
+ {l.wallet} |
+ {l.invoiceNo} |
+ {l.generatedOn} |
${l.amount} |
- {moment(l.date).format("D MMM")} |
+ {getPaymentStatus(l.status)} |
+ {l.paidOn} |
);
})}
@@ -146,4 +156,4 @@ function Transactions() {
);
}
-export default Transactions;
+export default Payments;
diff --git a/packages/nextjs/components/dash-wind/features/user/Login.tsx b/packages/nextjs/components/dash-wind/features/user/Login.tsx
index e4aa292..6ba37e7 100644
--- a/packages/nextjs/components/dash-wind/features/user/Login.tsx
+++ b/packages/nextjs/components/dash-wind/features/user/Login.tsx
@@ -5,10 +5,13 @@ import InputText from "../../components/Input/InputText";
import ErrorText from "../../components/Typography/ErrorText";
import { UpdateFormValues } from "../../types/FormTypes";
import LandingIntro from "./LandingIntro";
-import { Address, createWalletClient, custom } from "viem";
-import { polygonMumbai } from "viem/chains";
+// import { Address, createWalletClient, custom } from "viem";
+// import { polygonMumbai } from "viem/chains";
import { useContractRead } from "wagmi";
-import { setIsAdmin, setIsConnected } from "~~/auth/authSlice";
+import {
+ /*setIsAdmin,*/
+ setIsConnected,
+} from "~~/auth/authSlice";
import { web3auth } from "~~/auth/web3auth";
import { MyState, useMyDispatch, useMySelector } from "~~/components/dash-wind/app/store";
@@ -80,11 +83,11 @@ function Login() {
await web3auth.connect();
if (web3auth.connected) {
dispatch(setIsConnected({ isConnected: true }));
- await determineIfAccountIsAdmin();
- if (!isEmployee) {
- // until the hook is working, this is going to prevent us from being directed to the dashboard
- return;
- }
+ // await determineIfAccountIsAdmin();
+ // if (!isEmployee) {
+ // // until the hook is working, this is going to prevent us from being directed to the dashboard
+ // return;
+ // }
router.push("/dapp/dashboard");
}
} catch (error) {
@@ -94,11 +97,11 @@ function Login() {
async function determineIfAccountIsAdmin() {
// set loading === true ???
- const address = await getAccounts();
- if (!address) {
- console.error("from determineIfAccountIsAdmin - address is undefined");
- return;
- }
+ // const address = await getAccounts();
+ // if (!address) {
+ // console.error("from determineIfAccountIsAdmin - address is undefined");
+ // return;
+ // }
if (!owner) {
console.error("From determineIfAccountIsAdmin: ownerData from Payroll Contract is undefined");
@@ -108,27 +111,27 @@ function Login() {
/*-------------------------------------*/
// Kaz & Trevor
// need to see what shape `owner` will be on return
- const isAdmin = address === owner ? true : false;
- dispatch(setIsAdmin({ isAdmin: isAdmin }));
+ // const isAdmin = address === owner ? true : false;
+ // dispatch(setIsAdmin({ isAdmin: isAdmin }));
/*-------------------------------------*/
// set loading === false ???
}
- async function getAccounts() {
- if (!web3auth.provider) {
- console.log("from login - getAccounts: provider not defined");
- return;
- }
- const client = createWalletClient({
- // account: privateKeyToAccount('0x...'); // from viem
- chain: polygonMumbai,
- transport: custom(web3auth.provider),
- });
-
- // Get user's public address
- const [address] = await client.getAddresses();
- return address as Address;
- }
+ // async function getAccounts() {
+ // if (!web3auth.provider) {
+ // console.log("from login - getAccounts: provider not defined");
+ // return;
+ // }
+ // const client = createWalletClient({
+ // // account: privateKeyToAccount('0x...'); // from viem
+ // chain: polygonMumbai,
+ // transport: custom(web3auth.provider),
+ // });
+
+ // // Get user's public address
+ // const [address] = await client.getAddresses();
+ // return address as Address;
+ // }
const submitForm = (e: React.FormEvent) => {
e.preventDefault();
diff --git a/packages/nextjs/components/dash-wind/pages/protected/Transactions.tsx b/packages/nextjs/components/dash-wind/pages/protected/Payments.tsx
similarity index 68%
rename from packages/nextjs/components/dash-wind/pages/protected/Transactions.tsx
rename to packages/nextjs/components/dash-wind/pages/protected/Payments.tsx
index 1fcdb58..0d82980 100644
--- a/packages/nextjs/components/dash-wind/pages/protected/Transactions.tsx
+++ b/packages/nextjs/components/dash-wind/pages/protected/Payments.tsx
@@ -1,16 +1,16 @@
import { useEffect } from "react";
import { setPageTitle } from "../../features/common/headerSlice";
-import Transactions from "../../features/transactions";
+import Payments from "../../features/payments";
import { useMyDispatch } from "~~/components/dash-wind/app/store";
function InternalPage() {
const dispatch = useMyDispatch();
useEffect(() => {
- dispatch(setPageTitle({ title: "Transactions" }));
+ dispatch(setPageTitle({ title: "Payments" }));
}, [dispatch]);
- return ;
+ return ;
}
export default InternalPage;
diff --git a/packages/nextjs/components/dash-wind/routes/index.ts b/packages/nextjs/components/dash-wind/routes/index.ts
index 2cd295f..fa97cb9 100644
--- a/packages/nextjs/components/dash-wind/routes/index.ts
+++ b/packages/nextjs/components/dash-wind/routes/index.ts
@@ -2,82 +2,87 @@
import { lazy } from "react";
const Dashboard = lazy(() => import("../pages/protected/Dashboard"));
-const Welcome = lazy(() => import("../pages/protected/Welcome"));
+// const Welcome = lazy(() => import("../pages/protected/Welcome"));
const Page404 = lazy(() => import("../pages/protected/_404"));
-const Blank = lazy(() => import("../pages/protected/Blank"));
-const Charts = lazy(() => import("../pages/protected/Charts"));
+// const Blank = lazy(() => import("../pages/protected/Blank"));
+// const Charts = lazy(() => import("../pages/protected/Charts"));
// const Leads = lazy(() => import("../pages/protected/Leads"));
-const Integration = lazy(() => import("../pages/protected/Integration"));
-const Calendar = lazy(() => import("../pages/protected/Calendar"));
-const Team = lazy(() => import("../pages/protected/Team"));
-const Transactions = lazy(() => import("../pages/protected/Transactions"));
+// const Integration = lazy(() => import("../pages/protected/Integration"));
+// const Calendar = lazy(() => import("../pages/protected/Calendar"));
+// const Team = lazy(() => import("../pages/protected/Team"));
+// const Transactions = lazy(() => import("../pages/protected/Transactions"));
+const Payments = lazy(() => import("../pages/protected/Payments"));
const Bills = lazy(() => import("../pages/protected/Bills"));
-const ProfileSettings = lazy(() => import("../pages/protected/ProfileSettings"));
-const GettingStarted = lazy(() => import("../pages/GettingStarted"));
+// const ProfileSettings = lazy(() => import("../pages/protected/ProfileSettings"));
+// const GettingStarted = lazy(() => import("../pages/GettingStarted"));
// const DocFeatures = lazy(() => import("../pages/DocFeatures"));
-const DocComponents = lazy(() => import("../pages/DocComponents"));
+// const DocComponents = lazy(() => import("../pages/DocComponents"));
const routes = [
{
path: "/dashboard", // the url
component: Dashboard, // view rendered
},
- {
- path: "/welcome", // the url
- component: Welcome, // view rendered
- },
+ // {
+ // path: "/welcome", // the url
+ // component: Welcome, // view rendered
+ // },
// {
// path: "/leads",
// component: Leads,
// },
+ // {
+ // path: "/settings-team",
+ // component: Team,
+ // },
+ // {
+ // path: "/calendar",
+ // component: Calendar,
+ // },
{
- path: "/settings-team",
- component: Team,
- },
- {
- path: "/calendar",
- component: Calendar,
- },
- {
- path: "/transactions",
- component: Transactions,
- },
- {
- path: "/settings-profile",
- component: ProfileSettings,
+ path: "/payments",
+ component: Payments,
},
+ // {
+ // path: "/transactions",
+ // component: Transactions,
+ // },
+ // {
+ // path: "/settings-profile",
+ // component: ProfileSettings,
+ // },
{
path: "/settings-billing",
component: Bills,
},
- {
- path: "/getting-started",
- component: GettingStarted,
- },
+ // {
+ // path: "/getting-started",
+ // component: GettingStarted,
+ // },
// {
// path: "/features",
// component: DocFeatures,
// },
- {
- path: "/components",
- component: DocComponents,
- },
- {
- path: "/integration",
- component: Integration,
- },
- {
- path: "/charts",
- component: Charts,
- },
+ // {
+ // path: "/components",
+ // component: DocComponents,
+ // },
+ // {
+ // path: "/integration",
+ // component: Integration,
+ // },
+ // {
+ // path: "/charts",
+ // component: Charts,
+ // },
{
path: "/404",
component: Page404,
},
- {
- path: "/blank",
- component: Blank,
- },
+ // {
+ // path: "/blank",
+ // component: Blank,
+ // },
];
export default routes;
diff --git a/packages/nextjs/components/dash-wind/routes/sidebar.ts b/packages/nextjs/components/dash-wind/routes/sidebar.ts
index 82d638e..efd9537 100644
--- a/packages/nextjs/components/dash-wind/routes/sidebar.ts
+++ b/packages/nextjs/components/dash-wind/routes/sidebar.ts
@@ -17,7 +17,8 @@ import Squares2X2Icon from "@heroicons/react/24/outline/Squares2X2Icon";
// import TableCellsIcon from "@heroicons/react/24/outline/TableCellsIcon";
import UserIcon from "@heroicons/react/24/outline/UserIcon";
import UsersIcon from "@heroicons/react/24/outline/UsersIcon";
-import WalletIcon from "@heroicons/react/24/outline/WalletIcon";
+
+// import WalletIcon from "@heroicons/react/24/outline/WalletIcon";
const iconClasses = `h-6 w-6`;
const submenuIconClasses = `h-5 w-5`;
@@ -36,12 +37,18 @@ export const adminRoutes = [
name: "Employees", // name that appear in Sidebar
},
{
- path: "/dapp/transactions", // url
+ path: "/dapp/payments", // url
icon: CurrencyDollarIcon, // icon component
className: iconClasses,
- name: "Transactions", // name that appear in Sidebar
+ name: "Payments", // name that appear in Sidebar
},
// {
+ // path: "/dapp/transactions", // url
+ // icon: CurrencyDollarIcon, // icon component
+ // className: iconClasses,
+ // name: "Transactions", // name that appear in Sidebar
+ // },
+ // {
// path: "/dapp/charts", // url
// icon: ChartBarIcon, // icon component
// className: iconClasses,
@@ -71,12 +78,12 @@ export const adminRoutes = [
// className: submenuIconClasses,
// name: "Profile", // name that appear in Sidebar
// },
- {
- path: "/dapp/settings-billing",
- icon: WalletIcon,
- className: submenuIconClasses,
- name: "Billing",
- },
+ // {
+ // path: "/dapp/settings-billing",
+ // icon: WalletIcon,
+ // className: submenuIconClasses,
+ // name: "Billing",
+ // },
// {
// path: "/dapp/settings-team", // url
// icon: UsersIcon, // icon component
@@ -100,12 +107,18 @@ export const userRoutes = [
// name: "Leads", // name that appear in Sidebar
// },
{
- path: "/dapp/transactions", // url
+ path: "/dapp/payments", // url
icon: CurrencyDollarIcon, // icon component
className: iconClasses,
- name: "Transactions", // name that appear in Sidebar
+ name: "Payments", // name that appear in Sidebar
},
// {
+ // path: "/dapp/transactions", // url
+ // icon: CurrencyDollarIcon, // icon component
+ // className: iconClasses,
+ // name: "Transactions", // name that appear in Sidebar
+ // },
+ // {
// path: "/dapp/charts", // url
// icon: ChartBarIcon, // icon component
// className: iconClasses,
@@ -135,12 +148,12 @@ export const userRoutes = [
className: submenuIconClasses,
name: "Profile", // name that appear in Sidebar
},
- {
- path: "/dapp/settings-billing",
- icon: WalletIcon,
- className: submenuIconClasses,
- name: "Billing",
- },
+ // {
+ // path: "/dapp/settings-billing",
+ // icon: WalletIcon,
+ // className: submenuIconClasses,
+ // name: "Billing",
+ // },
// {
// path: "/dapp/settings-team", // url
// icon: UsersIcon, // icon component
diff --git a/packages/nextjs/components/dash-wind/utils/dummyData.ts b/packages/nextjs/components/dash-wind/utils/dummyData.ts
index 98a6559..e4d7b9b 100644
--- a/packages/nextjs/components/dash-wind/utils/dummyData.ts
+++ b/packages/nextjs/components/dash-wind/utils/dummyData.ts
@@ -71,158 +71,287 @@ export const CALENDAR_INITIAL_EVENTS = [
endTime: moment().add(16, "d").endOf("day"),
},
];
-export const RECENT_TRANSACTIONS = [
+export const RECENT_PAYMENTS = [
{
+ invoiceNo: "#4567",
name: "Alex",
avatar: "https://reqres.in/img/faces/1-image.jpg",
- email: "alex@dashwind.com",
+ wallet: "0x...",
+ status: "Pending",
location: "Paris",
- amount: 100,
- date: moment().endOf("day"),
+ amount: "34,989",
+ generatedOn: moment(new Date())
+ .add(-30 * 2, "days")
+ .format("DD MMM YYYY"),
+ paidOn: "-",
},
{
+ invoiceNo: "#4523",
name: "Ereena",
avatar: "https://reqres.in/img/faces/2-image.jpg",
- email: "ereena@dashwind.com",
+ wallet: "0x...",
+ status: "Pending",
location: "London",
- amount: 190,
- date: moment().add(-1, "d").endOf("day"),
+ amount: "34,989",
+ generatedOn: moment(new Date())
+ .add(-30 * 2, "days")
+ .format("DD MMM YYYY"),
+ paidOn: "-",
},
{
+ invoiceNo: "#4453",
name: "John",
avatar: "https://reqres.in/img/faces/3-image.jpg",
- email: "jhon@dashwind.com",
+ wallet: "0x...",
+ status: "Paid",
location: "Canada",
- amount: 112,
- date: moment().add(-1, "d").endOf("day"),
+ amount: "34,989",
+ generatedOn: moment(new Date())
+ .add(-30 * 3, "days")
+ .format("DD MMM YYYY"),
+ paidOn: moment(new Date())
+ .add(-24 * 2, "days")
+ .format("DD MMM YYYY"),
},
{
+ invoiceNo: "#4359",
name: "Matrix",
avatar: "https://reqres.in/img/faces/4-image.jpg",
- email: "matrix@dashwind.com",
+ wallet: "0x...",
+ status: "Paid",
location: "Peru",
- amount: 111,
- date: moment().add(-1, "d").endOf("day"),
+ amount: "34,989",
+ generatedOn: moment(new Date())
+ .add(-30 * 3, "days")
+ .format("DD MMM YYYY"),
+ paidOn: moment(new Date())
+ .add(-24 * 2, "days")
+ .format("DD MMM YYYY"),
},
{
+ invoiceNo: "#3359",
name: "Virat",
avatar: "https://reqres.in/img/faces/5-image.jpg",
- email: "virat@dashwind.com",
+ wallet: "0x...",
+ status: "Paid",
location: "London",
- amount: 190,
- date: moment().add(-2, "d").endOf("day"),
+ amount: "34,989",
+ generatedOn: moment(new Date())
+ .add(-30 * 3, "days")
+ .format("DD MMM YYYY"),
+ paidOn: moment(new Date())
+ .add(-24 * 2, "days")
+ .format("DD MMM YYYY"),
},
{
+ invoiceNo: "#3367",
name: "Miya",
avatar: "https://reqres.in/img/faces/6-image.jpg",
- email: "miya@dashwind.com",
+ wallet: "0x...",
+ status: "Paid",
location: "Paris",
- amount: 230,
- date: moment().add(-2, "d").endOf("day"),
+ amount: "34,989",
+ generatedOn: moment(new Date())
+ .add(-30 * 3, "days")
+ .format("DD MMM YYYY"),
+ paidOn: moment(new Date())
+ .add(-24 * 2, "days")
+ .format("DD MMM YYYY"),
},
{
+ invoiceNo: "#3359",
name: "Virat",
avatar: "https://reqres.in/img/faces/3-image.jpg",
- email: "virat@dashwind.com",
+ wallet: "0x...",
+ status: "Paid",
location: "Canada",
- amount: 331,
- date: moment().add(-2, "d").endOf("day"),
+ amount: "34,989",
+ generatedOn: moment(new Date())
+ .add(-30 * 3, "days")
+ .format("DD MMM YYYY"),
+ paidOn: moment(new Date())
+ .add(-24 * 2, "days")
+ .format("DD MMM YYYY"),
},
{
+ invoiceNo: "#2359",
name: "Matrix",
avatar: "https://reqres.in/img/faces/1-image.jpg",
- email: "matrix@dashwind.com",
+ wallet: "0x...",
+ status: "Paid",
location: "London",
- amount: 581,
- date: moment().add(-2, "d").endOf("day"),
+ amount: "34,989",
+ generatedOn: moment(new Date())
+ .add(-30 * 3, "days")
+ .format("DD MMM YYYY"),
+ paidOn: moment(new Date())
+ .add(-24 * 2, "days")
+ .format("DD MMM YYYY"),
},
{
+ invoiceNo: "#4567",
name: "Ereena",
avatar: "https://reqres.in/img/faces/3-image.jpg",
- email: "ereena@dashwind.com",
+ wallet: "0x...",
+ status: "Paid",
location: "Tokyo",
- amount: 151,
- date: moment().add(-2, "d").endOf("day"),
+ amount: "34,989",
+ generatedOn: moment(new Date())
+ .add(-30 * 3, "days")
+ .format("DD MMM YYYY"),
+ paidOn: moment(new Date())
+ .add(-24 * 2, "days")
+ .format("DD MMM YYYY"),
},
{
+ invoiceNo: "#4523",
name: "John",
avatar: "https://reqres.in/img/faces/2-image.jpg",
- email: "jhon@dashwind.com",
+ wallet: "0x...",
+ status: "Paid",
location: "Paris",
- amount: 91,
- date: moment().add(-2, "d").endOf("day"),
+ amount: "34,989",
+ generatedOn: moment(new Date())
+ .add(-30 * 3, "days")
+ .format("DD MMM YYYY"),
+ paidOn: moment(new Date())
+ .add(-24 * 2, "days")
+ .format("DD MMM YYYY"),
},
{
+ invoiceNo: "#4453",
name: "Virat",
avatar: "https://reqres.in/img/faces/3-image.jpg",
- email: "virat@dashwind.com",
+ wallet: "0x...",
+ status: "Paid",
location: "Canada",
- amount: 161,
- date: moment().add(-3, "d").endOf("day"),
+ amount: "34,989",
+ generatedOn: moment(new Date())
+ .add(-30 * 3, "days")
+ .format("DD MMM YYYY"),
+ paidOn: moment(new Date())
+ .add(-24 * 2, "days")
+ .format("DD MMM YYYY"),
},
{
+ invoiceNo: "#4359",
name: "Matrix",
avatar: "https://reqres.in/img/faces/4-image.jpg",
- email: "matrix@dashwind.com",
+ wallet: "0x...",
+ status: "Paid",
location: "US",
- amount: 121,
- date: moment().add(-3, "d").endOf("day"),
+ amount: "34,989",
+ generatedOn: moment(new Date())
+ .add(-30 * 3, "days")
+ .format("DD MMM YYYY"),
+ paidOn: moment(new Date())
+ .add(-24 * 2, "days")
+ .format("DD MMM YYYY"),
},
{
+ invoiceNo: "#3359",
name: "Ereena",
avatar: "https://reqres.in/img/faces/6-image.jpg",
- email: "jhon@dashwind.com",
+ wallet: "0x...",
+ status: "Paid",
location: "Tokyo",
- amount: 713,
- date: moment().add(-3, "d").endOf("day"),
+ amount: "34,989",
+ generatedOn: moment(new Date())
+ .add(-30 * 3, "days")
+ .format("DD MMM YYYY"),
+ paidOn: moment(new Date())
+ .add(-24 * 2, "days")
+ .format("DD MMM YYYY"),
},
{
+ invoiceNo: "#3367",
name: "John",
avatar: "https://reqres.in/img/faces/2-image.jpg",
- email: "ereena@dashwind.com",
+ wallet: "0x...",
+ status: "Paid",
location: "London",
- amount: 217,
- date: moment().add(-3, "d").endOf("day"),
+ amount: "34,989",
+ generatedOn: moment(new Date())
+ .add(-30 * 3, "days")
+ .format("DD MMM YYYY"),
+ paidOn: moment(new Date())
+ .add(-24 * 2, "days")
+ .format("DD MMM YYYY"),
},
{
+ invoiceNo: "#3359",
name: "Virat",
avatar: "https://reqres.in/img/faces/3-image.jpg",
- email: "virat@dashwind.com",
+ wallet: "0x...",
+ status: "Paid",
location: "Paris",
- amount: 117,
- date: moment().add(-3, "d").endOf("day"),
+ amount: "34,989",
+ generatedOn: moment(new Date())
+ .add(-30 * 3, "days")
+ .format("DD MMM YYYY"),
+ paidOn: moment(new Date())
+ .add(-24 * 2, "days")
+ .format("DD MMM YYYY"),
},
{
+ invoiceNo: "#2359",
name: "Miya",
avatar: "https://reqres.in/img/faces/7-image.jpg",
- email: "jhon@dashwind.com",
+ wallet: "0x...",
+ status: "Paid",
location: "Canada",
- amount: 612,
- date: moment().add(-3, "d").endOf("day"),
+ amount: "34,989",
+ generatedOn: moment(new Date())
+ .add(-30 * 3, "days")
+ .format("DD MMM YYYY"),
+ paidOn: moment(new Date())
+ .add(-24 * 2, "days")
+ .format("DD MMM YYYY"),
},
{
+ invoiceNo: "#4567",
name: "Matrix",
avatar: "https://reqres.in/img/faces/3-image.jpg",
- email: "matrix@dashwind.com",
+ wallet: "0x...",
+ status: "Paid",
location: "London",
- amount: 631,
- date: moment().add(-3, "d").endOf("day"),
+ amount: "34,989",
+ generatedOn: moment(new Date())
+ .add(-30 * 3, "days")
+ .format("DD MMM YYYY"),
+ paidOn: moment(new Date())
+ .add(-24 * 2, "days")
+ .format("DD MMM YYYY"),
},
{
+ invoiceNo: "#4523",
name: "Virat",
avatar: "https://reqres.in/img/faces/2-image.jpg",
- email: "ereena@dashwind.com",
+ wallet: "0x...",
+ status: "Paid",
location: "Tokyo",
- amount: 151,
- date: moment().add(-3, "d").endOf("day"),
+ amount: "34,989",
+ generatedOn: moment(new Date())
+ .add(-30 * 3, "days")
+ .format("DD MMM YYYY"),
+ paidOn: moment(new Date())
+ .add(-24 * 2, "days")
+ .format("DD MMM YYYY"),
},
{
+ invoiceNo: "#4453",
name: "Ereena",
avatar: "https://reqres.in/img/faces/3-image.jpg",
- email: "virat@dashwind.com",
+ wallet: "0x...",
+ status: "Paid",
location: "Paris",
- amount: 617,
- date: moment().add(-3, "d").endOf("day"),
+ amount: "34,989",
+ generatedOn: moment(new Date())
+ .add(-30 * 3, "days")
+ .format("DD MMM YYYY"),
+ paidOn: moment(new Date())
+ .add(-24 * 2, "days")
+ .format("DD MMM YYYY"),
},
];
export const EMPLOYEES = [
diff --git a/packages/nextjs/pages/dapp/transactions.tsx b/packages/nextjs/pages/dapp/payments.tsx
similarity index 81%
rename from packages/nextjs/pages/dapp/transactions.tsx
rename to packages/nextjs/pages/dapp/payments.tsx
index 8e93acf..ecd3598 100644
--- a/packages/nextjs/pages/dapp/transactions.tsx
+++ b/packages/nextjs/pages/dapp/payments.tsx
@@ -1,6 +1,6 @@
import { ReactElement } from "react";
import type { NextPageWithLayout } from "../_app";
-import Transactions from "~~/components/dash-wind/pages/protected/Transactions";
+import Payments from "~~/components/dash-wind/pages/protected/Payments";
// import { MetaHeader } from "~~/components/MetaHeader";
import DashLayout from "~~/components/layouts/DashLayout";
@@ -9,7 +9,7 @@ import DashLayout from "~~/components/layouts/DashLayout";
}
const DappDashboard: NextPageWithLayout = () => {
- return ;
+ return ;
};
DappDashboard.getLayout = function getLayout(page: ReactElement) {
From 02094caea63997ea164a3d99dc0619b78b203e87 Mon Sep 17 00:00:00 2001
From: Spencer Schoeneman <78219375+Spencer-Sch@users.noreply.github.com>
Date: Fri, 8 Dec 2023 11:58:47 -0600
Subject: [PATCH 09/10] Contracts - mesh Spencer and Trevor changes (#22)
* get wallet address and display in Header
* isOwner - login flow - wagmiConnect attempt
* add web3auth-wagmi-connector
* ignore .env
* remove .env
* add missing imports
* fix package issue
* add another package
* add 'contracts' to IERC20 import path - also format...
* fix issue with ccip package
---------
Co-authored-by: funkornaut001
---
NOTES.md | 33 +
package.json | 3 +
packages/hardhat/contracts/Payroll.sol | 4 +-
packages/hardhat/contracts/PayrollFactory.sol | 207 +++---
.../hardhat/contracts/TokenTransferor.sol | 644 +++++++++---------
packages/hardhat/deploy/03_deploy_payroll.ts | 2 +-
packages/hardhat/package.json | 2 +
packages/nextjs/.env.example | 5 +-
packages/nextjs/.gitignore | 1 +
packages/nextjs/auth/web3auth.ts | 17 +
.../dash-wind/containers/Header.tsx | 39 +-
.../dash-wind/features/employees/index.tsx | 3 +-
.../dash-wind/features/user/Login.tsx | 211 ++++--
.../dash-wind/features/user/Register.tsx | 11 +-
.../web-3-crew/register-page/DeployForm.tsx | 71 +-
.../web-3-crew/register-page/form.tsx | 73 ++
packages/nextjs/package.json | 1 +
packages/nextjs/scaffold.config.ts | 2 +-
packages/nextjs/services/web3/wagmiConfig.tsx | 12 +-
yarn.lock | 163 ++++-
20 files changed, 988 insertions(+), 516 deletions(-)
create mode 100644 NOTES.md
create mode 100644 packages/nextjs/components/web-3-crew/register-page/form.tsx
diff --git a/NOTES.md b/NOTES.md
new file mode 100644
index 0000000..3ad5d59
--- /dev/null
+++ b/NOTES.md
@@ -0,0 +1,33 @@
+PayrollFactory deployed to: 0xfe44aB0B966E57F126130BE6401546c7351484ad --mumbai
+TokenTransferor deployed to: 0x21d762ab159676d3bd05e12A95699C1d0b043A48 --mumbai
+Payroll deployed to: 0xDBAbe75848c608bDA3382f0D68219542032B3fEa --mumbai
+All deployed from 0x9768818565ED5968fAACC6F66ca02CBf2785dB84
+
+https://api-testnet.polygonscan.com/IEWN9BX92EIKVXR22UKQQ9A4UWEF5J9IRG
+
+There is no log out / wallet modal on Register Deploy
+Need a rundown of how to log in with wallet and connect to mumbai chain without AA
+
+I shouldnt need to change the web3auth client id correct?
+
+deploy contract in register flow is stuck at getting account address...
+
+## 12/7
+Funkornaut@gmail -- 0xBB65877a1207572321dE0ad64A2e196545904a09
+trevfost503@gmail -- 0xfd330177602f092b72a3b65893602067Ab69cE2c
+sign in with funkornaut through gmail -- 0x1aD394b0c57dbC57f16A5A54b4ccee436b678287
+
+deployed new payroll 0xAB34603b0A8c54f9F9Fe9207b98da8ac354dB68e
+- added function isOwner returns bool, owner() func on ownable works but since we then needed to look for bool on front end thought that would simplify from end logic / routing
+- think that the LogIn flow is a little better
+
+Login.tsx
+Web3 modal pops up when you hit login button - should only pop up if there is a contract address in the form
+
+Now logOut if funky
+Cant log in with Metamask
+
+DeployForm
+- just using useContractWrite was able to click button and make action got this error in browser console
+ next-dev.js:20 ConnectorNotFoundError: Connector not found at writeContract (chunk-TSH6VVF4.js:2348:1)
+
diff --git a/package.json b/package.json
index c4d4ea4..7b46570 100644
--- a/package.json
+++ b/package.json
@@ -36,5 +36,8 @@
},
"resolutions": {
"usehooks-ts@^2.7.2": "patch:usehooks-ts@npm:^2.7.2#./.yarn/patches/usehooks-ts-npm-2.7.2-fceffe0e43.patch"
+ },
+ "dependencies": {
+ "@web3auth/web3auth-wagmi-connector": "^5.0.1"
}
}
diff --git a/packages/hardhat/contracts/Payroll.sol b/packages/hardhat/contracts/Payroll.sol
index a09a1f8..b4df6d1 100644
--- a/packages/hardhat/contracts/Payroll.sol
+++ b/packages/hardhat/contracts/Payroll.sol
@@ -527,8 +527,8 @@ ITokenTransferor public ccip;
}
}
- function getOwner() public view returns (address){
- return owner();
+ function isOwner(address _address) public view returns (bool){
+ return _address == owner();
}
function getEmployeePaymentSplits(address _employeeAddress) public view returns (PaymentSplit memory){
diff --git a/packages/hardhat/contracts/PayrollFactory.sol b/packages/hardhat/contracts/PayrollFactory.sol
index 991e528..a738a9b 100644
--- a/packages/hardhat/contracts/PayrollFactory.sol
+++ b/packages/hardhat/contracts/PayrollFactory.sol
@@ -1,98 +1,121 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
-import {TokenTransferor} from "./TokenTransferor.sol";
-import {Payroll} from "./Payroll.sol";
-import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
-import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
-import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
-import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
-import {IERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/token/ERC20/IERC20.sol";
+import { TokenTransferor } from "./TokenTransferor.sol";
+import { Payroll } from "./Payroll.sol";
+import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
+import { IRouterClient } from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
+import { OwnerIsCreator } from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
+import { Client } from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
+import { IERC20 } from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/IERC20.sol";
contract PayrollFactory is Ownable {
- // Set the deployment fee
- uint256 public deployment_fee = 1 wei; // Example fee
-
- IRouterClient public router;
- IERC20 public linkToken;
- IERC20 public paymentToken;
-
- error InvalidAddress();
- error InsufficientFunds(uint256 amount);
-
- event TokenTransferorDeployed(address indexed deployer, address contractAddress);
- event PayrollDeployed(address indexed deployer, address contractAddress, address tokenTransferorAddress);
-
- ///@param _router The address of the Chainlink Router for local chain
- ///@param _linkToken The address of the LINK token for local chain
- ///@param _paymentToken The address of the payment token for Payroll Contract on native chain
- constructor(address _router, address _linkToken, address _paymentToken) {
- router = IRouterClient(_router);
- linkToken = IERC20(_linkToken);
- paymentToken = IERC20(_paymentToken);
- }
-
- ///@dev Deploys TokenTransferor and Payroll contracts
- function deployPayrollAndTokenTransferor() external payable returns (address payrollAddress, address tokenTransferorAddress) {
- if(msg.value < deployment_fee){
- revert InsufficientFunds(msg.value);
- }
-
- tokenTransferorAddress = deployTokenTransferor();
- payrollAddress = deployPayroll(tokenTransferorAddress);
- return (payrollAddress, tokenTransferorAddress);
- }
-
- // Function to deploy TokenTransferor
- ///@dev transfers ownership of TokenTransferor to caller of deployPayrollAndTokenTransferor
- function deployTokenTransferor() internal returns (address) {
- //require(msg.value >= deployment_fee, "Insufficient funds sent for deployment");
-
- TokenTransferor newTokenTransferor = new TokenTransferor(address(router), address(linkToken));
- newTokenTransferor.transferOwnership(msg.sender);
- emit TokenTransferorDeployed(msg.sender, address(newTokenTransferor));
- return address(newTokenTransferor);
- }
-
- /// Function to deploy Payroll
- ///@dev transfers ownership of Payroll to caller of deployPayrollAndTokenTransferor
- ///@param tokenTransferorAddress The address of the newly deployed TokenTransferor contract
- function deployPayroll(address tokenTransferorAddress) internal returns (address) {
- //require(msg.value >= deployment_fee, "Insufficient funds sent for deployment");
-
- Payroll newPayroll = new Payroll(tokenTransferorAddress, address(paymentToken));
- newPayroll.transferOwnership(msg.sender); // Transfer ownership
- emit PayrollDeployed(msg.sender, address(newPayroll), tokenTransferorAddress);
- return address(newPayroll);
- }
-
- /// Functions to update deployment params so that they can be changed in the future or for deployment on other chains
- function updateDeploymentFee(uint256 newFee) external onlyOwner {
- deployment_fee = newFee;
- }
-
- function changeRouter(address newRouter) external onlyOwner {
- router = IRouterClient(newRouter);
- }
-
- function changeLinkToken(address newLinkToken) external onlyOwner {
- linkToken = IERC20(newLinkToken);
- }
-
- function changePaymentToken(address newPaymentToken) external onlyOwner {
- paymentToken = IERC20(newPaymentToken);
- }
-
- // Fallback function to receive ETH
- receive() external payable {}
-
- // Function to withdraw collected fees
- function withdrawFees(address payable _beneficiary) public onlyOwner {
- // Only contract owner should be able to withdraw fees
- // Implement ownership and access control
- if (_beneficiary == address(0)) {
- revert InvalidAddress();
- }
- _beneficiary.transfer(address(this).balance);
- }
+ // Set the deployment fee
+ uint256 public deployment_fee = 1 wei; // Example fee
+
+ IRouterClient public router;
+ IERC20 public linkToken;
+ IERC20 public paymentToken;
+
+ error InvalidAddress();
+ error InsufficientFunds(uint256 amount);
+
+ event TokenTransferorDeployed(
+ address indexed deployer,
+ address contractAddress
+ );
+ event PayrollDeployed(
+ address indexed deployer,
+ address contractAddress,
+ address tokenTransferorAddress
+ );
+
+ ///@param _router The address of the Chainlink Router for local chain
+ ///@param _linkToken The address of the LINK token for local chain
+ ///@param _paymentToken The address of the payment token for Payroll Contract on native chain
+ constructor(address _router, address _linkToken, address _paymentToken) {
+ router = IRouterClient(_router);
+ linkToken = IERC20(_linkToken);
+ paymentToken = IERC20(_paymentToken);
+ }
+
+ ///@dev Deploys TokenTransferor and Payroll contracts
+ function deployPayrollAndTokenTransferor()
+ external
+ payable
+ returns (address payrollAddress, address tokenTransferorAddress)
+ {
+ if (msg.value < deployment_fee) {
+ revert InsufficientFunds(msg.value);
+ }
+
+ tokenTransferorAddress = deployTokenTransferor();
+ payrollAddress = deployPayroll(tokenTransferorAddress);
+ return (payrollAddress, tokenTransferorAddress);
+ }
+
+ // Function to deploy TokenTransferor
+ ///@dev transfers ownership of TokenTransferor to caller of deployPayrollAndTokenTransferor
+ function deployTokenTransferor() internal returns (address) {
+ //require(msg.value >= deployment_fee, "Insufficient funds sent for deployment");
+
+ TokenTransferor newTokenTransferor = new TokenTransferor(
+ address(router),
+ address(linkToken)
+ );
+ newTokenTransferor.transferOwnership(msg.sender);
+ emit TokenTransferorDeployed(msg.sender, address(newTokenTransferor));
+ return address(newTokenTransferor);
+ }
+
+ /// Function to deploy Payroll
+ ///@dev transfers ownership of Payroll to caller of deployPayrollAndTokenTransferor
+ ///@param tokenTransferorAddress The address of the newly deployed TokenTransferor contract
+ function deployPayroll(
+ address tokenTransferorAddress
+ ) internal returns (address) {
+ //require(msg.value >= deployment_fee, "Insufficient funds sent for deployment");
+
+ Payroll newPayroll = new Payroll(
+ tokenTransferorAddress,
+ address(paymentToken)
+ );
+ newPayroll.transferOwnership(msg.sender); // Transfer ownership
+ emit PayrollDeployed(
+ msg.sender,
+ address(newPayroll),
+ tokenTransferorAddress
+ );
+ return address(newPayroll);
+ }
+
+ /// Functions to update deployment params so that they can be changed in the future or for deployment on other chains
+ function updateDeploymentFee(uint256 newFee) external onlyOwner {
+ deployment_fee = newFee;
+ }
+
+ function changeRouter(address newRouter) external onlyOwner {
+ router = IRouterClient(newRouter);
+ }
+
+ function changeLinkToken(address newLinkToken) external onlyOwner {
+ linkToken = IERC20(newLinkToken);
+ }
+
+ function changePaymentToken(address newPaymentToken) external onlyOwner {
+ paymentToken = IERC20(newPaymentToken);
+ }
+
+ // Fallback function to receive ETH
+ receive() external payable {}
+
+ // Function to withdraw collected fees
+ function withdrawFees(address payable _beneficiary) public onlyOwner {
+ // Only contract owner should be able to withdraw fees
+ // Implement ownership and access control
+ if (_beneficiary == address(0)) {
+ revert InvalidAddress();
+ }
+ _beneficiary.transfer(address(this).balance);
+ }
}
diff --git a/packages/hardhat/contracts/TokenTransferor.sol b/packages/hardhat/contracts/TokenTransferor.sol
index 8b4c70b..5a2738d 100644
--- a/packages/hardhat/contracts/TokenTransferor.sol
+++ b/packages/hardhat/contracts/TokenTransferor.sol
@@ -1,10 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
-import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
-import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
-import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
-import {IERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/token/ERC20/IERC20.sol";
+import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
+import { IRouterClient } from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
+import { Client } from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
+import { IERC20 } from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/IERC20.sol";
/**
* THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
@@ -14,236 +14,234 @@ import {IERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-sol
/// @title - A simple contract for transferring tokens across chains.
contract TokenTransferor is Ownable {
- // Custom errors to provide more descriptive revert messages.
- error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); // Used to make sure contract has enough balance to cover the fees.
- error NothingToWithdraw(); // Used when trying to withdraw Ether but there's nothing to withdraw.
- error FailedToWithdrawEth(address owner, address target, uint256 value); // Used when the withdrawal of Ether fails.
- error DestinationChainNotAllowlisted(uint64 destinationChainSelector); // Used when the destination chain has not been allowlisted by the contract owner.
- error CallerNotWhiteListed(address caller); // Used when the caller is not whitelisted by the contract owner.
-
- // Event emitted when the tokens are transferred to an account on another chain.
- event TokensTransferred(
- bytes32 indexed messageId, // The unique ID of the message.
- uint64 indexed destinationChainSelector, // The chain selector of the destination chain.
- address receiver, // The address of the receiver on the destination chain.
- address token, // The token address that was transferred.
- uint256 tokenAmount, // The token amount that was transferred.
- address feeToken, // the token address used to pay CCIP fees.
- uint256 fees // The fees paid for sending the message.
- );
-
- // Mapping to keep track of allowlisted destination chains.
- mapping(uint64 => bool) public allowlistedChains;
-
- // Mapping to keep track of whitelisted addressed that can call functions on this contract
- mapping(address => bool) public whitelistedAddresses;
-
- // Mutable extra args
- struct ExtraArgs {
- uint256 gasLimit;
- bool strict;
- }
-
- ExtraArgs public currentExtraArgs;
-
- IRouterClient private s_router;
-
- IERC20 private s_linkToken;
-
- // @notice Constructor initializes the contract with the router address.
- // @param _router The address of the router contract.
- // @param _link The address of the link contract.
- // mumbai router 0x70499c328e1e2a3c41108bd3730f6670a44595d1
- // mumbai LINK 0x326C977E6efc84E512bB9C30f76E30c160eD06FB
- constructor(address _router, address _link) {
- s_router = IRouterClient(_router);
- s_linkToken = IERC20(_link);
-
- // Initialixe default values for ExtraArgs
- currentExtraArgs = ExtraArgs ({
- gasLimit: 0,
- strict: false
- });
- }
-
- /// @dev Modifier that checks if the chain with the given destinationChainSelector is allowlisted.
- /// @param _destinationChainSelector The selector of the destination chain.
- modifier onlyAllowlistedChain(uint64 _destinationChainSelector) {
- if (!allowlistedChains[_destinationChainSelector])
- revert DestinationChainNotAllowlisted(_destinationChainSelector);
- _;
- }
-
- /// @dev Modifier that checks if the caller is allowlisted.
- modifier onlyAllowlistedAddress() {
- if (!whitelistedAddresses[msg.sender]) revert CallerNotWhiteListed(msg.sender);
- _;
- }
-
- /// @dev Updates the allowlist status of a destination chain for transactions.
- /// @notice This function can only be called by the owner.
- /// @param _destinationChainSelector The selector of the destination chain to be updated.
- /// @param allowed The allowlist status to be set for the destination chain.
- function allowlistDestinationChain(
- uint64 _destinationChainSelector,
- bool allowed
- ) external onlyOwner {
- allowlistedChains[_destinationChainSelector] = allowed;
- }
-
- /// @notice Transfer tokens to receiver on the destination chain.
- /// @notice pay in LINK.
- /// @notice the token must be in the list of supported tokens.
- /// @notice This function can only be called by allowlisted addresses.
- /// @dev Assumes your contract has sufficient LINK tokens to pay for the fees.
- /// @param _destinationChainSelector The identifier (aka selector) for the destination blockchain.
- /// @param _receiver The address of the recipient on the destination blockchain.
- /// @param _token token address.
- /// @param _amount token amount.
- /// @return messageId The ID of the message that was sent.
- function transferTokensPayLINK(
- uint64 _destinationChainSelector,
- address _receiver,
- address _token,
- uint256 _amount
- )
- external
- onlyAllowlistedAddress
- onlyAllowlistedChain(_destinationChainSelector)
- returns (bytes32 messageId)
- {
- // Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
- // address(linkToken) means fees are paid in LINK
- Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(
- _receiver,
- _token,
- _amount,
- address(s_linkToken)
- );
-
- // Get the fee required to send the message
- uint256 fees = s_router.getFee(
- _destinationChainSelector,
- evm2AnyMessage
- );
-
- if (fees > s_linkToken.balanceOf(address(this)))
- revert NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees);
-
- // approve the Router to transfer LINK tokens on contract's behalf. It will spend the fees in LINK
- s_linkToken.approve(address(s_router), fees);
-
- // approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token
- IERC20(_token).approve(address(s_router), _amount);
-
- // Send the message through the router and store the returned message ID
- messageId = s_router.ccipSend(
- _destinationChainSelector,
- evm2AnyMessage
- );
-
- // Emit an event with message details
- emit TokensTransferred(
- messageId,
- _destinationChainSelector,
- _receiver,
- _token,
- _amount,
- address(s_linkToken),
- fees
- );
-
- // Return the message ID
- return messageId;
- }
-
- /// @notice Transfer tokens to receiver on the destination chain.
- /// @notice Pay in native gas such as ETH on Ethereum or MATIC on Polgon.
- /// @notice the token must be in the list of supported tokens.
- /// @notice This function can only be called by allowlisted addresses.
- /// @dev Assumes your contract has sufficient native gas like ETH on Ethereum or MATIC on Polygon.
- /// @param _destinationChainSelector The identifier (aka selector) for the destination blockchain.
- /// @param _receiver The address of the recipient on the destination blockchain.
- /// @param _token token address.
- /// @param _amount token amount.
- /// @return messageId The ID of the message that was sent.
- function transferTokensPayNative(
- uint64 _destinationChainSelector,
- address _receiver,
- address _token,
- uint256 _amount
- )
- external
- onlyAllowlistedAddress
- onlyAllowlistedChain(_destinationChainSelector)
- returns (bytes32 messageId)
- {
- // Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
- // address(0) means fees are paid in native gas
- Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(
- _receiver,
- _token,
- _amount,
- address(0)
- );
-
- // Get the fee required to send the message
- uint256 fees = s_router.getFee(
- _destinationChainSelector,
- evm2AnyMessage
- );
-
- if (fees > address(this).balance)
- revert NotEnoughBalance(address(this).balance, fees);
-
- // approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token
- IERC20(_token).approve(address(s_router), _amount);
-
- // Send the message through the router and store the returned message ID
- messageId = s_router.ccipSend{value: fees}(
- _destinationChainSelector,
- evm2AnyMessage
- );
-
- // Emit an event with message details
- emit TokensTransferred(
- messageId,
- _destinationChainSelector,
- _receiver,
- _token,
- _amount,
- address(0),
- fees
- );
-
- // Return the message ID
- return messageId;
- }
-
- /// @notice Construct a CCIP message.
- /// @dev This function will create an EVM2AnyMessage struct with all the necessary information for tokens transfer.
- /// @param _receiver The address of the receiver.
- /// @param _token The token to be transferred.
- /// @param _amount The amount of the token to be transferred.
- /// @param _feeTokenAddress The address of the token used for fees. Set address(0) for native gas.
- /// @return Client.EVM2AnyMessage Returns an EVM2AnyMessage struct which contains information for sending a CCIP message.
- function _buildCCIPMessage(
- address _receiver,
- address _token,
- uint256 _amount,
- address _feeTokenAddress
- ) internal view returns (Client.EVM2AnyMessage memory) {
- // Set the token amounts
- Client.EVMTokenAmount[]
- memory tokenAmounts = new Client.EVMTokenAmount[](1);
- tokenAmounts[0] = Client.EVMTokenAmount({
- token: _token,
- amount: _amount
- });
-
- // Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
-
- /**
+ // Custom errors to provide more descriptive revert messages.
+ error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); // Used to make sure contract has enough balance to cover the fees.
+ error NothingToWithdraw(); // Used when trying to withdraw Ether but there's nothing to withdraw.
+ error FailedToWithdrawEth(address owner, address target, uint256 value); // Used when the withdrawal of Ether fails.
+ error DestinationChainNotAllowlisted(uint64 destinationChainSelector); // Used when the destination chain has not been allowlisted by the contract owner.
+ error CallerNotWhiteListed(address caller); // Used when the caller is not whitelisted by the contract owner.
+
+ // Event emitted when the tokens are transferred to an account on another chain.
+ event TokensTransferred(
+ bytes32 indexed messageId, // The unique ID of the message.
+ uint64 indexed destinationChainSelector, // The chain selector of the destination chain.
+ address receiver, // The address of the receiver on the destination chain.
+ address token, // The token address that was transferred.
+ uint256 tokenAmount, // The token amount that was transferred.
+ address feeToken, // the token address used to pay CCIP fees.
+ uint256 fees // The fees paid for sending the message.
+ );
+
+ // Mapping to keep track of allowlisted destination chains.
+ mapping(uint64 => bool) public allowlistedChains;
+
+ // Mapping to keep track of whitelisted addressed that can call functions on this contract
+ mapping(address => bool) public whitelistedAddresses;
+
+ // Mutable extra args
+ struct ExtraArgs {
+ uint256 gasLimit;
+ bool strict;
+ }
+
+ ExtraArgs public currentExtraArgs;
+
+ IRouterClient private s_router;
+
+ IERC20 private s_linkToken;
+
+ // @notice Constructor initializes the contract with the router address.
+ // @param _router The address of the router contract.
+ // @param _link The address of the link contract.
+ // mumbai router 0x70499c328e1e2a3c41108bd3730f6670a44595d1
+ // mumbai LINK 0x326C977E6efc84E512bB9C30f76E30c160eD06FB
+ constructor(address _router, address _link) {
+ s_router = IRouterClient(_router);
+ s_linkToken = IERC20(_link);
+
+ // Initialixe default values for ExtraArgs
+ currentExtraArgs = ExtraArgs({ gasLimit: 0, strict: false });
+ }
+
+ /// @dev Modifier that checks if the chain with the given destinationChainSelector is allowlisted.
+ /// @param _destinationChainSelector The selector of the destination chain.
+ modifier onlyAllowlistedChain(uint64 _destinationChainSelector) {
+ if (!allowlistedChains[_destinationChainSelector])
+ revert DestinationChainNotAllowlisted(_destinationChainSelector);
+ _;
+ }
+
+ /// @dev Modifier that checks if the caller is allowlisted.
+ modifier onlyAllowlistedAddress() {
+ if (!whitelistedAddresses[msg.sender])
+ revert CallerNotWhiteListed(msg.sender);
+ _;
+ }
+
+ /// @dev Updates the allowlist status of a destination chain for transactions.
+ /// @notice This function can only be called by the owner.
+ /// @param _destinationChainSelector The selector of the destination chain to be updated.
+ /// @param allowed The allowlist status to be set for the destination chain.
+ function allowlistDestinationChain(
+ uint64 _destinationChainSelector,
+ bool allowed
+ ) external onlyOwner {
+ allowlistedChains[_destinationChainSelector] = allowed;
+ }
+
+ /// @notice Transfer tokens to receiver on the destination chain.
+ /// @notice pay in LINK.
+ /// @notice the token must be in the list of supported tokens.
+ /// @notice This function can only be called by allowlisted addresses.
+ /// @dev Assumes your contract has sufficient LINK tokens to pay for the fees.
+ /// @param _destinationChainSelector The identifier (aka selector) for the destination blockchain.
+ /// @param _receiver The address of the recipient on the destination blockchain.
+ /// @param _token token address.
+ /// @param _amount token amount.
+ /// @return messageId The ID of the message that was sent.
+ function transferTokensPayLINK(
+ uint64 _destinationChainSelector,
+ address _receiver,
+ address _token,
+ uint256 _amount
+ )
+ external
+ onlyAllowlistedAddress
+ onlyAllowlistedChain(_destinationChainSelector)
+ returns (bytes32 messageId)
+ {
+ // Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
+ // address(linkToken) means fees are paid in LINK
+ Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(
+ _receiver,
+ _token,
+ _amount,
+ address(s_linkToken)
+ );
+
+ // Get the fee required to send the message
+ uint256 fees = s_router.getFee(
+ _destinationChainSelector,
+ evm2AnyMessage
+ );
+
+ if (fees > s_linkToken.balanceOf(address(this)))
+ revert NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees);
+
+ // approve the Router to transfer LINK tokens on contract's behalf. It will spend the fees in LINK
+ s_linkToken.approve(address(s_router), fees);
+
+ // approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token
+ IERC20(_token).approve(address(s_router), _amount);
+
+ // Send the message through the router and store the returned message ID
+ messageId = s_router.ccipSend(
+ _destinationChainSelector,
+ evm2AnyMessage
+ );
+
+ // Emit an event with message details
+ emit TokensTransferred(
+ messageId,
+ _destinationChainSelector,
+ _receiver,
+ _token,
+ _amount,
+ address(s_linkToken),
+ fees
+ );
+
+ // Return the message ID
+ return messageId;
+ }
+
+ /// @notice Transfer tokens to receiver on the destination chain.
+ /// @notice Pay in native gas such as ETH on Ethereum or MATIC on Polgon.
+ /// @notice the token must be in the list of supported tokens.
+ /// @notice This function can only be called by allowlisted addresses.
+ /// @dev Assumes your contract has sufficient native gas like ETH on Ethereum or MATIC on Polygon.
+ /// @param _destinationChainSelector The identifier (aka selector) for the destination blockchain.
+ /// @param _receiver The address of the recipient on the destination blockchain.
+ /// @param _token token address.
+ /// @param _amount token amount.
+ /// @return messageId The ID of the message that was sent.
+ function transferTokensPayNative(
+ uint64 _destinationChainSelector,
+ address _receiver,
+ address _token,
+ uint256 _amount
+ )
+ external
+ onlyAllowlistedAddress
+ onlyAllowlistedChain(_destinationChainSelector)
+ returns (bytes32 messageId)
+ {
+ // Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
+ // address(0) means fees are paid in native gas
+ Client.EVM2AnyMessage memory evm2AnyMessage = _buildCCIPMessage(
+ _receiver,
+ _token,
+ _amount,
+ address(0)
+ );
+
+ // Get the fee required to send the message
+ uint256 fees = s_router.getFee(
+ _destinationChainSelector,
+ evm2AnyMessage
+ );
+
+ if (fees > address(this).balance)
+ revert NotEnoughBalance(address(this).balance, fees);
+
+ // approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token
+ IERC20(_token).approve(address(s_router), _amount);
+
+ // Send the message through the router and store the returned message ID
+ messageId = s_router.ccipSend{ value: fees }(
+ _destinationChainSelector,
+ evm2AnyMessage
+ );
+
+ // Emit an event with message details
+ emit TokensTransferred(
+ messageId,
+ _destinationChainSelector,
+ _receiver,
+ _token,
+ _amount,
+ address(0),
+ fees
+ );
+
+ // Return the message ID
+ return messageId;
+ }
+
+ /// @notice Construct a CCIP message.
+ /// @dev This function will create an EVM2AnyMessage struct with all the necessary information for tokens transfer.
+ /// @param _receiver The address of the receiver.
+ /// @param _token The token to be transferred.
+ /// @param _amount The amount of the token to be transferred.
+ /// @param _feeTokenAddress The address of the token used for fees. Set address(0) for native gas.
+ /// @return Client.EVM2AnyMessage Returns an EVM2AnyMessage struct which contains information for sending a CCIP message.
+ function _buildCCIPMessage(
+ address _receiver,
+ address _token,
+ uint256 _amount,
+ address _feeTokenAddress
+ ) internal view returns (Client.EVM2AnyMessage memory) {
+ // Set the token amounts
+ Client.EVMTokenAmount[]
+ memory tokenAmounts = new Client.EVMTokenAmount[](1);
+ tokenAmounts[0] = Client.EVMTokenAmount({
+ token: _token,
+ amount: _amount
+ });
+
+ // Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
+
+ /**
* @todo
* DO NOT HARDCODE EXTRAARGS
@@ -251,90 +249,96 @@ contract TokenTransferor is Ownable {
This allows you to build it off-chain and pass it in a call to a function or store it in a variable that you can update on-demand.
This makes extraArgs compatible with future CCIP upgrades.
*/
- return
- Client.EVM2AnyMessage({
- receiver: abi.encode(_receiver), // ABI-encoded receiver address
- data: "", // No data
- tokenAmounts: tokenAmounts, // The amount and type of token being transferred
- extraArgs: Client._argsToBytes(
- // Additional arguments, setting gas limit to 0 as we are not sending any data and non-strict sequencing mode
- Client.EVMExtraArgsV1({
- gasLimit: currentExtraArgs.gasLimit,
- strict: currentExtraArgs.strict
- })
- ),
- // Set the feeToken to a feeTokenAddress, indicating specific asset will be used for fees
- feeToken: _feeTokenAddress
- });
- }
-
- /// @notice Fallback function to allow the contract to receive Ether.
- /// @dev This function has no function body, making it a default function for receiving Ether.
- /// It is automatically called when Ether is transferred to the contract without any data.
- receive() external payable {}
-
- /// @notice Allows the contract owner to withdraw the entire balance of Ether from the contract.
- /// @dev This function reverts if there are no funds to withdraw or if the transfer fails.
- /// It should only be callable by the owner of the contract.
- /// @param _beneficiary The address to which the Ether should be transferred.
- function withdraw(address _beneficiary) public onlyOwner {
- // Retrieve the balance of this contract
- uint256 amount = address(this).balance;
-
- // Revert if there is nothing to withdraw
- if (amount == 0) revert NothingToWithdraw();
-
- // Attempt to send the funds, capturing the success status and discarding any return data
- (bool sent, ) = _beneficiary.call{value: amount}("");
-
- // Revert if the send failed, with information about the attempted transfer
- if (!sent) revert FailedToWithdrawEth(msg.sender, _beneficiary, amount);
- }
-
- /// @notice Allows the owner of the contract to withdraw all tokens of a specific ERC20 token.
- /// @dev This function reverts with a 'NothingToWithdraw' error if there are no tokens to withdraw.
- /// @param _beneficiary The address to which the tokens will be sent.
- /// @param _token The contract address of the ERC20 token to be withdrawn.
- function withdrawToken(
- address _beneficiary,
- address _token
- ) public onlyOwner {
- // Retrieve the balance of this contract
- uint256 amount = IERC20(_token).balanceOf(address(this));
-
- // Revert if there is nothing to withdraw
- if (amount == 0) revert NothingToWithdraw();
-
- IERC20(_token).transfer(_beneficiary, amount);
- }
-
- function updateExtraArgs(uint256 _gasLimit, bool _strict) public onlyOwner {
- currentExtraArgs.gasLimit = _gasLimit;
- currentExtraArgs.strict = _strict;
- }
-
- function getExtraArgs() public view returns (uint256, bool) {
- return (currentExtraArgs.gasLimit, currentExtraArgs.strict);
- }
-
- function addToWhitelist(address _address) public onlyOwner {
- whitelistedAddresses[_address] = true;
- }
-
- function removeFromWhitelist(address _address) public onlyOwner {
- whitelistedAddresses[_address] = false;
- }
-
- function isWhitelisted(address _address) public view returns (bool) {
- return whitelistedAddresses[_address];
- }
-
- function isChainIdAllowed(uint64 _chainId) public view returns (bool) {
- return allowlistedChains[_chainId];
- }
-
- function transferOwnership(address newOwner) public override onlyOwner {
- require(newOwner != address(0), "Ownable: new owner is the zero address");
- _transferOwnership(newOwner);
- }
+ return
+ Client.EVM2AnyMessage({
+ receiver: abi.encode(_receiver), // ABI-encoded receiver address
+ data: "", // No data
+ tokenAmounts: tokenAmounts, // The amount and type of token being transferred
+ extraArgs: Client._argsToBytes(
+ // Additional arguments, setting gas limit to 0 as we are not sending any data and non-strict sequencing mode
+ Client.EVMExtraArgsV1({
+ gasLimit: currentExtraArgs.gasLimit
+ })
+ // Client.EVMExtraArgsV1({
+ // gasLimit: currentExtraArgs.gasLimit,
+ // strict: currentExtraArgs.strict
+ // })
+ ),
+ // Set the feeToken to a feeTokenAddress, indicating specific asset will be used for fees
+ feeToken: _feeTokenAddress
+ });
+ }
+
+ /// @notice Fallback function to allow the contract to receive Ether.
+ /// @dev This function has no function body, making it a default function for receiving Ether.
+ /// It is automatically called when Ether is transferred to the contract without any data.
+ receive() external payable {}
+
+ /// @notice Allows the contract owner to withdraw the entire balance of Ether from the contract.
+ /// @dev This function reverts if there are no funds to withdraw or if the transfer fails.
+ /// It should only be callable by the owner of the contract.
+ /// @param _beneficiary The address to which the Ether should be transferred.
+ function withdraw(address _beneficiary) public onlyOwner {
+ // Retrieve the balance of this contract
+ uint256 amount = address(this).balance;
+
+ // Revert if there is nothing to withdraw
+ if (amount == 0) revert NothingToWithdraw();
+
+ // Attempt to send the funds, capturing the success status and discarding any return data
+ (bool sent, ) = _beneficiary.call{ value: amount }("");
+
+ // Revert if the send failed, with information about the attempted transfer
+ if (!sent) revert FailedToWithdrawEth(msg.sender, _beneficiary, amount);
+ }
+
+ /// @notice Allows the owner of the contract to withdraw all tokens of a specific ERC20 token.
+ /// @dev This function reverts with a 'NothingToWithdraw' error if there are no tokens to withdraw.
+ /// @param _beneficiary The address to which the tokens will be sent.
+ /// @param _token The contract address of the ERC20 token to be withdrawn.
+ function withdrawToken(
+ address _beneficiary,
+ address _token
+ ) public onlyOwner {
+ // Retrieve the balance of this contract
+ uint256 amount = IERC20(_token).balanceOf(address(this));
+
+ // Revert if there is nothing to withdraw
+ if (amount == 0) revert NothingToWithdraw();
+
+ IERC20(_token).transfer(_beneficiary, amount);
+ }
+
+ function updateExtraArgs(uint256 _gasLimit, bool _strict) public onlyOwner {
+ currentExtraArgs.gasLimit = _gasLimit;
+ currentExtraArgs.strict = _strict;
+ }
+
+ function getExtraArgs() public view returns (uint256, bool) {
+ return (currentExtraArgs.gasLimit, currentExtraArgs.strict);
+ }
+
+ function addToWhitelist(address _address) public onlyOwner {
+ whitelistedAddresses[_address] = true;
+ }
+
+ function removeFromWhitelist(address _address) public onlyOwner {
+ whitelistedAddresses[_address] = false;
+ }
+
+ function isWhitelisted(address _address) public view returns (bool) {
+ return whitelistedAddresses[_address];
+ }
+
+ function isChainIdAllowed(uint64 _chainId) public view returns (bool) {
+ return allowlistedChains[_chainId];
+ }
+
+ function transferOwnership(address newOwner) public override onlyOwner {
+ require(
+ newOwner != address(0),
+ "Ownable: new owner is the zero address"
+ );
+ _transferOwnership(newOwner);
+ }
}
diff --git a/packages/hardhat/deploy/03_deploy_payroll.ts b/packages/hardhat/deploy/03_deploy_payroll.ts
index a0980ad..ca1c6a2 100644
--- a/packages/hardhat/deploy/03_deploy_payroll.ts
+++ b/packages/hardhat/deploy/03_deploy_payroll.ts
@@ -6,7 +6,7 @@ async function main() {
console.log("Deploying contracts with the account:", deployer.address);
// Add the addresses of the dependencies of your Payroll contract
- const ccipTokenTransferorAddress = "0xFFc15127eB60C4Ab1a2e5e0319615ca0982952f2"; // deployed via script
+ const ccipTokenTransferorAddress = "0x21d762ab159676d3bd05e12A95699C1d0b043A48"; // deployed via script
const bnmTokenAddress = "0xf1E3A5842EeEF51F2967b3F05D45DD4f4205FF40"; //BnM Mumbai
// Deploy the Payroll contract
diff --git a/packages/hardhat/package.json b/packages/hardhat/package.json
index d630720..3b72ce7 100644
--- a/packages/hardhat/package.json
+++ b/packages/hardhat/package.json
@@ -46,6 +46,8 @@
"typescript": "^5.1.6"
},
"dependencies": {
+ "@chainlink/contracts": "^0.8.0",
+ "@chainlink/contracts-ccip": "^1.2.1",
"@openzeppelin/contracts": "^4.8.1",
"dotenv": "^16.0.3",
"envfile": "^6.18.0",
diff --git a/packages/nextjs/.env.example b/packages/nextjs/.env.example
index 6769588..47c95bd 100644
--- a/packages/nextjs/.env.example
+++ b/packages/nextjs/.env.example
@@ -12,7 +12,8 @@
NEXT_PUBLIC_ALCHEMY_API_KEY=
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=
-NEXT_PUBLIC_FACTORY_CONTRACT_ADDRESS="" # Polygon Mumbai
-NEXT_PUBLIC_PAYROLL_CONTRACT_ADDRESS="" # Polygon Mumbai
+NEXT_PUBLIC_FACTORY_CONTRACT_ADDRESS= # Polygon Mumbai
+NEXT_PUBLIC_PAYROLL_CONTRACT_ADDRESS= # Polygon Mumbai
+NEXT_PUBLIC_TOKEN_TRANSFEROR_CONTRACT_ADDRESS= # Polygon Mumbai
NEXT_PUBLIC_LOCAL_CHAIN_ID=0 # REPLACE WITH LOCAL DEV CHAIN
NEXT_PUBLIC_TESTNET_CHAIN_ID=80001 # Polygon Mumbai
diff --git a/packages/nextjs/.gitignore b/packages/nextjs/.gitignore
index 6985d55..16dad0e 100644
--- a/packages/nextjs/.gitignore
+++ b/packages/nextjs/.gitignore
@@ -26,6 +26,7 @@ yarn-error.log*
.pnpm-debug.log*
# local env files
+.env
.env.local
.env.development.local
.env.test.local
diff --git a/packages/nextjs/auth/web3auth.ts b/packages/nextjs/auth/web3auth.ts
index f6da7db..b6713f3 100644
--- a/packages/nextjs/auth/web3auth.ts
+++ b/packages/nextjs/auth/web3auth.ts
@@ -1,11 +1,28 @@
// import { useEffect, useState } from "react";
import { Web3Auth } from "@web3auth/modal";
+//import { Web3AuthConnector } from "@web3auth/web3auth-wagmi-connector";
+
// import { createWalletClient, custom } from "viem";
// import { polygonMumbai } from "viem/chains";
// import { AuthProvider, setAuthProvider, setIsConnected } from "~~/auth/authSlice";
// import { MyState, useMyDispatch, useMySelector } from "~~/components/dash-wind/app/store";
+// set up your web3auth instance with all the features you want
+// const web3AuthInstance = new Web3Auth({
+// clientId: "BM0SLNkhMCfIygw0Xi79dG6qbWGMN0o0mEeDjRT0dxlP3BEok9pnu5aqxCNfj2TZ9XT7sQaXm0ltuWbCQ1tsRNI",
+// chainConfig: {
+// chainNamespace: "eip155",
+// chainId: "0x13881", // Mumbai
+// rpcTarget: "https://rpc.ankr.com/polygon_mumbai",
+// displayName: "Mumbai Testnet",
+// blockExplorer: "https://mumbai.polygonscan.com/",
+// ticker: "MATIC",
+// tickerName: "Polygon",
+// },
+// web3AuthNetwork: "sapphire_mainnet",
+// });
+
export const web3auth = new Web3Auth({
clientId: "BM0SLNkhMCfIygw0Xi79dG6qbWGMN0o0mEeDjRT0dxlP3BEok9pnu5aqxCNfj2TZ9XT7sQaXm0ltuWbCQ1tsRNI", // Get your Client ID from the Web3Auth Dashboard
web3AuthNetwork: "sapphire_devnet", // Web3Auth Network
diff --git a/packages/nextjs/components/dash-wind/containers/Header.tsx b/packages/nextjs/components/dash-wind/containers/Header.tsx
index 20d32ac..8ea2c1d 100644
--- a/packages/nextjs/components/dash-wind/containers/Header.tsx
+++ b/packages/nextjs/components/dash-wind/containers/Header.tsx
@@ -5,6 +5,8 @@ import { useRouter } from "next/router";
import { openRightDrawer } from "../features/common/rightDrawerSlice";
import { RIGHT_DRAWER_TYPES } from "../utils/globalConstantUtil";
import { themeChange } from "theme-change";
+import { Address, createWalletClient, custom } from "viem";
+import { polygonMumbai } from "viem/chains";
import Bars3Icon from "@heroicons/react/24/outline/Bars3Icon";
import BellIcon from "@heroicons/react/24/outline/BellIcon";
import MoonIcon from "@heroicons/react/24/outline/MoonIcon";
@@ -13,7 +15,7 @@ import { setIsAdmin, setIsConnected } from "~~/auth/authSlice";
import { web3auth } from "~~/auth/web3auth";
// import UserIcon from "@heroicons/react/24/outline/UserIcon";
import { MyState, useMyDispatch, useMySelector } from "~~/components/dash-wind/app/store";
-import { Address } from "~~/components/web-3-crew/Address";
+import { Address as AddressDisplay } from "~~/components/web-3-crew/Address";
function Header() {
const dispatch = useMyDispatch();
@@ -22,8 +24,19 @@ function Header() {
const [currentTheme, setCurrentTheme] = useState(
typeof window !== "undefined" ? localStorage.getItem("theme") : null,
);
+ const [address, setAddress] = useState(null);
const router = useRouter();
+ useEffect(() => {
+ async function getAddress() {
+ const address = await getAccounts();
+ if (address) {
+ setAddress(address);
+ }
+ }
+ getAddress();
+ }, []);
+
useEffect(() => {
themeChange(false);
if (currentTheme === null) {
@@ -36,6 +49,23 @@ function Header() {
// 👆 false parameter is required for react project
}, [currentTheme]);
+ async function getAccounts() {
+ if (!web3auth.provider) {
+ console.log("from login - getAccounts: provider not defined");
+ return;
+ }
+ const client = createWalletClient({
+ // account: privateKeyToAccount('0x...'); // from viem
+ chain: polygonMumbai,
+ transport: custom(web3auth.provider),
+ });
+
+ // Get user's public address
+ const [address] = await client.getAddresses();
+ //console.log("user address: ", address);
+ return address as Address;
+ }
+
// Opening right sidebar for notification
const openNotification = () => {
dispatch(openRightDrawer({ header: "Notifications", bodyType: RIGHT_DRAWER_TYPES.NOTIFICATION }));
@@ -107,12 +137,7 @@ function Header() {
state.auth);
-
const router = useRouter();
const dispatch = useMyDispatch();
+ //const userAddress = getAccounts();
+ // state to store user address once it is fetched
+ const [userAddress, setUserAddress] = useState(null);
+
+ useEffect(() => {
+ async function fetchAddress() {
+ const address = await getAccounts();
+ if (address) {
+ // not sure what this issue is with this
+ setUserAddress(address);
+ }
+ }
+ fetchAddress();
+ }, []);
+
+ const payrollABI = Payroll.abi;
/*-------------------------------------*/
// Kaz & Trevor
// getOwner address to test against user's address
// need to see what shape `owner` will be on return
+ // contract interaction calls isOwner()
const {
- data: owner,
+ data: isOwner,
// isError,
// isLoading,
} = useContractRead({
address: process.env.NEXT_PUBLIC_PAYROLL_CONTRACT_ADDRESS,
- // abi: payrollContractAbi,
- functionName: "getOwner",
+ abi: payrollABI,
+ functionName: "isOwner",
+ args: userAddress ? [userAddress] : [],
chainId: Number(chainId),
- });
+ }) as { data: boolean | undefined };
+
+ console.log("is owner: ", isOwner);
+ console.log("user address contract read: ", [getAccounts()]);
+
+ useEffect(() => {
+ if (typeof isOwner !== "undefined") {
+ // Ensure the value is not undefined
+ if (isOwner) {
+ // Redirect to owner's dashboard
+ router.push("/dapp/dashboard");
+ } else {
+ // Redirect to employee or general user dashboard
+ router.push("/dapp/dashboard");
+ }
+ }
+ }, [isOwner, router]);
+
/*-------------------------------------*/
/*-------------------------------------*/
// Kaz & Trevor
//
- const {
- data: isEmployee,
- // isError,
- // isLoading,
- } = useContractRead({
- address: process.env.NEXT_PUBLIC_PAYROLL_CONTRACT_ADDRESS,
- // abi: payrollContractAbi,
- functionName: "doesEmployeeExist",
- args: [
- /* problem: figure out how to getAccount() address here */
- ],
- chainId: Number(chainId),
- });
+ // const {
+ // data: isEmployee,
+ // // isError,
+ // // isLoading,
+ // } = useContractRead({
+ // address: process.env.NEXT_PUBLIC_PAYROLL_CONTRACT_ADDRESS,
+ // abi: payrollABI,
+ // functionName: "doesEmployeeExist",
+ // args: [
+ // /* problem: figure out how to getAccount() address here */
+ // userAddress
+ // ],
+ // chainId: Number(chainId),
+ // });
/*-------------------------------------*/
+ // // Web3Auth
+ // async function login() {
+ // if (isConnected) {
+ // // await determineIfAccountIsAdmin();
+ // // if (!isEmployee) {
+ // // //until the hook is working, this is going to prevent us from being directed to the dashboard
+ // // return;
+ // // }
+ // router.push("/dapp/dashboard");
+ // return;
+ // }
+
+ // try {
+ // await web3auth.connect();
+ // if (web3auth.connected) {
+ // dispatch(setIsConnected({ isConnected: true }));
+ // //const userAddress = await getAccounts(); // Retrieve user's address
+
+ // await determineIfAccountIsAdmin();
+ // if (!isEmployee) {
+ // // until the hook is working, this is going to prevent us from being directed to the dashboard
+ // return;
+ // }
+ // router.push("/dapp/dashboard");
+ // }
+ // } catch (error) {
+ // console.error(error);
+ // }
+ // }
+
// Web3Auth
async function login() {
if (isConnected) {
- await determineIfAccountIsAdmin();
- if (!isEmployee) {
- // until the hook is working, this is going to prevent us from being directed to the dashboard
+ // await determineIfAccountIsAdmin();
+ if (!isOwner) {
+ //until the hook is working, this is going to prevent us from being directed to the dashboard
+ router.push("/dapp/dashboard");
+
return;
}
- router.push("/dapp/dashboard");
return;
}
@@ -83,61 +155,68 @@ function Login() {
await web3auth.connect();
if (web3auth.connected) {
dispatch(setIsConnected({ isConnected: true }));
+
+ //const userAddress = await getAccounts(); // Retrieve user's address
+
// await determineIfAccountIsAdmin();
- // if (!isEmployee) {
- // // until the hook is working, this is going to prevent us from being directed to the dashboard
- // return;
- // }
- router.push("/dapp/dashboard");
+ if (!isOwner) {
+ // until the hook is working, this is going to prevent us from being directed to the dashboard
+ router.push("/dapp/dashboard");
+
+ return;
+ }
}
} catch (error) {
console.error(error);
}
}
- async function determineIfAccountIsAdmin() {
- // set loading === true ???
- // const address = await getAccounts();
- // if (!address) {
- // console.error("from determineIfAccountIsAdmin - address is undefined");
- // return;
- // }
+ // async function determineIfAccountIsAdmin() {
+ // // set loading === true ???
+ // const userAddress = getAccounts();
+ // if (!userAddress) {
+ // console.error("from determineIfAccountIsAdmin - address is undefined");
+ // return;
+ // }
+
+ // if (!owner) {
+ // console.error("From determineIfAccountIsAdmin: ownerData from Payroll Contract is undefined");
+ // return;
+ // }
+
+ // /*-------------------------------------*/
+ // // Kaz & Trevor
+ // // need to see what shape `owner` will be on return
+ // const isAdmin = userAddress === owner ? true : false;
+ // dispatch(setIsAdmin({ isAdmin: isAdmin }));
+ // /*-------------------------------------*/
+ // // set loading === false ???
+ // }
- if (!owner) {
- console.error("From determineIfAccountIsAdmin: ownerData from Payroll Contract is undefined");
+ // func to grab the connected wallet address
+ async function getAccounts() {
+ if (!web3auth.provider) {
+ console.log("from login - getAccounts: provider not defined");
return;
}
+ const client = createWalletClient({
+ // account: privateKeyToAccount('0x...'); // from viem
+ chain: polygonMumbai,
+ transport: custom(web3auth.provider),
+ });
- /*-------------------------------------*/
- // Kaz & Trevor
- // need to see what shape `owner` will be on return
- // const isAdmin = address === owner ? true : false;
- // dispatch(setIsAdmin({ isAdmin: isAdmin }));
- /*-------------------------------------*/
- // set loading === false ???
+ //console.log("web3auth provider: ", web3auth.provider);
+ // Get user's public address
+ const [userAddress] = await client.getAddresses();
+ console.log("user address: ", userAddress);
+ return userAddress as Address;
}
- // async function getAccounts() {
- // if (!web3auth.provider) {
- // console.log("from login - getAccounts: provider not defined");
- // return;
- // }
- // const client = createWalletClient({
- // // account: privateKeyToAccount('0x...'); // from viem
- // chain: polygonMumbai,
- // transport: custom(web3auth.provider),
- // });
-
- // // Get user's public address
- // const [address] = await client.getAddresses();
- // return address as Address;
- // }
-
const submitForm = (e: React.FormEvent) => {
e.preventDefault();
setErrorMessage("");
- if (loginObj.emailId.trim() === "") return setErrorMessage("Email is required!");
+ // if (loginObj.emailId.trim() === "") return setErrorMessage("Email is required!");
if (loginObj.contractAddress.trim() === "") return setErrorMessage("Contract Address is required!");
};
@@ -165,14 +244,14 @@ function Login() {
updateFormValue={updateFormValue}
/>
-
+ /> */}
{errorMessage}
diff --git a/packages/nextjs/components/dash-wind/features/user/Register.tsx b/packages/nextjs/components/dash-wind/features/user/Register.tsx
index 035ba0d..9629106 100644
--- a/packages/nextjs/components/dash-wind/features/user/Register.tsx
+++ b/packages/nextjs/components/dash-wind/features/user/Register.tsx
@@ -24,7 +24,7 @@ function Register() {
const [walletAddress, setWalletAddress] = useState(null);
const dispatch = useMyDispatch();
-
+ //@note is this working? working for register page?
async function login() {
try {
await web3auth.connect();
@@ -49,6 +49,8 @@ function Register() {
// Get user's public address
const [address] = await client.getAddresses();
+ console.log("user address: ", address);
+
return address;
}
@@ -61,9 +63,12 @@ function Register() {
setRegisterState("loading");
console.log("logging in company...");
await login();
- // Account Abstraction goes here...?
- console.log("getting account address...");
+ // Account Abstraction goes here...? -- kinda is with login
+ // grab account address
const address = await getAccounts();
+ console.log("connected address: ", address);
+
+ console.log("getting account address...");
if (address) {
// Prompt user to fund wallet?
setWalletAddress(address);
diff --git a/packages/nextjs/components/web-3-crew/register-page/DeployForm.tsx b/packages/nextjs/components/web-3-crew/register-page/DeployForm.tsx
index acbef6f..84d8ad7 100644
--- a/packages/nextjs/components/web-3-crew/register-page/DeployForm.tsx
+++ b/packages/nextjs/components/web-3-crew/register-page/DeployForm.tsx
@@ -1,36 +1,66 @@
import React from "react";
import Link from "next/link";
-import { Abi, Address, parseEther } from "viem";
-import { useContractWrite, usePrepareContractWrite } from "wagmi";
+import PayrollFactory from "../../../../hardhat/artifacts/contracts/PayrollFactory.sol/PayrollFactory.json";
+import { Address, parseEther } from "viem";
+import { useContractWrite } from "wagmi";
+import { web3auth } from "~~/auth/web3auth";
import ErrorText from "~~/components/dash-wind/components/Typography/ErrorText";
import { Address as AddressDisplay } from "~~/components/scaffold-eth/Address";
+//import { useDeployedContractInfo } from "~~/hooks/scaffold-eth";
+
interface props {
ownerAddress: Address | null;
}
-
-const payrollFactoryAddress = "";
-const payrollFactoryABI: Abi = [];
+// Check if the user is connected
+const isConnected = web3auth.connected;
+const payrollFactoryAddress = "0xfe44aB0B966E57F126130BE6401546c7351484ad";
+const payrollFactoryABI = PayrollFactory.abi;
export default function DeployForm({ ownerAddress }: props) {
+ //const { data: contractData } = useDeployedContractInfo("PayrollFactory");
+
/*-------------------------------------*/
- // Kaz & Trevor
+ // Kaz & Trevor -- think this is the right way to do it
+ // but running in browser it seems to always be looking for the connected address
+ // see no action in the terminal when the button is clicked
// deploy company contract after registration of account
- const { config } = usePrepareContractWrite({
+ // const { config } = usePrepareContractWrite({
+ // address: payrollFactoryAddress,
+ // // abi: contractData?.abi,
+ // abi: payrollFactoryABI,
+ // functionName: "deployPayrollAndTokenTransferor",
+ // value: parseEther("1", "wei"),
+ // onSuccess(data) {
+ // console.log("contract deployed! Data: ", data); //will data be the contract addresses?
+ // },
+ // onError(error) {
+ // console.error("contract deploy error!", error); //error message
+ // },
+ // });
+
+ //console.log("config: ", config);
+
+ const { data, isLoading, isSuccess, write } = useContractWrite({
address: payrollFactoryAddress,
abi: payrollFactoryABI,
functionName: "deployPayrollAndTokenTransferor",
- value: parseEther("1", "wei"),
- onSuccess(data) {
- console.log("contract deployed! Data: ", data);
- },
- onError(error) {
- console.error("contract deploy error!", error);
- },
});
+ console.log("write: ", write);
+ console.log("data: ", data);
- const { data, isLoading, isSuccess, write } = useContractWrite(config);
- /*-------------------------------------*/
+ // Function to handle button click
+ const handleDeployClick = () => {
+ if (isConnected) {
+ write({
+ args: [],
+ //from: ownerAddress,
+ value: parseEther("1", "wei"),
+ });
+ } else {
+ console.log("Wallet not connected");
+ }
+ };
return (
@@ -44,7 +74,14 @@ export default function DeployForm({ ownerAddress }: props) {
{/*