diff --git a/.github/workflows/manual-deploy-obscuro-gateway.yml b/.github/workflows/manual-deploy-obscuro-gateway.yml index e1f08b7131..23ae713c23 100644 --- a/.github/workflows/manual-deploy-obscuro-gateway.yml +++ b/.github/workflows/manual-deploy-obscuro-gateway.yml @@ -52,7 +52,7 @@ jobs: - name: Build and Push Docker Image run: | - DOCKER_BUILDKIT=1 docker build -t ${{ vars.DOCKER_BUILD_TAG_GATEWAY }} -f ./tools/walletextension/Dockerfile . + DOCKER_BUILDKIT=1 docker build --build-arg TESTNET_TYPE=${{ github.event.inputs.testnet_type }} -t ${{ vars.DOCKER_BUILD_TAG_GATEWAY }} -f ./tools/walletextension/Dockerfile . docker push ${{ vars.DOCKER_BUILD_TAG_GATEWAY }} # This will fail some deletions due to resource dependencies ( ie. you must first delete the vm before deleting the disk) diff --git a/.gitignore b/.gitignore index 9ed5a8b472..e57a7bf2d8 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,9 @@ docs/_site # db files tools/walletextension/main/.obscuro +# static files generated by npm run build +tools/walletextension/api/static + # contracts # Logs diff --git a/tools/walletextension/Dockerfile b/tools/walletextension/Dockerfile index f917c7804b..bbc53fcbc1 100644 --- a/tools/walletextension/Dockerfile +++ b/tools/walletextension/Dockerfile @@ -9,6 +9,7 @@ FROM golang:1.20-alpine3.18 as system # set the base libs to build / run RUN apk add build-base bash git ENV CGO_ENABLED=1 +ARG TESTNET_TYPE # Standard build stage that initializes the go dependencies FROM system as get-dependencies @@ -16,6 +17,10 @@ FROM system as get-dependencies # setup container data structure RUN mkdir -p /home/obscuro/go-obscuro + +# Install Node.js and npm (needed for frontend) +RUN apk add --update nodejs npm + # Ensures container layer caching when dependencies are not changed WORKDIR /home/obscuro/go-obscuro COPY go.mod . @@ -27,7 +32,22 @@ FROM get-dependencies as build-wallet # make sure the geth network code is available COPY . /home/obscuro/go-obscuro -# build the contract deployer exec +# Create .env file for frontend +WORKDIR /home/obscuro/go-obscuro/tools/walletextension/frontend +RUN if [ "$TESTNET_TYPE" = "dev-testnet" ]; then \ + echo "NEXT_PUBLIC_API_GATEWAY_URL=https://dev-testnet.obscu.ro" > .env; \ + elif [ "$TESTNET_TYPE" = "uat-testnet" ]; then \ + echo "NEXT_PUBLIC_API_GATEWAY_URL=https://uat-testnet.obscu.ro" > .env; \ + elif [ "$TESTNET_TYPE" = "sepolia-testnet" ]; then \ + echo "NEXT_PUBLIC_API_GATEWAY_URL=https://testnet.obscu.ro" > .env; \ + else \ + echo "NEXT_PUBLIC_API_GATEWAY_URL=http://127.0.0.1:3000" > .env; \ + fi +# Run npm build for frontend +RUN npm install +RUN npm run build + +# build the gateway executable WORKDIR /home/obscuro/go-obscuro/tools/walletextension/main RUN --mount=type=cache,target=/root/.cache/go-build \ go build -o ../bin/wallet_extension_linux diff --git a/tools/walletextension/README.md b/tools/walletextension/README.md index c7ef89fe32..d11b4da5cc 100644 --- a/tools/walletextension/README.md +++ b/tools/walletextension/README.md @@ -1,9 +1,12 @@ -# 👛 The Ten wallet extension +# The Ten gateway See the documentation [here](https://docs.obscu.ro/wallet-extension/wallet-extension/). ## Developer notes +Running gateway frontend locally requires building static files first. +To do that, run `npm run build` in `tools/walletextension/frontend` folder. + The precompiled binaries for macOS ARM64, macOS AMD64, Windows AMD64 and Linux AMD64 can be built by running the following commands from the `tools/walletextension/main` folder: diff --git a/tools/walletextension/api/server.go b/tools/walletextension/api/server.go index b725975ef4..dc6b512e25 100644 --- a/tools/walletextension/api/server.go +++ b/tools/walletextension/api/server.go @@ -11,13 +11,11 @@ import ( "github.com/ten-protocol/go-ten/tools/walletextension/common" ) -//go:embed static -//go:embed staticOG +//go:embed all:static var staticFiles embed.FS const ( - staticDir = "static" - staticDirOG = "staticOG" + staticDir = "static" ) // Server is a wrapper for the http server @@ -67,13 +65,7 @@ func createHTTPServer(address string, routes []Route) *http.Server { if err != nil { panic(fmt.Sprintf("could not serve static files. Cause: %s", err)) } - serveMux.Handle(common.PathViewingKeys, http.StripPrefix(common.PathViewingKeys, http.FileServer(http.FS(noPrefixStaticFiles)))) - - noPrefixStaticFilesOG, err := fs.Sub(staticFiles, staticDirOG) - if err != nil { - panic(fmt.Errorf("could not serve static files. Cause: %w", err).Error()) - } - serveMux.Handle(common.PathObscuroGateway, http.StripPrefix(common.PathObscuroGateway, http.FileServer(http.FS(noPrefixStaticFilesOG)))) + serveMux.Handle(common.PathObscuroGateway, http.StripPrefix(common.PathObscuroGateway, http.FileServer(http.FS(noPrefixStaticFiles)))) // Creates the actual http server with a ReadHeaderTimeout to avoid Potential Slowloris Attack server := &http.Server{Addr: address, Handler: serveMux, ReadHeaderTimeout: common.ReaderHeadTimeout} diff --git a/tools/walletextension/api/static/favicon-32x32.png b/tools/walletextension/api/static/favicon-32x32.png deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tools/walletextension/api/staticOG/favicon.ico b/tools/walletextension/api/static/favicon.ico similarity index 100% rename from tools/walletextension/api/staticOG/favicon.ico rename to tools/walletextension/api/static/favicon.ico diff --git a/tools/walletextension/api/static/index.html b/tools/walletextension/api/static/index.html deleted file mode 100644 index 8bdd8fb131..0000000000 --- a/tools/walletextension/api/static/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - Obscuro | Generate viewing key - - - - - - - - -

â— . Hello frens. Welcome to the Obscuro wallet extension! â— .

- - - -

- - - \ No newline at end of file diff --git a/tools/walletextension/api/static/javascript.js b/tools/walletextension/api/static/javascript.js deleted file mode 100644 index b57d372df1..0000000000 --- a/tools/walletextension/api/static/javascript.js +++ /dev/null @@ -1,77 +0,0 @@ -const eventClick = "click"; -const eventDomLoaded = "DOMContentLoaded"; -const idGenerateViewingKey = "generateViewingKey"; -const idStatus = "status"; -const pathGenerateViewingKey = "/generateviewingkey/"; -const pathSubmitViewingKey = "/submitviewingkey/"; -const methodPost = "post"; -const jsonHeaders = { - "Accept": "application/json", - "Content-Type": "application/json" -}; -const metamaskRequestAccounts = "eth_requestAccounts"; -const metamaskPersonalSign = "personal_sign"; -const personalSignPrefix = "vk"; - -const initialize = () => { - const generateViewingKeyButton = document.getElementById(idGenerateViewingKey); - const statusArea = document.getElementById(idStatus); - - generateViewingKeyButton.addEventListener(eventClick, async () => { - if (typeof ethereum === "undefined") { - statusArea.innerText = "`ethereum` object is not available. Please install and enable MetaMask." - return - } - - const accounts = await ethereum.request({method: metamaskRequestAccounts}); - if (accounts.length === 0) { - statusArea.innerText = "No MetaMask accounts found." - return - } - // Accounts is "An array of a single, hexadecimal Ethereum address string.", so we grab the single entry at index zero. - const account = accounts[0]; - - const addressJson = {"address": account} - const viewingKeyResp = await fetch( - pathGenerateViewingKey, { - method: methodPost, - headers: jsonHeaders, - body: JSON.stringify(addressJson) - } - ); - if (!viewingKeyResp.ok) { - statusArea.innerText = "Failed to generate viewing key." - return - } - - const viewingKey = await viewingKeyResp.text(); - - const signature = await ethereum.request({ - method: metamaskPersonalSign, - // Without a prefix such as 'vk', personal_sign transforms the data for security reasons. - params: [personalSignPrefix + viewingKey, account] - }).catch(_ => { return -1 }) - if (signature === -1) { - statusArea.innerText = "Failed to sign viewing key." - return - } - - const signedViewingKeyJson = {"signature": signature, "address": account} - const submitViewingKeyResp = await fetch( - pathSubmitViewingKey, { - method: methodPost, - headers: jsonHeaders, - body: JSON.stringify(signedViewingKeyJson) - } - ); - - let checksummedAccount = Web3.utils.toChecksumAddress(account); - if (submitViewingKeyResp.ok) { - statusArea.innerText = `Account: ${checksummedAccount}\nViewing key: ${viewingKey}\nSigned bytes: ${signature}` - } else { - statusArea.innerText = "Failed to submit viewing key to enclave." - } - }) -} - -window.addEventListener(eventDomLoaded, initialize); \ No newline at end of file diff --git a/tools/walletextension/api/static/style.css b/tools/walletextension/api/static/style.css deleted file mode 100644 index 2c9cadb9cd..0000000000 --- a/tools/walletextension/api/static/style.css +++ /dev/null @@ -1,34 +0,0 @@ -html { - font-family: sans-serif; - line-height: 1.5; -} - -body { - color: white; - background-color: black; - margin: 1rem; -} - -a { - color: white; -} - -textarea { - color: white; - background-color: black; - font-family: sans-serif; - border: solid 1px white; - border-radius: 3px; -} - -button { - color: white; - background-color: black; - border: solid 1px white; - border-radius: 3px; -} - -/*Formats the block-decoder form.*/ -form { display: table; } -label { display: table-cell; } -input { display: table-cell; } \ No newline at end of file diff --git a/tools/walletextension/api/staticOG/MetaMaskIcon.png b/tools/walletextension/api/staticOG/MetaMaskIcon.png deleted file mode 100644 index 89f257be07..0000000000 Binary files a/tools/walletextension/api/staticOG/MetaMaskIcon.png and /dev/null differ diff --git a/tools/walletextension/api/staticOG/Metamask Network Icon.png b/tools/walletextension/api/staticOG/Metamask Network Icon.png deleted file mode 100644 index 1e966077f8..0000000000 Binary files a/tools/walletextension/api/staticOG/Metamask Network Icon.png and /dev/null differ diff --git a/tools/walletextension/api/staticOG/check.svg b/tools/walletextension/api/staticOG/check.svg deleted file mode 100644 index d7a74c26cc..0000000000 --- a/tools/walletextension/api/staticOG/check.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tools/walletextension/api/staticOG/copy.svg b/tools/walletextension/api/staticOG/copy.svg deleted file mode 100644 index d66a75b562..0000000000 --- a/tools/walletextension/api/staticOG/copy.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/tools/walletextension/api/staticOG/favicon-32x32.png b/tools/walletextension/api/staticOG/favicon-32x32.png deleted file mode 100644 index ec512be020..0000000000 Binary files a/tools/walletextension/api/staticOG/favicon-32x32.png and /dev/null differ diff --git a/tools/walletextension/api/staticOG/index.html b/tools/walletextension/api/staticOG/index.html deleted file mode 100644 index bb27540b89..0000000000 --- a/tools/walletextension/api/staticOG/index.html +++ /dev/null @@ -1,132 +0,0 @@ - - - - Obscuro Gateway - - - - - - - - - - -
- -
- -
- -
-
-
-
-
-

Welcome to the Obscuro Gateway

-
-
- Three clicks to setup encrypted communication between MetaMask - and the Obscuro Network.: -
    -
  1. Hit Connect to Obscuro and start your journey
  2. -
  3. - Allow MetaMask to switch networks to the Obscuro Testnet -
  4. -
  5. - Sign the Signature Request (this is not a - transaction) -
  6. -
-
- - - - - -
-
- You are set up on Obscuro. Your connected accounts are below: -
- - - - - - Connect to Obscuro Testnet - - - - - - - - - - - -
AccountConnected
- - -

-
-
-
-
-
-
-
-
- -
- - - - diff --git a/tools/walletextension/api/staticOG/javascript.js b/tools/walletextension/api/staticOG/javascript.js deleted file mode 100644 index 51db8aec41..0000000000 --- a/tools/walletextension/api/staticOG/javascript.js +++ /dev/null @@ -1,539 +0,0 @@ -const eventClick = "click"; -const eventDomLoaded = "DOMContentLoaded"; -const idJoin = "join"; -const idMoreInfo = "moreInfo"; -const idRevokeUserID = "revokeUserID"; -const idStatus = "status"; -const idAccountsTable = "accountsTable"; -const idTableBody = "tableBody"; -const idInformation = "information"; -const idInformation2 = "information2"; -const idWelcome = "welcome"; -const idRequestTokens = "requestTokens"; -const idBegin = "begin-box"; -const idSpinner = "spinner"; -const obscuroGatewayVersion = "v1"; -const pathJoin = obscuroGatewayVersion + "/join/"; -const pathAuthenticate = obscuroGatewayVersion + "/authenticate/"; -const pathQuery = obscuroGatewayVersion + "/query/"; -const pathRevoke = obscuroGatewayVersion + "/revoke/"; -const pathVersion = "/version/"; -const obscuroChainIDDecimal = 443; -const userIDHexLength = 40; -const methodPost = "post"; -const methodGet = "get"; -const jsonHeaders = { - Accept: "application/json", - "Content-Type": "application/json", -}; - -const metamaskPersonalSign = "personal_sign"; -const obscuroChainIDHex = "0x" + obscuroChainIDDecimal.toString(16); // Convert to hexadecimal and prefix with '0x' - -function isValidUserIDFormat(value) { - return typeof value === "string" && value.length === userIDHexLength; -} - -let obscuroGatewayAddress = - window.location.protocol + "//" + window.location.host; - -const obscuroscanLink = "https://testnet.obscuroscan.com"; - -let provider = null; - -async function fetchAndDisplayVersion() { - try { - const versionResp = await fetch(pathVersion, { - method: methodGet, - headers: jsonHeaders, - }); - if (!versionResp.ok) { - throw new Error("Failed to fetch the version"); - } - - let response = await versionResp.text(); - - const versionDiv = document.getElementById("versionDisplay"); - versionDiv.textContent = "Version: " + response; - } catch (error) { - console.error("Error fetching the version:", error); - } -} - -function getNetworkName(gatewayAddress) { - switch (gatewayAddress) { - case "https://uat-testnet.obscu.ro": - return "Obscuro UAT-Testnet"; - case "https://dev-testnet.obscu.ro": - return "Obscuro Dev-Testnet"; - default: - return "Obscuro Testnet"; - } -} - -function getRPCFromUrl(gatewayAddress) { - // get the correct RPC endpoint for each network - switch (gatewayAddress) { - // case 'https://testnet.obscu.ro': - // return 'https://rpc.sepolia-testnet.obscu.ro' - case "https://sepolia-testnet.obscu.ro": - return "https://rpc.sepolia-testnet.obscu.ro"; - case "https://uat-testnet.obscu.ro": - return "https://rpc.uat-testnet.obscu.ro"; - case "https://dev-testnet.obscu.ro": - return "https://rpc.dev-testnet.obscu.ro"; - default: - return gatewayAddress; - } -} - -async function addNetworkToMetaMask(ethereum, userID, chainIDDecimal) { - // add network to MetaMask - try { - await ethereum.request({ - method: "wallet_addEthereumChain", - params: [ - { - chainId: obscuroChainIDHex, - chainName: getNetworkName(obscuroGatewayAddress), - nativeCurrency: { - name: "Sepolia Ether", - symbol: "ETH", - decimals: 18, - }, - rpcUrls: [ - getRPCFromUrl(obscuroGatewayAddress) + - "/" + - obscuroGatewayVersion + - "/?token=" + - userID, - ], - blockExplorerUrls: ["https://testnet.obscuroscan.io"], - }, - ], - }); - } catch (error) { - console.error(error); - return false; - } - return true; -} - -async function authenticateAccountWithObscuroGatewayEIP712(ethereum, account, userID) { - const isAuthenticated = await accountIsAuthenticated(account, userID) - - if (isAuthenticated) { - return "Account is already authenticated" - } - - const typedData = { - types: { - EIP712Domain: [ - { name: "name", type: "string" }, - { name: "version", type: "string" }, - { name: "chainId", type: "uint256" }, - ], - Authentication: [ - { name: "Encryption Token", type: "address" }, - ], - }, - primaryType: "Authentication", - domain: { - name: "Ten", - version: "1.0", - chainId: obscuroChainIDDecimal, - }, - message: { - "Encryption Token": "0x"+userID - }, - }; - - const data = JSON.stringify(typedData); - const signature = await ethereum.request({ - method: "eth_signTypedData_v4", - params: [account, data], - }).catch(_ => { - console.log("signing failed!") - return -1; - }); - - - const authenticateUserURL = pathAuthenticate+"?token="+userID - const authenticateFields = {"signature": signature, "address": account } - const authenticateResp = await fetch( - authenticateUserURL, { - method: methodPost, - headers: jsonHeaders, - body: JSON.stringify(authenticateFields) - } - ); - return await authenticateResp.text() -} - - -async function accountIsAuthenticated(account, userID) { - const queryAccountUserID = pathQuery + "?token=" + userID + "&a=" + account; - const isAuthenticatedResponse = await fetch(queryAccountUserID, { - method: methodGet, - headers: jsonHeaders, - }); - let response = await isAuthenticatedResponse.text(); - let jsonResponseObject = JSON.parse(response); - return jsonResponseObject.status; -} - -async function revokeUserID(userID) { - const queryAccountUserID = pathRevoke + "?token=" + userID; - const revokeResponse = await fetch(queryAccountUserID, { - method: methodGet, - headers: jsonHeaders, - }); - return revokeResponse.ok; -} - -function getRandomIntAsString(min, max) { - min = Math.ceil(min); - max = Math.floor(max); - const randomInt = Math.floor(Math.random() * (max - min + 1)) + min; - return randomInt.toString(); -} - -async function getUserID() { - try { - if (await isObscuroChain()) { - return await provider.send("eth_getStorageAt", [ - "getUserID", - getRandomIntAsString(0, 1000), - null, - ]); - } else { - return null; - } - } catch (e) { - console.log(e); - return null; - } -} - -async function connectAccounts() { - try { - return await window.ethereum.request({ method: "eth_requestAccounts" }); - } catch (error) { - // TODO: Display warning to user to allow it and refresh page... - console.error("User denied account access:", error); - return null; - } -} - -async function isMetamaskConnected() { - let accounts; - try { - accounts = await provider.listAccounts(); - return accounts.length > 0; - } catch (error) { - console.log("Unable to get accounts"); - } - return false; -} - -// Check if Metamask is available on mobile or as a plugin in browser -// (https://docs.metamask.io/wallet/how-to/integrate-with-mobile/) -function checkIfMetamaskIsLoaded() { - if (window.ethereum) { - handleEthereum(); - } else { - const statusArea = document.getElementById(idStatus); - const table = document.getElementById("accountsTable"); - table.style.display = "none"; - statusArea.innerText = "Connecting to Metamask..."; - window.addEventListener("ethereum#initialized", handleEthereum, { - once: true, - }); - - // If the event is not dispatched by the end of the timeout, - // the user probably doesn't have MetaMask installed. - setTimeout(handleEthereum, 3000); // 3 seconds - } -} - -function handleEthereum() { - const { ethereum } = window; - if (ethereum && ethereum.isMetaMask) { - provider = new ethers.providers.Web3Provider(window.ethereum); - initialize(); - } else { - const statusArea = document.getElementById(idStatus); - statusArea.innerText = "Please install MetaMask to use Obscuro Gateway."; - } -} - -async function populateAccountsTable(document, tableBody, userID) { - tableBody.innerHTML = ""; - const accounts = await provider.listAccounts(); - for (const account of accounts) { - const row = document.createElement("tr"); - - const accountCell = document.createElement("td"); - - const accountLink = document.createElement("a"); - accountLink.href = obscuroscanLink; - accountLink.textContent = account; - accountLink.target = "_blank"; - accountCell.appendChild(accountLink); - - row.appendChild(accountCell); - - const statusCell = document.createElement("td"); - - let x = await accountIsAuthenticated(account, userID); - - if (x === true) { - statusCell.textContent = "\u2705"; - } else { - const connectButton = document.createElement("button"); - connectButton.textContent = "Connect"; - connectButton.style.cursor = "pointer"; - connectButton.addEventListener("click", async (event) => { - event.preventDefault(); - await authenticateAccountWithObscuroGatewayEIP712(ethereum, account, userID); - }); - statusCell.appendChild(connectButton); - } - - const copyIcon = document.createElement("img"); - copyIcon.src = "./copy.svg"; - copyIcon.style.cursor = "pointer"; - copyIcon.style.width = "20px"; - - copyIcon.addEventListener("click", () => { - const textToCopy = accountCell.textContent; - const tempInput = document.createElement("input"); - document.body.appendChild(tempInput); - tempInput.value = textToCopy; - tempInput.select(); - document.execCommand("copy"); - document.body.removeChild(tempInput); - - copyIcon.src = "./check.svg"; - setTimeout(() => { - copyIcon.src = "./copy.svg"; - }, 2000); - }); - - accountCell.appendChild(copyIcon); - row.appendChild(statusCell); - - tableBody.appendChild(row); - } -} - -async function isObscuroChain() { - let currentChain = await ethereum.request({ method: "eth_chainId" }); - return currentChain === obscuroChainIDHex; -} - -async function switchToObscuroNetwork() { - try { - await ethereum.request({ - method: "wallet_switchEthereumChain", - params: [{ chainId: obscuroChainIDHex }], - }); - return 0; - } catch (switchError) { - return switchError.code; - } - return -1; -} - -const initialize = async () => { - const joinButton = document.getElementById(idJoin); - const moreInfoButton = document.getElementById(idMoreInfo); - const revokeUserIDButton = document.getElementById(idRevokeUserID); - const statusArea = document.getElementById(idStatus); - const informationArea = document.getElementById(idInformation); - const informationArea2 = document.getElementById(idInformation2); - const welcome = document.getElementById(idWelcome); - const requestTokens = document.getElementById(idRequestTokens); - const beginBox = document.getElementById(idBegin); - const spinner = document.getElementById(idSpinner); - - const accountsTable = document.getElementById(idAccountsTable); - const tableBody = document.getElementById(idTableBody); - - // getUserID from the gateway with getStorageAt method - let userID = await getUserID(); - - function displayOnlyJoin() { - joinButton.style.display = "block"; - moreInfoButton.style.display = "block"; - revokeUserIDButton.style.display = "none"; - requestTokens.style.display = "none"; - accountsTable.style.display = "none"; - informationArea.style.display = "block"; - informationArea2.style.display = "none"; - welcome.style.display = "block"; - - beginBox.style.visibility = "visible"; - spinner.style.visibility = "hidden"; - } - - async function displayConnectedAndJoinedSuccessfully() { - joinButton.style.display = "none"; - moreInfoButton.style.display = "none"; - informationArea.style.display = "none"; - informationArea2.style.display = "block"; - revokeUserIDButton.style.display = "block"; - accountsTable.style.display = "block"; - welcome.style.display = "none"; - requestTokens.style.display = "block"; - - await populateAccountsTable(document, tableBody, userID); - } - - async function displayCorrectScreenBasedOnMetamaskAndUserID() { - // check if we are on Obscuro Chain - if (await isObscuroChain()) { - // check if we have valid userID in rpcURL - if (isValidUserIDFormat(userID)) { - return await displayConnectedAndJoinedSuccessfully(); - } - } - return displayOnlyJoin(); - } - - // load the current version - await fetchAndDisplayVersion(); - - await displayCorrectScreenBasedOnMetamaskAndUserID(); - - joinButton.addEventListener(eventClick, async () => { - // clean up any previous errors - statusArea.innerText = ""; - // check if we are on an obscuro chain - if (await isObscuroChain()) { - userID = await getUserID(); - if (!isValidUserIDFormat(userID)) { - statusArea.innerText = - "Existing Obscuro network detected in MetaMask. Please remove before hitting begin"; - } - } else { - // we are not on an Obscuro network - try to switch - let switched = await switchToObscuroNetwork(); - // error 4902 means that the chain does not exist - if (switched === 4902 || !isValidUserIDFormat(await getUserID())) { - // join the network - const joinResp = await fetch(pathJoin, { - method: methodGet, - headers: jsonHeaders, - }); - if (!joinResp.ok) { - console.log("Error joining Obscuro Gateway"); - statusArea.innerText = - "Error joining Obscuro Gateway. Please try again later."; - return; - } - userID = await joinResp.text(); - - // add Obscuro network - await addNetworkToMetaMask(window.ethereum, userID); - } - - // we have to check if user has accounts connected with metamask - and promt to connect if not - if (!(await isMetamaskConnected())) { - await connectAccounts(); - } - - // connect all accounts - // Get an accounts and prompt user to sign joining with a selected account - const accounts = await provider.listAccounts(); - if (accounts.length === 0) { - statusArea.innerText = "No MetaMask accounts found."; - return; - } - - userID = await getUserID(); - beginBox.style.visibility = "hidden"; - spinner.style.visibility = "visible"; - for (const account of accounts) { - await authenticateAccountWithObscuroGatewayEIP712(ethereum, account, userID); - accountsTable.style.display = "block"; - await populateAccountsTable(document, tableBody, userID); - } - - // if accounts change we want to give user chance to add them to Obscuro - window.ethereum.on("accountsChanged", async function (accounts) { - if (isValidUserIDFormat(await getUserID())) { - userID = await getUserID(); - for (const account of accounts) { - await authenticateAccountWithObscuroGatewayEIP712( - ethereum, - account, - userID - ); - accountsTable.style.display = "block"; - await populateAccountsTable(document, tableBody, userID); - } - } - }); - - await displayConnectedAndJoinedSuccessfully(); - beginBox.style.visibility = "visible"; - spinner.style.visibility = "hidden"; - } - }); - - revokeUserIDButton.addEventListener(eventClick, async () => { - beginBox.style.visibility = "hidden"; - spinner.style.visibility = "visible"; - let result = await revokeUserID(userID); - - await populateAccountsTable(document, tableBody, userID); - - if (result) { - displayOnlyJoin(); - } else { - statusArea.innerText = "Revoking UserID failed"; - } - }); - - beginBox.style.visibility = "visible"; - spinner.style.visibility = "hidden"; -}; - -$("#moreInfo").click(function () { - var buttonId = "four"; - $("#modal-container").removeAttr("class").addClass(buttonId); - $("body").addClass("modal-active"); -}); - -$("#modal-container").click(function () { - $(this).addClass("out"); - $(this).addClass("disappear"); - $("body").removeClass("modal-active"); -}); - -const chars = - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+;:'|?/><~"; - -const randomChar = () => chars[Math.floor(Math.random() * (chars.length - 1))], - randomString = (length) => Array.from(Array(length)).map(randomChar).join(""); - -const card = document.querySelector(".card"), - letters = card.querySelector(".card-letters"); - -const handleOnMove = (e) => { - const rect = card.getBoundingClientRect(), - x = e.clientX - rect.left, - y = e.clientY - rect.top; - - letters.style.setProperty("--x", `${x}px`); - letters.style.setProperty("--y", `${y}px`); - - letters.innerText = randomString(1700); -}; - -card.onmousemove = (e) => handleOnMove(e); - -card.ontouchmove = (e) => handleOnMove(e.touches[0]); - -window.addEventListener(eventDomLoaded, checkIfMetamaskIsLoaded); diff --git a/tools/walletextension/api/staticOG/style.css b/tools/walletextension/api/staticOG/style.css deleted file mode 100644 index d8b58d4695..0000000000 --- a/tools/walletextension/api/staticOG/style.css +++ /dev/null @@ -1,605 +0,0 @@ -html { - height: 100%; -} -body { - margin: 0; - padding: 0; - font-family: sans-serif; - background: linear-gradient(#000, #141e30); - color: #f6f6f6; - font-size: 1rem; - font-weight: 400; - line-height: 1.6; - font-style: normal; - font-stretch: normal; -} - -ol li { - padding: 5px 0px; -} - -.logo { - width: 100px; - height: 100px; - object-fit: contain; - padding: 10px; -} - -.begin-box { - padding: 40px; - background: rgba(0, 0, 0, 0.5); - box-sizing: border-box; - box-shadow: 0 15px 25px rgba(0, 0, 0, 0.6); - border-radius: 10px; - visibility: hidden; - width: 800px; -} - -.begin-box h2 { - margin: 0 0 30px; - padding: 0; - color: #fff; - text-align: center; -} - -.begin-box .user-box { - position: relative; -} - -.begin-box .user-box input { - width: 100%; - padding: 10px 0; - font-size: 16px; - color: #fff; - margin-bottom: 30px; - border: none; - border-bottom: 1px solid #fff; - outline: none; - background: transparent; -} -.begin-box .user-box label { - position: absolute; - top: 0; - left: 0; - padding: 10px 0; - font-size: 16px; - color: #fff; - pointer-events: none; - transition: 0.5s; -} - -.begin-box .user-box input:focus ~ label, -.begin-box .user-box input:valid ~ label { - top: -20px; - left: 0; - color: #03e9f4; - font-size: 12px; -} - -.begin-box form a { - position: relative; - display: inline-block; - padding: 10px 20px; - color: #03e9f4; - font-size: 16px; - text-decoration: none; - text-transform: uppercase; - overflow: hidden; - transition: 0.5s; - margin-top: 40px; - letter-spacing: 4px; -} - -.begin-box a:hover { - background: #03e9f4; - color: #fff; - border-radius: 5px; - box-shadow: 0 0 5px #03e9f4, 0 0 25px #03e9f4, 0 0 50px #03e9f4, - 0 0 100px #03e9f4; -} - -.begin-box a span { - position: absolute; - display: block; -} - -.begin-box a span:nth-child(1) { - top: 0; - left: -100%; - width: 100%; - height: 2px; - background: linear-gradient(90deg, transparent, #03e9f4); - animation: btn-anim1 1s linear infinite; -} - -@keyframes btn-anim1 { - 0% { - left: -100%; - } - 50%, - 100% { - left: 100%; - } -} - -.begin-box a span:nth-child(2) { - top: -100%; - right: 0; - width: 2px; - height: 100%; - background: linear-gradient(180deg, transparent, #03e9f4); - animation: btn-anim2 1s linear infinite; - animation-delay: 0.25s; -} - -@keyframes btn-anim2 { - 0% { - top: -100%; - } - 50%, - 100% { - top: 100%; - } -} - -.begin-box a span:nth-child(3) { - bottom: 0; - right: -100%; - width: 100%; - height: 2px; - background: linear-gradient(270deg, transparent, #03e9f4); - animation: btn-anim3 1s linear infinite; - animation-delay: 0.5s; -} - -@keyframes btn-anim3 { - 0% { - right: -100%; - } - 50%, - 100% { - right: 100%; - } -} - -.begin-box a span:nth-child(4) { - bottom: -100%; - left: 0; - width: 2px; - height: 100%; - background: linear-gradient(360deg, transparent, #03e9f4); - animation: btn-anim4 1s linear infinite; - animation-delay: 0.75s; -} - -@keyframes btn-anim4 { - 0% { - bottom: -100%; - } - 50%, - 100% { - bottom: 100%; - } -} - -a { - color: white; -} - -textarea { - color: white; - background-color: black; - font-family: sans-serif; - border: solid 1px white; - border-radius: 3px; -} - -button { - color: white; - background-color: black; - border: solid 1px white; - border-radius: 3px; -} - -/*Formats the block-decoder form.*/ -form { - display: table; -} -label { - display: table-cell; -} -input { - display: table-cell; -} - -/* basic style for the accounts table*/ -table { - display: none; - border-collapse: collapse; - width: 100%; - margin: auto; /* Center the table */ - background-color: #000000; /* Dark Background */ - color: white; /* Light Text */ - margin-top: 20px; -} -th, -td { - border: 1px solid #666; /* Darker Border */ - padding: 15px; - text-align: center; -} - -td a { - color: white !important; - display: initial !important; - letter-spacing: 1px !important; - text-decoration: underline !important; -} - -td a:hover { - color: #03e9f4 !important; - background: none !important; - border: none !important; - box-shadow: none !important; -} - -th { - background-color: #111111; /* Slightly Darker Header */ -} - -h2 { - font-weight: normal; -} - -#join { - margin: auto; -} - -#moreInfo { - width: 80px; -} - -#versionDisplay { - position: fixed; - bottom: 10px; - right: 10px; - font-size: 0.5rem; -} - -#status { - color: red; -} - -.joined-button { - margin-top: 10px; - padding: 10px; -} - -#revokeUserID { - display: none; -} - -.spinner { - position: relative; - margin: auto; - box-sizing: border-box; - background-clip: padding-box; - width: 200px; - height: 200px; - border-radius: 100px; - border: 4px solid rgba(255, 255, 255, 0.1); - -webkit-mask: linear-gradient(rgba(0, 0, 0, 0.1), #000000 90%); - transform-origin: 50% 60%; - transform: perspective(200px) rotateX(66deg); - animation: spinner-wiggle 1.2s infinite; -} - -@keyframes spinner-wiggle { - 30% { - transform: perspective(200px) rotateX(66deg); - } - 40% { - transform: perspective(200px) rotateX(65deg); - } - 50% { - transform: perspective(200px) rotateX(68deg); - } - 60% { - transform: perspective(200px) rotateX(64deg); - } -} -.spinner:before, -.spinner:after { - content: ""; - position: absolute; - margin: -4px; - box-sizing: inherit; - width: inherit; - height: inherit; - border-radius: inherit; - opacity: 0.05; - border: inherit; - border-color: transparent; - animation: spinner-spin 1.2s cubic-bezier(0.6, 0.2, 0, 0.8) infinite, - spinner-fade 1.2s linear infinite; -} - -.spinner:before { - border-top-color: #66e6ff; -} - -.spinner:after { - border-top-color: #f0db75; - animation-delay: 0.3s; -} - -@keyframes spinner-spin { - 100% { - transform: rotate(360deg); - } -} -@keyframes spinner-fade { - 20% { - opacity: 0.1; - } - 40% { - opacity: 1; - } - 60% { - opacity: 0.1; - } -} - -.wrapper { - position: relative; - top: 50%; - margin: auto; - width: 800px; - height: 500px; -} - -:root { - --background-rgb: 2 6 23; - --background-light-rgb: 30 41 59; - - --hyperplexed-main-rgb: 41 121 255; - --hyperplexed-main-light-rgb: 56 182 255; - --hyperplexed-secondary-rgb: 42 252 152; - - --card-size: 800px; - --font-size: 0.9rem; -} - -.card-track { - height: 100%; - width: var(--card-size); - display: flex; - align-items: center; - position: relative; -} - -.card-wrapper { - width: 800px; - position: relative; -} - -.card { - display: flex; - align-items: center; - justify-content: center; - aspect-ratio: 1; - position: relative; - border-radius: 2rem; - overflow: hidden; - cursor: pointer; - width: 100%; - height: 100%; -} - -.card-image { - width: 100%; - height: 100%; - align-items: center; - justify-content: center; - position: relative; - z-index: 4; -} - -.card-gradient { - height: 100%; - width: 100%; - position: absolute; - background: radial-gradient( - rgb(var(--background-light-rgb)) 40%, - rgb(var(--hyperplexed-main-rgb)) 50%, - rgb(var(--hyperplexed-main-light-rgb)), - rgb(var(--hyperplexed-secondary-rgb)) - ); - mix-blend-mode: darken; - pointer-events: none; - z-index: 3; -} - -.card-letters { - --x: 0px; - --y: 0px; - position: absolute; - left: 0px; - top: 0px; - height: 85%; - width: 100%; - color: white; - font-size: var(--font-size); - font-weight: 500; - word-wrap: break-word; - opacity: 0; - transition: opacity 400ms; - -webkit-mask-image: radial-gradient( - calc(var(--card-size) * 0.5) circle at var(--x) var(--y), - rgb(255 255 255) 20%, - rgb(255 255 255 / 25%), - transparent - ); - scale: 1.03; -} - -.card:hover .card-letters { - opacity: 1; -} - -* { - box-sizing: border-box; -} - -html.modal-active, -body.modal-active { - overflow: hidden; -} - -#modal-container { - position: fixed; - display: table; - height: 100%; - width: 100%; - top: 0; - left: 0; - transform: scale(0); - z-index: 6; -} -#modal-container.four { - z-index: 5; - transform: scale(1); -} -#modal-container.four .modal-background { - background: rgba(0, 0, 0, 0.7); -} -#modal-container.four .modal-background .modal { - animation: blowUpModal 0.5s cubic-bezier(0.165, 0.84, 0.44, 1) forwards; -} -#modal-container.four + .content { - z-index: 6; - animation: blowUpContent 0.5s cubic-bezier(0.165, 0.84, 0.44, 1) forwards; -} -#modal-container.four.out .modal-background .modal { - animation: blowUpModalTwo 0.5s cubic-bezier(0.165, 0.84, 0.44, 1) forwards; -} -#modal-container.four.out + .content { - animation: blowUpContentTwo 0.5s cubic-bezier(0.165, 0.84, 0.44, 1) forwards; -} -.disappear { - animation: blowUpModalTwo 0.5s cubic-bezier(0.165, 0.84, 0.44, 1) forwards; -} -#modal-container .modal-background { - display: table-cell; - background: rgba(0, 0, 0, 0.8); - text-align: center; - vertical-align: middle; -} -#modal-container .modal-background .modal { - background: #000; - padding: 50px; - display: inline-block; - border-radius: 3px; - position: relative; - width: 800px; -} -#modal-container .modal-background .modal p { - text-align: left; -} -#modal-container .modal-background .modal h2 { - font-size: 25px; - line-height: 25px; - margin-bottom: 15px; -} -#modal-container .modal-background .modal .modal-svg { - position: absolute; - top: 0; - left: 0; - height: 100%; - width: 100%; - border-radius: 3px; -} -#modal-container .modal-background .modal .modal-svg rect { - stroke: #000; - stroke-width: 2px; - stroke-dasharray: 778; - stroke-dashoffset: 778; -} - -.content { - min-height: 100%; - height: 100%; - background: #000; - position: relative; - z-index: 5; -} -.content h1 { - padding: 75px 0 30px 0; - text-align: center; - font-size: 30px; - line-height: 30px; -} -.content .buttons { - max-width: 800px; - margin: 0 auto; - padding: 0; - text-align: center; -} -.content .buttons .button { - display: inline-block; - text-align: center; - padding: 10px 15px; - margin: 10px; - background: red; - font-size: 18px; - background-color: #efefef; - border-radius: 3px; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); - cursor: pointer; -} -.content .buttons .button:hover { - color: white; - background: #009bd5; -} - -@keyframes blowUpContent { - 0% { - transform: scale(1); - opacity: 1; - } - 99.9% { - transform: scale(2); - opacity: 0; - } - 100% { - transform: scale(0); - } -} -@keyframes blowUpContentTwo { - 0% { - transform: scale(2); - opacity: 0; - } - 100% { - transform: scale(1); - opacity: 1; - } -} -@keyframes blowUpModal { - 0% { - transform: scale(0); - } - 100% { - transform: scale(1); - } -} -@keyframes blowUpModalTwo { - 0% { - transform: scale(1); - opacity: 1; - } - 100% { - transform: scale(0); - opacity: 0; - } -} diff --git a/tools/walletextension/api/staticOG/ten.svg b/tools/walletextension/api/staticOG/ten.svg deleted file mode 100644 index 1c51495c5b..0000000000 --- a/tools/walletextension/api/staticOG/ten.svg +++ /dev/null @@ -1,6 +0,0 @@ - -TEN. - diff --git a/tools/walletextension/frontend/next.config.js b/tools/walletextension/frontend/next.config.js index a843cbee09..45ddca84d4 100644 --- a/tools/walletextension/frontend/next.config.js +++ b/tools/walletextension/frontend/next.config.js @@ -1,6 +1,8 @@ /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, + output: 'export', + distDir: '../api/static' } module.exports = nextConfig diff --git a/tools/walletextension/frontend/src/api/gateway.ts b/tools/walletextension/frontend/src/api/gateway.ts index 2e057b3d9d..dbaf0964c6 100644 --- a/tools/walletextension/frontend/src/api/gateway.ts +++ b/tools/walletextension/frontend/src/api/gateway.ts @@ -2,11 +2,12 @@ import { apiRoutes } from "../routes"; import { httpRequest } from "."; import { pathToUrl } from "../routes/router"; import { AuthenticationResponse } from "@/types/interfaces/GatewayInterfaces"; +import { tenGatewayAddress } from "../lib/constants"; export async function fetchVersion(): Promise { return await httpRequest({ method: "get", - url: pathToUrl(apiRoutes.version), + url: tenGatewayAddress + pathToUrl(apiRoutes.version), }); } @@ -16,7 +17,7 @@ export async function accountIsAuthenticated( ): Promise { return await httpRequest({ method: "get", - url: pathToUrl(apiRoutes.queryAccountToken), + url: tenGatewayAddress + pathToUrl(apiRoutes.queryAccountToken), searchParams: { token, a: account, @@ -33,7 +34,7 @@ export const authenticateUser = async ( ) => { return await httpRequest({ method: "post", - url: pathToUrl(apiRoutes.authenticate), + url: tenGatewayAddress + pathToUrl(apiRoutes.authenticate), data: authenticateFields, searchParams: { token, @@ -44,7 +45,7 @@ export const authenticateUser = async ( export async function revokeAccountsApi(token: string): Promise { return await httpRequest({ method: "get", - url: pathToUrl(apiRoutes.revoke), + url: tenGatewayAddress + pathToUrl(apiRoutes.revoke), searchParams: { token, }, @@ -54,6 +55,6 @@ export async function revokeAccountsApi(token: string): Promise { export async function joinTestnet(): Promise { return await httpRequest({ method: "get", - url: pathToUrl(apiRoutes.join), + url: tenGatewayAddress + pathToUrl(apiRoutes.join), }); } diff --git a/tools/walletextension/frontend/src/lib/constants.ts b/tools/walletextension/frontend/src/lib/constants.ts index 8bf15165be..e6079b5fa4 100644 --- a/tools/walletextension/frontend/src/lib/constants.ts +++ b/tools/walletextension/frontend/src/lib/constants.ts @@ -1,4 +1,5 @@ -export const tenGatewayAddress = process.env.NEXT_PUBLIC_API_GATEWAY_URL; +export const tenGatewayAddress = process.env.NEXT_PUBLIC_API_GATEWAY_URL || "http://127.0.0.1:3000"; + export const tenscanLink = "https://testnet.tenscan.com"; export const socialLinks = { diff --git a/tools/walletextension/frontend/src/lib/utils.ts b/tools/walletextension/frontend/src/lib/utils.ts index f4d0c7abfd..483a82f59f 100644 --- a/tools/walletextension/frontend/src/lib/utils.ts +++ b/tools/walletextension/frontend/src/lib/utils.ts @@ -39,22 +39,6 @@ export function getNetworkName() { } } -export function getRPCFromUrl() { - // get the correct RPC endpoint for each network - switch (testnetUrls.default.url) { - // case 'https://testnet.obscu.ro': - // return 'https://rpc.sepolia-testnet.obscu.ro' - case testnetUrls.sepolia.url: - return testnetUrls.sepolia.rpc; - case testnetUrls.uat.url: - return testnetUrls.uat.rpc; - case testnetUrls.dev.url: - return testnetUrls.dev.rpc; - default: - return testnetUrls.default.url; - } -} - export async function isTenChain() { let currentChain = await ethereum.request({ method: "eth_chainId", diff --git a/tools/walletextension/frontend/src/services/useGatewayService.ts b/tools/walletextension/frontend/src/services/useGatewayService.ts index c2920d9076..5b548b4389 100644 --- a/tools/walletextension/frontend/src/services/useGatewayService.ts +++ b/tools/walletextension/frontend/src/services/useGatewayService.ts @@ -2,8 +2,8 @@ import { ToastType } from "@/types/interfaces"; import { joinTestnet } from "../api/gateway"; import { useWalletConnection } from "../components/providers/wallet-provider"; import { showToast } from "../components/ui/use-toast"; -import { SWITCHED_CODE, tenGatewayVersion } from "../lib/constants"; -import { getRPCFromUrl, isTenChain, isValidTokenFormat } from "../lib/utils"; +import {SWITCHED_CODE, tenGatewayAddress, tenGatewayVersion} from "../lib/constants"; +import { isTenChain, isValidTokenFormat } from "../lib/utils"; import { addNetworkToMetaMask, connectAccounts, @@ -45,7 +45,7 @@ const useGatewayService = () => { if (switched === SWITCHED_CODE || (token && !isValidTokenFormat(token))) { const user = await joinTestnet(); const rpcUrls = [ - `${getRPCFromUrl()}/${tenGatewayVersion}/?token=${user}`, + `${tenGatewayAddress}/${tenGatewayVersion}/?token=${user}`, ]; await addNetworkToMetaMask(rpcUrls); } diff --git a/tools/walletextension/main/main.go b/tools/walletextension/main/main.go index 119a3d707c..b9e9ed73ab 100644 --- a/tools/walletextension/main/main.go +++ b/tools/walletextension/main/main.go @@ -74,7 +74,7 @@ func main() { }() walletExtensionAddr := fmt.Sprintf("%s:%d", common.Localhost, config.WalletExtensionPortHTTP) - fmt.Printf("💡 Wallet extension started - visit http://%s/viewingkeys/ to generate an ephemeral viewing key.\n", walletExtensionAddr) + fmt.Printf("💡 Wallet extension started \n") // Some tests rely on seeing this message. Removed in next PR. fmt.Printf("💡 Obscuro Gateway started - visit http://%s to use it.\n", walletExtensionAddr) select {}