Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions .changeset/large-rockets-spend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@avalabs/avalanche-module': minor
'@avalabs/hypercore-module': minor
'@internal/utils': minor
'@avalabs/bitcoin-module': minor
'@avalabs/evm-module': minor
'@avalabs/svm-module': minor
'@avalabs/vm-module-types': minor
---

add value details to avalanche tx approval screens
11 changes: 10 additions & 1 deletion packages-internal/utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
export { TokenService } from './services/token-service/token-service';
export { getExchangeRates } from './services/pricing-service/exchange-rates';
export { addressItem, textItem, currencyItem, nodeIDItem, dataItem, dateItem } from './utils/detail-item';
export {
addressItem,
textItem,
currencyItem,
nodeIDItem,
dataItem,
dateItem,
transferListItem,
collapsibleGroupItem,
} from './utils/detail-item';
export { retry, RetryBackoffPolicy } from './utils/retry';
export { fetchAndVerify } from './utils/fetch-and-verify';
export { getCoreHeaders } from './utils/get-core-headers';
Expand Down
16 changes: 16 additions & 0 deletions packages-internal/utils/src/utils/detail-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import {
type AddressListItem,
type NetworkItemValue,
type NetworkItem,
type Transfer,
type TransferListItem,
type CollapsibleGroupItem,
type DetailSection,
} from '@avalabs/vm-module-types';

export const fundsRecipientItem = (
Expand Down Expand Up @@ -87,3 +91,15 @@ export const networkItem = (label: string, value: NetworkItemValue): NetworkItem
type: DetailItemType.NETWORK,
value,
});

export const transferListItem = (label: string, value: Transfer[]): TransferListItem => ({
label,
type: DetailItemType.TRANSFER_LIST,
value,
});

export const collapsibleGroupItem = (label: string, value: DetailSection[]): CollapsibleGroupItem => ({
label,
type: DetailItemType.COLLAPSIBLE_GROUP,
value,
});
2 changes: 1 addition & 1 deletion packages/avalanche-module/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"@avalabs/core-coingecko-sdk": "3.1.0-alpha.97",
"@avalabs/core-etherscan-sdk": "3.1.0-alpha.97",
"@avalabs/core-utils-sdk": "3.1.0-alpha.97",
"@avalabs/core-wallets-sdk": "3.1.0-alpha.97",
"@avalabs/core-wallets-sdk": "3.1.0-canary.303fde90",
"@avalabs/crypto-sdk": "1.0.0",
"@avalabs/glacier-sdk": "3.1.0-alpha.97",
"@avalabs/types": "3.1.0-alpha.97",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import { rpcErrors } from '@metamask/rpc-errors';
import { UnsignedTx, EVMUnsignedTx, AVM, utils, EVM } from '@avalabs/avalanchejs';
import { AppName, NetworkVMType, RpcMethod, type ApprovalController, type Network } from '@avalabs/vm-module-types';
import {
AlertType,
AppName,
NetworkVMType,
RpcMethod,
type ApprovalController,
type Network,
} from '@avalabs/vm-module-types';
import { avalancheSendTransaction } from './avalanche-send-transaction';
import { Avalanche } from '@avalabs/core-wallets-sdk';
import { getAddressesByIndices } from './utils/get-addresses-by-indices';
import { getProvider } from '../../utils/get-provider';
import { retry } from '@internal/utils/src/utils/retry';

const GLACIER_API_URL = 'https://glacier-api.avax.network';
const AVAX_ASSET_ID = 'avaxAssetId';

jest.mock('@avalabs/core-wallets-sdk');
jest.mock('@avalabs/avalanchejs');
Expand All @@ -17,6 +25,16 @@ jest.mock('@internal/utils/src/utils/retry', () => ({
retry: jest.fn(),
}));

const emptyValueDetails = {
outputs: [],
inputAmounts: {},
outputAmounts: {},
totalAvaxInput: 0n,
totalAvaxOutput: 0n,
totalAvaxBurned: 0n,
isValidAvaxBurnedAmount: true,
};

const utxosMock = [{ utxoId: '1' }, { utxoId: '2' }];

const mockOnTransactionConfirmed = jest.fn();
Expand Down Expand Up @@ -47,6 +65,7 @@ const mockGetProvider = getProvider as jest.MockedFunction<typeof getProvider>;
const mockProvider = {
issueTxHex: issueTxHexMock,
getApiP: mockGetApiP,
getContext: () => ({ avaxAssetID: AVAX_ASSET_ID }),
evmRpc: {
waitForTransaction: mockWaitForTransaction,
},
Expand Down Expand Up @@ -197,6 +216,7 @@ describe('avalanche_sendTransaction handler', () => {

it('should return error if fails to parse transaction', async () => {
(Avalanche.parseAvalancheTx as jest.Mock).mockReturnValueOnce({
...emptyValueDetails,
type: 'unknown',
});
(utils.parse as jest.Mock).mockReturnValueOnce([undefined, undefined, new Uint8Array([0, 1, 2])]);
Expand All @@ -219,6 +239,7 @@ describe('avalanche_sendTransaction handler', () => {

(utils.unpackWithManager as jest.Mock).mockReturnValueOnce(tx);
(Avalanche.parseAvalancheTx as jest.Mock).mockReturnValueOnce({
...emptyValueDetails,
type: 'import',
});
(utils.parse as jest.Mock).mockReturnValueOnce([undefined, undefined, new Uint8Array([0, 1, 2])]);
Expand Down Expand Up @@ -296,7 +317,7 @@ describe('avalanche_sendTransaction handler', () => {
signingData: {
type: 'avalanche_sendTransaction',
unsignedTxJson: '{"foo":"bar"}',
data: { type: 'import' },
data: { ...emptyValueDetails, type: 'import' },
vm: 'AVM',
},
});
Expand All @@ -313,6 +334,7 @@ describe('avalanche_sendTransaction handler', () => {
(utils.hexToBuffer as jest.Mock).mockReturnValueOnce(new Uint8Array([0, 1, 2]));
(utils.parse as jest.Mock).mockReturnValueOnce([undefined, undefined, new Uint8Array([0, 1, 2])]);
(Avalanche.parseAvalancheTx as jest.Mock).mockReturnValueOnce({
...emptyValueDetails,
type: 'import',
});
(Avalanche.createAvalancheEvmUnsignedTx as jest.Mock).mockReturnValueOnce(unsignedTxMock);
Expand Down Expand Up @@ -373,7 +395,7 @@ describe('avalanche_sendTransaction handler', () => {
signingData: {
type: 'avalanche_sendTransaction',
unsignedTxJson: '{"foo":"bar"}',
data: { type: 'import' },
data: { ...emptyValueDetails, type: 'import' },
vm: 'EVM',
},
});
Expand All @@ -397,6 +419,43 @@ describe('avalanche_sendTransaction handler', () => {
});
});

it('returns burn amount checker warning properly when isValidAvaxBurnedAmount is false', async () => {
const params = testParams(testRequestParams);

(utils.unpackWithManager as jest.Mock).mockReturnValueOnce({ vm: AVM });
(Avalanche.parseAvalancheTx as jest.Mock).mockReturnValueOnce({
...emptyValueDetails,
isValidAvaxBurnedAmount: false,
type: 'import',
});
(utils.parse as jest.Mock).mockReturnValueOnce([undefined, undefined, new Uint8Array([0, 1, 2])]);

await avalancheSendTransaction(params);

expect(mockApprovalController.requestApproval).toHaveBeenCalledWith(
expect.objectContaining({
displayData: expect.objectContaining({ alert: expect.objectContaining({ type: AlertType.WARNING }) }),
}),
);
});

it('does not return burn amount checker warning when isValidAvaxBurnedAmount is true', async () => {
const params = testParams(testRequestParams);

(utils.unpackWithManager as jest.Mock).mockReturnValueOnce({ vm: AVM });
(Avalanche.parseAvalancheTx as jest.Mock).mockReturnValueOnce({
...emptyValueDetails,
type: 'import',
});
(utils.parse as jest.Mock).mockReturnValueOnce([undefined, undefined, new Uint8Array([0, 1, 2])]);

await avalancheSendTransaction(params);

expect(mockApprovalController.requestApproval).toHaveBeenCalledWith(
expect.objectContaining({ displayData: expect.objectContaining({ alert: undefined }) }),
);
});

it('merges resolved auth headers into the Glacier UTXO request', async () => {
const params = {
...testParams(testRequestParams),
Expand All @@ -406,6 +465,7 @@ describe('avalanche_sendTransaction handler', () => {

(utils.unpackWithManager as jest.Mock).mockReturnValueOnce(tx);
(Avalanche.parseAvalancheTx as jest.Mock).mockReturnValueOnce({
...emptyValueDetails,
type: 'import',
});
(utils.parse as jest.Mock).mockReturnValueOnce([undefined, undefined, new Uint8Array([0, 1, 2])]);
Expand All @@ -430,6 +490,7 @@ describe('avalanche_sendTransaction handler', () => {
jest.clearAllMocks();

(Avalanche.parseAvalancheTx as jest.Mock).mockReturnValueOnce({
...emptyValueDetails,
type: 'import',
});

Expand Down Expand Up @@ -519,6 +580,7 @@ describe('avalanche_sendTransaction handler', () => {
jest.clearAllMocks();

(Avalanche.parseAvalancheTx as jest.Mock).mockReturnValueOnce({
...emptyValueDetails,
type: 'import',
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { parseTxDisplayTitle } from './utils/parse-tx-display-title';
import { getCoreHeaders, retry, rpcErrorOpts } from '@internal/utils';
import { getAddressesByIndices } from './utils/get-addresses-by-indices';
import { getTransactionDetailSections } from '../../utils/get-transaction-detail-sections';
import { getExcessiveBurnAlert } from '../../utils/get-excessive-burn-alert';
import { getExplorerAddressByNetwork } from '../get-transaction-history/utils';
import { getAccountFromContext } from '../../utils/get-account-from-context';

Expand Down Expand Up @@ -149,6 +150,7 @@ export const avalancheSendTransaction = async ({
network,
signerAccount: currentAddress,
recipients: getCrossChainRecipients(unsignedTx.getTx(), txDetails, isTestnet),
avaxAssetId: provider.getContext().avaxAssetID,
});

// Throw an error if we can't parse the transaction details
Expand All @@ -167,6 +169,7 @@ export const avalancheSendTransaction = async ({
},
details,
networkFeeSelector: false,
alert: getExcessiveBurnAlert(txDetails),
};

// prompt user for approval
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,43 +22,50 @@ import {
type StakingDetails,
type SubnetDetails,
type TxDetails,
type TxValueDetails,
} from '@avalabs/vm-module-types';

export const isAddPermissionlessDelegatorTx = (tx: TxDetails): tx is AddPermissionlessDelegatorTx =>
type WithValueDetails<T> = TxValueDetails & T;

export const isAddPermissionlessDelegatorTx = (tx: TxDetails): tx is WithValueDetails<AddPermissionlessDelegatorTx> =>
tx.type === TxType.AddPermissionlessDelegator;
export const isAddPermissionlessValidatorTx = (tx: TxDetails): tx is AddPermissionlessValidatorTx =>
export const isAddPermissionlessValidatorTx = (tx: TxDetails): tx is WithValueDetails<AddPermissionlessValidatorTx> =>
tx.type === TxType.AddPermissionlessValidator;
export const isExportTx = (tx: TxDetails): tx is ExportTx => tx.type === TxType.Export;
export const isImportTx = (tx: TxDetails): tx is ImportTx => tx.type === TxType.Import;
export const isBaseTx = (tx: TxDetails): tx is BaseTx => tx.type === TxType.Base;
export const isAddSubnetValidatorTx = (tx: TxDetails): tx is AddSubnetValidatorTx =>
export const isExportTx = (tx: TxDetails): tx is WithValueDetails<ExportTx> => tx.type === TxType.Export;
export const isImportTx = (tx: TxDetails): tx is WithValueDetails<ImportTx> => tx.type === TxType.Import;
export const isBaseTx = (tx: TxDetails): tx is WithValueDetails<BaseTx> => tx.type === TxType.Base;
export const isAddSubnetValidatorTx = (tx: TxDetails): tx is WithValueDetails<AddSubnetValidatorTx> =>
tx.type === TxType.AddSubnetValidator;
export const isCreateChainTx = (tx: TxDetails): tx is CreateChainTx => tx.type === TxType.CreateChain;
export const isCreateSubnetTx = (tx: TxDetails): tx is CreateSubnetTx => tx.type === TxType.CreateSubnet;
export const isRemoveSubnetValidatorTx = (tx: TxDetails): tx is RemoveSubnetValidatorTx =>
export const isCreateChainTx = (tx: TxDetails): tx is WithValueDetails<CreateChainTx> => tx.type === TxType.CreateChain;
export const isCreateSubnetTx = (tx: TxDetails): tx is WithValueDetails<CreateSubnetTx> =>
tx.type === TxType.CreateSubnet;
export const isRemoveSubnetValidatorTx = (tx: TxDetails): tx is WithValueDetails<RemoveSubnetValidatorTx> =>
tx.type === TxType.RemoveSubnetValidator;
export const isConvertSubnetToL1Tx = (tx: TxDetails): tx is ConvertSubnetToL1Tx => tx.type === TxType.ConvertSubnetToL1;
export const isDisableL1ValidatorTx = (tx: TxDetails): tx is DisableL1ValidatorTx =>
export const isConvertSubnetToL1Tx = (tx: TxDetails): tx is WithValueDetails<ConvertSubnetToL1Tx> =>
tx.type === TxType.ConvertSubnetToL1;
export const isDisableL1ValidatorTx = (tx: TxDetails): tx is WithValueDetails<DisableL1ValidatorTx> =>
tx.type === TxType.DisableL1Validator;
export const isIncreaseL1ValidatorBalanceTx = (tx: TxDetails): tx is IncreaseL1ValidatorBalanceTx =>
export const isIncreaseL1ValidatorBalanceTx = (tx: TxDetails): tx is WithValueDetails<IncreaseL1ValidatorBalanceTx> =>
tx.type === TxType.IncreaseL1ValidatorBalance;
export const isRegisterL1ValidatorTx = (tx: TxDetails): tx is RegisterL1ValidatorTx =>
export const isRegisterL1ValidatorTx = (tx: TxDetails): tx is WithValueDetails<RegisterL1ValidatorTx> =>
tx.type === TxType.RegisterL1Validator;
export const isSetL1ValidatorWeightTx = (tx: TxDetails): tx is SetL1ValidatorWeightTx =>
export const isSetL1ValidatorWeightTx = (tx: TxDetails): tx is WithValueDetails<SetL1ValidatorWeightTx> =>
tx.type === TxType.SetL1ValidatorWeight;
export const isAddAutoRenewedValidatorTx = (tx: TxDetails): tx is AddAutoRenewedValidatorTx =>
export const isAddAutoRenewedValidatorTx = (tx: TxDetails): tx is WithValueDetails<AddAutoRenewedValidatorTx> =>
tx.type === TxType.AddAutoRenewedValidator;
export const isSetAutoRenewedValidatorConfigTx = (tx: TxDetails): tx is SetAutoRenewedValidatorConfigTx =>
tx.type === TxType.SetAutoRenewedValidatorConfig;
export const isSetAutoRenewedValidatorConfigTx = (
tx: TxDetails,
): tx is WithValueDetails<SetAutoRenewedValidatorConfigTx> => tx.type === TxType.SetAutoRenewedValidatorConfig;

export const isStakingDetails = (tx: TxDetails): tx is StakingDetails =>
export const isStakingDetails = (tx: TxDetails): tx is WithValueDetails<StakingDetails> =>
isAddPermissionlessDelegatorTx(tx) ||
isAddPermissionlessValidatorTx(tx) ||
isAddSubnetValidatorTx(tx) ||
isRemoveSubnetValidatorTx(tx) ||
isAddAutoRenewedValidatorTx(tx) ||
isSetAutoRenewedValidatorConfigTx(tx);
export const isExportImportTxDetails = (tx: TxDetails): tx is ExportImportTxDetails => isExportTx(tx) || isImportTx(tx);
export const isChainDetails = (tx: TxDetails): tx is ChainDetails => isBaseTx(tx);
export const isBlockchainDetails = (tx: TxDetails): tx is BlockchainDetails => isCreateChainTx(tx);
export const isSubnetDetails = (tx: TxDetails): tx is SubnetDetails => isCreateSubnetTx(tx);
export const isExportImportTxDetails = (tx: TxDetails): tx is WithValueDetails<ExportImportTxDetails> =>
isExportTx(tx) || isImportTx(tx);
export const isChainDetails = (tx: TxDetails): tx is WithValueDetails<ChainDetails> => isBaseTx(tx);
export const isBlockchainDetails = (tx: TxDetails): tx is WithValueDetails<BlockchainDetails> => isCreateChainTx(tx);
export const isSubnetDetails = (tx: TxDetails): tx is WithValueDetails<SubnetDetails> => isCreateSubnetTx(tx);
Loading
Loading