diff --git a/tools/bridge-frontend/.nvmrc b/tools/bridge-frontend/.nvmrc new file mode 100644 index 0000000000..7ea6a59d34 --- /dev/null +++ b/tools/bridge-frontend/.nvmrc @@ -0,0 +1 @@ +v20.11.0 diff --git a/tools/bridge-frontend/api/general.ts b/tools/bridge-frontend/api/general.ts new file mode 100644 index 0000000000..fae43a0be3 --- /dev/null +++ b/tools/bridge-frontend/api/general.ts @@ -0,0 +1,20 @@ +import { INetworkConfig, ResponseDataInterface } from "@/src/types"; +import { httpRequest } from "@/api"; +import { pathToUrl } from "@/src/routes/router"; +import { apiRoutes } from "@/src/routes"; + +export const fetchTestnetStatus = async (): Promise< + ResponseDataInterface +> => { + return await httpRequest>({ + method: "get", + url: pathToUrl(apiRoutes.getHealthStatus), + }); +}; + +export const fetchNetworkConfig = async (): Promise => { + return await httpRequest({ + method: "get", + url: apiRoutes.getNetworkConfig, + }); +}; diff --git a/tools/bridge-frontend/api/index.ts b/tools/bridge-frontend/api/index.ts new file mode 100644 index 0000000000..4f9cb5448e --- /dev/null +++ b/tools/bridge-frontend/api/index.ts @@ -0,0 +1,90 @@ +import { apiHost } from "@/src/lib/constants"; +import axios, { AxiosInstance, AxiosRequestConfig } from "axios"; + +type HttpMethod = "get" | "post" | "put" | "patch" | "delete"; + +interface HttpOptions { + method?: HttpMethod; + url: string; + data?: Record; + params?: Record; + headers?: Record; + timeout?: number; + responseType?: + | "json" + | "arraybuffer" + | "blob" + | "document" + | "text" + | undefined; + download?: boolean; + searchParams?: Record; +} + +const baseConfig: AxiosRequestConfig = { + baseURL: apiHost, + timeout: 10000, +}; + +const https: AxiosInstance = axios.create(baseConfig); + +export const httpRequest = async ( + options: HttpOptions, + config: AxiosRequestConfig = {} +): Promise => { + const { + method = "get", + url, + data, + params, + headers, + timeout, + responseType, + searchParams, + } = options; + let query = ""; + if (searchParams) { + const filteredParams = Object.fromEntries( + Object.entries(searchParams).filter( + ([, value]) => value !== undefined && value !== null && value !== "" + ) + ); + if (Object.keys(filteredParams).length) { + query = new URLSearchParams(filteredParams).toString(); + } + } + + const httpConfig: AxiosRequestConfig = { + method, + url: query ? `${url}?${query}` : url, + data, + params, + headers: { ...(headers || {}) }, + timeout, + responseType: responseType, + ...config, + }; + try { + const response = await https(httpConfig); + return response.data as ResponseData; + } catch (error) { + handleHttpError(error); + throw error; + } +}; + +// Centralized error handling function +const handleHttpError = (error: any) => { + // if the error is a server error (status code 5xx) before handling + if (isAxiosError(error) && error.response && error.response.status >= 500) { + console.error("Server error:", error); + } else { + // other errors + console.error("An error occurred:", error); + } +}; + +// Type guard to check if the error is an AxiosError +const isAxiosError = (error: any): error is import("axios").AxiosError => { + return error.isAxiosError === true; +}; diff --git a/tools/bridge-frontend/artifacts/IBridge.sol/IBridge.dbg.json b/tools/bridge-frontend/artifacts/IBridge.sol/IBridge.dbg.json new file mode 100644 index 0000000000..aa212668a9 --- /dev/null +++ b/tools/bridge-frontend/artifacts/IBridge.sol/IBridge.dbg.json @@ -0,0 +1,4 @@ +{ + "_format": "hh-sol-dbg-1", + "buildInfo": "../../../build-info/a4921f6681a364e2d509435b886be59f.json" +} diff --git a/tools/bridge-frontend/artifacts/IBridge.sol/IBridge.json b/tools/bridge-frontend/artifacts/IBridge.sol/IBridge.json new file mode 100644 index 0000000000..6ed11dea72 --- /dev/null +++ b/tools/bridge-frontend/artifacts/IBridge.sol/IBridge.json @@ -0,0 +1,70 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "IBridge", + "sourceName": "src/bridge/IBridge.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "receiveAssets", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "sendERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "sendNative", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } + ], + "bytecode": "0x", + "deployedBytecode": "0x", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/tools/bridge-frontend/artifacts/IMessageBus.sol/IMessageBus.json b/tools/bridge-frontend/artifacts/IMessageBus.sol/IMessageBus.json new file mode 100644 index 0000000000..485ddd6a0c --- /dev/null +++ b/tools/bridge-frontend/artifacts/IMessageBus.sol/IMessageBus.json @@ -0,0 +1,311 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "sequence", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "nonce", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "topic", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "payload", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "consistencyLevel", + "type": "uint8" + } + ], + "name": "LogMessagePublished", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "sequence", + "type": "uint64" + } + ], + "name": "ValueTransfer", + "type": "event" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint64", + "name": "sequence", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "nonce", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "topic", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "payload", + "type": "bytes" + }, + { + "internalType": "uint8", + "name": "consistencyLevel", + "type": "uint8" + } + ], + "internalType": "struct Structs.CrossChainMessage", + "name": "crossChainMessage", + "type": "tuple" + } + ], + "name": "getMessageTimeOfFinality", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "nonce", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "topic", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "payload", + "type": "bytes" + }, + { + "internalType": "uint8", + "name": "consistencyLevel", + "type": "uint8" + } + ], + "name": "publishMessage", + "outputs": [ + { + "internalType": "uint64", + "name": "sequence", + "type": "uint64" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "receiveValueFromL2", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "retrieveAllFunds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "sendValueToL2", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint64", + "name": "sequence", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "nonce", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "topic", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "payload", + "type": "bytes" + }, + { + "internalType": "uint8", + "name": "consistencyLevel", + "type": "uint8" + } + ], + "internalType": "struct Structs.CrossChainMessage", + "name": "crossChainMessage", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "finalAfterTimestamp", + "type": "uint256" + } + ], + "name": "storeCrossChainMessage", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint64", + "name": "sequence", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "nonce", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "topic", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "payload", + "type": "bytes" + }, + { + "internalType": "uint8", + "name": "consistencyLevel", + "type": "uint8" + } + ], + "internalType": "struct Structs.CrossChainMessage", + "name": "crossChainMessage", + "type": "tuple" + } + ], + "name": "verifyMessageFinalized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/tools/bridge-frontend/artifacts/ManagementContract.sol/ManagementContract.bin b/tools/bridge-frontend/artifacts/ManagementContract.sol/ManagementContract.bin new file mode 100644 index 0000000000..eb6088e41d --- /dev/null +++ b/tools/bridge-frontend/artifacts/ManagementContract.sol/ManagementContract.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b5061001a3361001f565b610090565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3505050565b6143c180620000a06000396000f3fe60806040523480156200001157600080fd5b5060043610620002005760003560e01c8063728109961162000119578063a1a227fa11620000af578063db5d91b1116200007a578063db5d91b114620004f1578063e34fbfc81462000520578063e874eb201462000535578063f2fde38b146200054957600080fd5b8063a1a227fa146200049e578063a4ab2faa14620004b2578063a52f433c14620004c9578063d4fab88714620004da57600080fd5b80638415482611620000f057806384154826146200040a57806387059edb14620004305780638da5cb5b146200044757806398077e86146200047857600080fd5b80637281099614620003cf5780638129fc1c14620003d95780638236a7ba14620003e357600080fd5b8063440c953b116200019b57806368e10383116200016657806368e10383146200037e5780636a30d26c14620003955780636b9707d614620003ae578063715018a614620003c557600080fd5b8063440c953b146200031f5780634766573814620003295780635371a2161462000340578063568699c8146200035757600080fd5b80632c77c81f11620001dc5780632c77c81f14620002525780632f0cb9e314620002695780633e60a22f14620002a057806343348b2f14620002f057600080fd5b80620ddd27146200020557806303e72e481462000222578063073b6ef3146200023b575b600080fd5b6200020f600e5481565b6040519081526020015b60405180910390f35b620002396200023336600462001eb5565b62000560565b005b620002396200024c36600462002000565b62000673565b62000239620002633660046200209d565b62000906565b6200028f6200027a36600462002140565b600c6020526000908152604090205460ff1681565b604051901515815260200162000219565b620002d7620002b13660046200215a565b80516020818301810180516003825292820191909301209152546001600160a01b031681565b6040516001600160a01b03909116815260200162000219565b6200028f620003013660046200219b565b6001600160a01b031660009081526020819052604090205460ff1690565b6200020f60055481565b620002396200033a3660046200219b565b62000a76565b6200023962000351366004620021c0565b62000b46565b6200036e6200036836600462002140565b62000d4f565b60405162000219929190620022e3565b620002396200038f366004620022fe565b62000da8565b6200039f62000e95565b60405162000219919062002388565b62000239620003bf3660046200219b565b62000f78565b620002396200103e565b6200023962001056565b62000239620010da565b620003fa620003f436600462002140565b620012be565b60405162000219929190620023ee565b6200028f6200041b36600462002140565b600d6020526000908152604090205460ff1681565b620003fa6200044136600462002140565b620013ae565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b0316620002d7565b6200048f6200048936600462002140565b62001428565b6040516200021991906200240b565b600a54620002d7906001600160a01b031681565b6200028f620004c336600462002420565b620014dd565b600454610100900460ff166200028f565b62000239620004eb36600462002459565b6200156e565b6200028f620005023660046200219b565b6001600160a01b031660009081526001602052604090205460ff1690565b620002396200053136600462002504565b5050565b600b54620002d7906001600160a01b031681565b620002396200055a3660046200219b565b62001701565b6200056a6200175e565b60006001600160a01b03166003836040516200058791906200254a565b908152604051908190036020019020546001600160a01b031603620005e657600280546001810182556000919091527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace01620005e48382620025f7565b505b80600383604051620005f991906200254a565b90815260405190819003602001812080546001600160a01b039390931673ffffffffffffffffffffffffffffffffffffffff19909316929092179091557f17b2f9f5748931099ffee882b5b64f4a560b5c55da9b4f4e396dae3bb9f98cb590620006679084908490620026c4565b60405180910390a15050565b6000828152600860205260409020548114620006d65760405162461bcd60e51b815260206004820152600e60248201527f496e76616c696420666f726b494400000000000000000000000000000000000060448201526064015b60405180910390fd5b60006200074889898989604051602001620006f59493929190620026f1565b6040516020818303038152906040528051906020012086868080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250620017d592505050565b6001600160a01b03811660009081526020819052604090205490915060ff16620007b55760405162461bcd60e51b815260206004820152601660248201527f656e636c6176654944206e6f74206174746573746564000000000000000000006044820152606401620006cd565b600e8990556000805b8751811015620008e157600b5488516001600160a01b039091169063b6aed0cb908a9084908110620007f457620007f46200276d565b6020026020010151620008079062002783565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b1681526004810191909152426024820152604401600060405180830381600087803b1580156200085f57600080fd5b505af115801562000874573d6000803e3d6000fd5b50505050818882815181106200088e576200088e6200276d565b6020026020010151620008a19062002783565b6040805160208101939093528201526060016040516020818303038152906040528051906020012091508080620008d890620027be565b915050620007be565b506000908152600d60205260409020805460ff19166001179055505050505050505050565b60006200095885356200091d6020880188620027da565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250620017d592505050565b6001600160a01b03811660009081526020819052604090205490915060ff16620009c55760405162461bcd60e51b815260206004820152601660248201527f656e636c6176654944206e6f74206174746573746564000000000000000000006044820152606401620006cd565b6001600160a01b03811660009081526001602052604090205460ff1662000a2f5760405162461bcd60e51b815260206004820152601960248201527f656e636c6176654944206e6f7420612073657175656e636572000000000000006044820152606401620006cd565b62000a3a8562001805565b604051853581527fd6555bff8670bd3008dc064c30bb56d6ac7cb14ae801e36146fe4e7c6a504a58906020015b60405180910390a15050505050565b62000a806200175e565b6001600160a01b03811660009081526020819052604090205460ff1662000aea5760405162461bcd60e51b815260206004820152601660248201527f656e636c6176654944206e6f74206174746573746564000000000000000000006044820152606401620006cd565b6001600160a01b038116600081815260016020818152604092839020805460ff191690921790915590519182527ffe64c7181f0fc60e300dc02cca368cdfa94d7ca45902de3b9a9d80070e76093691015b60405180910390a150565b600b546040517fb201246f0000000000000000000000000000000000000000000000000000000081526001600160a01b039091169063b201246f9062000b9790879087908790879060040162002884565b60006040518083038186803b15801562000bb057600080fd5b505afa15801562000bc5573d6000803e3d6000fd5b5050505060008460405160200162000bde9190620028ec565b60408051601f1981840301815291815281516020928301206000818152600c90935291205490915060ff161562000c585760405162461bcd60e51b815260206004820152601860248201527f7769746864726177616c20616c7265616479207370656e7400000000000000006044820152606401620006cd565b6001600c60008760405160200162000c719190620028ec565b60408051808303601f190181529181528151602092830120835282820193909352908201600020805460ff191693151593909317909255600a546001600160a01b0316916399a3ad219162000ccc919089019089016200219b565b604080517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b1681526001600160a01b0390921660048301528801356024820152604401600060405180830381600087803b15801562000d2f57600080fd5b505af115801562000d44573d6000803e3d6000fd5b505050505050505050565b60408051606080820183526000808352602083019190915291810182905260008062000d7b85620013ae565b915091508162000d915760009590945092505050565b600094855260086020526040909420549492505050565b60045460ff161562000e235760405162461bcd60e51b815260206004820152602260248201527f6e6574776f726b2073656372657420616c726561647920696e697469616c697a60448201527f65640000000000000000000000000000000000000000000000000000000000006064820152608401620006cd565b60048054600160ff1991821681179092556001600160a01b038716600081815260208181526040808320805486168717905585825291829020805490941690941790925590519081527ffe64c7181f0fc60e300dc02cca368cdfa94d7ca45902de3b9a9d80070e760936910162000a67565b60606002805480602002602001604051908101604052809291908181526020016000905b8282101562000f6f57838290600052602060002001805462000edb9062002568565b80601f016020809104026020016040519081016040528092919081815260200182805462000f099062002568565b801562000f5a5780601f1062000f2e5761010080835404028352916020019162000f5a565b820191906000526020600020905b81548152906001019060200180831162000f3c57829003601f168201915b50505050508152602001906001019062000eb9565b50505050905090565b62000f826200175e565b6001600160a01b03811660009081526001602052604090205460ff1662000fec5760405162461bcd60e51b815260206004820152601960248201527f656e636c6176654944206e6f7420612073657175656e636572000000000000006044820152606401620006cd565b6001600160a01b038116600081815260016020908152604091829020805460ff1916905590519182527f0f279980343c7ca542fde9fa5396555068efb5cd560d9cf9c191aa2911079b47910162000b3b565b620010486200175e565b620010546000620018ba565b565b620010606200175e565b600a546040517f36d2da900000000000000000000000000000000000000000000000000000000081523360048201526001600160a01b03909116906336d2da9090602401600060405180830381600087803b158015620010bf57600080fd5b505af1158015620010d4573d6000803e3d6000fd5b50505050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000810460ff16159067ffffffffffffffff16600081158015620011265750825b905060008267ffffffffffffffff166001148015620011445750303b155b90508115801562001153575080155b156200118b576040517ff92ee8a900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b845467ffffffffffffffff191660011785558315620011c057845468ff00000000000000001916680100000000000000001785555b620011cb3362001938565b60006005556001600955604051620011e39062001dc9565b604051809103906000f08015801562001200573d6000803e3d6000fd5b50600b805473ffffffffffffffffffffffffffffffffffffffff199081166001600160a01b0393909316928317909155600a8054909116821790556040519081527fbd726cf82ac9c3260b1495107182e336e0654b25c10915648c0cc15b2bb72cbf9060200160405180910390a18315620012b757845468ff000000000000000019168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200162000a67565b5050505050565b6040805160608082018352600080835260208084018390528385018290528582526006815284822085519384019095528454835260018501805492958694939092840191906200130e9062002568565b80601f01602080910402602001604051908101604052809291908181526020018280546200133c9062002568565b80156200138d5780601f1062001361576101008083540402835291602001916200138d565b820191906000526020600020905b8154815290600101906020018083116200136f57829003601f168201915b50505091835250506002919091015460209091015280519094149492505050565b604080516060808201835260008083526020830191909152918101829052600083815260076020526040812054908190036200141357505060408051606081018252600080825282516020818101855282825283015291810182905290939092509050565b6200141e81620012be565b9250925050915091565b600281815481106200143957600080fd5b906000526020600020016000915090508054620014569062002568565b80601f0160208091040260200160405190810160405280929190818152602001828054620014849062002568565b8015620014d55780601f10620014a957610100808354040283529160200191620014d5565b820191906000526020600020905b815481529060010190602001808311620014b757829003601f168201915b505050505081565b600080805b83518110156200155557818482815181106200150257620015026200276d565b6020026020010151620015159062002783565b60408051602081019390935282015260600160405160208183030381529060405280519060200120915080806200154c90620027be565b915050620014e2565b506000908152600d602052604090205460ff1692915050565b6001600160a01b03851660009081526020819052604090205460ff1680620015ff5760405162461bcd60e51b815260206004820152602360248201527f726573706f6e64696e67206174746573746572206973206e6f7420617474657360448201527f74656400000000000000000000000000000000000000000000000000000000006064820152608401620006cd565b8115620016d6576000620016388787866040516020016200162393929190620028fc565b6040516020818303038152906040526200194d565b90506000620016488287620017d5565b9050876001600160a01b0316816001600160a01b031614620016d35760405162461bcd60e51b815260206004820152602c60248201527f63616c63756c61746564206164647265737320616e642061747465737465724960448201527f4420646f6e74206d6174636800000000000000000000000000000000000000006064820152608401620006cd565b50505b5050506001600160a01b039091166000908152602081905260409020805460ff191660011790555050565b6200170b6200175e565b6001600160a01b03811662001750576040517f1e4fbdf700000000000000000000000000000000000000000000000000000000815260006004820152602401620006cd565b6200175b81620018ba565b50565b33620017917f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b031690565b6001600160a01b03161462001054576040517f118cdaa7000000000000000000000000000000000000000000000000000000008152336004820152602401620006cd565b600080600080620017e786866200198c565b925092509250620017f98282620019dd565b50909150505b92915050565b80356000908152600660205260409020819062001823828262002944565b505060095460009081526007602052604090208135908190556200184960014362002a54565b4060405160200162001865929190918252602082015260400190565b60408051601f1981840301815291815281516020928301206009805460009081526008909452918320558054916200189d83620027be565b9190505550600554816040013511156200175b5760400135600555565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300805473ffffffffffffffffffffffffffffffffffffffff1981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3505050565b6200194262001af3565b6200175b8162001b5b565b60006200195b825162001b65565b826040516020016200196f92919062002a6a565b604051602081830303815290604052805190602001209050919050565b60008060008351604103620019ca5760208401516040850151606086015160001a620019bb8882858562001c0c565b955095509550505050620019d6565b50508151600091506002905b9250925092565b6000826003811115620019f457620019f462002ac9565b03620019fe575050565b600182600381111562001a155762001a1562002ac9565b0362001a4d576040517ff645eedf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600282600381111562001a645762001a6462002ac9565b0362001aa0576040517ffce698f700000000000000000000000000000000000000000000000000000000815260048101829052602401620006cd565b600382600381111562001ab75762001ab762002ac9565b0362000531576040517fd78bce0c00000000000000000000000000000000000000000000000000000000815260048101829052602401620006cd565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005468010000000000000000900460ff1662001054576040517fd7e6bcf800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6200170b62001af3565b6060600062001b748362001ce0565b600101905060008167ffffffffffffffff81111562001b975762001b9762001dd7565b6040519080825280601f01601f19166020018201604052801562001bc2576020820181803683370190505b5090508181016020015b600019017f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a850494508462001bcc57509392505050565b600080807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a084111562001c49575060009150600390508262001cd6565b604080516000808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa15801562001c9e573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b03811662001ccc5750600092506001915082905062001cd6565b9250600091508190505b9450945094915050565b6000807a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000831062001d2a577a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000830492506040015b6d04ee2d6d415b85acef8100000000831062001d57576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc10000831062001d7657662386f26fc10000830492506010015b6305f5e100831062001d8f576305f5e100830492506008015b612710831062001da457612710830492506004015b6064831062001db7576064830492506002015b600a8310620017ff5760010192915050565b6118ac8062002ae083390190565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff8111828210171562001e195762001e1962001dd7565b604052919050565b600082601f83011262001e3357600080fd5b813567ffffffffffffffff81111562001e505762001e5062001dd7565b62001e65601f8201601f191660200162001ded565b81815284602083860101111562001e7b57600080fd5b816020850160208301376000918101602001919091529392505050565b80356001600160a01b038116811462001eb057600080fd5b919050565b6000806040838503121562001ec957600080fd5b823567ffffffffffffffff81111562001ee157600080fd5b62001eef8582860162001e21565b92505062001f006020840162001e98565b90509250929050565b600082601f83011262001f1b57600080fd5b8135602067ffffffffffffffff8083111562001f3b5762001f3b62001dd7565b8260051b62001f4c83820162001ded565b938452858101830193838101908886111562001f6757600080fd5b84880192505b8583101562001fa85782358481111562001f875760008081fd5b62001f978a87838c010162001e21565b835250918401919084019062001f6d565b98975050505050505050565b60008083601f84011262001fc757600080fd5b50813567ffffffffffffffff81111562001fe057600080fd5b60208301915083602082850101111562001ff957600080fd5b9250929050565b60008060008060008060008060e0898b0312156200201d57600080fd5b883597506020890135965060408901359550606089013567ffffffffffffffff808211156200204b57600080fd5b620020598c838d0162001f09565b965060808b01359150808211156200207057600080fd5b506200207f8b828c0162001fb4565b999c989b5096999598969760a08701359660c0013595509350505050565b60008060008060608587031215620020b457600080fd5b843567ffffffffffffffff80821115620020cd57600080fd5b9086019060608289031215620020e257600080fd5b90945060208601359080821115620020f957600080fd5b620021078883890162001fb4565b909550935060408701359150808211156200212157600080fd5b508501602081880312156200213557600080fd5b939692955090935050565b6000602082840312156200215357600080fd5b5035919050565b6000602082840312156200216d57600080fd5b813567ffffffffffffffff8111156200218557600080fd5b620021938482850162001e21565b949350505050565b600060208284031215620021ae57600080fd5b620021b98262001e98565b9392505050565b60008060008084860360c0811215620021d857600080fd5b6080811215620021e757600080fd5b50849350608085013567ffffffffffffffff808211156200220757600080fd5b818701915087601f8301126200221c57600080fd5b8135818111156200222c57600080fd5b8860208260051b85010111156200224257600080fd5b95986020929092019750949560a00135945092505050565b60005b83811015620022775781810151838201526020016200225d565b50506000910152565b600081518084526200229a8160208601602086016200225a565b601f01601f19169290920160200192915050565b805182526000602082015160606020850152620022cf606085018262002280565b604093840151949093019390935250919050565b828152604060208201526000620021936040830184620022ae565b6000806000806000606086880312156200231757600080fd5b620023228662001e98565b9450602086013567ffffffffffffffff808211156200234057600080fd5b6200234e89838a0162001fb4565b909650945060408801359150808211156200236857600080fd5b50620023778882890162001fb4565b969995985093965092949392505050565b6000602080830181845280855180835260408601915060408160051b870101925083870160005b82811015620023e157603f19888603018452620023ce85835162002280565b94509285019290850190600101620023af565b5092979650505050505050565b8215158152604060208201526000620021936040830184620022ae565b602081526000620021b9602083018462002280565b6000602082840312156200243357600080fd5b813567ffffffffffffffff8111156200244b57600080fd5b620021938482850162001f09565b600080600080600060a086880312156200247257600080fd5b6200247d8662001e98565b94506200248d6020870162001e98565b9350604086013567ffffffffffffffff80821115620024ab57600080fd5b620024b989838a0162001e21565b94506060880135915080821115620024d057600080fd5b50620024df8882890162001e21565b92505060808601358015158114620024f657600080fd5b809150509295509295909350565b600080602083850312156200251857600080fd5b823567ffffffffffffffff8111156200253057600080fd5b6200253e8582860162001fb4565b90969095509350505050565b600082516200255e8184602087016200225a565b9190910192915050565b600181811c908216806200257d57607f821691505b6020821081036200259e57634e487b7160e01b600052602260045260246000fd5b50919050565b601f821115620025f257600081815260208120601f850160051c81016020861015620025cd5750805b601f850160051c820191505b81811015620025ee57828155600101620025d9565b5050505b505050565b815167ffffffffffffffff81111562002614576200261462001dd7565b6200262c8162002625845462002568565b84620025a4565b602080601f8311600181146200266457600084156200264b5750858301515b600019600386901b1c1916600185901b178555620025ee565b600085815260208120601f198616915b82811015620026955788860151825594840194600190910190840162002674565b5085821015620026b45787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b604081526000620026d9604083018562002280565b90506001600160a01b03831660208301529392505050565b600060808201868352602086818501528560408501526080606085015281855180845260a08601915060a08160051b870101935082870160005b828110156200275d57609f198887030184526200274a86835162002280565b955092840192908401906001016200272b565b50939a9950505050505050505050565b634e487b7160e01b600052603260045260246000fd5b805160208083015191908110156200259e5760001960209190910360031b1b16919050565b634e487b7160e01b600052601160045260246000fd5b600060018201620027d357620027d3620027a8565b5060010190565b6000808335601e19843603018112620027f257600080fd5b83018035915067ffffffffffffffff8211156200280e57600080fd5b60200191503681900382131562001ff957600080fd5b6001600160a01b0380620028388362001e98565b168352806200284a6020840162001e98565b1660208401525060408101356040830152606081013567ffffffffffffffff81168082146200287857600080fd5b80606085015250505050565b62002890818662002824565b60c060808201528260c082015260007f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff841115620028cd57600080fd5b8360051b808660e085013760a0830193909352500160e0019392505050565b60808101620017ff828462002824565b60006bffffffffffffffffffffffff19808660601b168352808560601b166014840152508251620029358160288501602087016200225a565b91909101602801949350505050565b813581556001808201602080850135601e198636030181126200296657600080fd5b8501803567ffffffffffffffff8111156200298057600080fd5b80360383830113156200299257600080fd5b620029aa81620029a3865462002568565b86620025a4565b6000601f821160018114620029e35760008315620029ca57508382018501355b600019600385901b1c1916600184901b17865562002a3f565b600086815260209020601f19841690835b8281101562002a1557868501880135825593870193908901908701620029f4565b508482101562002a355760001960f88660031b161c198785880101351681555b50508683881b0186555b50505050505050604082013560028201555050565b81810381811115620017ff57620017ff620027a8565b7f19457468657265756d205369676e6564204d6573736167653a0a00000000000081526000835162002aa481601a8501602088016200225a565b83519083019062002abd81601a8401602088016200225a565b01601a01949350505050565b634e487b7160e01b600052602160045260246000fdfe608060405234801561001057600080fd5b50338061003757604051631e4fbdf760e01b81526000600482015260240160405180910390fd5b61004081610046565b50610096565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b611807806100a56000396000f3fe6080604052600436106100e15760003560e01c80639730886d1161007f578063b201246f11610059578063b201246f1461030e578063b6aed0cb1461032e578063e138a8d21461034e578063f2fde38b1461036e57610155565b80639730886d1461029557806399a3ad21146102b5578063b1454caa146102d557610155565b8063346633fb116100bb578063346633fb1461022557806336d2da9014610238578063715018a6146102585780638da5cb5b1461026d57610155565b80630fcfbd11146101a25780630fe9188e146101d557806333a88c72146101f557610155565b36610155576040517f346633fb0000000000000000000000000000000000000000000000000000000081523360048201523460248201819052309163346633fb91906044016000604051808303818588803b15801561013f57600080fd5b505af1158015610153573d6000803e3d6000fd5b005b60405162461bcd60e51b815260206004820152600b60248201527f756e737570706f7274656400000000000000000000000000000000000000000060448201526064015b60405180910390fd5b3480156101ae57600080fd5b506101c26101bd366004610f80565b61038e565b6040519081526020015b60405180910390f35b3480156101e157600080fd5b506101536101f0366004610fb5565b610428565b34801561020157600080fd5b50610215610210366004610f80565b61049e565b60405190151581526020016101cc565b610153610233366004610fe3565b6104f1565b34801561024457600080fd5b5061015361025336600461100f565b6105d4565b34801561026457600080fd5b50610153610683565b34801561027957600080fd5b506000546040516001600160a01b0390911681526020016101cc565b3480156102a157600080fd5b506101536102b036600461102c565b610697565b3480156102c157600080fd5b506101536102d0366004610fe3565b6107e9565b3480156102e157600080fd5b506102f56102f03660046110a2565b610899565b60405167ffffffffffffffff90911681526020016101cc565b34801561031a57600080fd5b5061015361032936600461119b565b6108fa565b34801561033a57600080fd5b506101536103493660046111fd565b610afc565b34801561035a57600080fd5b5061015361036936600461121f565b610b98565b34801561037a57600080fd5b5061015361038936600461100f565b610d78565b600080826040516020016103a291906112ce565b60408051601f19818403018152918152815160209283012060008181526001909352912054909150806104215760405162461bcd60e51b815260206004820152602160248201527f54686973206d65737361676520776173206e65766572207375626d69747465646044820152601760f91b6064820152608401610199565b9392505050565b610430610dcf565b600081815260046020526040812054900361048d5760405162461bcd60e51b815260206004820152601a60248201527f537461746520726f6f7420646f6573206e6f742065786973742e0000000000006044820152606401610199565b600090815260046020526040812055565b600080826040516020016104b291906112ce565b60408051601f1981840301815291815281516020928301206000818152600190935291205490915080158015906104e95750428111155b949350505050565b60003411801561050057508034145b6105725760405162461bcd60e51b815260206004820152603060248201527f417474656d7074696e6720746f2073656e642076616c756520776974686f757460448201527f2070726f766964696e67204574686572000000000000000000000000000000006064820152608401610199565b600061057d33610e15565b6040805134815267ffffffffffffffff831660208201529192506001600160a01b0385169133917f50c536ac33a920f00755865b831d17bf4cff0b2e0345f65b16d52bfc004068b6910160405180910390a3505050565b6105dc610dcf565b6000816001600160a01b03164760405160006040518083038185875af1925050503d8060008114610629576040519150601f19603f3d011682016040523d82523d6000602084013e61062e565b606091505b505090508061067f5760405162461bcd60e51b815260206004820152601460248201527f6661696c65642073656e64696e672076616c75650000000000000000000000006044820152606401610199565b5050565b61068b610dcf565b6106956000610e73565b565b61069f610dcf565b60006106ab82426113cf565b90506000836040516020016106c091906112ce565b60408051601f198184030181529181528151602092830120600081815260019093529120549091501561075b5760405162461bcd60e51b815260206004820152602160248201527f4d657373616765207375626d6974746564206d6f7265207468616e206f6e636560448201527f21000000000000000000000000000000000000000000000000000000000000006064820152608401610199565b600081815260016020908152604082208490556002919061077e9087018761100f565b6001600160a01b0316815260208101919091526040016000908120906107aa60808701606088016113e2565b63ffffffff168152602080820192909252604001600090812080546001810182559082529190208591600402016107e182826115b0565b505050505050565b6107f1610dcf565b6000826001600160a01b03168260405160006040518083038185875af1925050503d806000811461083e576040519150601f19603f3d011682016040523d82523d6000602084013e610843565b606091505b50509050806108945760405162461bcd60e51b815260206004820152601460248201527f6661696c65642073656e64696e672076616c75650000000000000000000000006044820152606401610199565b505050565b60006108a433610e15565b9050336001600160a01b03167fb93c37389233beb85a3a726c3f15c2d15533ee74cb602f20f490dfffef7759378288888888886040516108e9969594939291906116ca565b60405180910390a295945050505050565b600081815260046020526040812054900361096a5760405162461bcd60e51b815260206004820152602a60248201527f526f6f74206973206e6f74207075626c6973686564206f6e2074686973206d6560448201526939b9b0b3b290313ab99760b11b6064820152608401610199565b6000818152600460205260409020544210156109d25760405162461bcd60e51b815260206004820152602160248201527f526f6f74206973206e6f7420636f6e736964657265642066696e616c207965746044820152601760f91b6064820152608401610199565b6000846040516020016109e5919061171a565b60408051601f198184030181528282528051602091820120908301829052600160608401527f760000000000000000000000000000000000000000000000000000000000000060808401529082015260a001604051602081830303815290604052805190602001209050610a8384848484604051602001610a6891815260200190565b60405160208183030381529060405280519060200120610ed0565b610af55760405162461bcd60e51b815260206004820152603360248201527f496e76616c696420696e636c7573696f6e2070726f6f6620666f722076616c7560448201527f65207472616e73666572206d6573736167652e000000000000000000000000006064820152608401610199565b5050505050565b610b04610dcf565b60008281526004602052604090205415610b865760405162461bcd60e51b815260206004820152602560248201527f526f6f7420616c726561647920616464656420746f20746865206d657373616760448201527f65206275730000000000000000000000000000000000000000000000000000006064820152608401610199565b60009182526004602052604090912055565b6000818152600460205260408120549003610c085760405162461bcd60e51b815260206004820152602a60248201527f526f6f74206973206e6f74207075626c6973686564206f6e2074686973206d6560448201526939b9b0b3b290313ab99760b11b6064820152608401610199565b600081815260046020526040902054421015610c705760405162461bcd60e51b815260206004820152602160248201527f526f6f74206973206e6f7420636f6e736964657265642066696e616c207965746044820152601760f91b6064820152608401610199565b600084604051602001610c8391906112ce565b60408051601f198184030181528282528051602091820120908301829052600160608401527f6d0000000000000000000000000000000000000000000000000000000000000060808401529082015260a001604051602081830303815290604052805190602001209050610d0684848484604051602001610a6891815260200190565b610af55760405162461bcd60e51b815260206004820152603060248201527f496e76616c696420696e636c7573696f6e2070726f6f6620666f722063726f7360448201527f7320636861696e206d6573736167652e000000000000000000000000000000006064820152608401610199565b610d80610dcf565b6001600160a01b038116610dc3576040517f1e4fbdf700000000000000000000000000000000000000000000000000000000815260006004820152602401610199565b610dcc81610e73565b50565b6000546001600160a01b03163314610695576040517f118cdaa7000000000000000000000000000000000000000000000000000000008152336004820152602401610199565b6001600160a01b0381166000908152600360205260408120805467ffffffffffffffff169160019190610e48838561177a565b92506101000a81548167ffffffffffffffff021916908367ffffffffffffffff160217905550919050565b600080546001600160a01b0383811673ffffffffffffffffffffffffffffffffffffffff19831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b600082610ede868685610ee8565b1495945050505050565b600081815b84811015610f2b57610f1782878784818110610f0b57610f0b6117a2565b90506020020135610f34565b915080610f23816117b8565b915050610eed565b50949350505050565b6000818310610f50576000828152602084905260409020610f5f565b60008381526020839052604090205b90505b92915050565b600060c08284031215610f7a57600080fd5b50919050565b600060208284031215610f9257600080fd5b813567ffffffffffffffff811115610fa957600080fd5b6104e984828501610f68565b600060208284031215610fc757600080fd5b5035919050565b6001600160a01b0381168114610dcc57600080fd5b60008060408385031215610ff657600080fd5b823561100181610fce565b946020939093013593505050565b60006020828403121561102157600080fd5b813561042181610fce565b6000806040838503121561103f57600080fd5b823567ffffffffffffffff81111561105657600080fd5b61106285828601610f68565b95602094909401359450505050565b63ffffffff81168114610dcc57600080fd5b60ff81168114610dcc57600080fd5b803561109d81611083565b919050565b6000806000806000608086880312156110ba57600080fd5b85356110c581611071565b945060208601356110d581611071565b9350604086013567ffffffffffffffff808211156110f257600080fd5b818801915088601f83011261110657600080fd5b81358181111561111557600080fd5b89602082850101111561112757600080fd5b602083019550809450505050606086013561114181611083565b809150509295509295909350565b60008083601f84011261116157600080fd5b50813567ffffffffffffffff81111561117957600080fd5b6020830191508360208260051b850101111561119457600080fd5b9250929050565b60008060008084860360c08112156111b257600080fd5b60808112156111c057600080fd5b50849350608085013567ffffffffffffffff8111156111de57600080fd5b6111ea8782880161114f565b9598909750949560a00135949350505050565b6000806040838503121561121057600080fd5b50508035926020909101359150565b6000806000806060858703121561123557600080fd5b843567ffffffffffffffff8082111561124d57600080fd5b61125988838901610f68565b9550602087013591508082111561126f57600080fd5b5061127c8782880161114f565b9598909750949560400135949350505050565b67ffffffffffffffff81168114610dcc57600080fd5b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b60208152600082356112df81610fce565b6001600160a01b03811660208401525060208301356112fd8161128f565b67ffffffffffffffff80821660408501526040850135915061131e82611071565b63ffffffff80831660608601526060860135925061133b83611071565b80831660808601525060808501359150601e1985360301821261135d57600080fd5b602091850191820191358181111561137457600080fd5b80360383131561138357600080fd5b60c060a086015261139860e0860182856112a5565b925050506113a860a08501611092565b60ff811660c0850152509392505050565b634e487b7160e01b600052601160045260246000fd5b80820180821115610f6257610f626113b9565b6000602082840312156113f457600080fd5b813561042181611071565b60008135610f6281611071565b6000808335601e1984360301811261142357600080fd5b83018035915067ffffffffffffffff82111561143e57600080fd5b60200191503681900382131561119457600080fd5b634e487b7160e01b600052604160045260246000fd5b600181811c9082168061147d57607f821691505b602082108103610f7a57634e487b7160e01b600052602260045260246000fd5b601f82111561089457600081815260208120601f850160051c810160208610156114c45750805b601f850160051c820191505b818110156107e1578281556001016114d0565b67ffffffffffffffff8311156114fb576114fb611453565b61150f836115098354611469565b8361149d565b6000601f841160018114611543576000851561152b5750838201355b600019600387901b1c1916600186901b178355610af5565b600083815260209020601f19861690835b828110156115745786850135825560209485019460019092019101611554565b50868210156115915760001960f88860031b161c19848701351681555b505060018560011b0183555050505050565b60008135610f6281611083565b81356115bb81610fce565b6001600160a01b038116905081548173ffffffffffffffffffffffffffffffffffffffff19821617835560208401356115f38161128f565b7bffffffffffffffff00000000000000000000000000000000000000008160a01b1690507fffffffff000000000000000000000000000000000000000000000000000000008184828516171785556040860135925061165183611071565b921760e09190911b90911617815561168961166e606084016113ff565b6001830163ffffffff821663ffffffff198254161781555050565b611696608083018361140c565b6116a48183600286016114e3565b505061067f6116b560a084016115a3565b6003830160ff821660ff198254161781555050565b67ffffffffffffffff87168152600063ffffffff808816602084015280871660408401525060a0606083015261170460a0830185876112a5565b905060ff83166080830152979650505050505050565b60808101823561172981610fce565b6001600160a01b03908116835260208401359061174582610fce565b1660208301526040838101359083015260608301356117638161128f565b67ffffffffffffffff811660608401525092915050565b67ffffffffffffffff81811683821601908082111561179b5761179b6113b9565b5092915050565b634e487b7160e01b600052603260045260246000fd5b6000600182016117ca576117ca6113b9565b506001019056fea2646970667358221220a449a197632e1117b118ced8f7cf97a1ae44b160775ee146e59b50cbc1b8dcaa64736f6c63430008140033a26469706673582212202db2c4edc3c70178755866034f9c9976755cd4234b737f234daf445437ad909f64736f6c63430008140033 \ No newline at end of file diff --git a/tools/bridge-frontend/artifacts/ManagementContract.sol/ManagementContract.json b/tools/bridge-frontend/artifacts/ManagementContract.sol/ManagementContract.json new file mode 100644 index 0000000000..c88c573434 --- /dev/null +++ b/tools/bridge-frontend/artifacts/ManagementContract.sol/ManagementContract.json @@ -0,0 +1,830 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "ECDSAInvalidSignature", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "length", + "type": "uint256" + } + ], + "name": "ECDSAInvalidSignatureLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "ECDSAInvalidSignatureS", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAddress", + "type": "address" + } + ], + "name": "ImportantContractAddressUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "messageBusAddress", + "type": "address" + } + ], + "name": "LogManagementContractCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "rollupHash", + "type": "bytes32" + } + ], + "name": "RollupAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "enclaveID", + "type": "address" + } + ], + "name": "SequencerEnclaveGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "enclaveID", + "type": "address" + } + ], + "name": "SequencerEnclaveRevoked", + "type": "event" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "Hash", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "Signature", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "LastSequenceNumber", + "type": "uint256" + } + ], + "internalType": "struct Structs.MetaRollup", + "name": "r", + "type": "tuple" + }, + { + "internalType": "string", + "name": "_rollupData", + "type": "string" + }, + { + "components": [ + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint64", + "name": "sequence", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "nonce", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "topic", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "payload", + "type": "bytes" + }, + { + "internalType": "uint8", + "name": "consistencyLevel", + "type": "uint8" + } + ], + "internalType": "struct Structs.CrossChainMessage[]", + "name": "messages", + "type": "tuple[]" + } + ], + "internalType": "struct Structs.HeaderCrossChainData", + "name": "", + "type": "tuple" + } + ], + "name": "AddRollup", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_addr", + "type": "address" + } + ], + "name": "Attested", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "sequence", + "type": "uint64" + } + ], + "internalType": "struct Structs.ValueTransferMessage", + "name": "_msg", + "type": "tuple" + }, + { + "internalType": "bytes32[]", + "name": "proof", + "type": "bytes32[]" + }, + { + "internalType": "bytes32", + "name": "root", + "type": "bytes32" + } + ], + "name": "ExtractNativeValue", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "GetImportantContractKeys", + "outputs": [ + { + "internalType": "string[]", + "name": "", + "type": "string[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "rollupHash", + "type": "bytes32" + } + ], + "name": "GetRollupByHash", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "Hash", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "Signature", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "LastSequenceNumber", + "type": "uint256" + } + ], + "internalType": "struct Structs.MetaRollup", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "number", + "type": "uint256" + } + ], + "name": "GetRollupByNumber", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "Hash", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "Signature", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "LastSequenceNumber", + "type": "uint256" + } + ], + "internalType": "struct Structs.MetaRollup", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "number", + "type": "uint256" + } + ], + "name": "GetUniqueForkID", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "Hash", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "Signature", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "LastSequenceNumber", + "type": "uint256" + } + ], + "internalType": "struct Structs.MetaRollup", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_addr", + "type": "address" + } + ], + "name": "GrantSequencerEnclave", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_enclaveID", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_initSecret", + "type": "bytes" + }, + { + "internalType": "string", + "name": "_genesisAttestation", + "type": "string" + } + ], + "name": "InitializeNetworkSecret", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_addr", + "type": "address" + } + ], + "name": "IsSequencerEnclave", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "IsWithdrawalAvailable", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "requestReport", + "type": "string" + } + ], + "name": "RequestNetworkSecret", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "attesterID", + "type": "address" + }, + { + "internalType": "address", + "name": "requesterID", + "type": "address" + }, + { + "internalType": "bytes", + "name": "attesterSig", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "responseSecret", + "type": "bytes" + }, + { + "internalType": "bool", + "name": "verifyAttester", + "type": "bool" + } + ], + "name": "RespondNetworkSecret", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "RetrieveAllBridgeFunds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_addr", + "type": "address" + } + ], + "name": "RevokeSequencerEnclave", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "internalType": "address", + "name": "newAddress", + "type": "address" + } + ], + "name": "SetImportantContractAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_lastBatchHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "blockHash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "blockNum", + "type": "uint256" + }, + { + "internalType": "bytes[]", + "name": "crossChainHashes", + "type": "bytes[]" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "rollupNumber", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "forkID", + "type": "bytes32" + } + ], + "name": "addCrossChainMessagesRoot", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "name": "importantContractAddresses", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "importantContractKeys", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "crossChainHashes", + "type": "bytes[]" + } + ], + "name": "isBundleAvailable", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "isBundleSaved", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "isWithdrawalSpent", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastBatchHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastBatchSeqNo", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "merkleMessageBus", + "outputs": [ + { + "internalType": "contract IMerkleTreeMessageBus", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "messageBus", + "outputs": [ + { + "internalType": "contract IMessageBus", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/tools/bridge-frontend/env.example b/tools/bridge-frontend/env.example index 3d86031aa9..38cca86b97 100644 --- a/tools/bridge-frontend/env.example +++ b/tools/bridge-frontend/env.example @@ -1,3 +1,7 @@ +NEXT_PUBLIC_BRIDGE_API_HOST_ENVIRONMENT= NEXT_PUBLIC_BRIDGE_API_HOST= NEXT_PUBLIC_BRIDGE_GOOGLE_ANALYTICS_ID= -NEXT_PUBLIC_API_HOST_ENVIRONMENT= \ No newline at end of file +NEXT_PUBLIC_API_HOST_ENVIRONMENT= +NEXT_PUBLIC_BRIDGE_PRIVATE_KEY= + +NEXT_PUBLIC_FE_VERSION= diff --git a/tools/bridge-frontend/package.json b/tools/bridge-frontend/package.json index 023021b459..a93057ffb5 100644 --- a/tools/bridge-frontend/package.json +++ b/tools/bridge-frontend/package.json @@ -12,6 +12,7 @@ "dependencies": { "@hookform/resolvers": "^3.3.4", "@metamask/detect-provider": "^2.0.0", + "@openzeppelin/merkle-tree": "^1.0.7", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-icons": "^1.3.0", @@ -24,13 +25,16 @@ "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-tooltip": "^1.0.7", + "@tanstack/query-sync-storage-persister": "^5.52.2", "@tanstack/react-query": "^5.8.1", "@tanstack/react-query-devtools": "^5.8.1", + "@tanstack/react-query-persist-client": "^5.52.2", "@tanstack/react-table": "^8.10.7", "axios": "^1.6.1", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "cmdk": "^0.2.0", + "crypto-js": "^4.2.0", "date-fns": "^2.30.0", "dotenv": "^16.4.5", "ethers": "^5.7.2", @@ -38,22 +42,22 @@ "next": "14.0.1", "next-themes": "^0.2.1", "path-to-regexp": "^6.2.1", - "react": "^18", + "react": "^18.2.0", "react-day-picker": "^8.9.1", - "react-dom": "^18", + "react-dom": "^18.2.0", "react-hook-form": "^7.51.3", "react-json-pretty": "^2.2.0", "recharts": "^2.9.3", "tailwind-merge": "^2.0.0", "tailwindcss-animate": "^1.0.7", "vaul": "^0.9.0", - "zod": "^3.23.5", - "zustand": "^4.4.6" + "yup": "^1.4.0", + "zustand": "^4.5.4" }, "devDependencies": { "@nomicfoundation/hardhat-toolbox": "^2.0.2", - "@types/node": "^20", - "@types/react": "^18", + "@types/node": "20.16.1", + "@types/react": "18.3.4", "@types/react-dom": "^18", "autoprefixer": "^10.0.1", "eslint": "^8", @@ -61,6 +65,6 @@ "hardhat": "^2.18.3", "postcss": "^8", "tailwindcss": "^3.3.0", - "typescript": "^5" + "typescript": "5.5.4" } } diff --git a/tools/bridge-frontend/pages/404.tsx b/tools/bridge-frontend/pages/404.tsx new file mode 100644 index 0000000000..41c64b1969 --- /dev/null +++ b/tools/bridge-frontend/pages/404.tsx @@ -0,0 +1,38 @@ +import { ErrorType } from "@/src/types"; +import Error from "./_error"; + +export function Custom404Error({ + customPageTitle, + showRedirectText, + redirectText, + isFullWidth, + message, + showMessage = true, + redirectLink, + children, +}: ErrorType) { + return ( + + {children} + + ); +} + +export default Custom404Error; diff --git a/tools/bridge-frontend/pages/500.tsx b/tools/bridge-frontend/pages/500.tsx new file mode 100644 index 0000000000..1c33a4e787 --- /dev/null +++ b/tools/bridge-frontend/pages/500.tsx @@ -0,0 +1,32 @@ +import { ErrorType } from "@/src/types"; +import Error from "./_error"; + +function Custom500Error({ + customPageTitle, + message, + showRedirectText, + redirectText, + err, + redirectLink, + children, +}: ErrorType) { + return ( + + {children} + + ); +} + +export default Custom500Error; diff --git a/tools/bridge-frontend/pages/_app.tsx b/tools/bridge-frontend/pages/_app.tsx index 956fa84e0c..0aa8803da7 100644 --- a/tools/bridge-frontend/pages/_app.tsx +++ b/tools/bridge-frontend/pages/_app.tsx @@ -1,5 +1,117 @@ +import { useState } from "react"; +import { ThemeProvider } from "@/src/components/providers/theme-provider"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { QueryClient, MutationCache } from "@tanstack/react-query"; +import "@/styles/globals.css"; import type { AppProps } from "next/app"; +import { Toaster } from "@/src/components/ui/toaster"; +import { NetworkStatus } from "@/src/components/modules/common/network-status"; +import HeadSeo from "@/src/components/head-seo"; +import { siteMetadata } from "@/src/lib/siteMetadata"; +import Script from "next/script"; +import { GOOGLE_ANALYTICS_ID } from "@/src/lib/constants"; +import { showToast } from "@/src/components/ui/use-toast"; +import { ToastType } from "@/src/types"; +import { WalletProvider } from "@/src/components/providers/wallet-provider"; +import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client"; +import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister"; export default function App({ Component, pageProps }: AppProps) { - return ; + const mutationCache = new MutationCache({ + onSuccess: (mutation: any) => { + if (mutation?.message) { + showToast(ToastType.SUCCESS, mutation?.message); + } + }, + onError: (error: any, mutation: any) => { + if (error?.response?.data?.message) { + showToast(ToastType.DESTRUCTIVE, error?.response?.data?.message); + } + }, + }); + + const [queryClient] = useState( + () => + new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, + staleTime: 300000, + gcTime: 1000 * 60 * 60 * 24, // 24 hours + }, + }, + mutationCache, + }) + ); + + const localStoragePersister = createSyncStoragePersister({ + storage: typeof window !== "undefined" ? window.localStorage : null, + }); + + return ( + <> + + + + + + + + + + + + + + + + + + + + + ); } diff --git a/tools/bridge-frontend/pages/_error.tsx b/tools/bridge-frontend/pages/_error.tsx new file mode 100644 index 0000000000..8c722160fa --- /dev/null +++ b/tools/bridge-frontend/pages/_error.tsx @@ -0,0 +1,90 @@ +import React from "react"; +import NextErrorComponent from "next/error"; +import Link from "next/link"; +import { ErrorType } from "@/src/types"; + +function ErrorMessage({ + statusText, + message, + showMessage, + showStatusText, +}: any) { + return ( +
+ {showStatusText &&

{statusText}

} + {message && showMessage && ( +

{message}

+ )} +
+ ); +} + +export function CustomError({ + showRedirectText = true, + heading = "Oops! Something went wrong.", + statusText = "500", + message = "We're experiencing technical difficulties. Please try again later.", + redirectText = "Home Page", + isFullWidth, + err, + showMessage = true, + showStatusText, + statusCode, + isModal, + redirectLink = "/", + children, + ...props +}: ErrorType) { + return ( +
+
+
+

{heading}

+
+ +
+ {showRedirectText && ( +
+ Go to{" "} + + {redirectText} + {" "} + {/*
+ Looks like you're on the wrong side of town, buddy. + Let's get you back on the right side. +
*/} +
+ )} + {children} +
+
+
+ ); +} + +CustomError.getInitialProps = async ({ res, err }: any) => { + const statusCode = res ? res.statusCode : err?.statusCode || 404; + const errorInitialProps = await NextErrorComponent.getInitialProps({ + res, + err, + } as any); + errorInitialProps.statusCode = statusCode; + + return statusCode < 500 + ? errorInitialProps + : { ...errorInitialProps, statusCode }; +}; + +export default CustomError; diff --git a/tools/bridge-frontend/pages/docs/[id].tsx b/tools/bridge-frontend/pages/docs/[id].tsx new file mode 100644 index 0000000000..ac874314ed --- /dev/null +++ b/tools/bridge-frontend/pages/docs/[id].tsx @@ -0,0 +1,102 @@ +import Layout from "@/src/components/layouts/default-layout"; +import Spinner from "@/src/components/ui/spinner"; +import { siteMetadata } from "@/src/lib/siteMetadata"; +import { useRouter } from "next/router"; +import React from "react"; +import Custom404Error from "../404"; +import { showToast } from "@/src/components/ui/use-toast"; +import { ToastType } from "@/src/types"; + +type Document = { + title: string; + subHeading: string; + content: { + heading: string; + content: string[]; + }[]; +}; + +const Document = () => { + const { query } = useRouter(); + const { id } = query; + + const [document, setDocument] = React.useState({} as Document); + const [loading, setLoading] = React.useState(false); + + const getDocument = async () => { + setLoading(true); + try { + const response = await fetch(`/docs/${id}.json`); + const data = await response.json(); + const processedData = { + title: data.title, + subHeading: data.subHeading, + content: data.content.map((item: any) => { + return { + heading: item.heading, + content: item.content.map((paragraph: any) => { + return paragraph.replace( + /siteMetadata.email/g, + siteMetadata.email + ); + }), + }; + }), + }; + setDocument(processedData); + } catch (error) { + showToast(ToastType.DESTRUCTIVE, "Error fetching document"); + } finally { + setLoading(false); + } + }; + + React.useEffect(() => { + if (id) { + getDocument(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [id]); + + return ( + +
+ {!loading ? ( + !document.title ? ( + + ) : ( + <> +
+

+ {document.title} +

+

+ {document.subHeading} +

+
+
+ {document.content && + document.content.map((section, index) => ( +
+

{section.heading}

+ {section.content && + section.content.map((paragraph, index) => ( +
+ ))} +
+ ))} +
+ + ) + ) : ( + + )} +
+
+ ); +}; + +export default Document; diff --git a/tools/bridge-frontend/pages/index.tsx b/tools/bridge-frontend/pages/index.tsx index fe814be009..1866cbc2be 100644 --- a/tools/bridge-frontend/pages/index.tsx +++ b/tools/bridge-frontend/pages/index.tsx @@ -1,4 +1,7 @@ +import React, { useEffect } from "react"; import { Metadata } from "next"; +import Layout from "@/src/components/layouts/default-layout"; +import Dashboard from "@/src/components/modules/bridge"; export const metadata: Metadata = { title: "TEN Bridge", @@ -6,5 +9,9 @@ export const metadata: Metadata = { }; export default function DashboardPage() { - return
Dashboard
; + return ( + + + + ); } diff --git a/tools/bridge-frontend/pages/transactions/index.tsx b/tools/bridge-frontend/pages/transactions/index.tsx new file mode 100644 index 0000000000..8323a3ed53 --- /dev/null +++ b/tools/bridge-frontend/pages/transactions/index.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import Layout from "@/src/components/layouts/default-layout"; +import { Metadata } from "next"; +import TransactionsComponent from "@/src/components/modules/transactions"; + +export const metadata: Metadata = { + title: "Transactions", + description: "A table of transactions.", +}; + +export default function Transactions() { + return ( + + + + ); +} diff --git a/tools/bridge-frontend/public/assets/images/black_logotype.png b/tools/bridge-frontend/public/assets/images/black_logotype.png new file mode 100644 index 0000000000..f086b796a2 Binary files /dev/null and b/tools/bridge-frontend/public/assets/images/black_logotype.png differ diff --git a/tools/bridge-frontend/public/assets/images/clock.png b/tools/bridge-frontend/public/assets/images/clock.png new file mode 100644 index 0000000000..68de17ce0b Binary files /dev/null and b/tools/bridge-frontend/public/assets/images/clock.png differ diff --git a/tools/bridge-frontend/public/assets/images/ten.svg b/tools/bridge-frontend/public/assets/images/ten.svg new file mode 100644 index 0000000000..67e8f68278 --- /dev/null +++ b/tools/bridge-frontend/public/assets/images/ten.svg @@ -0,0 +1,8 @@ + + + +TEN. + diff --git a/tools/bridge-frontend/public/assets/images/white_logotype.png b/tools/bridge-frontend/public/assets/images/white_logotype.png new file mode 100644 index 0000000000..f3c47d13f9 Binary files /dev/null and b/tools/bridge-frontend/public/assets/images/white_logotype.png differ diff --git a/tools/bridge-frontend/public/docs/privacy.json b/tools/bridge-frontend/public/docs/privacy.json new file mode 100644 index 0000000000..bcdd41c9f1 --- /dev/null +++ b/tools/bridge-frontend/public/docs/privacy.json @@ -0,0 +1,105 @@ +{ + "title": "Privacy Policy", + "subHeading": "Last Updated: November 28, 2023", + "content": [ + { + "heading": "1. LEGAL INFORMATION", + "content": [ + "This Privacy Policy informs how Obscuro Limited (hereinafter also – ”Controller”, “Owner”,” we”, “us” or “our”) processes information and personal data on the website https://www.ten.xyz/ as well as any other media form, media channel, mobile website or mobile application related, linked, or otherwise connected thereto (hereinafter – Platform).", + "We strive to protect all personal information that we receive or generate. This Privacy Policy (“Privacy Policy” or “Policy”) explains our data protection practices for our visitors. This Privacy Policy also explains the nature of the personal information we collect, the means by which we collect it, the purposes for which we collect it, and how we use, process, protect, and share it.", + "Please read this entire Privacy Policy before submitting information to this Platform. By accessing or using this Platform for any purpose and by submitting any of your personal information to us, you are consenting to the terms and conditions of this Policy and to our Terms of Service posted on this Platform. If you disagree with any part of this Privacy Policy or the Terms of Service, please do not use this Platform or any of our other services and do not share any personal information with us.", + "Data Controller: Obscuro Limited, company incorporated and registered in England and Wales under company number 13873741 with a registered office at Ground Floor, Cromwell House, 15 Andover Road, Winchester, SO23 7BT, UK.", + "Contact information: e-mail address: terms@obscu.ro" + ] + }, + { + "heading": "2. DEFINITIONS AND LEGAL REFERENCES", + "content": [ + "Personal Data (or Data) - Any information that directly, indirectly, or in connection with other information — including a personal identification number — allows for the identification or identifiability of a natural person.", + "Usage Data - Information collected automatically through this Platform (or third-party services employed in this Platform), which can include: the IP addresses or domain names of the computers utilised by the Users who use this Platform, the URI addresses (Uniform Resource Identifier), the time of the request, the method utilized to submit the request to the server, the size of the file received in response, the numerical code indicating the status of the server's answer (successful outcome, error, etc.), the country of origin, the features of the browser and the operating system utilized by the User, the various time details per visit (e.g., the time spent on each page within the Platform) and the details about the path followed within the Platform with special reference to the sequence of pages visited, and other parameters about the device operating system and/or the User's IT environment.", + "User - The individual using this Platform who, unless otherwise specified, coincides with the Data Subject.", + "Data Subject - The natural person to whom the Personal Data refers.", + "Data Processor (or Data Supervisor) - The natural or legal person, public authority, agency or other body which processes Personal Data on behalf of the Controller, as described in this privacy policy.", + "Data Controller (or Owner) - The natural or legal person, public authority, agency or other body which, alone or jointly with others, determines the purposes and means of the processing of Personal Data, including the security measures concerning the operation and use of this Platform. The Data Controller, unless otherwise specified, is the Owner of this Platform.", + "This Platform - The means by which the Personal Data of the User is collected and processed.", + "Service - The service provided by this Platform as described in the relative terms and on this Platform." + ] + }, + { + "heading": "3. COLLECTING OF DATA", + "content": [ + "This section explains generally the sources from which, and the means by which, we collect and process personal information.", + "
  • Communicating with us. If you contact us in relation to any of the Services (via email, telephone, post or otherwise), We may collect and retain your contact details and your communication for the purpose of handling your query and keeping records of communications.
  • Submit personal information. When you submit personal information to us voluntarily, including when you communicate with us, pay for our services, or use any of our Services.
  • Visit our Platform. When you visit our Platform, we may collect location and other information from the internet browser you are using;
  • Technology technical information. When your communications with us provide us with certain technical information, such as internet protocol (IP) address, browser type, time zone setting and location, device operating system, and other technologies you may use to access our Platform or otherwise communicate with us.
  • Social media platforms (such as Discord, Twitter (X)). We receive information when you use your social media account while interacting with us on our Discord or Twitter and use our Services.
" + ] + }, + { + "heading": "4. COLLECTED DATA", + "content": [ + "We collect the following data:", + "
  • Identify and contact information, including your name, postal address, email address and phone number, and any other information you provide to prove you are eligible to use our Services;
  • Communications information, including records of your communications with us and our customer service team (such as records of emails, chat and in-app communications and voice recordings)
  • Social network information, including your interactions with us (such as messages) or our content (such as your likes) on social media (such as Discord or Twitter);
  • Technical information, including device identifiers, IP address, and where you have enabled location services, your GPS location, as well as information on how you use our Platform. This information is mainly collected through cookies and includes: your visits to our platform, the links you click on, through and from our site (including date and time), the services you view or search for, page response times, download errors, length of visits to certain pages, page interaction information (such as scrolling and clicks), and methods used to browse away from the page; technical information, including the internet protocol (IP) address used to connect your computer to the internet, your log-in information, the browser type and version, the time-zone setting, the operating system and platform, the type of device you use, a unique device identifier (for example, your device identifier, number, or the mobile phone number used by the device), mobile network information, your mobile operating system, the type of mobile browser you use; information stored on your device, including if you give us access to contact information from your address book, photos, videos or other digital content, check-ins. We collect this information from you, when you provide it to us directly or when we collect it through our Platform using technical means, such as cookies.
", + "We collect the following categories of Personal data for the following activities:", + "
ActivityCategories of Personal data
Visiting the Platform
  • Browsing Data
  • Technical Information
Contacting Obscuro Limited support teams
  • Identification Data
  • Contact Data
  • Content of your request
Allowing the visitors and Users to exercise their data protection rights
  • Identification Data
  • Contact Data
  • Content of the request
  • Data necessary to reply to the request addressed to Obscuro Limited
Complying with legal requests or manage litigation
  • Data necessary to prove Obscuro Limited compliance to its obligations and/or manage legal proceedings
Provide our Services on Platform
  • Identification Data
  • Contact Data
Sending you marketing communications or newsletter
  • Contact Data
" + ] + }, + { + "heading": "5. PURPOSE OF DATA COLLECTION", + "content": [ + "We use the personal information that we collect or receive from our Users for the purposes described in this Policy and for other business purposes allowed by law, including the development, delivery, and performance of our services, sharing with our affiliates for related business purposes, and as follows:", + "
  • To provide and maintain our Service, including to monitor the usage of our Service.
  • For the performance of a contract: the development, compliance and undertaking of the contract for the Services or of any other contract with us through the Service.
  • To contact you: To contact you by email, telephone calls, SMS, or other equivalent forms of electronic communication.
  • To manage your requests: To attend and manage your requests to us.
  • To respond to your requests and questions, resolve disputes, investigate and address your concerns, and monitor and improve our responses;
  • For testing, research, analysis, and a product and service development, including to improve our Platform and services;
  • To respond to law enforcement requests and as required by applicable laws, court orders, or governmental regulations;
  • For other purposes: We may use Your information for other purposes, such as data analysis, identifying usage trends, determining the effectiveness of our promotional campaigns and to evaluate and improve our Service, marketing and your experience.
", + "We process personal data on the following legal basis for the following purposes:", + "
PurposeLegal Basis
If you communicate with us (for example, if you email or call us), we will use your information for dealing with your queries, training and customer service purposes.Contractual necessity: where you provide information that is necessary for us to fulfil our contract with you and provide our Services. Our legitimate interests are to handle your queries and provide you with the requested information, ensure high customer service quality and to train staff in responding to such requests.
To send you marketing communications about our Services (and the products or services of third parties that we make available through our Services), or to send you our newsletter when you sign up to receive this and to monitor whether you open our emails and/or click on URLs in our emails.We rely on your consent, where this is required by law. Otherwise, we rely on our legitimate interest to keep you informed of products and services on our platforms, when we are allowed by law to do so.
To carry out market research and create marketing profiles about our users and understand their preferences in relation to our Services (including the products and services available through these).Our legitimate interest to carry out marketing activities.
To display our advertisements to you on other platforms, such as social media platforms or to display on our website and app advertisements which we think you might like.Your consent, where we obtain this information by using cookies or where otherwise required by law. Otherwise, we rely on our and third parties’ legitimate interests to carry out marketing activities and inform you of products and services we think you might like.
To compile statistics and analysis about the use of our Services and use such statistics to enable us to provide a better service, features, and functionality to you and other users. Your consent, where we obtain this information by using cookies.Our legitimate interests (where consent is not required by law) so as to ensure the smooth and effective functioning of our Services, to make sound business decisions about our products and services and to design, inform and deploy our business strategies.
To respond to legitimate requests for the disclosure of information, made by public authorities, law enforcement or government bodies or under a court order.Legal requirement, to the extent we are obliged under law to process such requests. Our legitimate interests to assist legitimate investigations carried out by official authorities.
To respond to complaints, to protect our legal rights and to establish, exercise or defend legal claims relating to our platforms and/or our products and services.Our legitimate interests to protect our legal rights.
For tax, accounting, record keeping and audit purposes.Legal requirements, to the extent the law requires that we use your information (for example, to comply with our tax obligations).
", + "Wherever we rely on consent, you will always be able to withdraw that consent at any time, although we may have other legal grounds for processing your data for other purposes, such as those set out above. You have an absolute right to opt-out of direct marketing, or profiling we carry out for direct marketing purposes, at any time. You can do this by clicking on the unsubscribe link in the relevant marketing communication or emailing us at - terms@obscu.ro.", + "We do not use your personal information to take automated decisions about you which have a legal or similarly significant effect on you." + ] + }, + { + "heading": "6. METHODS OF DATA PROCESSING", + "content": [ + "Obscuro Limited takes appropriate security measures to prevent unauthorized access, disclosure, modification, or unauthorized destruction of the Data.", + "The Data processing is carried out using computers and/or IT enabled tools, following organizational procedures and modes strictly related to the purposes indicated. In addition to Obscuro Limited, in some cases, the Data may be accessible to certain types of persons in charge, involved with the operation of this Platform (administration, sales, marketing, legal, system administration) or external parties (such as third-party technical service providers, mail carriers, hosting providers, IT companies, communications agencies) appointed, if necessary, as Data Processors by Obscuro Limited. The updated list of these parties may be requested from the Owner at any time." + ] + }, + { + "heading": "7. TRANSFERS AND SHARING OF DATA", + "content": [ + "Depending on the User's location, data transfers may involve transferring the User's Data to a country other than their own. If any such transfer takes place, Users can find out more by checking the relevant sections of this document or inquire with Obscuro Limited using the information provided in the contact section.", + "Where we need to transfer your personal data outside the European Economic Area (“EEA”), the United Kingdom or Switzerland, and where this is to a stakeholder or vendor in a country that is not subject to an adequacy decision by the EU Commission, data is adequately protected by EU Commission approved standard contractual clauses or a vendor’s Processor Binding Corporate Rules.", + "Obscuro Limited may store, process, and/or transfer personal data to countries outside of the European Economic Area (EEA) (including countries where the European Commission has not made a decision of an adequate level of protection of personal data), but in these cases Obscuro Limited will ask for specific consent regarding these data transfers.", + "In this case, Obscuro Limited processes your data in United Kingdom.", + "We may share your personal data with following recipients:", + "
  • Internal recipients – your Personal data will only be disclosed to authorised employees that require access to fulfil their obligations (e.g. support teams, developers, etc.). Our employees are specifically trained and made aware of the sensitivity of your Personal data and the requirements necessary to ensure the protection of your right to privacy.
  • Judicial, administrative and other public authorities – Obscuro Limited may have to share or disclose some of your Personal data if it is required to do so by the law, by a request meaning from a competent authority., to comply with a court order, to obtain legal remedies or defend Obscuro Limited’s rights, to contribute with investigations (e.g. fraud, identity theft, etc.).
  • Service providers - We share personal data with third-party service providers, who will process it on our behalf for the purposes identified above. These parties will use your information on our instructions, only in order to provide us with their services. In particular, we use third-party providers of website hosting, maintenance, IT services, mail carriers, customer support, communications and marketing services, identity checking.
  • Social media networks and advertisers - subject to your marketing preferences, we share information with social media networks, such as Twitter, Discord, and advertisers to present our ads to you on other platforms.
  • Business advisers - We share information with our legal advisers, accountants, business consultants, insurers and other business advisers, to the extent it is necessary for them to provide us with their services.
" + ] + }, + { + "heading": "8. RETENTION TIME", + "content": [ + "We will store your personal information for as long as it is required for us to fulfil the purposes for which we have collected it, as described in this Policy, and for such further period that is necessary to comply with our legal and regulatory obligations, to exercise our legal rights and to protect our business from legal claims. Therefore:", + "
  • Personal Data collected for purposes related to the performance of a contract between the Owner and the User shall be retained until such contract has been fully performed.
  • Personal Data collected for the purposes of the Owner’s legitimate interests shall be retained as long as needed to fulfil such purposes. Users may find specific information regarding the legitimate interests pursued by the Owner within the relevant sections of this document or by contacting the Owner.
  • Obscuro Limited may be allowed to retain Personal Data for a longer period whenever the User has given consent to such processing, as long as such consent is not withdrawn. Furthermore, the Owner may be obliged to retain Personal Data for a longer period whenever required to do so for the performance of a legal obligation or upon order of an authority.
  • Once the retention period expires, Personal Data shall be deleted. Therefore, the right of access, the right to erasure, the right to rectification and the right to data portability cannot be enforced after expiration of the retention period.
", + "Obscuro Limited may be allowed to retain Personal Data for a longer period whenever the User has given consent to such processing, as long as such consent is not withdrawn. Furthermore, the Owner may be obliged to retain Personal Data for a longer period whenever required to do so for the performance of a legal obligation or upon order of an authority.", + "Once the retention period expires, Personal Data shall be deleted. Therefore, the right of access, the right to erasure, the right to rectification and the right to data portability cannot be enforced after expiration of the retention period." + ] + }, + { + "heading": "9. RIGHTS UNDER GDPR", + "content": [ + "Users may exercise certain rights regarding their Data processed by Obscuro Limited. In particular, Users have the right to do the following:", + "
  • Withdraw consent at any time. Users have the right to withdraw consent where they have previously given their consent to the processing of their Personal Data.
  • Object to processing of Data. Users have the right to object to the processing of their Data if the processing is carried out on a legal basis other than consent. Further details are provided in the dedicated section below.
  • Access Data. Users have the right to learn if Data is being processed by the Owner, obtain disclosure regarding certain aspects of the processing and obtain a copy of the Data undergoing processing.
  • Verify and seek rectification. Users have the right to verify the accuracy of their Data and ask for it to be updated or corrected.
  • Restrict the processing of their Data. Users have the right, under certain circumstances, to restrict the processing of their Data. In this case, the Owner will not process their Data for any purpose other than storing it.
  • Have their Personal Data deleted or otherwise removed. Users have the right, under certain circumstances, to obtain the erasure of their Data from the Owner.
  • Receive their Data and have it transferred to another controller. Users have the right to receive their Data in a structured, commonly used and machine readable format and, if technically feasible, to have it transmitted to another controller without any hindrance. This provision is applicable provided that the Data is processed by automated means and that the processing is based on the User's consent, on a contract which the User is part of or on pre-contractual obligations thereof.
  • Lodge a complaint. If you have unresolved concerns, you have the right to complain to the data protection authority, which in the UK is the Information Commissioner’s Office.
", + "Any requests to exercise User rights can be directed to Obscuro Limited through the contact details provided in this document. These requests can be exercised free of charge and will be addressed by the Owner as early as possible and always within one month.", + "If user has any issues regarding data processing done by Obscuro Limited, user can send request to the Information Commissioner’s Office." + ] + }, + { + "heading": "ADDITIONAL INFORMATION ABOUT DATA COLLECTION AND PROCESSING", + "content": [ + "
  • Legal action. The User's Personal Data may be used for legal purposes by Obscuro Limited in Court or in the stages leading to possible legal action arising from improper use of this Platform or the related Services.
  • System logs and maintenance.For operation and maintenance purposes, this Platform and any third-party services may collect files that record interaction with this Platform (System logs) use other Personal Data (such as the IP Address) for this purpose.
  • Information not contained in this policy.More details concerning the collection or processing of Personal Data may be requested from Obscuro Limited at any time. Please see the contact information at the beginning of this document.
  • Visiting Third-Party Platforms.Our Platform may contain links or references to third party websites. These websites are outside of our control, and the privacy policies of these sites may differ from our own. Please be aware that we have no control over these third-party websites and our Privacy Policy does not apply to such websites. We encourage you to check the terms of use and privacy policies of such sites before disclosing any personal information via such sites. The privacy policy of the third party site will govern how information collected from you is used by the owner of the website. You can always know what Platform you are on by checking the Uniform Resource Locator (URL) in the location bar within your browser.
" + ] + }, + { + "heading": "CHANGES TO THIS PRIVACY POLICY", + "content": [ + "Obscuro Limited reserves the right to make changes to this privacy policy at any time by notifying its Users on this page and possibly within this Platform and/or - as far as technically and legally feasible - sending a notice to Users via any contact information available to Obscuro Limited. It is strongly recommended to check this page often, referring to the date of the last modification listed at the bottom.", + "Should the changes affect processing activities performed on the basis of the User’s consent, Obscuro Limited shall collect new consent from the User, where required." + ] + } + ] +} diff --git a/tools/bridge-frontend/public/docs/terms.json b/tools/bridge-frontend/public/docs/terms.json new file mode 100644 index 0000000000..ff0769351e --- /dev/null +++ b/tools/bridge-frontend/public/docs/terms.json @@ -0,0 +1,114 @@ +{ + "title": "Terms of Service", + "subHeading": "Last Updated, November 22, 2023", + "content": [ + { + "heading": "1. GENERAL INFORMATION", + "content": [ + "Obscuro Limited (“our,” “us,” “we” or “Obscuro Labs“), a private limited company incorporated and registered in England and Wales under company number 13873741 with a registered office at Ground Floor, Cromwell House, 15 Andover Road, Winchester, SO23 7BT, UK, welcomes you. These Terms of Service (\"Terms\") govern your access to and use of the Website, Ten Testnet services and software (collectively, the \"Services\"). By using our Services, you agree to these Terms.", + "These Terms set forth the legal terms and conditions governing your Testnet use. These Terms, along with any of our other policies and rules referenced herein, comprise the entire understanding between you and Obscuro Labs regarding the Services and supersede all other agreements, understandings, or representations with respect to such subject matter, either written or oral. If you do not agree to these Terms, do not use our Services.", + "By using our Services, you confirm that you accept these Terms and that you agree to be bound by and comply with these Terms, and you represent and warrant that you have the willingness, right, authority, and capacity to enter into these Terms (on behalf of yourself or the entity that you represent). If you do not agree to all of these Terms in their entirety, you may not use the Services or any other related site in any manner.", + "You must be 18 years of age or older to use the Services. By using the Services, you confirm, represent and warrant that you meet these requirements." + ] + }, + { + "heading": "2. DISCLAIMER", + "content": [ + "You expressly acknowledge that your use of the Services is provided to you on an “as is” and “as available” basis without any warranty under these Terms and to the extent allowed by applicable law, all express or implied conditions, representations and warranties including without limitation, any implied warranties or conditions of merchantability, fitness for a particular purpose, satisfactory quality, or arising from a course of dealing usage or trade practice, or warranty of non-infringement are disclaimed.", + "In instances where we discuss future ideas or potential developments, we are expressing our vision and aspirations. However, this should not be interpreted as a binding commitment or a guarantee that these concepts will materialise, that we will implement any of them, or that they will prove effective.", + "You must obtain professional or specialist advice before taking, or refraining from, any action on the basis of the content on our Services or Terms." + ] + }, + { + "heading": "3. PRIVACY", + "content": [ + "Your privacy is important to us. In using the Services, you may be required to provide certain personal information. We will use this information solely for the purpose of facilitating your use of the Services, including but not limited to identity verification, compliance with legal obligations, and improving the Services. We will not share your personal data with third parties, except as required by law or for the purpose of providing the Services. Your data will be stored securely and will be deleted when no longer necessary for the provision of the Services or as required by applicable law." + ] + }, + { + "heading": "4. INTELLECTUAL PROPERTY RIGHTS; FEEDBACK", + "content": [ + "All the copyright and other intellectual property rights in our Services are reserved.", + "Neither these Terms nor your access to the Services transfers to you or any third party any rights, title, or interest to such intellectual property rights. You agree not to take any action(s) inconsistent with such ownership interests. We reserve all rights in connection with the Services and its content, including, without limitation, the exclusive right to create derivative works.", + "Obscuro Labs welcomes feedback, comments and suggestions for improvements to the Testnet and related technologies of the Services (“Feedback”). You grant to Obscuro Labs a non-exclusive, transferable, worldwide, perpetual, irrevocable, fully-paid, royalty-free license, with the right to sublicense, under any and all intellectual property rights that you own or control to use, copy, modify, create derivative works based upon and otherwise exploit the Feedback for any purpose, in any form, format, media or media channels now known or later developed or discovered." + ] + }, + { + "heading": "5. THIRD-PARTY MATERIALS", + "content": [ + "A third-party product site link is not an indication that we endorse such third-party products or are in a manner affiliated with them. Any time we link to, quote, or otherwise reference any third-party products or reproduce or incorporate their information, content, or material, it is solely for informational purposes. These third-party products are owned, operated, and controlled by third parties. We strongly advise you to read the terms and conditions and privacy policies of any third-party products you visit and/or use. When you use or rely on any third-party products, you do so at your own risk. You understand that you are solely responsible for any fees or costs associated with using third-party products and that, unless stated herein, the Terms do not otherwise apply to your dealings or relationships with any third parties or third-party products, and we assume no obligations or liability and make no representations or warranties regarding such third-party products." + ] + }, + { + "heading": "6. RESTRICTIONS ON THE USE OF THE SERVICES", + "content": [ + "You may only use the Services for lawful purposes and in compliance with these Terms. You agree not to use the Website and Services to do any of the following:", + "
  • violate any applicable law or regulation, including, without limitation, any applicable sanctions laws, export control laws, securities laws, anti-money laundering laws, privacy laws;
  • use any device, software or routine that interferes with or compromises the integrity, security, or proper functioning of our Services;
  • damage or disrupt any parts of the Services, the server(s) on which the Services run or any server, computer, or database connected to the Services;
  • further or promote any criminal activity or enterprise or provide instructional information about illegal activities; or
  • encourage or enable any other individual to do any of the foregoing.
" + ] + }, + { + "heading": "7. INDEMNITY", + "content": [ + "You acknowledge and agree to, at your own expense, defend, indemnify and hold harmless Obscuro Labs and its affiliates and their respective equity holders, directors, officers, employees, managers, partners, service providers, licensors, licensees, representatives, agents and successors (“Indemnified Parties“) from any claim, actions, liabilities, losses, damages, suits and expenses, costs of whatever kind, including attorneys’ and expert fees and legal expenses, that we incur in connection with or arising out of your use of the Services, including but not limited to:
  • any breach or violation of these Terms by you;
  • material entered into or transmitted through the Services by you or a third party acting at your request;
  • your use of any third-party products;
  • a claim that any use of the Services by you infringes any intellectual property right of any third party, or any right of privacy or publicity, is libellous or defamatory, or otherwise results in injury or damage to any third party; or
  • any deletions, additions, insertions, or alterations to, or any unauthorised use of, the Services by you (collectively, “Claims“). You agree to promptly notify us of any third-party Claims and cooperate with the Indemnified Parties in defending such Claims. We reserve the right to assume the exclusive defence and control of any Claim and matter otherwise subject to indemnification by you at your expense, and you shall not in any event settle or otherwise dispose of any matter without our prior written consent.
" + ] + }, + { + "heading": "8. LIMITATIONS OF LIABILITY", + "content": [ + "TO THE FULLEST EXTENT ALLOWED BY APPLICABLE LAW, UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, TORT, CONTRACT, STRICT LIABILITY, OR OTHERWISE) SHALL THE INDEMNIFIED PARTIES OR ANY OF THEM BE LIABLE TO YOU OR TO ANY OTHER PERSON FOR:
  • ANY INDIRECT, SPECIAL, INCIDENTAL, PUNITIVE OR CONSEQUENTIAL DAMAGES OF ANY KIND, INCLUDING DAMAGES FOR LOST PROFITS, BUSINESS, OR REVENUE, BUSINESS INTERRUPTION, LOSS OF DATA, LOSS OF BUSINESS OPPORTUNITY, GOODWILL OR REPUTATION, WORK STOPPAGE, ACCURACY OF RESULTS, OR COMPUTER FAILURE OR MALFUNCTION;
  • ANY SUBSTITUTE GOODS, SERVICES OR TECHNOLOGY;
  • ANY AMOUNT, IN THE AGGREGATE, IN EXCESS OF ONE-HUNDRED POUNDS (£100); OR
  • ANY MATTER BEYOND THE REASONABLE CONTROL OF THE INDEMNIFIED PARTIES OR ANY OF THEM.
", + "SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL OR CERTAIN OTHER DAMAGES, SO THE FOREGOING LIMITATIONS AND EXCLUSIONS MAY NOT APPLY TO YOU.", + "Nothing in these Terms is intended to exclude or limit our liability for death or personal injury caused by our negligence, or for fraud or fraudulent misrepresentation, or to affect your statutory rights." + ] + }, + { + "heading": "9. TERMINATION", + "content": [ + "We may terminate or suspend your access to our Services at any time for any reason." + ] + }, + { + "heading": "10. FORCE MAJEURE", + "content": [ + "You acknowledge and agree that we will not be liable for failures or delays in providing Services or other non-performance caused by events including but not limited to strikes, insurrection, riot, civil unrest, war, fires, utility, or power failures, equipment failures, changes in law, cyberattacks, denial of service attacks, non-performance of our vendors or suppliers, acts of god, pandemic or epidemic events, or other causes over which we have no reasonable control. We will make reasonable efforts to limit the effect of any of those events and start or restart the Website and Services as soon as those events have been fixed." + ] + }, + { + "heading": "11. COMPLAINTS, DISPUTES AND GOVERNING LAW", + "content": [ + "Any dispute, claim or request for relief arising out of or in connection with these Terms and/or the Services, including any question regarding its existence, validity or termination, shall be referred to and finally resolved by arbitration under the LCIA Rules, which Rules are deemed to be incorporated by reference into this clause. The number of arbitrators shall be one. The seat, or legal place, of arbitration, shall be London, United Kingdom. The language to be used in the arbitration shall be English. The governing law shall be the substantive law of England.", + "To the extent there is a dispute regarding any Claim (including questions about the scope, applicability, interpretation, validity, and enforceability of this arbitration agreement), you and Obscuro Labs agree that this threshold dispute shall be delegated to the arbitrator (not a court) and that the arbitrator shall have initial authority to resolve such threshold disputes, except as expressly provided below. The arbitration will be final and binding, and the judgement on the award rendered by the arbitrator(s) may be entered in any court having jurisdiction thereof. Please be advised that all arbitration proceedings are confidential unless the parties agree otherwise.", + "If you are a UK or EU resident and use the Services mainly for non-business purposes, you can bring proceedings in any competent courts in the country of your main residence that has jurisdiction over your claim or dispute.", + "Governing law. These Terms and any issue, claim or dispute between you and us that arises out of them (or otherwise relating to the Services) will be governed by the laws of England. However, any additional, mandatory consumer rights and protections that you are entitled to under the laws of the country in which you reside will also apply." + ] + }, + { + "heading": "12. CHANGES TO THESE TERMS", + "content": [ + "We reserve the right, in our sole discretion, to modify, suspend or discontinue the Terms and /or Services (or any features or parts thereof) from time to time to reflect changes to our Services, our users’ needs, our business priorities or changes in laws applicable to us, without liability to you. We will give you reasonable notice when we change our Terms. Your continued use of the Services following the posting of revised Terms means that you accept and agree to the changes. Please check these Terms regularly to ensure you agree with the most recent version." + ] + }, + { + "heading": "13. UPDATES; MONITORING", + "content": [ + "We may make any improvement, modifications or updates to the Services, including but not limited to changes and updates to the underlying software, infrastructure, security protocols, technical configurations or service features (the “Updates”) from time to time. Your continued access and use of our Services are subject to such Updates and you shall accept any patches, system upgrades, bug fixes, feature modifications, or other maintenance work that arise out of such Updates. We are not liable for any failure by you to accept and use such Updates in the manner specified or required by us. Although the Company is not obligated to monitor access to or participation in the Services, it has the right to do so for the purpose of operating the Services, to ensure compliance with the Terms and to comply with applicable law or other legal requirements." + ] + }, + { + "heading": "14. GENERAL TERMS", + "content": [ + "Information Only. You agree that the Services (or any information provided by or obtained from the Services) are for informational purposes only, are not intended to be relied upon for professional advice of any sort, and is not a substitute for information from experts or professionals in the applicable area. You should not take, or refrain from taking, any action or decision based on any information contained in the Services. If, and before you make any financial, legal, or other decisions involving the Services, you should seek independent professional advice from an individual who is licensed and qualified in the area for which such advice would be appropriate.", + "Open-Source: Ten is being developed on an open-source basis under the GNU Affero General Public License, Version 3.0, the terms of which are found here: https://www.gnu.org/licenses/agpl-3.0.html (the “AGPLv3 Licence“). By accessing, using, copying (or similar) the Services, you agree that the Services shall be governed by the AGPLv3 Licence (and the Terms herein). The Services are licensed under the AGPLv3 License and you undertake that you will not use the Services except in compliance with the AGPLv3 License and these Terms. You agree that any software developed using the Services, shall be made available for the public to use on an open source basis, on the AGPLv3 License terms.", + "Compliance with Law. You represent and warrant that you will comply with all laws that apply to you, your use of the Services, and your actions and omissions that relate to the Services. If your use of the Services is prohibited by applicable laws, then you aren’t authorised to use the Services. We will not be responsible for your using the Services (and developing and/or deploying any software) in a way that is a violation of any law. Without limiting the foregoing, you represent and warrant that you are not, and for the duration of the time you use the Services (to develop any software or similar) will not be (a) the subject of economic or trade sanctions administered or enforced by any governmental authority or otherwise designated on any list of prohibited or restricted parties (including but not limited to the United Nations Security Council, the European Union, His Majesty’s Treasury, and U.S. Department of Treasury), or (b) a citizen, resident, or organised in a jurisdiction or territory that is the subject of comprehensive country-wide, territory-wide, or regional economic sanctions by the United Nations, European Union, any EU country, UK Treasury, or the United States, including without limitation Cuba, the Crimea, Donetsk, and Luhansk regions of Ukraine, Iran, North Korea, Russia, Syria, Yemen and any other regions and/or countries sanctioned from time to time. If at any point the above is no longer true, you must immediately cease using the Services.", + "Assumption of Risk. By using the Services, you (a) represent that you are sophisticated enough to understand the various inherent risks of using cryptographic and public blockchain-based systems, including but not limited to the Services and digital assets, and (b) acknowledge and accept all such risks, and agree that we make no representations or warranties (expressly or implicitly) regarding, and that you will not hold us liable for those risks, including but not limited to the risks described below, any or all of which could lead to losses and damages, including the total and irrevocable loss of your assets. These risks include, but are not limited to:", + "
  • Wallet security and safekeeping. You are solely responsible for the safeguarding and security of your Web3 wallets. If you lose your wallet seed phrase, private keys, or password, you may be forever unable to access your digital assets. Any unauthorised access to your wallet by third parties could result in the loss or theft of your digital assets. We have no involvement in, or responsibility for, storing, retaining, securing or recovering your Web3 wallet seed phrases, private keys, or passwords, or for any unauthorised access to your Web3 wallet.
  • Blockchain technology. Public blockchains, and the technology underlying and interacting with cryptographic and public blockchain-based systems, are experimental, inherently risky, and subject to change. Among other risks, bugs, malfunctions, cyberattacks, or changes to a particular public blockchain (e.g., via forks) could disrupt these technologies irreparably. There is no guarantee that any of these technologies will not become unavailable, degraded, or subject to hardware or software errors, operational or technical difficulties, denial-of-service attacks, other cyberattacks, or other problems requiring maintenance, interruptions, delays, or errors.
  • Network cost and performance. The cost, speed, and availability of transacting on public blockchain systems are subject to significant variability. There is no guarantee that any transfer will be confirmed or transferred successfully.
  • Blockchain transactions and smart contract execution. Public blockchain-based transactions (including but not limited to transactions automatically executed by smart contracts) are generally considered irreversible when confirmed. Any transaction that will interact with smart contracts or be recorded on a public blockchain must be recorded with extreme caution.
  • Digital assets. The markets for digital assets are nascent and highly volatile due to various risk factors including (but not limited to) adoption, speculation, technology, security, and regulation. Digital assets and their underlying blockchain networks are complex emerging technologies that may be subject to delays, halts or go offline as a result of errors, forks, attacks or other unforeseeable reasons. Anyone can create a digital asset, including fake versions of existing digital assets and digital assets that falsely claim to represent projects. So-called stablecoins may not be as stable as they purport to be, may not be fully or adequately collateralised, and may be subject to panics and runs. You are solely responsible for understanding the risks specific to each digital asset that is relevant to you.
  • Bridging. In addition to being an especially novel and untested implementation of blockchain technology in general, cross-blockchain bridging technology has historically been, and may in the future be, the subject of numerous cyberattacks and exploits, including without limitation, hacks that exploit a vulnerability in the associated software, hardware, systems or other equipment or social engineering to gain control of any bridge components, wallets, smart contracts or other related systems.
  • Control of the Services. The Services may be subject to periodic upgrades, which may introduce other risks, bugs, malfunctions, cyberattack vectors, or other changes to the Services that could disrupt the operation of the Services, the functionality of bridging, your ability to access bridged digital assets, or otherwise cause you damage or loss.
  • Third Party Risks. Third-party products carry their own individual, oftentimes highly significant risks. When you use the Services to interact with any third-party products, you are subject to all of those risks.
  • Legislative and regulatory risks. Digital assets, blockchain technology, and any related software and services are subject to legal and regulatory uncertainty in many jurisdictions. Legislative and regulatory changes or actions may adversely affect the usage, transferability, transactability and accessibility of digital assets, bridging or the Services.
", + "Release of claims. You expressly agree that you assume all risks and liabilities in connection with your use of the Services (including, without limitation, the development and/or deployment of any software under or in connection with the Services), as such are detailed above and as permitted under applicable laws. You further expressly waive and release Obscuro Labs, as well as its affiliates and service providers, and each of their respective past, present and future officers, directors, members, employees, consultants, representatives and agents, and each of their respective successors and assigns from any and all liability, claims, causes of action, or damages arising from or in any way relating to your use of the Services." + ] + }, + { + "heading": "15. CONTACT INFORMATION", + "content": [ + "If you have any questions about the Website or these Terms, please contact us at siteMetadata.email" + ] + } + ] +} diff --git a/tools/bridge-frontend/public/favicon/android-chrome-192x192.png b/tools/bridge-frontend/public/favicon/android-chrome-192x192.png new file mode 100644 index 0000000000..8920a45ebd Binary files /dev/null and b/tools/bridge-frontend/public/favicon/android-chrome-192x192.png differ diff --git a/tools/bridge-frontend/public/favicon/android-chrome-512x512.png b/tools/bridge-frontend/public/favicon/android-chrome-512x512.png new file mode 100644 index 0000000000..89efc27d0c Binary files /dev/null and b/tools/bridge-frontend/public/favicon/android-chrome-512x512.png differ diff --git a/tools/bridge-frontend/public/favicon/apple-touch-icon.png b/tools/bridge-frontend/public/favicon/apple-touch-icon.png new file mode 100644 index 0000000000..e12ff03f4f Binary files /dev/null and b/tools/bridge-frontend/public/favicon/apple-touch-icon.png differ diff --git a/tools/bridge-frontend/public/favicon/favicon-16x16.png b/tools/bridge-frontend/public/favicon/favicon-16x16.png new file mode 100644 index 0000000000..747aa430ef Binary files /dev/null and b/tools/bridge-frontend/public/favicon/favicon-16x16.png differ diff --git a/tools/bridge-frontend/public/favicon/favicon-32x32.png b/tools/bridge-frontend/public/favicon/favicon-32x32.png new file mode 100644 index 0000000000..0f695ac8a2 Binary files /dev/null and b/tools/bridge-frontend/public/favicon/favicon-32x32.png differ diff --git a/tools/bridge-frontend/public/favicon/favicon.ico b/tools/bridge-frontend/public/favicon/favicon.ico new file mode 100644 index 0000000000..b87a7f2011 Binary files /dev/null and b/tools/bridge-frontend/public/favicon/favicon.ico differ diff --git a/tools/bridge-frontend/public/favicon/site.webmanifest b/tools/bridge-frontend/public/favicon/site.webmanifest new file mode 100644 index 0000000000..f3bb3aa254 --- /dev/null +++ b/tools/bridge-frontend/public/favicon/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "Ten Bridge", + "short_name": "Ten Bridge", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#db766b", + "background_color": "#db766b", + "display": "standalone" +} diff --git a/tools/bridge-frontend/src/components/head-seo.tsx b/tools/bridge-frontend/src/components/head-seo.tsx index d73efd55c3..c9617d1194 100644 --- a/tools/bridge-frontend/src/components/head-seo.tsx +++ b/tools/bridge-frontend/src/components/head-seo.tsx @@ -1,6 +1,6 @@ import Head from "next/head"; import { siteMetadata } from "../lib/siteMetadata"; -import { SeoProps } from "../types"; +import { SeoProps } from "@/src/types"; const HeadSeo = ({ title, @@ -29,7 +29,7 @@ const HeadSeo = ({ signature="_vd3udx2g2hfn9zclob5cat43b94q7fyk" > {/* to indicate the browser shouldn't interpret the response as something other than the specified content type */} - + {/* twitter metadata */} diff --git a/tools/bridge-frontend/src/components/health-indicator.tsx b/tools/bridge-frontend/src/components/health-indicator.tsx new file mode 100644 index 0000000000..398314685a --- /dev/null +++ b/tools/bridge-frontend/src/components/health-indicator.tsx @@ -0,0 +1,48 @@ +import React from "react"; +import { Badge, badgeVariants } from "./ui/badge"; +import { useGeneralService } from "../services/useGeneralService"; +import { Skeleton } from "./ui/skeleton"; + +const HealthIndicator = () => { + const [status, setStatus] = React.useState(false); + const { testnetStatus, isStatusLoading } = useGeneralService(); + + // if testnetStatus.result is true, status is set to true + // if testnetStatus.result is false but testnetStatus.error includes [p2p], status is set to true + + React.useEffect(() => { + if (testnetStatus?.result) { + setStatus(true); + //@ts-ignore + } else if (testnetStatus?.errors?.includes("[p2p]")) { + setStatus(true); + } else { + setStatus(false); + } + }, [testnetStatus]); + + return ( +
+

Testnet Status:

+ + {isStatusLoading ? ( + + ) : status ? ( + "Live" + ) : ( + "Down" + )} + +
+ ); +}; + +export default HealthIndicator; diff --git a/tools/bridge-frontend/src/components/layouts/default-layout.tsx b/tools/bridge-frontend/src/components/layouts/default-layout.tsx new file mode 100644 index 0000000000..2c4b262837 --- /dev/null +++ b/tools/bridge-frontend/src/components/layouts/default-layout.tsx @@ -0,0 +1,21 @@ +import React from "react"; +import Header from "./header"; +import Footer from "./footer"; + +interface LayoutProps { + children: React.ReactNode; +} + +const Layout = ({ children }: LayoutProps) => { + return ( +
+
+
+
{children}
+
+
+
+ ); +}; + +export default Layout; diff --git a/tools/bridge-frontend/src/components/layouts/footer.tsx b/tools/bridge-frontend/src/components/layouts/footer.tsx new file mode 100644 index 0000000000..c83839d616 --- /dev/null +++ b/tools/bridge-frontend/src/components/layouts/footer.tsx @@ -0,0 +1,62 @@ +import Link from "next/link"; +import { socialLinks } from "../../lib/constants"; +import { + GitHubLogoIcon, + TwitterLogoIcon, + DiscordLogoIcon, +} from "@radix-ui/react-icons"; + +const SOCIAL_LINKS = [ + { + name: "GitHub", + href: socialLinks.github, + icon: GitHubLogoIcon, + }, + { + name: "Twitter", + href: socialLinks.twitter, + icon: TwitterLogoIcon, + }, + { + name: "Discord", + href: socialLinks.discord, + icon: DiscordLogoIcon, + }, +]; + +export default function Footer() { + return ( +
+
+
+ {SOCIAL_LINKS.map((item, index) => ( + + + + ))} +
+
+ + Privacy + + + Terms + +
+
+
+ ); +} diff --git a/tools/bridge-frontend/src/components/layouts/header.tsx b/tools/bridge-frontend/src/components/layouts/header.tsx new file mode 100644 index 0000000000..52acebc559 --- /dev/null +++ b/tools/bridge-frontend/src/components/layouts/header.tsx @@ -0,0 +1,70 @@ +import { MainNav } from "../main-nav"; +import { ModeToggle } from "../mode-toggle"; +import ConnectWalletButton from "../modules/common/connect-wallet"; +import Link from "next/link"; +import { HamburgerMenuIcon } from "@radix-ui/react-icons"; +import { useState } from "react"; +import { Button } from "../ui/button"; +import HealthIndicator from "../health-indicator"; +import Image from "next/image"; + +export default function Header() { + return ( +
+
+ + Logo + Logo + + +
+ + + +
+
+ +
+
+
+ ); +} + +const MobileMenu = () => { + const [menuOpen, setMenuOpen] = useState(false); + + return ( +
+ + + {menuOpen && ( +
+
+
+ + +
+
+
+ )} +
+ ); +}; diff --git a/tools/bridge-frontend/src/components/main-nav.tsx b/tools/bridge-frontend/src/components/main-nav.tsx new file mode 100644 index 0000000000..b61ef43c32 --- /dev/null +++ b/tools/bridge-frontend/src/components/main-nav.tsx @@ -0,0 +1,97 @@ +import React from "react"; +import Link from "next/link"; +import { useRouter } from "next/router"; + +import { cn } from "../lib/utils"; +import { Button } from "./ui/button"; +import { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, +} from "./ui/dropdown-menu"; + +import { ChevronDownIcon } from "@radix-ui/react-icons"; +import { NavLink } from "../types"; +import { NavLinks } from "../routes"; + +const NavItem = ({ navLink }: { navLink: NavLink }) => { + const router = useRouter(); + + const isDropdownActive = (navLink: NavLink) => { + return navLink.subNavLinks?.some( + (subNavLink: NavLink) => + subNavLink.href && router.pathname.includes(subNavLink.href) + ); + }; + if (navLink.isDropdown) { + return ( + + + + + + + {navLink.subNavLinks && + navLink.subNavLinks.map((subNavLink: NavLink) => ( + + + + ))} + + + + ); + } else if (navLink.isExternal) { + return ( + + {navLink.label} + + ); + } else { + return ( + + {navLink.label} + + ); + } +}; + +export const MainNav = ({ + className, + ...props +}: React.HTMLAttributes) => ( + +); diff --git a/tools/bridge-frontend/src/components/mode-toggle.tsx b/tools/bridge-frontend/src/components/mode-toggle.tsx new file mode 100644 index 0000000000..653c922006 --- /dev/null +++ b/tools/bridge-frontend/src/components/mode-toggle.tsx @@ -0,0 +1,40 @@ +"use client"; + +import * as React from "react"; +import { Moon, Sun } from "lucide-react"; +import { useTheme } from "next-themes"; + +import { Button } from "./ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "./ui/dropdown-menu"; + +export function ModeToggle() { + const { setTheme } = useTheme(); + + return ( + + + + + + setTheme("light")}> + Light + + setTheme("dark")}> + Dark + + setTheme("system")}> + System + + + + ); +} diff --git a/tools/bridge-frontend/src/components/modules/bridge/amount-input.tsx b/tools/bridge-frontend/src/components/modules/bridge/amount-input.tsx new file mode 100644 index 0000000000..311e7f14f3 --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/bridge/amount-input.tsx @@ -0,0 +1,33 @@ +import useCustomHookForm from "@/src/hooks/useCustomHookForm"; +import { FormField, FormItem, FormControl, FormMessage } from "../../ui/form"; +import { Input } from "../../ui/input"; + +export const AmountInput = ({ + form, + walletConnected, +}: { + form: ReturnType; + + walletConnected: boolean; +}) => { + return ( + ( + + + + + + + )} + /> + ); +}; diff --git a/tools/bridge-frontend/src/components/modules/bridge/chain-select.tsx b/tools/bridge-frontend/src/components/modules/bridge/chain-select.tsx new file mode 100644 index 0000000000..cddd8a3648 --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/bridge/chain-select.tsx @@ -0,0 +1,50 @@ +import { IChain } from "@/src/types"; +import { FormField, FormItem, FormControl, FormMessage } from "../../ui/form"; +import { + Select, + SelectTrigger, + SelectContent, + SelectItem, +} from "../../ui/select"; +import useCustomHookForm from "@/src/hooks/useCustomHookForm"; + +export const ChainSelect = ({ + form, + chains, + name, +}: { + form: ReturnType; + chains: IChain[]; + name: string; +}) => { + return ( + ( + + + + + )} + /> + ); +}; diff --git a/tools/bridge-frontend/src/components/modules/bridge/index.tsx b/tools/bridge-frontend/src/components/modules/bridge/index.tsx new file mode 100644 index 0000000000..bb66521278 --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/bridge/index.tsx @@ -0,0 +1,163 @@ +import React from "react"; +import { + CardHeader, + CardTitle, + CardContent, + Card, + CardDescription, +} from "@/src/components/ui/card"; +import { Separator } from "../../ui/separator"; +import { Form } from "@/src/components/ui/form"; +import { DrawerDialog } from "../common/drawer-dialog"; +import { useWatch } from "react-hook-form"; +import useCustomHookForm from "@/src/hooks/useCustomHookForm"; +import { bridgeSchema } from "@/src/schemas/bridge"; +import useWalletStore from "@/src/stores/wallet-store"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +import { useBridgeUtils } from "@/src/hooks/useBridgeUtils"; +import { SubmitButton } from "./submit-button"; +import { TransferToSection } from "./transfer-to-section"; +import { SwitchNetworkButton } from "./switch-network-button"; +import { TransferFromSection } from "./transfer-from-section"; +import { CHAINS, TOKENS } from "@/src/lib/constants"; +import { handleStorage } from "@/src/lib/utils"; +import { showToast } from "../../ui/use-toast"; +import { ToastType } from "@/src/types"; + +export default function Dashboard() { + const queryClient = useQueryClient(); + const { + useBridgeTransaction, + useTokenBalance, + handleSwitchNetwork, + getDefaultValues, + } = useBridgeUtils(); + const { address, walletConnected, isL1ToL2, loading, provider } = + useWalletStore(); + + const tokens = TOKENS[isL1ToL2 ? "L1" : "L2"]; + const fromChains = CHAINS[isL1ToL2 ? "L1" : "L2"]; + const toChains = CHAINS[isL1ToL2 ? "L2" : "L1"]; + + const defaultValues = getDefaultValues(isL1ToL2, address); + + const form = useCustomHookForm(bridgeSchema, { defaultValues }); + const { handleSubmit, control, setValue, reset, setError, formState } = form; + + const [fromChain, toChain, token, receiver, amount] = useWatch({ + control, + name: ["fromChain", "toChain", "token", "receiver", "amount"], + }); + + const { tokenBalance, isBalanceLoading, isBalanceFetching, refreshBalance } = + useTokenBalance( + tokens, + token, + address, + fromChain, + walletConnected, + loading + ); + + const [open, setOpen] = React.useState(false); + + const { initiateBridgeTransaction } = useBridgeTransaction( + address, + token, + tokens, + receiver, + tokenBalance, + setError + ); + + const { mutate, isPending } = useMutation({ + mutationFn: initiateBridgeTransaction, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["bridgeTransactions", isL1ToL2 ? "l1" : "l2"], + }); + reset(); + }, + }); + + const setAmount = React.useCallback( + (value: number) => { + if (!token) { + setError("token", { + type: "manual", + message: "Please select a token to get the balance", + }); + return; + } + const amount = Math.floor(((tokenBalance * value) / 100) * 100) / 100; + setValue("amount", amount.toString()); + }, + [tokenBalance, token, setError, setValue] + ); + + React.useEffect(() => { + const storedReceiver = handleStorage.get("tenBridgeReceiver"); + setValue("receiver", storedReceiver ? storedReceiver : address); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [address]); + + React.useEffect(() => { + setValue("fromChain", isL1ToL2 ? CHAINS.L1[0].value : CHAINS.L2[0].value); + setValue("toChain", isL1ToL2 ? CHAINS.L2[0].value : CHAINS.L1[0].value); + setValue("token", isL1ToL2 ? TOKENS.L1[0].value : TOKENS.L2[0].value); + }, [isL1ToL2, setValue]); + + return ( +
+ + + Bridge + + You are currently bridging from {isL1ToL2 ? "L1" : "L2"} to{" "} + {isL1ToL2 ? "L2" : "L1"}. + + + + +
+ mutate(data))}> + + + + + + + +
+
+
+ ); +} diff --git a/tools/bridge-frontend/src/components/modules/bridge/percentage-buttons.tsx b/tools/bridge-frontend/src/components/modules/bridge/percentage-buttons.tsx new file mode 100644 index 0000000000..a6a0cdbf81 --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/bridge/percentage-buttons.tsx @@ -0,0 +1,27 @@ +import { PERCENTAGES } from "@/src/lib/constants"; +import { Button } from "../../ui/button"; + +export const PercentageButtons = ({ + setAmount, +}: { + setAmount: (amount: number) => void; +}) => { + return ( +
+
+ {PERCENTAGES.map((percentage) => ( + + ))} +
+
+ ); +}; diff --git a/tools/bridge-frontend/src/components/modules/bridge/submit-button.tsx b/tools/bridge-frontend/src/components/modules/bridge/submit-button.tsx new file mode 100644 index 0000000000..5ae944d60c --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/bridge/submit-button.tsx @@ -0,0 +1,47 @@ +import { Button } from "../../ui/button"; +import ConnectWalletButton from "../common/connect-wallet"; +import { ethers } from "ethers"; + +export const SubmitButton = ({ + walletConnected, + loading, + tokenBalance, + provider, + hasValue, + isSubmitting, +}: { + walletConnected: boolean; + loading: boolean; + tokenBalance: number; + provider: ethers.providers.Web3Provider | null; + hasValue: boolean; + isSubmitting: boolean; +}) => { + return ( +
+ {walletConnected ? ( + + ) : ( + + )} +
+ ); +}; diff --git a/tools/bridge-frontend/src/components/modules/bridge/switch-network-button.tsx b/tools/bridge-frontend/src/components/modules/bridge/switch-network-button.tsx new file mode 100644 index 0000000000..70ba896af9 --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/bridge/switch-network-button.tsx @@ -0,0 +1,25 @@ +import { ArrowDownUpIcon } from "lucide-react"; +import { Button } from "../../ui/button"; + +export const SwitchNetworkButton = ({ + handleSwitchNetwork, + loading, +}: { + handleSwitchNetwork: React.MouseEventHandler; + loading: boolean; +}) => { + return ( +
+ +
+ ); +}; diff --git a/tools/bridge-frontend/src/components/modules/bridge/token-select.tsx b/tools/bridge-frontend/src/components/modules/bridge/token-select.tsx new file mode 100644 index 0000000000..e89b1cc73c --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/bridge/token-select.tsx @@ -0,0 +1,47 @@ +import { IToken } from "@/src/types"; +import { FormField, FormItem, FormControl, FormMessage } from "../../ui/form"; +import { + Select, + SelectTrigger, + SelectContent, + SelectItem, + SelectValue, +} from "../../ui/select"; + +export const TokenSelect = ({ + form, + tokens, +}: { + form: any; + tokens: IToken[]; +}) => { + return ( + ( + + + + + )} + /> + ); +}; diff --git a/tools/bridge-frontend/src/components/modules/bridge/transfer-from-section.tsx b/tools/bridge-frontend/src/components/modules/bridge/transfer-from-section.tsx new file mode 100644 index 0000000000..82f6ab9fd5 --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/bridge/transfer-from-section.tsx @@ -0,0 +1,68 @@ +import { IChain, IToken } from "@/src/types"; +import { Separator } from "../../ui/separator"; +import { Skeleton } from "../../ui/skeleton"; +import { AmountInput } from "./amount-input"; +import { ChainSelect } from "./chain-select"; +import { PercentageButtons } from "./percentage-buttons"; +import { TokenSelect } from "./token-select"; +import useCustomHookForm from "@/src/hooks/useCustomHookForm"; +import { RefreshCcwDotIcon } from "lucide-react"; +import Spinner from "../../ui/spinner"; + +export const TransferFromSection = ({ + form, + fromChains, + tokens, + tokenBalance, + balanceLoading, + balanceFetching, + setAmount, + walletConnected, + refreshBalance, +}: { + form: ReturnType; + fromChains: IChain[]; + tokens: IToken[]; + tokenBalance: number; + balanceLoading: boolean; + balanceFetching: boolean; + setAmount: (value: number) => void; + walletConnected: boolean; + refreshBalance: () => void; +}) => { + return ( +
+
+ Transfer from + +
+
+
+ +
+

Balance:

+ + {balanceLoading ? : tokenBalance || "0.00"}{" "} + {balanceLoading || balanceFetching ? ( + + ) : ( + + )} + +
+
+ +
+ + +
+
+
+ ); +}; diff --git a/tools/bridge-frontend/src/components/modules/bridge/transfer-to-section.tsx b/tools/bridge-frontend/src/components/modules/bridge/transfer-to-section.tsx new file mode 100644 index 0000000000..d05f423dfc --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/bridge/transfer-to-section.tsx @@ -0,0 +1,103 @@ +import { PlusIcon } from "lucide-react"; +import { Button } from "../../ui/button"; +import { Skeleton } from "../../ui/skeleton"; +import TruncatedAddress from "../common/truncated-address"; +import { ChainSelect } from "./chain-select"; +import { IChain } from "@/src/types"; +import { isAddress } from "ethers/lib/utils"; +import useCustomHookForm from "@/src/hooks/useCustomHookForm"; +import { ethers } from "ethers"; +import useContractStore from "@/src/stores/contract-store"; +import { estimateGas } from "@/src/lib/utils/contractUtils"; +import { useQuery } from "@tanstack/react-query"; + +export const TransferToSection = ({ + form, + toChains, + loading, + receiver, + address, + setOpen, +}: { + form: ReturnType; + toChains: IChain[]; + loading: boolean; + receiver?: string; + address: string; + setOpen: (open: boolean) => void; +}) => { + const { bridgeContract } = useContractStore(); + + const fetchGasEstimate = async () => { + const amount = form.watch("amount"); + + if (!receiver) { + throw new Error("Enter receiver address."); + } + + if (!amount) { + throw new Error("Enter amount to estimate gas fee."); + } + + if (!bridgeContract) { + throw new Error("Bridge contract is not available."); + } + + return await estimateGas(receiver, amount, bridgeContract); + }; + + const { data: gasEstimate, isLoading: isEstimating } = useQuery({ + queryKey: ["gasEstimate", receiver, form.watch("amount")], + queryFn: fetchGasEstimate, + enabled: !!receiver && !!form.watch("amount"), + }); + + return ( +
+
+ Transfer to + +
+
+ +
+
+
+
+

Amount to Receive

+ + {form.watch("amount") || 0} {form.getValues().token} + +
+
+

Est. Gas Fee

+ {isEstimating ? ( + + ) : ( + + {gasEstimate + ? `${ethers.utils.formatEther(gasEstimate)} ETH` + : "0.00 ETH"} + + )} +
+
+
+
+ To: + {receiver && isAddress(receiver as string) ? ( + + ) : address ? ( + + ) : null} +
+
+ ); +}; diff --git a/tools/bridge-frontend/src/components/modules/common/connect-wallet.tsx b/tools/bridge-frontend/src/components/modules/common/connect-wallet.tsx new file mode 100644 index 0000000000..2ae8d66cf1 --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/common/connect-wallet.tsx @@ -0,0 +1,95 @@ +import React from "react"; +import { Button } from "../../ui/button"; +import { + Link2Icon, + LinkBreak2Icon, + ExclamationTriangleIcon, +} from "@radix-ui/react-icons"; +import { cn, downloadMetaMask, ethereum } from "@/src/lib/utils"; +import useWalletStore from "@/src/stores/wallet-store"; +import { ButtonVariants } from "@/src/types"; +import TruncatedAddress from "./truncated-address"; + +interface ConnectWalletButtonProps { + className?: string; + text?: string; + variant?: ButtonVariants; +} + +const ConnectWalletButton = ({ + className, + text = "Connect Wallet", + variant = "outline", +}: ConnectWalletButtonProps) => { + const { + walletConnected, + connectWallet, + disconnectWallet, + isWrongNetwork, + switchNetwork, + address, + } = useWalletStore(); + + const handleClick = () => { + if (!ethereum) { + downloadMetaMask(); + return; + } + + if (isWrongNetwork) { + switchNetwork(); + return; + } + + if (walletConnected) { + disconnectWallet(); + } else { + connectWallet(); + } + }; + + const renderButtonContent = () => { + if (!ethereum) { + return ( + <> + + Download MetaMask + + ); + } + + if (isWrongNetwork) { + return ( + <> + + Unsupported network + + ); + } + + return walletConnected ? ( + <> + + {} + + ) : ( + <> + + {text} + + ); + }; + + return ( + + ); +}; + +export default ConnectWalletButton; diff --git a/tools/bridge-frontend/src/components/modules/common/copy.tsx b/tools/bridge-frontend/src/components/modules/common/copy.tsx new file mode 100644 index 0000000000..2f53db5842 --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/common/copy.tsx @@ -0,0 +1,23 @@ +import React from "react"; +import { Button } from "../../ui/button"; +import { useCopy } from "../../../hooks/useCopy"; +import { CopyIcon, CheckIcon } from "@radix-ui/react-icons"; + +const Copy = ({ value }: { value: string | number | undefined }) => { + const { copyToClipboard, copied } = useCopy(); + if (!value) return null; + return ( + + ); +}; + +export default Copy; diff --git a/tools/bridge-frontend/src/components/modules/common/data-table/data-table-column-header.tsx b/tools/bridge-frontend/src/components/modules/common/data-table/data-table-column-header.tsx new file mode 100644 index 0000000000..07fbb0cc3a --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/common/data-table/data-table-column-header.tsx @@ -0,0 +1,71 @@ +import { + ArrowDownIcon, + ArrowUpIcon, + CaretSortIcon, + EyeNoneIcon, +} from "@radix-ui/react-icons"; +import { Column } from "@tanstack/react-table"; + +import { cn } from "@/src/lib/utils"; +import { Button } from "@/src/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/src/components/ui/dropdown-menu"; + +interface DataTableColumnHeaderProps + extends React.HTMLAttributes { + column: Column; + title: string; +} + +export function DataTableColumnHeader({ + column, + title, + className, +}: DataTableColumnHeaderProps) { + if (!column.getCanSort()) { + return
{title}
; + } + + return ( +
+ + + + + + column.toggleSorting(false)}> + + Asc + + column.toggleSorting(true)}> + + Desc + + + column.toggleVisibility(false)}> + + Hide + + + +
+ ); +} diff --git a/tools/bridge-frontend/src/components/modules/common/data-table/data-table-faceted-filter.tsx b/tools/bridge-frontend/src/components/modules/common/data-table/data-table-faceted-filter.tsx new file mode 100644 index 0000000000..641973c663 --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/common/data-table/data-table-faceted-filter.tsx @@ -0,0 +1,147 @@ +import * as React from "react"; +import { CheckIcon, PlusCircledIcon } from "@radix-ui/react-icons"; +import { Column } from "@tanstack/react-table"; + +import { cn } from "@/src/lib/utils"; +import { Badge } from "@/src/components/ui/badge"; +import { Button } from "@/src/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, +} from "@/src/components/ui/command"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/src/components/ui/popover"; +import { Separator } from "@/src/components/ui/separator"; + +interface DataTableFacetedFilterProps { + column?: Column; + title?: string; + options: { + label: string; + value: string; + icon?: React.ComponentType<{ className?: string }>; + }[]; +} + +export function DataTableFacetedFilter({ + column, + title, + options, +}: DataTableFacetedFilterProps) { + const facets = column?.getFacetedUniqueValues(); + const selectedValues = new Set(column?.getFilterValue() as string[]); + + return ( + + + + + + + + + No results found. + + {options.map((option) => { + const isSelected = selectedValues.has(option.value); + return ( + { + if (isSelected) { + selectedValues.delete(option.value); + } else { + selectedValues.add(option.value); + } + const filterValues = Array.from(selectedValues); + column?.setFilterValue( + filterValues.length ? filterValues : undefined + ); + }} + > +
+ +
+ {option.icon && ( + + )} + {option.label} + {facets?.get(option.value) && ( + + {facets.get(option.value)} + + )} +
+ ); + })} +
+ {selectedValues.size > 0 && ( + <> + + + column?.setFilterValue(undefined)} + className="justify-center text-center" + > + Clear filters + + + + )} +
+
+
+
+ ); +} diff --git a/tools/bridge-frontend/src/components/modules/common/data-table/data-table-pagination.tsx b/tools/bridge-frontend/src/components/modules/common/data-table/data-table-pagination.tsx new file mode 100644 index 0000000000..0a61d0f786 --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/common/data-table/data-table-pagination.tsx @@ -0,0 +1,144 @@ +import { + ChevronLeftIcon, + ChevronRightIcon, + DoubleArrowLeftIcon, +} from "@radix-ui/react-icons"; +import { PaginationState, Table } from "@tanstack/react-table"; +import { Button } from "@/src/components/ui/button"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/src/components/ui/select"; +import { Input } from "@/src/components/ui/input"; +import { useState } from "react"; + +interface DataTablePaginationProps { + table: Table; + refetch?: () => void; + setPagination: (pagination: PaginationState) => void; +} + +export function DataTablePagination({ + table, + refetch, + setPagination, +}: DataTablePaginationProps) { + const [page, setPage] = useState(table.getState().pagination.pageIndex); + + const handlePageChange = (e: React.ChangeEvent) => { + setPage(Number(e.target.value)); + }; + + const handleKey = (e: React.KeyboardEvent) => { + if ( + e.key === "Enter" && + page > 0 && + page !== table.getState().pagination.pageIndex + ) { + table.setPageIndex(page); + refetch?.(); + } + }; + + return ( +
+
+ Showing {table?.getFilteredRowModel()?.rows?.length} row(s) +
+
+
+

Rows per page

+ +
+
+ Page + e.target.select()} + onBlur={() => setPage(table.getState().pagination.pageIndex)} + /> + {/* uncomment the following line when total count feature is implemented */} + {/* of {formatNumber(table.getPageCount())} */} +
+
+ + + + {/* uncomment the following line when total count feature is implemented */} + {/* */} +
+
+
+ ); +} diff --git a/tools/bridge-frontend/src/components/modules/common/data-table/data-table-row-actions.tsx b/tools/bridge-frontend/src/components/modules/common/data-table/data-table-row-actions.tsx new file mode 100644 index 0000000000..0f8c652451 --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/common/data-table/data-table-row-actions.tsx @@ -0,0 +1,65 @@ +"use client"; + +import { DotsHorizontalIcon } from "@radix-ui/react-icons"; +import { Row } from "@tanstack/react-table"; +import { Button } from "@/src/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuTrigger, +} from "@/src/components/ui/dropdown-menu"; + +interface DataTableRowActionsProps { + row: Row; + labels: { label: string; value: string }[] | null; +} + +export function DataTableRowActions({ + row, + labels, +}: DataTableRowActionsProps) { + return ( + + + + + + View + {labels === null ? null : ( + <> + + + Labels + + + {labels?.map((label) => ( + + {label.label} + + ))} + + + + + )} + + + + ); +} diff --git a/tools/bridge-frontend/src/components/modules/common/data-table/data-table-toolbar.tsx b/tools/bridge-frontend/src/components/modules/common/data-table/data-table-toolbar.tsx new file mode 100644 index 0000000000..93bee611c0 --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/common/data-table/data-table-toolbar.tsx @@ -0,0 +1,68 @@ +"use client"; + +import { Cross2Icon, ReloadIcon } from "@radix-ui/react-icons"; +import { Table } from "@tanstack/react-table"; + +import { Button } from "@/src/components/ui/button"; +import { Input } from "@/src/components/ui/input"; +import { DataTableViewOptions } from "./data-table-view-options"; + +import { DataTableFacetedFilter } from "./data-table-faceted-filter"; + +interface DataTableToolbarProps { + table: Table; + refetch?: () => void; + toolbar?: { + column: string; + title: string; + options: { label: string; value: string }[]; + }[]; +} +export function DataTableToolbar({ + table, + toolbar, + refetch, +}: DataTableToolbarProps) { + const isFiltered = table.getState().columnFilters.length > 0; + + return ( +
+
+ {toolbar?.map( + (item, index) => + table.getColumn(item.column) && ( + + ) + )} + {isFiltered && ( + + )} +
+
+ {refetch && ( + + )} + +
+
+ ); +} diff --git a/tools/bridge-frontend/src/components/modules/common/data-table/data-table-view-options.tsx b/tools/bridge-frontend/src/components/modules/common/data-table/data-table-view-options.tsx new file mode 100644 index 0000000000..b8975ff5cb --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/common/data-table/data-table-view-options.tsx @@ -0,0 +1,59 @@ +"use client"; + +import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu"; +import { MixerHorizontalIcon } from "@radix-ui/react-icons"; +import { Table } from "@tanstack/react-table"; + +import { Button } from "@/src/components/ui/button"; +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuSeparator, +} from "@/src/components/ui/dropdown-menu"; + +interface DataTableViewOptionsProps { + table: Table; +} + +export function DataTableViewOptions({ + table, +}: DataTableViewOptionsProps) { + return ( + + + + + + Toggle columns + + {table + .getAllColumns() + .filter( + (column) => + typeof column.accessorFn !== "undefined" && column.getCanHide() + ) + .map((column) => { + return ( + column.toggleVisibility(!!value)} + > + {column.id} + + ); + })} + + + ); +} diff --git a/tools/bridge-frontend/src/components/modules/common/data-table/data-table.tsx b/tools/bridge-frontend/src/components/modules/common/data-table/data-table.tsx new file mode 100644 index 0000000000..982835d6d4 --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/common/data-table/data-table.tsx @@ -0,0 +1,211 @@ +"use client"; + +import * as React from "react"; +import { + ColumnDef, + ColumnFiltersState, + OnChangeFn, + PaginationState, + SortingState, + VisibilityState, + flexRender, + getCoreRowModel, + getFacetedRowModel, + getFacetedUniqueValues, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from "@tanstack/react-table"; + +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/src/components/ui/table"; +import { DataTablePagination } from "./data-table-pagination"; +import { DataTableToolbar } from "./data-table-toolbar"; +import { useRouter } from "next/router"; +import { Skeleton } from "@/src/components/ui/skeleton"; +import { Button } from "@/src/components/ui/button"; + +interface DataTableProps { + columns: ColumnDef[]; + data: TData[]; + toolbar?: { + column: string; + title: string; + options: { label: string; value: string }[]; + }[]; + updateQueryParams?: (query: any) => void; + refetch?: () => void; + total: number; + isLoading?: boolean; + noPagination?: boolean; + noResultsText?: string; + noResultsMessage?: string; +} + +export function DataTable({ + columns, + data, + toolbar, + refetch, + total, + isLoading, + noPagination, + noResultsText, + noResultsMessage, +}: DataTableProps) { + const { query, push, pathname } = useRouter(); + const [rowSelection, setRowSelection] = React.useState({}); + const [columnVisibility, setColumnVisibility] = + React.useState({}); + const [columnFilters, setColumnFilters] = React.useState( + [] + ); + const [sorting, setSorting] = React.useState([]); + + const pagination = React.useMemo(() => { + return { + pageIndex: Number(query.page) || 1, + pageSize: Number(query.size) || 20, + }; + }, [query.page, query.size]); + + const setPagination: OnChangeFn = (func) => { + const { pageIndex, pageSize } = + typeof func === "function" ? func(pagination) : func; + const newPageIndex = pagination.pageSize !== pageSize ? 1 : pageIndex; + const params = { + ...query, + page: newPageIndex > 0 ? newPageIndex : 1, + size: pageSize <= 100 ? pageSize : 100, + }; + push({ pathname, query: params }); + }; + + const table = useReactTable({ + data, + columns, + state: { + sorting, + columnVisibility, + rowSelection, + columnFilters, + pagination, + }, + onPaginationChange: setPagination, + manualPagination: true, + // pageCount: Math.ceil(total / pagination.pageSize), + enableRowSelection: true, + onRowSelectionChange: setRowSelection, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + onColumnVisibilityChange: setColumnVisibility, + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + getFacetedRowModel: getFacetedRowModel(), + getFacetedUniqueValues: getFacetedUniqueValues(), + }); + + return ( +
+ {data && ( + + )} +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ); + })} + + ))} + + + {isLoading ? ( + <> + + + + + + + ) : data && table?.getRowModel()?.rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ))} + + )) + ) : ( + + + {pagination.pageIndex > 1 ? ( +

+ No {noResultsText || "results"} found for the selected + filters. + +

+ ) : ( +

+ {noResultsMessage || + `No ${noResultsText || "results"} found.`} +

+ )} +
+
+ )} +
+
+
+ {data && !isLoading && !noPagination && ( + + )} +
+ ); +} diff --git a/tools/bridge-frontend/src/components/modules/common/data-table/skeleton-loader.tsx b/tools/bridge-frontend/src/components/modules/common/data-table/skeleton-loader.tsx new file mode 100644 index 0000000000..89c5627fda --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/common/data-table/skeleton-loader.tsx @@ -0,0 +1,23 @@ +import { Skeleton } from "@/src/components/ui/skeleton"; +import { TableHeader, TableRow, TableBody } from "@/src/components/ui/table"; +import { Table } from "lucide-react"; +import React from "react"; + +const DataTableSkeleton = ({ columns }: { columns: number }) => { + const renderSkeletonColumns = () => { + return Array.from({ length: columns }).map((_, index) => ( + + )); + }; + + return ( + + + {renderSkeletonColumns()} + + {renderSkeletonColumns()} +
+ ); +}; + +export default DataTableSkeleton; diff --git a/tools/bridge-frontend/src/components/modules/common/destination-address-form.tsx b/tools/bridge-frontend/src/components/modules/common/destination-address-form.tsx new file mode 100644 index 0000000000..3cec2ccff9 --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/common/destination-address-form.tsx @@ -0,0 +1,50 @@ +import { cn } from "@/src/lib/utils"; +import { Terminal } from "lucide-react"; +import React from "react"; +import { Alert, AlertDescription } from "../../ui/alert"; +import { Button } from "../../ui/button"; +import { Label } from "../../ui/label"; +import { Input } from "../../ui/input"; +import useCustomHookForm from "@/src/hooks/useCustomHookForm"; +import { handleStorage } from "@/src/lib/utils"; + +export default function FormComponent({ + form, + className, + setOpen, +}: { + form: ReturnType; + className?: string; + setOpen: (value: boolean) => void; +}) { + const receiver = form.getValues("receiver"); + const addAddressToMainForm = (e: any) => { + e.preventDefault(); + const address = e.target.form[0].value; + form.setValue("receiver", address); + handleStorage.save("tenBridgeReceiver", address); + setOpen(false); + }; + return ( +
+
+ + +
+ + + + Make sure the address is correct before submitting. + + + +
+ ); +} diff --git a/tools/bridge-frontend/src/components/modules/common/drawer-dialog.tsx b/tools/bridge-frontend/src/components/modules/common/drawer-dialog.tsx new file mode 100644 index 0000000000..3fa3d9a9ab --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/common/drawer-dialog.tsx @@ -0,0 +1,70 @@ +import { + Dialog, + DialogTrigger, + DialogContent, + DialogTitle, + DialogDescription, +} from "../../ui/dialog"; +import React from "react"; +import { Button } from "../../ui/button"; +import { DialogHeader } from "../../ui/dialog"; +import { + Drawer, + DrawerTrigger, + DrawerContent, + DrawerHeader, + DrawerTitle, + DrawerDescription, + DrawerFooter, + DrawerClose, +} from "../../ui/drawer"; +import { useMediaQuery } from "@/src/hooks/useMediaQuery"; +import FormComponent from "./destination-address-form"; +import useCustomHookForm from "@/src/hooks/useCustomHookForm"; + +export function DrawerDialog({ + form, + open, + setOpen, +}: { + form: ReturnType; + open: boolean; + setOpen: (value: boolean) => void; +}) { + const isDesktop = useMediaQuery("(min-width: 768px)"); + + if (isDesktop) { + return ( + + + + Enter Transfer Address + + This address will be used to transfer the asset to. + + + + + + ); + } + + return ( + + + + Enter Transfer Address + + This address will be used to transfer the asset to. + + + + + + + + + + + ); +} diff --git a/tools/bridge-frontend/src/components/modules/common/empty-state.tsx b/tools/bridge-frontend/src/components/modules/common/empty-state.tsx new file mode 100644 index 0000000000..1a3a94cf9e --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/common/empty-state.tsx @@ -0,0 +1,44 @@ +import Image from "next/image"; +import React from "react"; + +const EmptyState = ({ + title, + description, + icon, + imageSrc, + imageAlt, + action, +}: { + title?: string; + description?: string; + icon?: React.ReactNode; + imageSrc?: string; + imageAlt?: string; + action?: React.ReactNode; +}) => { + return ( +
+
+ {icon &&
{icon}
} + {imageSrc && ( + {imageAlt + )} + {title && ( +

+ {title} +

+ )} + {description && ( +

{description}

+ )} + {action &&
{action}
} +
+
+ ); +}; + +export default EmptyState; diff --git a/tools/bridge-frontend/src/components/modules/common/network-status.tsx b/tools/bridge-frontend/src/components/modules/common/network-status.tsx new file mode 100644 index 0000000000..8f999f4015 --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/common/network-status.tsx @@ -0,0 +1,38 @@ +import React from "react"; + +const MessageContent = ( +

+ You seem to be offline +
Please check your internet connection. +

+); + +export const NetworkStatus = ({ message = MessageContent }) => { + const [isOnline, setIsOnline] = React.useState(true); + + React.useEffect(() => { + const setOnlineStatus = () => { + setIsOnline(navigator.onLine); + }; + + window.addEventListener("online", setOnlineStatus); + window.addEventListener("offline", setOnlineStatus); + + return () => { + window.removeEventListener("online", setOnlineStatus); + window.removeEventListener("offline", setOnlineStatus); + }; + }, []); + + return ( +
+
+ {message} +
+
+ ); +}; diff --git a/tools/bridge-frontend/src/components/modules/common/truncated-address.tsx b/tools/bridge-frontend/src/components/modules/common/truncated-address.tsx new file mode 100644 index 0000000000..8846eb826d --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/common/truncated-address.tsx @@ -0,0 +1,49 @@ +import React from "react"; + +import { + TooltipProvider, + Tooltip, + TooltipTrigger, + TooltipContent, +} from "@radix-ui/react-tooltip"; +import Copy from "./copy"; + +const TruncatedAddress = ({ + address, + prefixLength = 6, + suffixLength = 4, + showCopy = true, +}: { + address: string; + prefixLength?: number; + suffixLength?: number; + showCopy?: boolean; +}) => { + const truncatedAddress = + address && + `${address.substring(0, prefixLength)}...${address.substring( + address.length - suffixLength + )}`; + + return ( + <> + {address ? ( +
+ + + {truncatedAddress} + +

{address}

+
+
+
+ {showCopy && } +
+ ) : ( +
N/A
+ )} + + ); +}; + +export default TruncatedAddress; diff --git a/tools/bridge-frontend/src/components/modules/transactions/columns.tsx b/tools/bridge-frontend/src/components/modules/transactions/columns.tsx new file mode 100644 index 0000000000..6b0e939ee5 --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/transactions/columns.tsx @@ -0,0 +1,81 @@ +"use client"; + +import { ColumnDef } from "@tanstack/react-table"; +import { Badge, badgeVariants } from "@/src/components/ui/badge"; + +import { statuses } from "@/src/components/modules/transactions/constants"; +import { DataTableColumnHeader } from "@/src/components/modules/common/data-table/data-table-column-header"; +import TruncatedAddress from "../common/truncated-address"; +import { Transactions } from "@/src/types"; + +export const columns: ColumnDef[] = [ + { + id: "status", + accessorKey: "status", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const status = statuses.find((s) => s.value === row.original.status); + return ( + + {status?.icon && } + {status?.label} + + ); + }, + enableSorting: false, + enableHiding: false, + }, + + { + accessorKey: "blockNumber", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + return ( +
+ + #{Number(row.getValue("blockNumber"))} + +
+ ); + }, + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: "blockHash", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + return ; + }, + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: "transactionHash", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + return ; + }, + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: "address", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + return ; + }, + enableSorting: false, + enableHiding: false, + }, +]; diff --git a/tools/bridge-frontend/src/components/modules/transactions/constants.tsx b/tools/bridge-frontend/src/components/modules/transactions/constants.tsx new file mode 100644 index 0000000000..c613979cdd --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/transactions/constants.tsx @@ -0,0 +1,47 @@ +import { TransactionStatus } from "@/src/types"; +import { CheckIcon, Cross2Icon } from "@radix-ui/react-icons"; +import { HourglassIcon } from "lucide-react"; + +export const statuses = [ + { + value: TransactionStatus.Success, + label: "Success", + icon: CheckIcon, + variant: "success", + }, + { + value: TransactionStatus.Failure, + label: "Failure", + icon: Cross2Icon, + variant: "destructive", + }, + { + value: TransactionStatus.Pending, + label: "Pending", + icon: HourglassIcon, + variant: "warning", + }, +]; + +export const types = [ + { + value: "0x0", + label: "Legacy", + variant: "primary", + }, + { + value: "0x1", + label: "Access List", + variant: "secondary", + }, + { + value: "0x2", + label: "Dynamic Fee", + variant: "outline", + }, + { + value: "0x3", + label: "Blob", + variant: "outline", + }, +]; diff --git a/tools/bridge-frontend/src/components/modules/transactions/index.tsx b/tools/bridge-frontend/src/components/modules/transactions/index.tsx new file mode 100644 index 0000000000..4e642dc5dc --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/transactions/index.tsx @@ -0,0 +1,125 @@ +import React from "react"; +import { columns } from "@/src/components/modules/transactions/columns"; +import { columns as pending } from "@/src/components/modules/transactions/pending-columns"; +import { DataTable } from "@/src/components/modules/common/data-table/data-table"; +import { getItem } from "@/src/lib/utils"; +import { useContractsService } from "@/src/services/useContractsService"; +import { ItemPosition } from "@/src/types"; +import useWalletStore from "@/src/stores/wallet-store"; +import { useQuery } from "@tanstack/react-query"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../ui/tabs"; +import { getPendingBridgeTransactions } from "@/src/lib/utils/txnUtils"; +import { DataTableColumnHeader } from "../common/data-table/data-table-column-header"; +import { Button } from "../../ui/button"; + +export default function TransactionsComponent() { + const { isL1ToL2 } = useWalletStore(); + const { getBridgeTransactions, finaliseTransaction, finalisingTxHashes } = + useContractsService(); + + const pendingColumns = [ + ...pending, + { + accessorKey: "actions", + header: ({ column }: { column: any }) => ( + + ), + cell: ({ row }: { row: any }) => { + const txHash = row.getValue("txHash"); + const isDisabled = finalisingTxHashes.has(txHash as string); + return ( + + ); + }, + enableSorting: false, + enableHiding: false, + }, + ]; + + const { + data: transactions = [], + isLoading: isTransactionsLoading, + refetch, + } = useQuery({ + queryKey: ["bridgeTransactions", isL1ToL2 ? "l1" : "l2"], + queryFn: () => getBridgeTransactions(), + refetchInterval: 30000, + refetchOnMount: true, + }); + + const { + data: pendingTransactions = [], + isLoading: isPendingTransactionsLoading, + refetch: refetchPending, + } = useQuery({ + queryKey: ["bridgePendingTransactions", isL1ToL2 ? "l1" : "l2"], + queryFn: () => getPendingBridgeTransactions(), + refetchInterval: 30000, + refetchOnMount: true, + }); + + const firstBatchHeight = getItem(transactions, "blockNumber"); + const lastBatchHeight = getItem( + transactions, + "blockNumber", + ItemPosition.LAST + ); + + return ( +
+
+
+

+ Latest {isL1ToL2 ? "L1-L2" : "L2-L1"} Transactions +

+ {transactions?.length > 0 && ( +

+ Showing transactions in batch + {firstBatchHeight !== lastBatchHeight && "es"} #{firstBatchHeight}{" "} + {firstBatchHeight !== lastBatchHeight && "to #" + lastBatchHeight} +

+ )} +
+
+ + + Completed Transactions + Pending + + + + + + + + +
+ ); +} diff --git a/tools/bridge-frontend/src/components/modules/transactions/pending-columns.tsx b/tools/bridge-frontend/src/components/modules/transactions/pending-columns.tsx new file mode 100644 index 0000000000..96451ffd1b --- /dev/null +++ b/tools/bridge-frontend/src/components/modules/transactions/pending-columns.tsx @@ -0,0 +1,98 @@ +"use client"; + +import { ColumnDef } from "@tanstack/react-table"; +import { Badge, badgeVariants } from "@/src/components/ui/badge"; + +import { statuses } from "@/src/components/modules/transactions/constants"; +import { DataTableColumnHeader } from "@/src/components/modules/common/data-table/data-table-column-header"; +import TruncatedAddress from "../common/truncated-address"; +import { IPendingTx } from "@/src/types"; +import { formatDistanceToNow } from "date-fns"; + +export const columns: ColumnDef[] = [ + { + id: "status", + accessorKey: "status", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const status = statuses.find((s) => s.value === "Pending"); + return ( + + {status?.icon && } + {status?.label} + + ); + }, + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: "timestamp", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + return ( +
+ + {formatDistanceToNow(new Date(row.getValue("timestamp")), { + addSuffix: true, + })} + +
+ ); + }, + }, + { + accessorKey: "txHash", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + return ; + }, + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: "resumeStep", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + return ( +
+ + {row.getValue("resumeStep")} + +
+ ); + }, + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: "receiver", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + return ; + }, + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: "value", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + return {row.getValue("value")}; + }, + enableSorting: false, + enableHiding: false, + }, +]; diff --git a/tools/bridge-frontend/src/components/providers/theme-provider.tsx b/tools/bridge-frontend/src/components/providers/theme-provider.tsx new file mode 100644 index 0000000000..d4b4bbfd90 --- /dev/null +++ b/tools/bridge-frontend/src/components/providers/theme-provider.tsx @@ -0,0 +1,9 @@ +'use client' + +import * as React from 'react' +import { ThemeProvider as NextThemesProvider } from 'next-themes' +import { type ThemeProviderProps } from 'next-themes/dist/types' + +export function ThemeProvider({ children, ...props }: ThemeProviderProps) { + return {children} +} diff --git a/tools/bridge-frontend/src/components/providers/wallet-provider.tsx b/tools/bridge-frontend/src/components/providers/wallet-provider.tsx new file mode 100644 index 0000000000..833455b93c --- /dev/null +++ b/tools/bridge-frontend/src/components/providers/wallet-provider.tsx @@ -0,0 +1,34 @@ +import React, { createContext, useContext, useEffect } from "react"; +import { + WalletConnectionContextType, + WalletConnectionProviderProps, +} from "@/src/types"; +import useWalletStore from "@/src/stores/wallet-store"; + +const WalletContext = createContext(null); + +const WalletProvider = ({ children }: WalletConnectionProviderProps) => { + const { restoreWalletState } = useWalletStore(); + + useEffect(() => { + restoreWalletState(); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const value = {}; + + return ( + {children} + ); +}; + +const useWallet = () => { + const context = useContext(WalletContext); + if (!context) { + throw new Error("useWallet must be used within a WalletProvider"); + } + return context; +}; + +export { WalletProvider, useWallet }; diff --git a/tools/bridge-frontend/src/components/ui/alert.tsx b/tools/bridge-frontend/src/components/ui/alert.tsx new file mode 100644 index 0000000000..ef4d6dbee7 --- /dev/null +++ b/tools/bridge-frontend/src/components/ui/alert.tsx @@ -0,0 +1,64 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "../../lib/utils"; + +const alertVariants = cva( + "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", + { + variants: { + variant: { + default: "bg-background text-foreground", + destructive: + "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", + info: "border-info/50 text-info dark:border-info [&>svg]:text-info", + success: + "border-success/50 text-success dark:border-success [&>svg]:text-success", + warning: + "border-warning/50 text-warning dark:border-warning [&>svg]:text-warning", + }, + }, + defaultVariants: { + variant: "default", + }, + } +); + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)); +Alert.displayName = "Alert"; + +const AlertTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertTitle.displayName = "AlertTitle"; + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertDescription.displayName = "AlertDescription"; + +export { Alert, AlertTitle, AlertDescription }; diff --git a/tools/bridge-frontend/src/components/ui/badge.tsx b/tools/bridge-frontend/src/components/ui/badge.tsx new file mode 100644 index 0000000000..257a849ad0 --- /dev/null +++ b/tools/bridge-frontend/src/components/ui/badge.tsx @@ -0,0 +1,41 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "../../lib/utils"; + +const badgeVariants = cva( + "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + info: "border-transparent bg-info text-info-foreground hover:bg-info/80", + warning: + "border-transparent bg-warning text-info-foreground hover:bg-warning/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + success: + "border-transparent bg-success text-success-foreground hover:bg-success/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +); + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ); +} + +export { Badge, badgeVariants }; diff --git a/tools/bridge-frontend/src/components/ui/button.tsx b/tools/bridge-frontend/src/components/ui/button.tsx new file mode 100644 index 0000000000..9671abe4f8 --- /dev/null +++ b/tools/bridge-frontend/src/components/ui/button.tsx @@ -0,0 +1,105 @@ +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import { cn } from "../../lib/utils"; +import { Loader } from "lucide-react"; + +const buttonVariants = cva( + "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + clear: "bg-transparent text-primary-foreground outline-none", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10 p-1", + wrap: "h-full w-full", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; + loading?: boolean; + loadingText?: string; +} + +const Button = React.forwardRef( + ( + { + className, + variant, + size, + asChild = false, + loading = false, + loadingText = "Loading...", + children, + ...props + }, + ref + ) => { + const Comp = asChild ? Slot : "button"; + return ( + + {loading ? ( + <> + + {loadingText} + + ) : ( + children + )} + + ); + } +); + +Button.displayName = "Button"; + +interface LinkButtonProps + extends React.AnchorHTMLAttributes, + VariantProps { + asChild?: boolean; + loading?: boolean; +} + +const LinkButton = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "a"; + return ( + + ); + } +); + +LinkButton.displayName = "LinkButton"; + +export { Button, buttonVariants, LinkButton }; diff --git a/tools/bridge-frontend/src/components/ui/card.tsx b/tools/bridge-frontend/src/components/ui/card.tsx new file mode 100644 index 0000000000..6abe706a68 --- /dev/null +++ b/tools/bridge-frontend/src/components/ui/card.tsx @@ -0,0 +1,86 @@ +import * as React from "react"; + +import { cn } from "../../lib/utils"; + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +Card.displayName = "Card"; + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardHeader.displayName = "CardHeader"; + +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)); +CardTitle.displayName = "CardTitle"; + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)); +CardDescription.displayName = "CardDescription"; + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)); +CardContent.displayName = "CardContent"; + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardFooter.displayName = "CardFooter"; + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardDescription, + CardContent, +}; diff --git a/tools/bridge-frontend/src/components/ui/command.tsx b/tools/bridge-frontend/src/components/ui/command.tsx new file mode 100644 index 0000000000..fd81fd9b4c --- /dev/null +++ b/tools/bridge-frontend/src/components/ui/command.tsx @@ -0,0 +1,153 @@ +import * as React from "react"; +import { DialogProps } from "@radix-ui/react-dialog"; +import { Command as CommandPrimitive } from "cmdk"; +import { Search } from "lucide-react"; + +import { cn } from "@/src/lib/utils"; +import { Dialog, DialogContent } from "@/src/components/ui/dialog"; + +const Command = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Command.displayName = CommandPrimitive.displayName; + +interface CommandDialogProps extends DialogProps {} + +const CommandDialog = ({ children, ...props }: CommandDialogProps) => { + return ( + + + + {children} + + + + ); +}; + +const CommandInput = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ + +
+)); + +CommandInput.displayName = CommandPrimitive.Input.displayName; + +const CommandList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandList.displayName = CommandPrimitive.List.displayName; + +const CommandEmpty = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>((props, ref) => ( + +)); + +CommandEmpty.displayName = CommandPrimitive.Empty.displayName; + +const CommandGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandGroup.displayName = CommandPrimitive.Group.displayName; + +const CommandSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +CommandSeparator.displayName = CommandPrimitive.Separator.displayName; + +const CommandItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandItem.displayName = CommandPrimitive.Item.displayName; + +const CommandShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ); +}; +CommandShortcut.displayName = "CommandShortcut"; + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +}; diff --git a/tools/bridge-frontend/src/components/ui/dialog.tsx b/tools/bridge-frontend/src/components/ui/dialog.tsx new file mode 100644 index 0000000000..e7fc0c8651 --- /dev/null +++ b/tools/bridge-frontend/src/components/ui/dialog.tsx @@ -0,0 +1,120 @@ +import * as React from "react"; +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { X } from "lucide-react"; + +import { cn } from "../../lib/utils"; + +const Dialog = DialogPrimitive.Root; + +const DialogTrigger = DialogPrimitive.Trigger; + +const DialogPortal = DialogPrimitive.Portal; + +const DialogClose = DialogPrimitive.Close; + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogHeader.displayName = "DialogHeader"; + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogFooter.displayName = "DialogFooter"; + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +}; diff --git a/tools/bridge-frontend/src/components/ui/drawer.tsx b/tools/bridge-frontend/src/components/ui/drawer.tsx new file mode 100644 index 0000000000..de003550ae --- /dev/null +++ b/tools/bridge-frontend/src/components/ui/drawer.tsx @@ -0,0 +1,116 @@ +import * as React from "react"; +import { Drawer as DrawerPrimitive } from "vaul"; + +import { cn } from "@/src/lib/utils"; + +const Drawer = ({ + shouldScaleBackground = true, + ...props +}: React.ComponentProps) => ( + +); +Drawer.displayName = "Drawer"; + +const DrawerTrigger = DrawerPrimitive.Trigger; + +const DrawerPortal = DrawerPrimitive.Portal; + +const DrawerClose = DrawerPrimitive.Close; + +const DrawerOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName; + +const DrawerContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + +
+ {children} + + +)); +DrawerContent.displayName = "DrawerContent"; + +const DrawerHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DrawerHeader.displayName = "DrawerHeader"; + +const DrawerFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DrawerFooter.displayName = "DrawerFooter"; + +const DrawerTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DrawerTitle.displayName = DrawerPrimitive.Title.displayName; + +const DrawerDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DrawerDescription.displayName = DrawerPrimitive.Description.displayName; + +export { + Drawer, + DrawerPortal, + DrawerOverlay, + DrawerTrigger, + DrawerClose, + DrawerContent, + DrawerHeader, + DrawerFooter, + DrawerTitle, + DrawerDescription, +}; diff --git a/tools/bridge-frontend/src/components/ui/dropdown-menu.tsx b/tools/bridge-frontend/src/components/ui/dropdown-menu.tsx new file mode 100644 index 0000000000..a149167bf2 --- /dev/null +++ b/tools/bridge-frontend/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,198 @@ +import * as React from "react"; +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; +import { Check, ChevronRight, Circle } from "lucide-react"; + +import { cn } from "../../lib/utils"; + +const DropdownMenu = DropdownMenuPrimitive.Root; + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; + +const DropdownMenuGroup = DropdownMenuPrimitive.Group; + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal; + +const DropdownMenuSub = DropdownMenuPrimitive.Sub; + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)); +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName; + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName; + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)); +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ( + +)); +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)); +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName; + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)); +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ( + +)); +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ); +}; +DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +}; diff --git a/tools/bridge-frontend/src/components/ui/form.tsx b/tools/bridge-frontend/src/components/ui/form.tsx new file mode 100644 index 0000000000..5f0cb4646b --- /dev/null +++ b/tools/bridge-frontend/src/components/ui/form.tsx @@ -0,0 +1,177 @@ +import * as React from "react"; +import * as LabelPrimitive from "@radix-ui/react-label"; +import { Slot } from "@radix-ui/react-slot"; +import { + Controller, + ControllerProps, + FieldPath, + FieldValues, + FormProvider, + useFormContext, +} from "react-hook-form"; + +import { cn } from "@/src/lib/utils"; +import { Label } from "@/src/components/ui/label"; + +const Form = FormProvider; + +type FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +> = { + name: TName; +}; + +const FormFieldContext = React.createContext( + {} as FormFieldContextValue +); + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +>({ + ...props +}: ControllerProps) => { + return ( + + + + ); +}; + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext); + const itemContext = React.useContext(FormItemContext); + const { getFieldState, formState } = useFormContext(); + + const fieldState = getFieldState(fieldContext.name, formState); + + if (!fieldContext) { + throw new Error("useFormField should be used within "); + } + + const { id } = itemContext; + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + }; +}; + +type FormItemContextValue = { + id: string; +}; + +const FormItemContext = React.createContext( + {} as FormItemContextValue +); + +const FormItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const id = React.useId(); + + return ( + +
+ + ); +}); +FormItem.displayName = "FormItem"; + +const FormLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + const { error, formItemId } = useFormField(); + + return ( +