diff --git a/.env b/.env
new file mode 100644
index 0000000..5ee4451
--- /dev/null
+++ b/.env
@@ -0,0 +1,13 @@
+# Environment variables declared in this file are automatically made available to Prisma.
+# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
+
+# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
+# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
+
+# DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"
+DATABASE_URL="file:./dev.db"
+
+NEXT_PUBLIC_BASE_API_URL = 'http://localhost:3000'
+NEXT_PUBLIC_ORIGIN = 'http://localhost:3000,https://multisig-ton.oraidex.io/,'
+
+r
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index fd3dbb5..783eb36 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,3 +34,5 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
+
+/prisma/dev.db
diff --git a/Dockerfile b/Dockerfile
index 9302ed1..36fbd74 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -15,6 +15,12 @@ RUN \
else echo "Lockfile not found." && exit 1; \
fi
+# Check if dev.db exists and run Prisma migrate if not
+RUN \
+ if [ ! -f "dev.db" ]; \
+ then npx prisma migrate deploy; \
+ else echo "Database exists, skipping migration"; \
+ fi
# Rebuild the source code only when needed
FROM base AS builder
diff --git a/app/api/multisig/[id]/route.ts b/app/api/multisig/[id]/route.ts
new file mode 100644
index 0000000..5beff90
--- /dev/null
+++ b/app/api/multisig/[id]/route.ts
@@ -0,0 +1,104 @@
+import { NextRequest, NextResponse } from "next/server";
+import { PrismaClient } from "@prisma/client";
+
+const prisma = new PrismaClient();
+
+export async function GET(req: NextRequest, { params }) {
+ try {
+ const { id } = params || {};
+ const url = new URL(req.url);
+
+ if (!id) {
+ return new NextResponse(
+ JSON.stringify({ error: "Address multisig notfound!" }),
+ {
+ status: 400,
+ }
+ );
+ }
+
+ const result = await prisma.multisig.findFirst({
+ where: {
+ address: id,
+ },
+ });
+
+ return new NextResponse(JSON.stringify({ data: result }), { status: 200 });
+ } catch (error) {
+ console.log("Error get multisig record: ", error);
+ return new NextResponse(
+ JSON.stringify({ error: "Failed to get multisig" }),
+ {
+ status: 500,
+ }
+ );
+ }
+}
+
+export async function PATCH(req: NextRequest, { params }) {
+ try {
+ const { name, address, createdBy, importedBy, signers, proposers } =
+ await req.json();
+
+ const { id } = params || {};
+ if (!id) {
+ return new NextResponse(
+ JSON.stringify({ error: "Address multisig notfound!" }),
+ {
+ status: 400,
+ }
+ );
+ }
+
+ const url = new URL(req.url);
+ const addressUser = url.searchParams.get("userAddress");
+
+ if (!addressUser) {
+ return new NextResponse(
+ JSON.stringify({ error: "addressUser is required" }),
+ {
+ status: 400,
+ }
+ );
+ }
+
+ const updateData: any = {};
+
+ if (name) {
+ updateData["name"] = name;
+ }
+ if (importedBy) {
+ updateData["importedBy"] = importedBy;
+ }
+ if (signers) {
+ updateData["signers"] = signers;
+ }
+ if (proposers) {
+ updateData["proposers"] = proposers;
+ }
+
+ const result = await prisma.multisig.upsert({
+ where: { address: id },
+ update: updateData,
+ create: {
+ ...updateData,
+ address: id,
+ createdBy: addressUser,
+ },
+ });
+
+ return new NextResponse(
+ JSON.stringify({
+ message: "Multisig updated successfully",
+ data: result,
+ }),
+ { status: 200 }
+ );
+ } catch (error) {
+ console.log("Error updating multisig record: ", error);
+ return new NextResponse(
+ JSON.stringify({ error: "Failed to update multisig" }),
+ { status: 500 }
+ );
+ }
+}
diff --git a/app/api/multisig/route.ts b/app/api/multisig/route.ts
new file mode 100644
index 0000000..47dc895
--- /dev/null
+++ b/app/api/multisig/route.ts
@@ -0,0 +1,104 @@
+import { NextRequest, NextResponse } from "next/server";
+import { PrismaClient } from "@prisma/client";
+
+const prisma = new PrismaClient();
+
+export async function GET(req: NextRequest) {
+ try {
+ const url = new URL(req.url);
+ const addressUser = url.searchParams.get("addressUser");
+
+ if (!addressUser) {
+ return new NextResponse(
+ JSON.stringify({ error: "addressUser is required" }),
+ {
+ status: 400,
+ }
+ );
+ }
+
+ const result = await prisma.multisig.findMany({
+ where: {
+ OR: [
+ { createdBy: addressUser },
+ { importedBy: { contains: addressUser } },
+ { signers: { contains: addressUser } },
+ { proposers: { contains: addressUser } },
+ ],
+ },
+ });
+
+ return new NextResponse(JSON.stringify({ data: result }), { status: 200 });
+ } catch (error) {
+ console.log("Error get multisig record: ", error);
+ return new NextResponse(
+ JSON.stringify({ error: "Failed to get multisig" }),
+ {
+ status: 500,
+ }
+ );
+ }
+}
+
+export async function POST(req: NextRequest) {
+ try {
+ const { name, address, createdBy, importedBy, signers, proposers } =
+ await req.json();
+
+ const result = await prisma.multisig.create({
+ data: {
+ name,
+ address,
+ createdBy,
+ importedBy,
+ signers,
+ proposers,
+ },
+ });
+
+ return new NextResponse(
+ JSON.stringify({ message: "Multisig added successfully", data: result }),
+ { status: 200 }
+ );
+ } catch (error) {
+ console.log("Error adding multisig record: ", error);
+ return new NextResponse(
+ JSON.stringify({ error: "Failed to add multisig" }),
+ {
+ status: 500,
+ }
+ );
+ }
+}
+
+// export async function PATCH(req: NextRequest) {
+// try {
+// const { name, address, createdBy, importedBy, signers, proposers } =
+// await req.json();
+
+// const result = await prisma.multisig.update({
+// where: { address },
+// data: {
+// name,
+// createdBy,
+// importedBy: JSON.stringify(importedBy),
+// signers: JSON.stringify(signers),
+// proposers: JSON.stringify(proposers),
+// },
+// });
+
+// return new NextResponse(
+// JSON.stringify({
+// message: "Multisig updated successfully",
+// data: result,
+// }),
+// { status: 200 }
+// );
+// } catch (error) {
+// console.log("Error updating multisig record: ", error);
+// return new NextResponse(
+// JSON.stringify({ error: "Failed to update multisig" }),
+// { status: 500 }
+// );
+// }
+// }
diff --git a/app/api/other/.keep b/app/api/other/.keep
new file mode 100644
index 0000000..e69de29
diff --git a/components/layout/connectButton/index.tsx b/components/layout/connectButton/index.tsx
index 7935c74..fe6e9e0 100644
--- a/components/layout/connectButton/index.tsx
+++ b/components/layout/connectButton/index.tsx
@@ -131,8 +131,6 @@ const ConnectButton: FC<{ fullWidth?: boolean }> = ({ fullWidth }) => {
return;
}
- console.log("userFriendlyAddress", userFriendlyAddress, wallet);
-
handleSetTonAddress({ tonAddress: userFriendlyAddress });
handleSetTonWallet({
tonWallet:
@@ -141,7 +139,6 @@ const ConnectButton: FC<{ fullWidth?: boolean }> = ({ fullWidth }) => {
});
}, [userFriendlyAddress, wallet]);
- console.log("tonAddress", tonAddress);
return (
{
const { handleSetListMultisig } = useMultisigActions();
const [tonConnectUI] = useTonConnectUI();
+ const router = useRouter();
const listMultisig = useGetListMultisig();
const tonAddress = useAuthTonAddress();
const [loading, setLoading] = useState(false);
const [threshold, setThreshold] = useState
();
+ const [name, setName] = useState();
// useGoBackBrowser();
- const [signers, setSigners] = useState<{ id: number; value: string }[]>([
+ const [signers, setSigners] = useState<
+ { id: number; value: string; name: string }[]
+ >([
{
id: 0,
value: undefined,
+ name: undefined,
},
]);
+ const { addMultisigSnapshot } = useHandleMultisigServer();
+
const addNewSigner = () => {
setSigners([
...signers,
{
id: (signers[signers.length - 1]?.id || 0) + 1,
value: undefined,
+ name: undefined,
},
]);
};
@@ -51,9 +61,9 @@ const CreateMultisig = () => {
setSigners(newSigner);
};
- const [proposers, setProposers] = useState<{ id: number; value: string }[]>(
- []
- );
+ const [proposers, setProposers] = useState<
+ { id: number; value: string; name: string }[]
+ >([]);
const addNewProposers = () => {
setProposers([
@@ -61,6 +71,7 @@ const CreateMultisig = () => {
{
id: (proposers[proposers.length - 1]?.id || 0) + 1,
value: undefined,
+ name: undefined,
},
]);
};
@@ -116,19 +127,24 @@ const CreateMultisig = () => {
],
validUntil: Date.now() + 1000 * 60 * 5,
});
- console.log({
+ // if (!listMultisig[tonAddress]) {
+ // listMultisig[tonAddress] = [multisig.address.toString()];
+ // } else {
+ // listMultisig[tonAddress].push(multisig.address.toString());
+ // }
+ // handleSetListMultisig({ listMultisig });
+
+ const dataSaveBE = {
+ name,
address: multisig.address.toString(),
- amount: toNano(1).toString(),
- stateInit: multisigConfigToCell(multisigConfig)
- .toBoc()
- .toString("base64"),
- });
- if (!listMultisig[tonAddress]) {
- listMultisig[tonAddress] = [multisig.address.toString()];
- } else {
- listMultisig[tonAddress].push(multisig.address.toString());
- }
- handleSetListMultisig({ listMultisig });
+ createdBy: tonAddress,
+ importedBy: JSON.stringify([]),
+ signers: JSON.stringify(signers),
+ proposers: JSON.stringify(proposers),
+ };
+ await addMultisigSnapshot(dataSaveBE);
+
+ router.push(`/multisig/${multisig.address}/detail`);
} catch (error) {
console.log("error", error);
displayToast(TToastType.TX_FAILED, {
@@ -142,6 +158,23 @@ const CreateMultisig = () => {
return (
+
+
+
+
+
+ {
+ e.preventDefault();
+ const value = e.target.value;
+ setName(value);
+ }}
+ />
+
+
@@ -150,15 +183,29 @@ const CreateMultisig = () => {
return (
#{index + 1}
+
{
+ e.preventDefault();
+ const value = e.target.value;
+
+ const newSigner = signers.map((oldS) =>
+ oldS.id === s.id ? { ...oldS, name: value } : oldS
+ );
+
+ setSigners(newSigner);
+ }}
+ />
{
e.preventDefault();
const value = e.target.value;
- console.log("value", value, s.id);
const newSigner = signers.map((oldS) =>
oldS.id === s.id ? { ...oldS, value } : oldS
);
@@ -184,10 +231,25 @@ const CreateMultisig = () => {
return (
#{index + 1}
+
{
+ e.preventDefault();
+ const value = e.target.value;
+
+ const newProposer = proposers.map((oldP) =>
+ oldP.id === p.id ? { ...oldP, name: value } : oldP
+ );
+
+ setProposers(newProposer);
+ }}
+ />
{
e.preventDefault();
const value = e.target.value;
diff --git a/components/page/multisig/detail/index.module.scss b/components/page/multisig/detail/index.module.scss
index 14ac8f6..540f7c6 100644
--- a/components/page/multisig/detail/index.module.scss
+++ b/components/page/multisig/detail/index.module.scss
@@ -98,13 +98,13 @@
font-size: 12px;
font-weight: 400;
}
+
.list {
display: flex;
flex-wrap: wrap;
// flex-direction: column;
gap: 6px;
-
.orderItem {
border: 1px solid #aee67f;
border-radius: 99px;
@@ -120,6 +120,10 @@
span {
color: #979995;
+
+ &.nameSuffix {
+ color: #fff;
+ }
}
}
}
diff --git a/components/page/multisig/detail/index.tsx b/components/page/multisig/detail/index.tsx
index 78e0c52..8ef167b 100644
--- a/components/page/multisig/detail/index.tsx
+++ b/components/page/multisig/detail/index.tsx
@@ -8,6 +8,11 @@ import { useAuthTonAddress } from "@/stores/authentication/selector";
import { useTonConnector } from "@/contexts/custom-ton-provider";
import { useEffect, useState } from "react";
import { Address, fromNano } from "@ton/core";
+import {
+ DEFAULT_BE_DATA,
+ parseJsonDataFromSqlite,
+} from "@/hooks/useHandleMultisigServer";
+import { toUserFriendlyAddress } from "@tonconnect/sdk";
const DetailMultisig = () => {
const [tonBalance, setTonBalance] = useState(0n);
@@ -22,11 +27,13 @@ const DetailMultisig = () => {
nextOrderSeqno: 0,
};
+ const parseAddress = (address: string) => Address.parse(address);
+
useEffect(() => {
const fetchTonBalance = async () => {
try {
const balance = await tonClient.getBalance(
- Address.parse(addressMultisig)
+ parseAddress(addressMultisig)
);
setTonBalance(balance);
} catch (error) {
@@ -36,10 +43,12 @@ const DetailMultisig = () => {
fetchTonBalance();
}, [addressMultisig, tonClient]);
+ const { data: dataBE = DEFAULT_BE_DATA } = data || {};
+
return (
-
+
{addressMultisig}
Switch to another multisig
@@ -48,23 +57,31 @@ const DetailMultisig = () => {
- {/* TODO: TON balance */}
{fromNano(tonBalance.toString())} TON
- {/* TODO: threshold */}
{`${threshold}/${signers?.length}`}
{(signers || []).map((e, index) => {
+ const signersBE = parseJsonDataFromSqlite(dataBE.signers);
+ const currentSigner = signersBE?.find(
+ (s: any) => s.value === toUserFriendlyAddress(e.toRawString())
+ );
+
return (
#{index + 1} -
- {e.toString()}
+
+ {toUserFriendlyAddress(e.toRawString())}
+
+ {`${currentSigner ? ` (${currentSigner.name})` : ""}`}
+
+
{tonAddress &&
- Address.parse(e.toString()).equals(
- Address.parse(tonAddress)
+ parseAddress(e.toString()).equals(
+ parseAddress(tonAddress)
) && It's You}
);
@@ -78,10 +95,29 @@ const DetailMultisig = () => {
No Proposers
) : (
proposers.map((e: Address, index: number) => {
+ const proposersBE = parseJsonDataFromSqlite(dataBE.proposers);
+ const currentProposer = proposersBE?.find(
+ (s: any) =>
+ s.value === toUserFriendlyAddress(e.toRawString())
+ );
+
return (
#{index + 1} -
- {e.toString()}
+
+ {toUserFriendlyAddress(e.toRawString())}
+
+ {`${
+ currentProposer ? ` (${currentProposer.name})` : ""
+ }`}
+
+
+ {tonAddress &&
+ parseAddress(e.toString()).equals(
+ parseAddress(tonAddress)
+ ) && (
+ It's You
+ )}
);
})
@@ -111,7 +147,7 @@ const DetailMultisig = () => {
-
+ {Number(nextOrderSeqno) > 0 &&
}
{[...Array(Number(nextOrderSeqno.toString())).keys()].map(
(e, idx) => {
diff --git a/components/page/multisig/edit-multisig/index.tsx b/components/page/multisig/edit-multisig/index.tsx
index cbd5f49..eb0cd36 100644
--- a/components/page/multisig/edit-multisig/index.tsx
+++ b/components/page/multisig/edit-multisig/index.tsx
@@ -2,45 +2,52 @@
import Loader from "@/components/commons/loader/Loader";
import { useTonConnector } from "@/contexts/custom-ton-provider";
+import { getSenderFromConnector } from "@/helper";
+import useGetMultisigData from "@/hooks/useGetMutisigData";
+import useHandleMultisigServer, {
+ DEFAULT_BE_DATA,
+ parseJsonDataFromSqlite,
+} from "@/hooks/useHandleMultisigServer";
import { useAuthTonAddress } from "@/stores/authentication/selector";
-import {
- useGetListMultisig,
- useMultisigActions,
-} from "@/stores/multisig/selector";
-import * as MultisigBuild from "@oraichain/ton-multiowner/dist/build/Multisig.compiled.json";
import {
Multisig,
MultisigConfig,
- multisigConfigToCell,
UpdateRequest,
} from "@oraichain/ton-multiowner/dist/wrappers/Multisig";
-import { Address, beginCell, Cell, storeStateInit, toNano } from "@ton/core";
+import { Address, toNano } from "@ton/core";
+import { toUserFriendlyAddress, useTonConnectUI } from "@tonconnect/ui-react";
import Link from "next/link";
+import { useParams, useRouter } from "next/navigation";
import { useEffect, useState } from "react";
-import styles from "./index.module.scss";
-import useGetMultisigData from "@/hooks/useGetMutisigData";
-import { useParams } from "next/navigation";
import NumberFormat from "react-number-format";
-import { getSenderFromConnector } from "@/helper";
-import { useTonConnectUI } from "@tonconnect/ui-react";
+import styles from "./index.module.scss";
+import { displayToast, TToastType } from "@/contexts/toasts/Toast";
const EditMultisig = () => {
const { tonClient } = useTonConnector();
const [tonConnectUI] = useTonConnectUI();
+ const router = useRouter();
const { id: addressMultisig } = useParams<{ id: string }>();
+ const [name, setName] = useState
();
const tonAddress = useAuthTonAddress();
const [loading, setLoading] = useState(false);
const [threshold, setThreshold] = useState();
- const [signers, setSigners] = useState<{ id: number; value: string }[]>([
+ const [signers, setSigners] = useState<
+ { id: number; value: string; name: string }[]
+ >([
{
id: 0,
value: undefined,
+ name: undefined,
},
]);
const { data } = useGetMultisigData({ addressMultisig });
- const [proposers, setProposers] = useState<{ id: number; value: string }[]>(
- []
- );
+ const [proposers, setProposers] = useState<
+ { id: number; value: string; name: string }[]
+ >([]);
+
+ const { updateMultisigSnapshot, getMultisigDetailSnapshot } =
+ useHandleMultisigServer();
const addNewSigner = () => {
setSigners([
@@ -48,6 +55,7 @@ const EditMultisig = () => {
{
id: (signers[signers.length - 1]?.id || 0) + 1,
value: undefined,
+ name: undefined,
},
]);
};
@@ -63,6 +71,7 @@ const EditMultisig = () => {
{
id: (proposers[proposers.length - 1]?.id || 0) + 1,
value: undefined,
+ name: undefined,
},
]);
};
@@ -111,8 +120,26 @@ const EditMultisig = () => {
expirationDate,
toNano(0.05)
);
+ const dataSaveBE = {
+ name,
+ address: multisig.address.toString(),
+ createdBy: tonAddress,
+ importedBy: JSON.stringify([]),
+ signers: JSON.stringify(signers),
+ proposers: JSON.stringify(proposers),
+ };
+ await updateMultisigSnapshot(
+ multisig.address.toString(),
+ dataSaveBE,
+ tonAddress
+ );
+
+ router.push(`/multisig/${multisig.address}/detail`);
} catch (error) {
console.log("error", error);
+ displayToast(TToastType.TX_FAILED, {
+ message: "Update multisig failed!",
+ });
} finally {
setLoading(false);
}
@@ -121,15 +148,35 @@ const EditMultisig = () => {
useEffect(() => {
if (data) {
+ const { data: dataBE = DEFAULT_BE_DATA } = data || {};
+ setName(dataBE.name);
setThreshold(Number(data.threshold));
setSigners(
(data.signers || []).map((s, ind) => {
- return { id: ind, value: s.toString() };
+ const signersBE = parseJsonDataFromSqlite(dataBE.signers);
+ const friendlyAddress = toUserFriendlyAddress(s.toRawString());
+ const currentSigner = signersBE?.find(
+ (e: any) => e.value === friendlyAddress
+ );
+ return {
+ id: ind,
+ value: friendlyAddress,
+ name: currentSigner?.name || "",
+ };
})
);
setProposers(
(data.proposers || []).map((p, ind) => {
- return { id: ind, value: p.toString() };
+ const proposersBE = parseJsonDataFromSqlite(dataBE.proposers);
+ const friendlyAddress = toUserFriendlyAddress(p.toRawString());
+ const currentProposer = proposersBE?.find(
+ (e: any) => e.value === friendlyAddress
+ );
+ return {
+ id: ind,
+ value: friendlyAddress,
+ name: currentProposer?.name || "",
+ };
})
);
}
@@ -140,6 +187,24 @@ const EditMultisig = () => {
+
+
+
+
+
+
+ {
+ e.preventDefault();
+ const value = e.target.value;
+ setName(value);
+ }}
+ />
+
+
@@ -148,6 +213,21 @@ const EditMultisig = () => {
return (
#{index + 1}
+
{
+ e.preventDefault();
+ const value = e.target.value;
+
+ const newSigner = signers.map((oldS) =>
+ oldS.id === s.id ? { ...oldS, name: value } : oldS
+ );
+
+ setSigners(newSigner);
+ }}
+ />
{
return (
#{index + 1}
+ {
+ e.preventDefault();
+ const value = e.target.value;
+
+ const newProposer = proposers.map((oldP) =>
+ oldP.id === p.id ? { ...oldP, name: value } : oldP
+ );
+
+ setProposers(newProposer);
+ }}
+ />
{
const [loading, setLoading] = useState(false);
@@ -23,14 +29,18 @@ const ImportMultisig = () => {
const { handleSetListMultisig } = useMultisigActions();
const listMultisig = useGetListMultisig();
const tonAddress = useAuthTonAddress();
- const {tonClient} = useTonConnector()
+ const { tonClient } = useTonConnector();
+ const { getMultisigDetailSnapshot, updateMultisigSnapshot } =
+ useHandleMultisigServer();
const onImport = async () => {
try {
setLoading(true);
const fmtMultisig = multisig.trim();
- const isDeployed = await tonClient.isContractDeployed(Address.parse(fmtMultisig));
- if(!isDeployed){
+ const isDeployed = await tonClient.isContractDeployed(
+ Address.parse(fmtMultisig)
+ );
+ if (!isDeployed) {
throw "Contract not deployed!";
}
@@ -40,13 +50,52 @@ const ImportMultisig = () => {
throw "Not a Multisig contract";
}
- if (!listMultisig[tonAddress]) {
- listMultisig[tonAddress] = [fmtMultisig];
- } else {
- const checkExist = listMultisig[tonAddress].includes(fmtMultisig);
- !checkExist && listMultisig[tonAddress].push(fmtMultisig);
- }
- handleSetListMultisig({ listMultisig });
+ const dataSnapshot = await getMultisigDetailSnapshot(fmtMultisig);
+ const importedBy = parseJsonDataFromSqlite(
+ dataSnapshot?.data?.importedBy
+ );
+
+ const updateIncluded = Array.from(
+ new Set([...(importedBy || []), tonAddress])
+ );
+
+ console.log("first", updateIncluded);
+
+ const { data: dataBE = DEFAULT_BE_DATA } = dataMultisig || {};
+ await updateMultisigSnapshot(
+ fmtMultisig,
+ {
+ ...dataBE,
+ address: dataBE.address || fmtMultisig,
+ name:
+ dataBE.name || reduceString(dataBE.address || fmtMultisig, 8, 8),
+ proposers:
+ dataBE.proposers ||
+ JSON.stringify(
+ dataMultisig.proposers.map((e) =>
+ toUserFriendlyAddress(e.toRawString())
+ )
+ ),
+ signers:
+ dataBE.signers ||
+ JSON.stringify(
+ dataMultisig.signers.map((e) =>
+ toUserFriendlyAddress(e.toRawString())
+ )
+ ),
+ importedBy: JSON.stringify(updateIncluded),
+ },
+ tonAddress
+ );
+
+ // if (!listMultisig[tonAddress]) {
+ // listMultisig[tonAddress] = [fmtMultisig];
+ // } else {
+ // const checkExist = listMultisig[tonAddress].includes(fmtMultisig);
+ // !checkExist && listMultisig[tonAddress].push(fmtMultisig);
+ // }
+ // handleSetListMultisig({ listMultisig });
+
router.push(`/multisig/${fmtMultisig}/detail`);
} catch (error) {
console.log("error", error);
@@ -78,10 +127,7 @@ const ImportMultisig = () => {
Back
-
diff --git a/components/page/onboard/index.tsx b/components/page/onboard/index.tsx
index 74ff8fe..08a3806 100644
--- a/components/page/onboard/index.tsx
+++ b/components/page/onboard/index.tsx
@@ -8,10 +8,24 @@ import {
} from "@/stores/multisig/selector";
import { useAuthTonAddress } from "@/stores/authentication/selector";
import { reduceString } from "@/libs/utils";
+import { useEffect, useState } from "react";
+import useHandleMultisigServer from "@/hooks/useHandleMultisigServer";
const MultisigList = () => {
const tonAddress = useAuthTonAddress();
const listMultisig = useGetListMultisig();
+ const [myMultisig, setMyMultisig] = useState([]);
+
+ const { getMultisigListSnapshot } = useHandleMultisigServer();
+
+ useEffect(() => {
+ (async () => {
+ const response = await getMultisigListSnapshot(tonAddress);
+ if (response?.data) {
+ setMyMultisig(response.data);
+ }
+ })();
+ }, [tonAddress]);
return (
@@ -25,14 +39,19 @@ const MultisigList = () => {
- {!!(listMultisig[tonAddress] || []).length &&
List multisigs
}
+ {!!(myMultisig || []).length &&
List multisigs
}
- {[...new Set(listMultisig[tonAddress] || [])].map((multisig, key) => (
-
- {reduceString(multisig, 8, 8)}
-
- ))}
+ {[...new Set(myMultisig || [])].map((multisig, key) => {
+ const { name = "", address = "" } = multisig || {};
+
+ return (
+
+ {name}
+ {/* {reduceString(multisig, 8, 8)} */}
+
+ );
+ })}
diff --git a/components/page/order/constants/index.ts b/components/page/order/constants/index.ts
index d529fc6..52b0085 100644
--- a/components/page/order/constants/index.ts
+++ b/components/page/order/constants/index.ts
@@ -18,6 +18,7 @@ export interface CustomMsg {
export interface OrderInput {
type: OrderType;
tokenAddress?: string;
+ supportToken?: string;
amount?: number;
toAddress?: string;
fromAddress?: string;
@@ -85,3 +86,10 @@ export const TokenAddressLabel = {
[OrderType["Force Transfer Jetton"]]: "Jetton Minter Address",
[OrderType["Set status for Jetton Wallet"]]: "Jetton Minter Address",
};
+
+export enum JETTON_MINTER_DEFAULT {
+ USDT = "EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs",
+ jUSDT = "EQBynBO23ywHy_CgarY9NK9FTz0yDsG82PtcbSTQgGoXwiuA",
+ jUSDC = "EQB-MPwrd1G6WKNkLz_VnV6WqBDd142KMQv-g1O-8QUA3728",
+ CUSTOM = "",
+}
diff --git a/components/page/order/create/index.tsx b/components/page/order/create/index.tsx
index de7e726..da86a5a 100644
--- a/components/page/order/create/index.tsx
+++ b/components/page/order/create/index.tsx
@@ -16,13 +16,14 @@ import {
import { Address, toNano } from "@ton/core";
import classNames from "classnames";
import Link from "next/link";
-import { useParams } from "next/navigation";
+import { useParams, useRouter } from "next/navigation";
import { useState } from "react";
import NumberFormat from "react-number-format";
import {
AmountLabel,
CustomMsg,
FromAddressLabel,
+ JETTON_MINTER_DEFAULT,
OrderInput,
OrderType,
StatusEnum,
@@ -39,6 +40,7 @@ const CreateOrder = () => {
const { data } = useGetMultisigData({ addressMultisig });
const [loading, setLoading] = useState(false);
const tonAddress = useAuthTonAddress();
+ const router = useRouter();
const [order, setOrder] = useState
({
type: 0,
@@ -72,12 +74,15 @@ const CreateOrder = () => {
order,
sender?.address
);
+
await multiSigContract.sendNewOrder(
sender,
[request],
expirationDate,
toNano(0.1)
);
+
+ router.push(`/multisig/${addressMultisig}/detail`);
} catch (error) {
console.log("error:>> ", error);
displayToast(TToastType.TX_FAILED, {
@@ -87,6 +92,7 @@ const CreateOrder = () => {
setLoading(false);
}
};
+
return (
@@ -112,6 +118,18 @@ const CreateOrder = () => {
});
return;
}
+ if (
+ [OrderType["Transfer Jetton"], OrderType["Mint Jetton"]].includes(
+ value
+ )
+ ) {
+ setOrder({
+ type: value,
+ supportToken: JETTON_MINTER_DEFAULT.jUSDT,
+ tokenAddress: JETTON_MINTER_DEFAULT.jUSDT,
+ });
+ return;
+ }
setOrder({
type: value,
});
@@ -133,6 +151,34 @@ const CreateOrder = () => {
*/}
+
+
+
+
+
{
/>
+
+
+
+ {
+ e.preventDefault();
+ setOrder({
+ ...order,
+ tokenAddress: e.target.value,
+ });
+ }}
+ />
+
+
{
/>
-
-
-
- {
- e.preventDefault();
- setOrder({
- ...order,
- tokenAddress: e.target.value,
- });
- }}
- />
-
-
Address.parse(address);
const DetailOrder = () => {
const tonAddress = useAuthTonAddress();
const [tonConnectUI] = useTonConnectUI();
@@ -28,6 +35,8 @@ const DetailOrder = () => {
const { balances } = useBalances({
tonWalletAddress: orderAddress?.toString() || "",
});
+ const { data: dataBE = DEFAULT_BE_DATA } = data || {};
+
const { tonClient } = useTonConnector();
const handleSignMultisig = async () => {
@@ -42,8 +51,12 @@ const DetailOrder = () => {
Address.parse(tonAddress)
);
await orderContract.sendApprove(sender, signerIndex, toNano("0.01"));
- } catch (e) {
- console.log(e);
+ } catch (error) {
+ console.log(error);
+
+ displayToast(TToastType.TX_FAILED, {
+ message: typeof error === "string" ? error : JSON.stringify(error),
+ });
}
};
@@ -78,13 +91,23 @@ const DetailOrder = () => {
{(orderDetail.signers || []).map((e, index) => {
+ const signersBE = parseJsonDataFromSqlite(dataBE.signers);
+ const currentSigner = signersBE?.find(
+ (s: any) => s.value === e.toString()
+ );
return (
#{index + 1} -
- {e.toString()}
- {e.toString() === tonAddress && (
- It's You
- )}
+
+ {e.toString()}
+
+ {`${currentSigner ? ` (${currentSigner.name})` : ""}`}
+
+
+ {tonAddress &&
+ parseAddress(e.toString()).equals(
+ parseAddress(tonAddress)
+ ) && It's You}
);
})}
@@ -96,11 +119,23 @@ const DetailOrder = () => {
No Proposers
) : (
proposers.map((e, index) => {
+ const proposersBE = parseJsonDataFromSqlite(dataBE.proposers);
+ const currentProposer = proposersBE?.find(
+ (p: any) => p.value === e.toString()
+ );
return (
#1
- {addressMultisig}
- It's You
+
+ {addressMultisig}
+
+ {`${currentProposer ? ` (${currentProposer.name})` : ""}`}
+
+
+ {tonAddress &&
+ parseAddress(e.toString()).equals(
+ parseAddress(tonAddress)
+ ) && It's You}
);
})
diff --git a/hooks/useGetMutisigData.ts b/hooks/useGetMutisigData.ts
index e8e1c77..1ef7d82 100644
--- a/hooks/useGetMutisigData.ts
+++ b/hooks/useGetMutisigData.ts
@@ -4,12 +4,17 @@ import { Multisig } from "@oraichain/ton-multiowner/dist/wrappers/Multisig";
import { getHttpEndpoint } from "@orbs-network/ton-access";
import { Address, TonClient } from "@ton/ton";
import { useEffect, useState } from "react";
+import useHandleMultisigServer, {
+ DEFAULT_BE_DATA,
+ MultisigSnapshot,
+} from "./useHandleMultisigServer";
export type MultisigDataType = {
nextOrderSeqno: bigint;
threshold: bigint;
signers: Address[];
proposers: Address[];
+ data: MultisigSnapshot;
};
export const getMultisigData = async (
@@ -30,6 +35,7 @@ const useGetMultisigData = ({
addressMultisig?: string;
}) => {
const [data, setData] = useState
(null);
+ const { getMultisigDetailSnapshot } = useHandleMultisigServer();
const getMultisigDetail = async (addressMultisig: string) => {
try {
@@ -48,12 +54,19 @@ const useGetMultisigData = ({
console.log("address", address.toString(), client);
const res = await getMultisigData(address, client);
+ const multisigOnBE = (await getMultisigDetailSnapshot(
+ address.toString()
+ )) || { data: DEFAULT_BE_DATA };
- console.log("res", res);
+ console.log("res", res, multisigOnBE);
if (res) {
- setData(res);
- return res;
+ const data = {
+ ...res,
+ ...(multisigOnBE?.data ? multisigOnBE : { data: DEFAULT_BE_DATA }),
+ };
+ setData(data);
+ return data;
}
} catch (error) {
console.log("error :>> getMultisigDetail", error);
diff --git a/hooks/useHandleMultisigServer.ts b/hooks/useHandleMultisigServer.ts
new file mode 100644
index 0000000..ff95742
--- /dev/null
+++ b/hooks/useHandleMultisigServer.ts
@@ -0,0 +1,140 @@
+export type MultisigSnapshot = {
+ id?: number;
+ name: string;
+ address: string;
+ createdBy: string;
+ importedBy: string;
+ signers: string;
+ proposers: string;
+};
+
+export const DEFAULT_BE_DATA = {
+ id: 0,
+ name: "",
+ address: "",
+ createdBy: "",
+ importedBy: "",
+ signers: "",
+ proposers: "",
+};
+
+export const BASE_API_URL =
+ process.env.NEXT_PUBLIC_BASE_API_URL || "http://localhost:3000";
+
+export const parseJsonDataFromSqlite = (dataStored: string) => {
+ try {
+ if (dataStored) {
+ const jsonData = JSON.parse(dataStored);
+ return jsonData;
+ }
+ } catch (error) {
+ console.log("Parse data db error", error);
+ }
+};
+
+const useHandleMultisigServer = () => {
+ const addMultisigSnapshot = async (data: MultisigSnapshot) => {
+ try {
+ const response = await fetch("/api/multisig", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(data),
+ });
+
+ const res = await response.json();
+ if (response.ok) {
+ console.log("MultisigSnapshot add successfully!");
+ return res;
+ } else {
+ throw res.error;
+ }
+ } catch (error) {
+ console.log("Snapshot Add error:", error);
+ }
+ };
+
+ const updateMultisigSnapshot = async (
+ id: string | number,
+ data: Partial,
+ userAddress: string
+ ) => {
+ try {
+ const response = await fetch(
+ `/api/multisig/${id}?userAddress=${userAddress}`,
+ {
+ method: "PATCH",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(data),
+ }
+ );
+
+ const res = await response.json();
+ if (response.ok) {
+ console.log("MultisigSnapshot update successfully!");
+ return res;
+ } else {
+ throw res.error;
+ }
+ } catch (error) {
+ console.log("Snapshot Update error:", error);
+ }
+ };
+
+ const getMultisigDetailSnapshot = async (id: string | number) => {
+ try {
+ const response = await fetch(`/api/multisig/${id}`, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+
+ const res = await response.json();
+ if (response.ok) {
+ console.log("MultisigSnapshot Get successfully!");
+ return res;
+ } else {
+ throw res.error;
+ }
+ } catch (error) {
+ console.log("Snapshot Get error:", error);
+ }
+ };
+
+ const getMultisigListSnapshot = async (userAddress: string | number) => {
+ try {
+ const response = await fetch(
+ `/api/multisig?addressUser=${encodeURIComponent(userAddress)}`,
+ {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ }
+ );
+
+ const res = await response.json();
+ if (response.ok) {
+ console.log("MultisigSnapshot Get List successfully!");
+ return res;
+ } else {
+ throw res.error;
+ }
+ } catch (error) {
+ console.log("Snapshot Get List error:", error);
+ }
+ };
+
+ return {
+ addMultisigSnapshot,
+ updateMultisigSnapshot,
+ getMultisigDetailSnapshot,
+ getMultisigListSnapshot,
+ };
+};
+
+export default useHandleMultisigServer;
diff --git a/middleware.ts b/middleware.ts
new file mode 100644
index 0000000..cfdb9b0
--- /dev/null
+++ b/middleware.ts
@@ -0,0 +1,29 @@
+import { NextRequest, NextResponse } from "next/server";
+
+const allowedOrigins = process.env.NEXT_PUBLIC_ORIGIN.split(",");
+// || [
+// "http://localhost:3000",
+// "https://multisig-ton.oraidex.io/",
+// ];
+
+const corsOptions = {
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
+ "Access-Control-Allow-Headers": "Content-Type, Authorization",
+};
+
+export function middleware(request: NextRequest) {
+ // Check the origin from the request
+ const origin = request.headers.get("origin") ?? "";
+ const isAllowedOrigin = allowedOrigins.includes(origin);
+
+ if (!isAllowedOrigin) {
+ return new NextResponse("Not allowed by CORS", { status: 403 });
+ }
+
+ // If the origin is allowed, continue the request
+ return NextResponse.next();
+}
+
+export const config = {
+ matcher: "/api/:path*",
+};
diff --git a/package.json b/package.json
index d4473bb..d86e403 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"@oraichain/ton-multiowner": "^0.0.1",
"@oraichain/tonbridge-contracts-sdk": "1.3.0",
"@orbs-network/ton-access": "^2.3.3",
+ "@prisma/client": "^5.18.0",
"@tanstack/react-query": "^5.49.2",
"@ton/core": "~0",
"@ton/crypto": "^3.2.0",
diff --git a/prisma/migrations/20240812113421_init/migration.sql b/prisma/migrations/20240812113421_init/migration.sql
new file mode 100644
index 0000000..ba361d8
--- /dev/null
+++ b/prisma/migrations/20240812113421_init/migration.sql
@@ -0,0 +1,13 @@
+-- CreateTable
+CREATE TABLE "Multisig" (
+ "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ "name" TEXT,
+ "address" TEXT NOT NULL,
+ "createdBy" TEXT NOT NULL,
+ "importedBy" TEXT,
+ "signers" TEXT NOT NULL,
+ "proposers" TEXT
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "Multisig_address_key" ON "Multisig"("address");
diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml
new file mode 100644
index 0000000..e5e5c47
--- /dev/null
+++ b/prisma/migrations/migration_lock.toml
@@ -0,0 +1,3 @@
+# Please do not edit this file manually
+# It should be added in your version-control system (i.e. Git)
+provider = "sqlite"
\ No newline at end of file
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
new file mode 100644
index 0000000..450d0e6
--- /dev/null
+++ b/prisma/schema.prisma
@@ -0,0 +1,24 @@
+// This is your Prisma schema file,
+// learn more about it in the docs: https://pris.ly/d/prisma-schema
+
+// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
+// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
+
+generator client {
+ provider = "prisma-client-js"
+}
+
+datasource db {
+ provider = "sqlite"
+ url = env("DATABASE_URL")
+}
+
+model Multisig {
+ id Int @id @default(autoincrement())
+ name String?
+ address String @unique
+ createdBy String
+ importedBy String?
+ signers String
+ proposers String?
+}
diff --git a/yarn.lock b/yarn.lock
index 79caf8d..94dc08f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1679,6 +1679,11 @@
"@pnpm/network.ca-file" "^1.0.1"
config-chain "^1.1.11"
+"@prisma/client@^5.18.0":
+ version "5.18.0"
+ resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.18.0.tgz#526e4281a448f214c0ff81d65c39243608c98294"
+ integrity sha512-BWivkLh+af1kqC89zCJYkHsRcyWsM8/JHpsDMM76DjP3ZdEquJhXa4IeX+HkWPnwJ5FanxEJFZZDTWiDs/Kvyw==
+
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"