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"