Skip to content

Commit

Permalink
Merge pull request #10 from mybucks-online/feat/base64-encoded-creden…
Browse files Browse the repository at this point in the history
…tials

Generate / Parse URL
  • Loading branch information
koko37 authored Dec 10, 2024
2 parents 3fea9ad + 78c95a0 commit 61f5ae9
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 41 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"date-fns": "^4.1.0",
"ethers": "^6.13.0",
"focus-trap-react": "^10.2.3",
"nanoid": "^5.0.9",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-idle-timer": "^5.7.2",
Expand Down
52 changes: 29 additions & 23 deletions src/components/Button.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import styled, { css } from "styled-components";

/**
* $variant: primary | secondary | outline
* $variant: primary | secondary | outline | danger
* $size: small | normal | block
*/
const Button = styled.button`
Expand All @@ -13,18 +13,18 @@ const Button = styled.button`
padding: ${({ theme }) => `${theme.sizes.x3s} ${theme.sizes.xl}`};
`
: $size === "block"
? css`
display: block;
width: 100%;
font-size: ${({ theme }) => theme.sizes.base};
font-weight: ${({ theme }) => theme.weights.highlight};
padding: ${({ theme }) => theme.sizes.base};
`
: css`
font-size: ${({ theme }) => theme.sizes.base};
font-weight: ${({ theme }) => theme.weights.highlight};
padding: ${({ theme }) => theme.sizes.base};
`};
? css`
display: block;
width: 100%;
font-size: ${({ theme }) => theme.sizes.base};
font-weight: ${({ theme }) => theme.weights.highlight};
padding: ${({ theme }) => theme.sizes.base};
`
: css`
font-size: ${({ theme }) => theme.sizes.base};
font-weight: ${({ theme }) => theme.weights.highlight};
padding: ${({ theme }) => theme.sizes.base};
`};
${({ $variant }) =>
$variant === "secondary"
Expand All @@ -34,16 +34,22 @@ const Button = styled.button`
border: none;
`
: $variant === "outline"
? css`
color: ${({ theme }) => theme.colors.primary};
background-color: ${({ theme }) => theme.colors.gray25};
border: 1px solid ${({ theme }) => theme.colors.primary};
`
: css`
background-color: ${({ theme }) => theme.colors.primary};
color: ${({ theme }) => theme.colors.gray25};
border: none;
`};
? css`
color: ${({ theme }) => theme.colors.primary};
background-color: ${({ theme }) => theme.colors.gray25};
border: 1px solid ${({ theme }) => theme.colors.primary};
`
: $variant === "danger"
? css`
color: ${({ theme }) => theme.colors.gray25};
background-color: ${({ theme }) => theme.colors.error};
border: none;
`
: css`
background-color: ${({ theme }) => theme.colors.primary};
color: ${({ theme }) => theme.colors.gray25};
border: none;
`};
line-height: 140%;
border-radius: ${({ theme }) => theme.radius.base};
Expand Down
21 changes: 14 additions & 7 deletions src/contexts/Store.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,20 @@ export const StoreContext = createContext({
password: "",
passcode: "",
hash: "",
setup: (p, pc, s, h) => {},
setup: () => {},
reset: () => {},

// evm | tron
network: DEFAULT_NETWORK,
chainId: DEFAULT_CHAIN_ID,
account: null,
updateNetwork: (n, c) => {},
updateNetwork: () => {},

loading: false,
inMenu: false,
openMenu: (m) => {},
openMenu: () => {},
showBalances: false,
setShowBalances: (f) => {},
setShowBalances: () => {},

nativeTokenName: "",
nativeTokenBalance: 0,
Expand All @@ -40,7 +40,7 @@ export const StoreContext = createContext({
fetchBalances: () => {},

selectedTokenAddress: "",
selectToken: (t) => {},
selectToken: () => {},
});

const StoreProvider = ({ children }) => {
Expand Down Expand Up @@ -156,10 +156,17 @@ const StoreProvider = ({ children }) => {
selectToken("");
};

const setup = (pw, pc, h) => {
const setup = (pw, pc, hsh, nw, cid) => {
setPassword(pw);
setPasscode(pc);
setHash(h);
setHash(hsh);

if (nw) {
setNetwork(nw);
}
if (cid) {
setChainId(cid);
}
};

const updateNetwork = (net, id) => {
Expand Down
38 changes: 38 additions & 0 deletions src/lib/conf.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Network } from "alchemy-sdk";
import { Buffer } from "buffer";
import { ethers } from "ethers";
import { nanoid } from "nanoid";
import { scrypt } from "scrypt-js";

const abi = new ethers.AbiCoder();
Expand Down Expand Up @@ -46,6 +47,25 @@ export const generateHash = async (password, passcode, cb = () => {}) => {
export const getEvmPrivateKey = (h) =>
ethers.keccak256(abi.encode(["string"], [h]));

const URL_DELIMITER = "\u0002";

export function generateUrl(password, passcode, network) {
const merged = Buffer.from(
password + URL_DELIMITER + passcode + URL_DELIMITER + network,
"utf-8"
);
const base64Encoded = merged.toString("base64");
const padding = nanoid(12);
return padding.slice(0, 6) + base64Encoded + padding.slice(6);
}

export function parseUrl(token) {
const payload = token.slice(6, token.length - 6);
const base64Decoded = Buffer.from(payload, "base64").toString("utf-8");
const [password, passcode, network] = base64Decoded.split(URL_DELIMITER);
return [password, passcode, network];
}

export const NETWORK = Object.freeze({
EVM: "evm",
TRON: "tron",
Expand Down Expand Up @@ -120,6 +140,24 @@ export const EVM_NETWORKS = [
},
];

export const findNetworkByName = (networkName) => {
if (networkName === NETWORK.TRON) {
return [NETWORK.TRON, 1];
}

const { chainId } = EVM_NETWORKS.find((item) => item.name === networkName);
return [NETWORK.EVM, chainId];
};

export const findNetworkNameByChainId = (network, chainId) => {
if (network === NETWORK.TRON) {
return NETWORK.TRON;
}

const { name } = EVM_NETWORKS.find((item) => item.chainId === chainId);
return name;
};

export const GAS_PRICE = Object.freeze({
HIGH: "high",
AVERAGE: "average",
Expand Down
5 changes: 5 additions & 0 deletions src/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@ export const queryPrice = async (base, quote = "USD") => {
return 0;
}
};

export const clearQueryParams = () => {
const url = window.location.origin + window.location.pathname;
window.history.replaceState({}, document.title, url);
};
42 changes: 34 additions & 8 deletions src/pages/Menu/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import ConfirmPasscodeModal from "@mybucks/components/ConfirmPasscodeModal";
import { Box as BaseBox, Container } from "@mybucks/components/Containers";
import { H3 } from "@mybucks/components/Texts";
import { StoreContext } from "@mybucks/contexts/Store";
import { findNetworkNameByChainId, generateUrl } from "@mybucks/lib/conf";

const Box = styled(BaseBox)`
display: flex;
Expand Down Expand Up @@ -41,34 +42,51 @@ const Button = styled(BaseButton)`
margin-bottom: ${({ theme }) => theme.sizes.x2l};
`;

const BACKUP_PASSWORD = 1;
const BACKUP_PRIVATE_KEY = 2;
const BACKUP_TRANSFER_LINK = 3;

const Menu = () => {
const [confirmPasscode, setConfirmPasscode] = useState(false);
const [isBackupPassword, setIsBackupPassword] = useState(false);
const { openMenu, account, password, passcode } = useContext(StoreContext);
const [nextStep, setNextStep] = useState(0);
const { openMenu, account, password, passcode, network, chainId } =
useContext(StoreContext);

const backupAddress = () => {
copy(account.address);
toast("Address copied into clipboard.");
};

const onClickPassword = () => {
setIsBackupPassword(true);
setNextStep(BACKUP_PASSWORD);
setConfirmPasscode(true);
};

const onClickPrivateKey = () => {
setIsBackupPassword(false);
setNextStep(BACKUP_PRIVATE_KEY);
setConfirmPasscode(true);
};

const onClickGenerateLink = () => {
setNextStep(BACKUP_TRANSFER_LINK);
setConfirmPasscode(true);
};

const onConfirmedPasscode = () => {
setConfirmPasscode(false);
if (isBackupPassword) {
if (nextStep === BACKUP_PASSWORD) {
copy(`${password} : ${passcode}`);
toast("Password copied into clipboard.");
} else {
} else if (nextStep === BACKUP_PRIVATE_KEY) {
copy(account.signer);
toast("Private key copied into clipboard.");
} else if (nextStep === BACKUP_TRANSFER_LINK) {
const networkName = findNetworkNameByChainId(network, chainId);
const link = generateUrl(password, passcode, networkName);
copy(
window.location.origin + window.location.pathname + "?wallet=" + link
);
toast("Wallet link copied into clipboard.");
}
};

Expand All @@ -86,8 +104,16 @@ const Menu = () => {
<Address>{account.address}</Address>

<Button onClick={backupAddress}>Address</Button>
<Button onClick={onClickPassword}>Password</Button>
<Button onClick={onClickPrivateKey}>Private Key</Button>

<Button onClick={onClickPassword} $variant="danger">
Password
</Button>
<Button onClick={onClickPrivateKey} $variant="danger">
Private Key
</Button>
<Button onClick={onClickGenerateLink} $variant="danger">
Transfer Link
</Button>
</Box>
</Container>

Expand Down
33 changes: 32 additions & 1 deletion src/pages/Signin/index.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useContext, useMemo, useState } from "react";
import { useContext, useEffect, useMemo, useState } from "react";
import styled from "styled-components";

import Button from "@mybucks/components/Button";
Expand All @@ -11,7 +11,9 @@ import Progress from "@mybucks/components/Progress";
import { H1 } from "@mybucks/components/Texts";
import { StoreContext } from "@mybucks/contexts/Store";
import {
findNetworkByName,
generateHash,
parseUrl,
PASSCODE_MAX_LENGTH,
PASSCODE_MIN_LENGTH,
PASSWORD_MAX_LENGTH,
Expand Down Expand Up @@ -185,6 +187,35 @@ const SignIn = () => {
[progress]
);

useEffect(() => {
const parseUrlAndSubmit = async () => {
// get "secret" param from URL
const url = new URL(window.location.href);
const params = new URLSearchParams(url.search);
const secret = params.get("wallet");
if (!secret) {
return;
}

// parse password, passcode, network name from "secret" param
const [pwd, pc, nn] = parseUrl(secret);
if (!pwd || !pc || !nn) {
return;
}
const [network, chainId] = findNetworkByName(nn);

// open wallet
setDisabled(true);
const hash = await generateHash(pwd, pc, (p) =>
setProgress(Math.floor(p * 100))
);
setup(pwd, pc, hash, network, chainId);
setDisabled(false);
};

parseUrlAndSubmit();
}, []);

const onSubmit = async () => {
setDisabled(true);
const hash = await generateHash(password, passcode, (p) =>
Expand Down
4 changes: 3 additions & 1 deletion src/pages/network/evm/Home.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import Link from "@mybucks/components/Link";
import NetworkSelector from "@mybucks/components/NetworkSelector";
import { StoreContext } from "@mybucks/contexts/Store";
import { BALANCE_PLACEHOLDER, LOADING_PLACEHOLDER } from "@mybucks/lib/conf";
import { truncate } from "@mybucks/lib/utils";
import { clearQueryParams, truncate } from "@mybucks/lib/utils";
import TokenBalanceRow from "@mybucks/pages/network/common/TokenBalanceRow";
import media from "@mybucks/styles/media";

Expand Down Expand Up @@ -177,6 +177,8 @@ const EvmHome = () => {
const close = () => {
reset();
copy("");

clearQueryParams();
};

return (
Expand Down
4 changes: 3 additions & 1 deletion src/pages/network/tron/Home.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import Link from "@mybucks/components/Link";
import NetworkSelector from "@mybucks/components/NetworkSelector";
import { StoreContext } from "@mybucks/contexts/Store";
import { BALANCE_PLACEHOLDER, LOADING_PLACEHOLDER } from "@mybucks/lib/conf";
import { truncate } from "@mybucks/lib/utils";
import { clearQueryParams, truncate } from "@mybucks/lib/utils";
import TokenBalanceRow from "@mybucks/pages/network/common/TokenBalanceRow";
import media from "@mybucks/styles/media";

Expand Down Expand Up @@ -192,6 +192,8 @@ const TronHome = () => {
const close = () => {
reset();
copy("");

clearQueryParams();
};

return (
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2915,6 +2915,11 @@ nanoid@^3.3.7:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==

nanoid@^5.0.9:
version "5.0.9"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.0.9.tgz#977dcbaac055430ce7b1e19cf0130cea91a20e50"
integrity sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==

natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
Expand Down

0 comments on commit 61f5ae9

Please sign in to comment.