Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release Adena Wallet 'v1.8.1' #285

Merged
merged 11 commits into from
Oct 23, 2023
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "adena-wallet",
"version": "1.8.0",
"version": "1.8.1",
"description": "Adena Wallet",
"license": "Adena License",
"private": true,
Expand Down
2 changes: 1 addition & 1 deletion packages/adena-extension/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "adena-extension",
"version": "1.8.0",
"version": "1.8.1",
"private": true,
"description": "Adena is a friendly browser extension wallet for the Gnoland blockchain.",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/adena-extension/public/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "Adena",
"description": "Adena is a friendly browser extension wallet for the Gnoland blockchain.",
"manifest_version": 3,
"version": "1.8.0",
"version": "1.8.1",
"action": {
"default_popup": "popup.html"
},
Expand Down
2 changes: 2 additions & 0 deletions packages/adena-extension/src/common/utils/client-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ export function parseTxsEachDate(txs: object[]) {
txDesc = `pkg: ${cur.pkg_path}`;
} else if (cur.type === '/vm.m_addpkg') {
txFunc = 'AddPkg';
} else if (cur.type === '/vm.m_run') {
txDesc = `Run`;
} else {
txDesc = '';
}
Expand Down
3 changes: 3 additions & 0 deletions packages/adena-extension/src/common/utils/encoding-util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function bytesToBase64(bytes: number[]) {
return Buffer.from(bytes).toString('base64');
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,36 @@ export const validateTrasactionMessageOfAddPkg = (message: { [key in string]: an
}
return true;
};

export const validateTrasactionMessageOfRun = (message: { [key in string]: any }) => {
if (!message.type || !message.value) {
return false;
}
if (message.type !== '/vm.m_run') {
return false;
}
if (typeof message.value !== 'object') {
return false;
}
if (typeof message.value.caller !== 'string') {
return false;
}
if (typeof message.value.send !== 'string') {
return false;
}
if (typeof message.value.package !== 'object') {
return false;
}

const packageValue = message.value.package;
if (typeof packageValue?.Name !== 'string') {
return false;
}
if (typeof packageValue?.Path !== 'string') {
return false;
}
if (!Array.isArray(packageValue?.Files)) {
return false;
}
return true;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import React, { useEffect, useMemo, useState } from 'react';
import ApproveTransaction from '@components/approve/approve-transaction/approve-transaction';
import { useLocation, useNavigate } from 'react-router-dom';
import { useCurrentAccount } from '@hooks/use-current-account';
import { InjectionMessage, InjectionMessageInstance } from '@inject/message';
import { createFaviconByHostname, decodeParameter, parseParmeters } from '@common/utils/client-utils';
import { useAdenaContext, useWalletContext } from '@hooks/use-context';
import { StdSignDoc, Account, isLedgerAccount } from 'adena-module';
import { RoutePath } from '@router/path';
import { validateInjectionData } from '@inject/message/methods';
import BigNumber from 'bignumber.js';
import { useNetwork } from '@hooks/use-network';
import { bytesToBase64 } from '@common/utils/encoding-util';

function mappedTransactionData(document: StdSignDoc) {
return {
messages: document.msgs,
contracts: document.msgs.map((message) => {
return {
type: message?.type || '',
function: message?.type === '/bank.MsgSend' ? 'Transfer' : message?.value?.func || '',
value: message?.value || '',
};
}),
gasWanted: document.fee.gas,
gasFee: `${document.fee.amount[0].amount}${document.fee.amount[0].denom}`,
document,
}
}

const DEFAULT_DENOM = 'GNOT';

const ApproveSignTransactionContainer: React.FC = () => {
const navigate = useNavigate();
const { gnoProvider } = useWalletContext();
const { walletService, transactionService } = useAdenaContext();
const { currentAccount } = useCurrentAccount();
const [transactionData, setTrasactionData] = useState<{ [key in string]: any } | undefined>(
undefined,
);
const { currentNetwork } = useNetwork();
const [hostname, setHostname] = useState('');
const location = useLocation();
const [requestData, setReqeustData] = useState<InjectionMessage>();
const [favicon, setFavicon] = useState<any>(null);
const [visibleTransactionInfo, setVisibleTransactionInfo] = useState(false);
const [document, setDocument] = useState<StdSignDoc>();

const networkFee = useMemo(() => {
if (!document || document.fee.amount.length === 0) {
return {
amount: '1',
denom: DEFAULT_DENOM
};
}
const networkFeeAmount = document.fee.amount[0].amount;
const networkFeeAmountOfGnot =
BigNumber(networkFeeAmount)
.shiftedBy(-6)
.toString();
return {
amount: networkFeeAmountOfGnot,
denom: DEFAULT_DENOM
};
}, [document]);

useEffect(() => {
checkLockWallet();
}, [walletService]);

const checkLockWallet = () => {
walletService.isLocked().then(locked => locked && navigate(RoutePath.ApproveLogin + location.search));
}

useEffect(() => {
if (location.search) {
initRequestData();
}
}, [location]);

const initRequestData = () => {
const data = parseParmeters(location.search);
const parsedData = decodeParameter(data['data']);
setReqeustData({ ...parsedData, hostname: data['hostname'] });
};

useEffect(() => {
if (currentAccount && requestData && gnoProvider) {
if (validate(currentAccount, requestData)) {
initFavicon();
initTransactionData();
}
}
}, [currentAccount, requestData, gnoProvider]);

const validate = (currentAccount: Account, requestData: InjectionMessage) => {
const validationMessage = validateInjectionData(currentAccount.getAddress('g'), requestData);
if (validationMessage) {
chrome.runtime.sendMessage(validationMessage);
return false;
}
return true;
}

const initFavicon = async () => {
const faviconData = await createFaviconByHostname(requestData?.hostname ?? '');
setFavicon(faviconData);
};

const initTransactionData = async () => {
if (!currentAccount || !requestData || !currentNetwork) {
return false;
}
try {
const document = await transactionService.createDocument(
currentAccount,
currentNetwork.networkId,
requestData?.data?.messages,
requestData?.data?.gasWanted,
requestData?.data?.gasFee,
requestData?.data?.memo,
);
setDocument(document);
setTrasactionData(mappedTransactionData(document));
setHostname(requestData?.hostname ?? '');
return true;
} catch (e) {
console.error(e);
const error: any = e;
if (error?.message === 'Transaction signing request was rejected by the user') {
chrome.runtime.sendMessage(
InjectionMessageInstance.failure(
'SIGN_REJECTED',
requestData?.data,
requestData?.key,
),
);
}
}
return false;
};

const sendTransaction = async () => {
if (!document || !currentAccount) {
chrome.runtime.sendMessage(
InjectionMessageInstance.failure('UNEXPECTED_ERROR', {}, requestData?.key),
);
return false;
}

try {
const signature = await transactionService.createSignature(
currentAccount,
document
);
const transactionBytes = await transactionService.createTransaction(document, signature);
const encodedTransaction = bytesToBase64(transactionBytes);
chrome.runtime.sendMessage(
InjectionMessageInstance.success('SIGN_TX', { encodedTransaction }, requestData?.key),
);
} catch (e) {
if (e instanceof Error) {
const message = e.message;
if (message.includes('Ledger')) {
return false;
}
chrome.runtime.sendMessage(
InjectionMessageInstance.failure(
'SIGN_FAILED',
{ error: { message } },
requestData?.key,
),
);
}
chrome.runtime.sendMessage(
InjectionMessageInstance.failure(
'SIGN_FAILED',
{},
requestData?.key,
),
);
}
return false;
};

const onToggleTransactionData = (visibleTransactionInfo: boolean) => {
setVisibleTransactionInfo(visibleTransactionInfo);
};

const onClickConfirm = () => {
if (!currentAccount) {
return;
}
if (isLedgerAccount(currentAccount)) {
navigate(RoutePath.ApproveSignLoading, {
state: {
document,
requestData
}
});
return;
}
sendTransaction();
};

const onClickCancel = () => {
chrome.runtime.sendMessage(
InjectionMessageInstance.failure('SIGN_REJECTED', {}, requestData?.key),
);
};

return (
<ApproveTransaction
title='Sign Transaction'
domain={hostname}
contracts={transactionData?.contracts}
loading={transactionData === undefined}
logo={favicon}
networkFee={networkFee}
onClickConfirm={onClickConfirm}
onClickCancel={onClickCancel}
onToggleTransactionData={onToggleTransactionData}
opened={visibleTransactionInfo}
transactionData={JSON.stringify(document, null, 2)}
/>
);
};

export default ApproveSignTransactionContainer;
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React, { useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { StdSignDoc, isLedgerAccount } from 'adena-module';
import ApproveLedgerLoading from '@components/approve/approve-ledger-loading/approve-ledger-loading';
import { InjectionMessage, InjectionMessageInstance } from '@inject/message';
import { useCurrentAccount } from '@hooks/use-current-account';
import { useAdenaContext } from '@hooks/use-context';
import { bytesToBase64 } from '@common/utils/encoding-util';

interface ApproveSignTransactionLedgerLoadingState {
requestData?: InjectionMessage;
document?: StdSignDoc;
}

const ApproveSignTransactionLedgerLoadingContainer: React.FC = () => {
const location = useLocation();
const { transactionService } = useAdenaContext();
const { document, requestData } = location.state as ApproveSignTransactionLedgerLoadingState;
const { currentAccount } = useCurrentAccount();
const [completed, setCompleted] = useState(false);

useEffect(() => {
if (currentAccount) {
requestTransaction();
}
}, [currentAccount]);

const requestTransaction = async () => {
if (completed) {
return;
}
const result = await createLedgerTransaction();
setCompleted(result);
setTimeout(() => !result && requestTransaction(), 1000);
};

const createLedgerTransaction = async () => {
if (!currentAccount || !document) {
return false;
}

if (!isLedgerAccount(currentAccount)) {
return false;
}

const result = await transactionService.createSignatureWithLedger(currentAccount, document).then(async (signature) => {
const transactionBytes = await transactionService.createTransaction(document, signature);
const encodedTransaction = bytesToBase64(transactionBytes);
chrome.runtime.sendMessage(
InjectionMessageInstance.success('SIGN_TX', { encodedTransaction }, requestData?.key),
);
return true;
}).catch((error: Error) => {
if (error.message === 'Transaction signing request was rejected by the user') {
chrome.runtime.sendMessage(
InjectionMessageInstance.failure('SIGN_REJECTED', {}, requestData?.key),
);
return true;
}
if (error.message.includes('Ledger')) {
return false;
}
return false;
});
return result;
};

const onClickCancel = () => {
if (!requestData) {
window.close();
return;
}
chrome.runtime.sendMessage(
InjectionMessageInstance.failure('SIGN_REJECTED', requestData.data, requestData.key),
);
}

return (
<ApproveLedgerLoading
onClickCancel={onClickCancel}
/>
);
};

export default ApproveSignTransactionLedgerLoadingContainer;
Loading