Skip to content

Commit

Permalink
Release Adena Wallet 'v1.8.1' (#285)
Browse files Browse the repository at this point in the history
  • Loading branch information
jinoosss authored Oct 23, 2023
2 parents b8df6e8 + f1835ed commit aa5ba5d
Show file tree
Hide file tree
Showing 22 changed files with 474 additions and 8 deletions.
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

0 comments on commit aa5ba5d

Please sign in to comment.