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! â— .
-
-Generate viewing key
-
-
-
-
-
\ 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
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- By connecting your wallet to Obscuro and signing the signature
- request you will get a unique user id, which is also your viewing
- key. It is contained in the RPC link and unique for each user. Do
- not share this unless you want others to see the details of your
- transactions.
-
-
-
- Signing the Signature Request is completely secure. It’s not a
- transaction so cannot spend any of your assets and it doesn’t give
- Obscuro control over your account.
-
-
- Basic RPC URL: https://testnet.obscu.ro
-
- Chain ID: 443
-
-
-
-
-
-
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 {}