From 270b9e56be7311defa96d8157afef94e7e04b4b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Augusto?= Date: Mon, 16 Sep 2024 21:13:55 +0100 Subject: [PATCH] docs(example-cbdc): update frontend to match new functionality * Separated Transfer functionality into local blockchain transfers and cross-chain transfers * Added new table with the state of token approvals made to the bridge * Highlight cross-chain functionality with red coloring * add helper page with instructions on how to use app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: André Augusto --- .../chaincode-typescript/src/tokenERC20.ts | 1 + .../package.json | 1 + .../src/App.tsx | 16 +- .../src/api-calls/besu-api.tsx | 30 ++++ .../src/api-calls/fabric-api.tsx | 28 +++- .../src/api-calls/gateway-api.tsx | 22 +-- .../src/components/ActionsContainer.tsx | 104 ++++++++----- .../src/components/ApprovalsTable.tsx | 75 ++++++++++ .../src/components/AssetReferencesTable.tsx | 2 +- .../src/components/Ledger.tsx | 122 ++++++--------- .../src/components/buttons/CriticalButton.tsx | 19 +++ .../src/components/buttons/NormalButton.tsx | 15 ++ .../dialogs/CrossChainTransferDialog.tsx | 140 ++++++++++++++++++ .../src/components/dialogs/MintDialog.tsx | 3 +- .../components/dialogs/PermissionDialog.tsx | 18 ++- .../src/components/dialogs/TransferDialog.tsx | 119 ++++----------- .../components/docs/DummyActionsContainer.tsx | 56 +++++++ .../src/index.tsx | 2 + .../src/pages/Helper.tsx | 107 +++++++++++++ .../src/pages/HomePage.tsx | 14 +- yarn.lock | 32 ++++ 21 files changed, 694 insertions(+), 232 deletions(-) create mode 100644 examples/cactus-example-cbdc-bridging-frontend/src/components/ApprovalsTable.tsx create mode 100644 examples/cactus-example-cbdc-bridging-frontend/src/components/buttons/CriticalButton.tsx create mode 100644 examples/cactus-example-cbdc-bridging-frontend/src/components/buttons/NormalButton.tsx create mode 100644 examples/cactus-example-cbdc-bridging-frontend/src/components/dialogs/CrossChainTransferDialog.tsx create mode 100644 examples/cactus-example-cbdc-bridging-frontend/src/components/docs/DummyActionsContainer.tsx create mode 100644 examples/cactus-example-cbdc-bridging-frontend/src/pages/Helper.tsx diff --git a/examples/cactus-example-cbdc-bridging-backend/src/fabric-contracts/satp-contract/chaincode-typescript/src/tokenERC20.ts b/examples/cactus-example-cbdc-bridging-backend/src/fabric-contracts/satp-contract/chaincode-typescript/src/tokenERC20.ts index 2f844511b3..043b1b943f 100644 --- a/examples/cactus-example-cbdc-bridging-backend/src/fabric-contracts/satp-contract/chaincode-typescript/src/tokenERC20.ts +++ b/examples/cactus-example-cbdc-bridging-backend/src/fabric-contracts/satp-contract/chaincode-typescript/src/tokenERC20.ts @@ -307,6 +307,7 @@ class TokenERC20Contract extends Contract { * @param {String} spender The spender who are able to transfer the tokens * @returns {Number} Return the amount of remaining tokens allowed to spent */ + @Transaction(false) async Allowance( ctx: Context, owner: string, diff --git a/examples/cactus-example-cbdc-bridging-frontend/package.json b/examples/cactus-example-cbdc-bridging-frontend/package.json index b6c37cd132..b81d97a6f8 100644 --- a/examples/cactus-example-cbdc-bridging-frontend/package.json +++ b/examples/cactus-example-cbdc-bridging-frontend/package.json @@ -36,6 +36,7 @@ "axios": "1.6.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^6.2.1", "react-scripts": "5.0.1", "typescript": "5.5.2", "uuid": "10.0.0", diff --git a/examples/cactus-example-cbdc-bridging-frontend/src/App.tsx b/examples/cactus-example-cbdc-bridging-frontend/src/App.tsx index 7e73dd240f..3fa4a8c6ff 100644 --- a/examples/cactus-example-cbdc-bridging-frontend/src/App.tsx +++ b/examples/cactus-example-cbdc-bridging-frontend/src/App.tsx @@ -1,14 +1,18 @@ -import React from "react"; +import { BrowserRouter, Routes, Route } from "react-router-dom"; import HomePage from "./pages/HomePage"; -import { CssBaseline } from "@mui/material"; +import Helper from "./pages/Helper"; import "./App.css"; function App() { return ( - <> - - - + + + + } /> + } /> + + + ); } diff --git a/examples/cactus-example-cbdc-bridging-frontend/src/api-calls/besu-api.tsx b/examples/cactus-example-cbdc-bridging-frontend/src/api-calls/besu-api.tsx index d6aec4656f..5e150aae43 100644 --- a/examples/cactus-example-cbdc-bridging-frontend/src/api-calls/besu-api.tsx +++ b/examples/cactus-example-cbdc-bridging-frontend/src/api-calls/besu-api.tsx @@ -35,6 +35,36 @@ export async function authorizeNTokensBesu( } } +export async function fetchAmountApprovedToBridge(frontendUser: string) { + const response = await fetch("http://localhost:9999/wrapper-address"); + const data = await response.json(); + const wrapperAddress = data.address; + + try { + const from = getEthAddress(frontendUser); + const response = await axios.post( + "http://localhost:4100/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-besu/invoke-contract", + { + contractName: BESU_CONTRACT_CBDC_ERC20_NAME, + invocationType: "CALL", + methodName: "allowance", + gas: 1000000, + params: [from, wrapperAddress], + signingCredential: { + ethAccount: from, + secret: getEthUserPrKey(frontendUser), + type: "PRIVATE_KEY_HEX", + }, + keychainId: CryptoMaterial.keychains.keychain2.id, + }, + ); + return parseInt(response.data.callOutput); + } catch (error) { + // there is no allowance, so we will return 0 + return 0; + } +} + export async function transferTokensBesu( frontendUserFrom: string, frontendUserTo: string, diff --git a/examples/cactus-example-cbdc-bridging-frontend/src/api-calls/fabric-api.tsx b/examples/cactus-example-cbdc-bridging-frontend/src/api-calls/fabric-api.tsx index 5b4b25b769..986ebe9378 100644 --- a/examples/cactus-example-cbdc-bridging-frontend/src/api-calls/fabric-api.tsx +++ b/examples/cactus-example-cbdc-bridging-frontend/src/api-calls/fabric-api.tsx @@ -29,7 +29,6 @@ export async function getFabricBalance(frontendUser: string) { console.error(error.msg); return -1; } - console.log(response); return parseInt(response.data.functionOutput); } @@ -219,6 +218,33 @@ export async function authorizeNTokensFabric(user: string, amount: string) { ); } +export async function fetchAmountApprovedToBridge(frontendUser: string) { + const owner = getFabricId(frontendUser); + let response; + + try { + response = await axios.post( + "http://localhost:4000/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-fabric/run-transaction", + { + contractName: FABRIC_CONTRACT_CBDC_ERC20_NAME, + channelName: FABRIC_CHANNEL_NAME, + params: [owner, CryptoMaterial.accounts.bridge.fabricID], + methodName: "Allowance", + invocationType: "FabricContractInvocationType.CALL", + signingCredential: { + keychainId: CryptoMaterial.keychains.keychain1.id, + keychainRef: getUserFromPseudonim(frontendUser), + }, + }, + ); + } catch (error) { + // there is no allowance, so we will return 0 + return 0; + } + + return parseInt(response.data.functionOutput); +} + export function getUserFromFabricId(fabricID: string): string { switch (fabricID) { case CryptoMaterial.accounts["userA"].fabricID: diff --git a/examples/cactus-example-cbdc-bridging-frontend/src/api-calls/gateway-api.tsx b/examples/cactus-example-cbdc-bridging-frontend/src/api-calls/gateway-api.tsx index 1598c4b73f..2ae5306335 100644 --- a/examples/cactus-example-cbdc-bridging-frontend/src/api-calls/gateway-api.tsx +++ b/examples/cactus-example-cbdc-bridging-frontend/src/api-calls/gateway-api.tsx @@ -81,15 +81,14 @@ export async function bridgeTokens( sender: string, recipient: string, sourceChain: string, - destinyChain: string, - originAmount: number, - destinyAmount: number, + destinationChain: string, + amount: number, ) { let senderAddress; let receiverAddress; let port; let sourceAsset; - let destinyAsset; + let destinationAsset; //only way we found to pass contract address from backend to frontend at each run of tests const response = await fetch("http://localhost:9999/contract-address"); const data = await response.json(); @@ -110,14 +109,17 @@ export async function bridgeTokens( port = "4110"; } - if (destinyChain === "Fabric") { + if (destinationChain === "Fabric") { toDLTNetworkID = "FabricSATPGateway"; receiverAddress = getFabricId(recipient); - destinyAsset = setFabricAsset(receiverAddress as string); + destinationAsset = setFabricAsset(receiverAddress as string); } else { toDLTNetworkID = "BesuSATPGateway"; receiverAddress = getEthAddress(recipient); - destinyAsset = setBesuAsset(receiverAddress as string, besuContractAddress); + destinationAsset = setBesuAsset( + receiverAddress as string, + besuContractAddress, + ); } try { await axios.post( @@ -126,13 +128,13 @@ export async function bridgeTokens( contextID: "MockID", fromDLTNetworkID, toDLTNetworkID, - fromAmount: originAmount, - toAmount: destinyAmount, + fromAmount: amount, + toAmount: amount, receiver: receiverAddress, originatorPubkey: senderAddress, beneficiaryPubkey: receiverAddress, sourceAsset, - destinyAsset, + destinyAsset: destinationAsset, }, ); } catch (error) { diff --git a/examples/cactus-example-cbdc-bridging-frontend/src/components/ActionsContainer.tsx b/examples/cactus-example-cbdc-bridging-frontend/src/components/ActionsContainer.tsx index 876a08f291..61268cd5b9 100644 --- a/examples/cactus-example-cbdc-bridging-frontend/src/components/ActionsContainer.tsx +++ b/examples/cactus-example-cbdc-bridging-frontend/src/components/ActionsContainer.tsx @@ -1,56 +1,61 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { useState, useEffect } from "react"; -import { styled } from "@mui/material/styles"; -import Button, { ButtonProps } from "@mui/material/Button"; +import Paper from "@mui/material/Paper"; import Grid from "@mui/material/Grid"; import CircularProgress from "@mui/material/CircularProgress"; import MintDialog from "./dialogs/MintDialog"; +import CrossChainTransferDialog from "./dialogs/CrossChainTransferDialog"; import TransferDialog from "./dialogs/TransferDialog"; import PermissionDialog from "./dialogs/PermissionDialog"; import { getFabricBalance } from "../api-calls/fabric-api"; import { getBesuBalance } from "../api-calls/besu-api"; import { SessionReference } from "../models/SessionReference"; +import { NormalButton } from "./buttons/NormalButton"; +import { CriticalButton } from "./buttons/CriticalButton"; -const NormalButton = styled(Button)(({ theme }) => ({ - margin: "auto", - width: "100%", - fontSize: "13px", - textTransform: "none", - background: "#2B9BF6", - color: "#FFFFFF", - border: "0.5px solid #000000", - "&:disabled": { - border: "0", - }, -})); +// const NormalButton = styled(Button)(({ theme }) => ({ +// margin: "auto", +// width: "100%", +// fontSize: "13px", +// textTransform: "none", +// background: "#2B9BF6", +// color: "#FFFFFF", +// border: "0.5px solid #000000", +// "&:disabled": { +// border: "0", +// }, +// })); -const CriticalButton = styled(Button)(({ theme }) => ({ - margin: "auto", - width: "100%", - fontSize: "13px", - textTransform: "none", - background: "#FF584B", - color: "#FFFFFF", - border: "0.5px solid #000000", - "&:hover": { - backgroundColor: "#444444", - color: "#FFFFFF", - }, - "&:disabled": { - border: "0", - }, -})); +// const CriticalButton = styled(Button)(({ theme }) => ({ +// margin: "auto", +// width: "100%", +// fontSize: "13px", +// textTransform: "none", +// background: "#FF584B", +// color: "#FFFFFF", +// border: "0.5px solid #000000", +// "&:hover": { +// backgroundColor: "#444444", +// color: "#FFFFFF", +// }, +// "&:disabled": { +// border: "0", +// }, +// })); export interface IActionsContainerOptions { user: string; ledger: string; sessionRefs: Array; + tokensApproved: number; } export default function ActionsContainer(props: IActionsContainerOptions) { const [amount, setAmount] = useState(0); const [mintDialog, setMintDialog] = useState(false); const [transferDialog, setTransferDialog] = useState(false); + const [crossChainTransferDialog, setCrossChainTransferDialog] = + useState(false); const [permissionDialog, setGivePermissionDialog] = useState(false); const [loading, setLoading] = useState(false); @@ -72,7 +77,13 @@ export default function ActionsContainer(props: IActionsContainerOptions) { }, [props.user, props.ledger]); return ( -
+ {loading ? (
)} {props.user !== "Bridge" && ( - - + setGivePermissionDialog(true)} > - Give Permission - + Approval + + + )} + {props.user !== "Bridge" && ( + + setCrossChainTransferDialog(true)} + > + Bridge + )} - {props.user !== "Bridge" && } {props.ledger === "Fabric" && props.user !== "Bridge" && ( )} @@ -170,14 +191,23 @@ export default function ActionsContainer(props: IActionsContainerOptions) { open={transferDialog} user={props.user} ledger={props.ledger} + balance={amount} onClose={() => setTransferDialog(false)} /> + setCrossChainTransferDialog(false)} + /> setGivePermissionDialog(false)} /> -
+ ); } diff --git a/examples/cactus-example-cbdc-bridging-frontend/src/components/ApprovalsTable.tsx b/examples/cactus-example-cbdc-bridging-frontend/src/components/ApprovalsTable.tsx new file mode 100644 index 0000000000..aaea6f4030 --- /dev/null +++ b/examples/cactus-example-cbdc-bridging-frontend/src/components/ApprovalsTable.tsx @@ -0,0 +1,75 @@ +import Table from "@mui/material/Table"; +import TableBody from "@mui/material/TableBody"; +import TableCell from "@mui/material/TableCell"; +import TableContainer from "@mui/material/TableContainer"; +import TableHead from "@mui/material/TableHead"; +import TableRow from "@mui/material/TableRow"; +import { SessionReference } from "../models/SessionReference"; + +const headCells = [ + { + id: "User", + first: true, + label: "User", + }, + { id: "Amount", label: "Amount" }, +]; + +function ItemsTableHead() { + return ( + + + {headCells.map((headCell) => ( + + {headCell.label} + + ))} + + + ); +} + +export interface IApprovalsTableOptions { + ledger: string; + sessionRefs: SessionReference[]; + aliceApprovals: number; + charlieApprovals: number; +} + +export default function ApprovalsTable(props: IApprovalsTableOptions) { + return ( + + + + + + + Alice + + {props.aliceApprovals} + + + + Charlie + + {props.charlieApprovals} + + +
+
+ ); +} diff --git a/examples/cactus-example-cbdc-bridging-frontend/src/components/AssetReferencesTable.tsx b/examples/cactus-example-cbdc-bridging-frontend/src/components/AssetReferencesTable.tsx index 8fe613ec06..f8211a46ec 100644 --- a/examples/cactus-example-cbdc-bridging-frontend/src/components/AssetReferencesTable.tsx +++ b/examples/cactus-example-cbdc-bridging-frontend/src/components/AssetReferencesTable.tsx @@ -15,7 +15,7 @@ const headCells = [ { id: "status", label: "Status" }, { id: "substatus", label: "Substatus" }, { id: "origin", label: "Origin" }, - { id: "destiny", label: "Destiny" }, + { id: "destination", label: "Destination" }, ]; function ItemsTableHead() { diff --git a/examples/cactus-example-cbdc-bridging-frontend/src/components/Ledger.tsx b/examples/cactus-example-cbdc-bridging-frontend/src/components/Ledger.tsx index 5e6393534b..a3c96fb155 100644 --- a/examples/cactus-example-cbdc-bridging-frontend/src/components/Ledger.tsx +++ b/examples/cactus-example-cbdc-bridging-frontend/src/components/Ledger.tsx @@ -2,8 +2,12 @@ import { useState, useEffect } from "react"; import Paper from "@mui/material/Paper"; import Grid from "@mui/material/Grid"; import ActionsContainer from "./ActionsContainer"; +import CircularProgress from "@mui/material/CircularProgress"; import { getSessionReferencesBridge } from "../api-calls/gateway-api"; +import { fetchAmountApprovedToBridge as fetchAmountApprovedToBridgeFabric } from "../api-calls/fabric-api"; +import { fetchAmountApprovedToBridge as fetchAmountApprovedToBridgeBesu } from "../api-calls/besu-api"; import AssetReferencesTable from "./AssetReferencesTable"; +import ApprovalsTable from "./ApprovalsTable"; export interface ILedgerOptions { ledger: string; @@ -11,16 +15,32 @@ export interface ILedgerOptions { export default function Ledger(props: ILedgerOptions) { const [sessionReferences, setAssetReferences] = useState([]); + const [aliceApprovals, setAliceApprovals] = useState(0); + const [charlieApprovals, setCharlieApprovals] = useState(0); + const [loading, setLoading] = useState(true); useEffect(() => { async function fetchData() { if (props.ledger === "Fabric") { const list = await getSessionReferencesBridge("4010"); setAssetReferences(list); + + const alice = await fetchAmountApprovedToBridgeFabric("Alice"); + setAliceApprovals(alice); + + const charlie = await fetchAmountApprovedToBridgeFabric("Charlie"); + setCharlieApprovals(charlie); } else { const list = await getSessionReferencesBridge("4110"); setAssetReferences(list); + + const alice = await fetchAmountApprovedToBridgeBesu("Alice"); + setAliceApprovals(alice); + + const charlie = await fetchAmountApprovedToBridgeBesu("Charlie"); + setCharlieApprovals(charlie); } + setLoading(false); } fetchData(); @@ -36,93 +56,41 @@ export default function Ledger(props: ILedgerOptions) { }} >

Hyperledger {props.ledger}

- {props.ledger === "Fabric" ? ( -

- Org1 -

- ) : ( -

- -- -

- )} - - - + - - - + - {props.ledger === "Fabric" ? ( -

- Org2 -

+

Token Approvals to Bridge

+ {loading ? ( +
+ +
) : ( -

- -- -

- )} - - - + )}

Sessions Status

(({}) => ({ + margin: "auto", + width: "100%", + fontSize: "13px", + textTransform: "none", + background: "#FF584B", + color: "#FFFFFF", + border: "0.5px solid #000000", + "&:hover": { + backgroundColor: "#444444", + color: "#FFFFFF", + }, + "&:disabled": { + border: "0", + }, +})); diff --git a/examples/cactus-example-cbdc-bridging-frontend/src/components/buttons/NormalButton.tsx b/examples/cactus-example-cbdc-bridging-frontend/src/components/buttons/NormalButton.tsx new file mode 100644 index 0000000000..8c140de929 --- /dev/null +++ b/examples/cactus-example-cbdc-bridging-frontend/src/components/buttons/NormalButton.tsx @@ -0,0 +1,15 @@ +import { styled } from "@mui/material/styles"; +import Button, { ButtonProps } from "@mui/material/Button"; + +export const NormalButton = styled(Button)(({}) => ({ + margin: "auto", + width: "100%", + fontSize: "13px", + textTransform: "none", + background: "#2B9BF6", + color: "#FFFFFF", + border: "0.5px solid #000000", + "&:disabled": { + border: "0", + }, +})); diff --git a/examples/cactus-example-cbdc-bridging-frontend/src/components/dialogs/CrossChainTransferDialog.tsx b/examples/cactus-example-cbdc-bridging-frontend/src/components/dialogs/CrossChainTransferDialog.tsx new file mode 100644 index 0000000000..2100ce68b3 --- /dev/null +++ b/examples/cactus-example-cbdc-bridging-frontend/src/components/dialogs/CrossChainTransferDialog.tsx @@ -0,0 +1,140 @@ +import { useState, useEffect, ChangeEvent } from "react"; +import DialogActions from "@mui/material/DialogActions"; +import DialogContent from "@mui/material/DialogContent"; +import DialogContentText from "@mui/material/DialogContentText"; +import DialogTitle from "@mui/material/DialogTitle"; +import TextField from "@mui/material/TextField"; +import MenuItem from "@mui/material/MenuItem"; +import Select, { SelectChangeEvent } from "@mui/material/Select"; +import Button from "@mui/material/Button"; +import Dialog from "@mui/material/Dialog"; +import Alert from "@mui/material/Alert"; +import { bridgeTokens } from "../../api-calls/gateway-api"; + +const recipients = ["Alice", "Charlie"]; + +export interface ICrossChainTransferDialogOptions { + open: boolean; + ledger: string; + user: string; + tokensApproved: number; + onClose: () => unknown; +} + +export default function CrossChainTransferDialog( + props: ICrossChainTransferDialogOptions, +) { + const [recipient, setRecipient] = useState(""); + const [amount, setAmount] = useState(0); + const [errorMessage, setErrorMessage] = useState(""); + const [sending, setSending] = useState(false); + + useEffect(() => { + if (props.open) { + setSending(false); + setRecipient(""); + setAmount(0); + } + }, [props.open]); + + const handleChangeamount = ( + event: ChangeEvent, + ) => { + const value = parseInt(event.target.value); + + if (value < 0) { + setErrorMessage("Amount must be a positive value"); + setAmount(0); + } else { + setErrorMessage(""); + setAmount(value); + } + + if (value > props.tokensApproved) { + setErrorMessage( + "Amount must be lower or equal to the amount approved to the bridge", + ); + setAmount(props.tokensApproved); + } else { + setErrorMessage(""); + setAmount(value); + } + }; + + const handleChangeRecipient = (event: SelectChangeEvent) => { + setRecipient(event.target.value); + }; + + const performCrossChainTransaction = async () => { + if (amount === 0) { + setErrorMessage("Amounts must be a positive value"); + } else { + setSending(true); + await bridgeTokens( + props.user, + recipient, + props.ledger, + props.ledger === "Fabric" ? "Besu" : "Fabric", + amount, + ); + } + + props.onClose(); + }; + + return ( + + {"Cross-Chain Transfer CBDC"} + + + Select the recipient of the tokens and the amount to transfer to the + other blockchain + + + + {errorMessage !== "" && ( + + {errorMessage} + + )} + + + {sending ? ( + + ) : ( +
+ + +
+ )} +
+
+ ); +} diff --git a/examples/cactus-example-cbdc-bridging-frontend/src/components/dialogs/MintDialog.tsx b/examples/cactus-example-cbdc-bridging-frontend/src/components/dialogs/MintDialog.tsx index bf03a6f447..973b012b12 100644 --- a/examples/cactus-example-cbdc-bridging-frontend/src/components/dialogs/MintDialog.tsx +++ b/examples/cactus-example-cbdc-bridging-frontend/src/components/dialogs/MintDialog.tsx @@ -9,13 +9,12 @@ import Dialog from "@mui/material/Dialog"; import Alert from "@mui/material/Alert"; import { mintTokensFabric } from "../../api-calls/fabric-api"; import { mintTokensBesu } from "../../api-calls/besu-api"; -//import { mintTokensFabric } from "../../api-calls/fabric-api"; export interface IMintDialogOptions { open: boolean; user: string; ledger: string; - onClose: () => any; + onClose: () => unknown; } export default function MintDialog(props: IMintDialogOptions) { diff --git a/examples/cactus-example-cbdc-bridging-frontend/src/components/dialogs/PermissionDialog.tsx b/examples/cactus-example-cbdc-bridging-frontend/src/components/dialogs/PermissionDialog.tsx index d84c61d00a..9c9ddb474d 100644 --- a/examples/cactus-example-cbdc-bridging-frontend/src/components/dialogs/PermissionDialog.tsx +++ b/examples/cactus-example-cbdc-bridging-frontend/src/components/dialogs/PermissionDialog.tsx @@ -14,7 +14,8 @@ export interface IPermissionDialogOptions { open: boolean; user: string; ledger: string; - onClose: () => any; + balance: number; + onClose: () => unknown; } export default function setGivePermissionDialog( @@ -43,6 +44,14 @@ export default function setGivePermissionDialog( setErrorMessage(""); setAmount(value); } + + if (value > props.balance) { + setErrorMessage("Amount must be lower or equal to user's balance"); + setAmount(props.balance); + } else { + setErrorMessage(""); + setAmount(value); + } }; const performAccessTransaction = async () => { @@ -61,11 +70,10 @@ export default function setGivePermissionDialog( return ( - {"Give Permission"} + {"Approve Funds to Bridge"} - How many tokens would you like to give permission to {props.user}"s - address? + How many tokens can the bridge use on your behalf? - + )} diff --git a/examples/cactus-example-cbdc-bridging-frontend/src/components/dialogs/TransferDialog.tsx b/examples/cactus-example-cbdc-bridging-frontend/src/components/dialogs/TransferDialog.tsx index 96de98d79d..11892d2245 100644 --- a/examples/cactus-example-cbdc-bridging-frontend/src/components/dialogs/TransferDialog.tsx +++ b/examples/cactus-example-cbdc-bridging-frontend/src/components/dialogs/TransferDialog.tsx @@ -11,59 +11,50 @@ import Dialog from "@mui/material/Dialog"; import Alert from "@mui/material/Alert"; import { transferTokensFabric } from "../../api-calls/fabric-api"; import { transferTokensBesu } from "../../api-calls/besu-api"; -import { bridgeTokens } from "../../api-calls/gateway-api"; const recipients = ["Alice", "Charlie"]; -const chains = ["Fabric", "Besu"]; + export interface ITransferDialogOptions { open: boolean; ledger: string; user: string; + balance: number; onClose: () => any; } export default function TransferDialog(props: ITransferDialogOptions) { - const [recipient, setRecipient] = useState(""); - const [originAmount, setSourceAmount] = useState(0); - const [destinyAmount, setDestinyAmount] = useState(0); - const [destinyChain, setDestinyChain] = useState(""); const [errorMessage, setErrorMessage] = useState(""); + const [recipient, setRecipient] = useState(""); + const [amount, setAmount] = useState(0); const [sending, setSending] = useState(false); useEffect(() => { if (props.open) { setSending(false); setRecipient(""); - setSourceAmount(0); - setDestinyAmount(0); - setDestinyChain(""); + setAmount(0); } }, [props.open]); - const handleChangeOriginAmount = ( + const handleChangeamount = ( event: ChangeEvent, ) => { const value = parseInt(event.target.value); if (value < 0) { setErrorMessage("Amount must be a positive value"); - setSourceAmount(0); + setAmount(0); } else { setErrorMessage(""); - setSourceAmount(value); + setAmount(value); } - }; - const handleChangeDestinyAmount = ( - event: ChangeEvent, - ) => { - const value = parseInt(event.target.value); - if (value < 0) { - setErrorMessage("Amount must be a positive value"); - setDestinyAmount(0); + if (value > props.balance) { + setErrorMessage("Amount must be lower or equal to current balance"); + setAmount(props.balance); } else { setErrorMessage(""); - setDestinyAmount(value); + setAmount(value); } }; @@ -71,38 +62,19 @@ export default function TransferDialog(props: ITransferDialogOptions) { setRecipient(event.target.value); }; - const handleChangeDestinyChain = (event: SelectChangeEvent) => { - setDestinyChain(event.target.value); - }; - - const performTransferTransaction = async () => { - if (originAmount === 0 || destinyAmount === 0) { + const performLocalTransferTransaction = async () => { + if (amount === 0) { setErrorMessage("Amounts must be a positive value"); } else { setSending(true); - if (props.ledger === destinyChain) { - if (props.ledger === "Fabric") { - await transferTokensFabric( - props.user, - recipient, - originAmount.toString(), - ); - } else { - await transferTokensBesu(props.user, recipient, originAmount); - } + + if (props.ledger === "Fabric") { + await transferTokensFabric(props.user, recipient, amount.toString()); } else { - await bridgeTokens( - props.user, - recipient, - props.ledger, - destinyChain, - originAmount, - destinyAmount, - ); + await transferTokensBesu(props.user, recipient, amount); } - - props.onClose(); } + props.onClose(); }; return ( @@ -110,8 +82,7 @@ export default function TransferDialog(props: ITransferDialogOptions) { {"Transfer CBDC"} - Select the recipient of the CBDC, target chain, and how many you would - transfer from {props.user}"s address? + Select the recipient of the tokens and the amount to transfer - - {errorMessage !== "" && ( @@ -190,7 +125,7 @@ export default function TransferDialog(props: ITransferDialogOptions) { ) : (
- +
)} diff --git a/examples/cactus-example-cbdc-bridging-frontend/src/components/docs/DummyActionsContainer.tsx b/examples/cactus-example-cbdc-bridging-frontend/src/components/docs/DummyActionsContainer.tsx new file mode 100644 index 0000000000..9032cee2d5 --- /dev/null +++ b/examples/cactus-example-cbdc-bridging-frontend/src/components/docs/DummyActionsContainer.tsx @@ -0,0 +1,56 @@ +import Paper from "@mui/material/Paper"; +import Grid from "@mui/material/Grid"; +import { NormalButton } from "../buttons/NormalButton"; +import { CriticalButton } from "../buttons/CriticalButton"; + +export default function DummyActionsContainer() { + return ( + + + + {"User A"} + + + + XXX CBDC + + + Mint + + + Transfer + + + Approval + + + Bridge + + + + ); +} diff --git a/examples/cactus-example-cbdc-bridging-frontend/src/index.tsx b/examples/cactus-example-cbdc-bridging-frontend/src/index.tsx index e36f828320..efa5f33648 100644 --- a/examples/cactus-example-cbdc-bridging-frontend/src/index.tsx +++ b/examples/cactus-example-cbdc-bridging-frontend/src/index.tsx @@ -1,4 +1,5 @@ import React from "react"; +import { CssBaseline } from "@mui/material"; import ReactDOM from "react-dom/client"; import "./index.css"; import App from "./App"; @@ -8,6 +9,7 @@ const root = ReactDOM.createRoot( ); root.render( + , ); diff --git a/examples/cactus-example-cbdc-bridging-frontend/src/pages/Helper.tsx b/examples/cactus-example-cbdc-bridging-frontend/src/pages/Helper.tsx new file mode 100644 index 0000000000..abad05f93a --- /dev/null +++ b/examples/cactus-example-cbdc-bridging-frontend/src/pages/Helper.tsx @@ -0,0 +1,107 @@ +import Typography from "@mui/material/Typography"; +import Box from "@mui/material/Box"; +import { Grid } from "@mui/material"; +import DummyActionsContainer from "../components/docs/DummyActionsContainer"; +import { NormalButton } from "../components/buttons/NormalButton"; +import { CriticalButton } from "../components/buttons/CriticalButton"; + +export default function Helper() { + return ( + + + Instructions + + + This application is a demonstration of a Central Bank Digital Currency + (CBDC) bridging solution between two blockchains (Hyperledger Fabric and + Hyperledger Besu) using the Secure Asset Transfer Protocol (SATP). The + application allows users to transfer tokens between the two blockchains. + + + + How to Use + + + Each user starts with a balance of 0 CBDC tokens on both Fabric and + Besu. Each user has a box that displays the user's name, the balance of + CBDC tokens, and the actions that the user can perform. The actions have + different colors based on being related to local actions (i.e., in the + same blockchain) (in blue) or related to cross-chain functionality + (red). + + + + + + + Mint + + + + To mint tokens to the user's account, click on the "Mint" button and + enter the amount of tokens to mint. + + + + Transfer + + + + The "Transfer" button allows the user to transfer a certain amount + of tokens in the same blockchain -- i.e., in the context of the same + smart contract. Select the recipient of the tokens and the amount to + transfer. The amount of tokens to transfer is capped by the user's + balance. The Transfer button is disabled if the user has no tokens. + + + + Approval + + + + The "Approval" button is used to approve the bridge to spend tokens + on on behalf of the user. This is a necessary step before initiating + cross-chain interaction. The approval button is disabled if the user + has no tokens. + + + + Bridge + + + + The "Bridge" button is used to transfer tokens between the two + blockchains using SATP. The bridge button is disabled if the user + has no tokens or if the user has not approved the bridge to spend + tokens on behalf of the user. The user must select the amount of + tokens to transfer and the recipient's address. The amount is capped + by the amount of tokens the user has approved the bridge to spend. + Once this action is executed, the user's balance is updated on both + blockchains and the id and status of the session are displayed on + the screen. + + + + + + Other Relevant Resources + + + For more information, visit the following resources: + + + + ); +} diff --git a/examples/cactus-example-cbdc-bridging-frontend/src/pages/HomePage.tsx b/examples/cactus-example-cbdc-bridging-frontend/src/pages/HomePage.tsx index 88af29bd31..1c738fe3d8 100644 --- a/examples/cactus-example-cbdc-bridging-frontend/src/pages/HomePage.tsx +++ b/examples/cactus-example-cbdc-bridging-frontend/src/pages/HomePage.tsx @@ -6,6 +6,8 @@ import { checkApiServer1Connection, checkApiServer2Connection, } from "../api-calls/common"; +import IconButton from "@mui/material/IconButton"; +import HelpIcon from "@mui/icons-material/Help"; import ConnectionErrorDialog from "../components/dialogs/ConnectionErrorDialog"; export default function HomePage() { @@ -56,13 +58,23 @@ export default function HomePage() { function BridgeImage() { return ( <> + + + +
=16.8" + react-dom: ">=16.8" + checksum: 10/4eee37839bd1a660807c090b4d272e4aa9b95d8a9a932cdcdf7c5b10735f39b6db73bad79b08a3012386a7e225ff6bf60435e2741fb7c68e137ac5a6295d4308 + languageName: node + linkType: hard + "react-router@npm:6.21.3": version: 6.21.3 resolution: "react-router@npm:6.21.3" @@ -46093,6 +46114,17 @@ __metadata: languageName: node linkType: hard +"react-router@npm:6.26.2": + version: 6.26.2 + resolution: "react-router@npm:6.26.2" + dependencies: + "@remix-run/router": "npm:1.19.2" + peerDependencies: + react: ">=16.8" + checksum: 10/496e855b53e61066c1791e354f5d79eab56a128d9722fdc6486c3ecd3b3a0bf9968e927028f429893b157f3cc10fc09e890a055847723ee242663e7995fedc9d + languageName: node + linkType: hard + "react-scripts@npm:5.0.1": version: 5.0.1 resolution: "react-scripts@npm:5.0.1"