Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions packages/adena-extension/src/common/utils/gas-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import BigNumber from 'bignumber.js';
import { GasToken } from '@common/constants/token.constant';

export const convertRawGasAmountToDisplayAmount = (rawAmount: string | number): string => {
try {
if (!rawAmount) {
return '0';
}

return BigNumber(rawAmount)
.shiftedBy(-GasToken.decimals)
.toFixed(GasToken.decimals)
.replace(/(\.\d*?)0+$/, '$1')
.replace(/\.$/, '');
} catch (e) {
console.warn('[convertRawGasAmountToDisplayAmount] Failed to convert:', {
rawAmount,
error: e,
});
return '0';
}
};
48 changes: 48 additions & 0 deletions packages/adena-extension/src/common/utils/signer-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Signature } from '@adena-wallet/sdk';
import { publicKeyToAddress } from 'adena-module';

/**
* Extract the signer addresses from the Signature array.
* @param signatures - Signature array
* @returns
*/
export const extractSignerAddresses = async (signatures: Signature[]): Promise<string[]> => {
if (!signatures || signatures.length === 0) {
return [];
}

try {
const addresses = await Promise.all(
signatures.map(async (signature) => {
if (!signature?.pubKey?.value) {
return '';
}

try {
const fullBytes = Uint8Array.from(atob(signature.pubKey.value), (c) => c.charCodeAt(0));
const pubKeyBytes = fullBytes.slice(2);
const address = await publicKeyToAddress(pubKeyBytes);
return address;
} catch (e) {
console.error('Failed to extract address from signature:', e);
return '';
}
}),
);

return addresses.filter((addr) => addr !== '');
} catch (e) {
console.error('Failed to extract signer addresses:', e);
return [];
}
};

/**
* Converts a Signature array into a serializable key string.
* A helper function for use as a queryKey in React Query.
* @param signatures - Signature array
* @returns
*/
export const serializeSignaturesKey = (signatures?: Signature[]): string => {
return signatures?.map((sig) => sig.pubKey?.value).join(',') || '';
};
3 changes: 3 additions & 0 deletions packages/adena-extension/src/common/validation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ export * from './validation-message';
export * from './validation-password';
export * from './validation-token';
export * from './validation-wallet';
export * from './validation-signature';
export * from './validation-signed-document';
export * from './validation-create-multisig-document';
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { MultisigConfig } from '@inject/types';
import { validateInvalidAddress } from './validation-address-book';

/**
* Validates multisig config object and its required fields
*/
export const validateMultisigConfigExists = (
multisigConfig: MultisigConfig | undefined,
): boolean => {
if (!multisigConfig) {
return false;
}

return (
'signers' in multisigConfig &&
'threshold' in multisigConfig &&
multisigConfig.signers !== undefined &&
multisigConfig.threshold !== undefined
);
};

/**
* Validates signers array format and minimum count
*/
export const validateMultisigSigners = (signers: any): boolean => {
return Array.isArray(signers) && signers.length >= 2;
};

/**
* Validates all signer addresses
*/
export const validateMultisigSignerAddresses = (signers: string[]): boolean => {
try {
for (const signer of signers) {
validateInvalidAddress(signer);
}
return true;
} catch (error) {
return false;
}
};

/**
* Validates threshold value
*/
export const validateMultisigThreshold = (threshold: any, signersCount: number): boolean => {
return typeof threshold === 'number' && threshold >= 1 && threshold <= signersCount;
};

/**
* Validates chain_id field
*/
export const validateChainId = (chain_id: any): boolean => {
return typeof chain_id === 'string' && chain_id.length > 0;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Validates a single signature object structure.
* Verifies the existence and format of pubKey (typeUrl, value) and signature fields.
*
* @param signature - The signature object to validate
* @returns true if signature structure is valid, false otherwise
*/
export const validateSignature = (signature: any): boolean => {
if (!signature || typeof signature !== 'object') {
return false;
}

if (
!signature.pubKey ||
typeof signature.pubKey.typeUrl !== 'string' ||
typeof signature.pubKey.value !== 'string'
) {
return false;
}

if (typeof signature.signature !== 'string') {
return false;
}

return true;
};

/**
* Validates an array of signature objects.
* Checks that all signatures in the array have valid structure.
*
* @param signatures - Array of signature objects to validate
* @returns true if all signatures are valid, false if any signature is invalid
*/
export const validateSignatures = (signatures: any[]): boolean => {
return signatures.every((signature) => validateSignature(signature));
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Validates basic fields of a signed document.
* Verifies that chain_id, account_number, sequence, and memo are all strings.
*
* @param signedDocument - The signed document object to validate
* @returns true if all basic fields are valid strings, false otherwise
*/
export const validateSignedDocumentFields = (signedDocument: any): boolean => {
return (
typeof signedDocument.chain_id === 'string' &&
typeof signedDocument.account_number === 'string' &&
typeof signedDocument.sequence === 'string' &&
typeof signedDocument.memo === 'string'
);
};

/**
* Validates the fee structure of a signed document.
* Verifies that fee object exists, gas is a non-empty string, and amount is a non-empty array.
*
* @param fee - The fee object to validate
* @returns true if fee structure is valid, false otherwise
*/

export const validateSignedDocumentFee = (fee: any): boolean => {
return (
fee &&
typeof fee.gas === 'string' &&
Array.isArray(fee.amount) &&
fee.gas.length > 0 &&
fee.amount.length > 0
);
};

/**
* Validates the messages array of a signed document.
* Verifies that msgs is an array and contains at least one message.
*
* @param msgs - The messages array to validate
* @returns true if messages array is valid and not empty, false otherwise
*/
export const validateSignedDocumentMessages = (msgs: any): boolean => {
return Array.isArray(msgs) && msgs.length > 0;
};
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,16 @@ export const validateInjectionDataWithAddress = (

export const validateInjectionTransactionType = (requestData: InjectionMessage): any => {
const messageTypes = ['/bank.MsgSend', '/vm.m_call', '/vm.m_addpkg', '/vm.m_run'];
return requestData.data?.messages.every((message: any) => messageTypes.includes(message?.type));

const msgs = requestData.data?.messages || requestData.data?.msgs || [];
return msgs.every((message: any) => messageTypes.includes(message?.type));
};

export const validateInjectionTransactionMessageWithAddress = (
requestData: InjectionMessage,
address: string,
): boolean => {
const messages = requestData.data?.messages;
const messages = requestData.data?.messages || requestData.data?.msgs || [];
for (const message of messages) {
let messageAddress = '';
switch (message?.type) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { ApproveSignedDocument, type ApproveSignedDocumentProps } from '.';
import { Meta, StoryObj } from '@storybook/react';
import { action } from '@storybook/addon-actions';

export default {
title: 'components/approve/ApproveTransaction',
component: ApproveSignedDocument,
} as Meta<typeof ApproveSignedDocument>;

export const Default: StoryObj<ApproveSignedDocumentProps> = {
args: {
domain: '',
loading: true,
logo: '',
title: 'Sign Transaction',
contracts: [
{
type: '/vm.m_call',
function: 'GetBoardIDFromName',
value: '',
},
],
networkFee: {
amount: '0.0048',
denom: 'GNOT',
},
transactionData: '',
opened: false,
onToggleTransactionData: action('openTransactionData'),
onClickConfirm: action('confirm'),
onClickCancel: action('cancel'),
},
};
Loading
Loading