From 74594c1eecbb59dc62d10f01764d1fb368f08f2e Mon Sep 17 00:00:00 2001 From: bferenc Date: Wed, 23 Sep 2026 13:07:03 +0200 Subject: [PATCH 1/5] feat: add valuedetails --- packages-internal/utils/src/index.ts | 10 +- .../utils/src/utils/detail-item.ts | 8 ++ packages/avalanche-module/package.json | 2 +- .../avalanche-send-transaction.test.ts | 19 ++- .../avalanche-send-transaction/typeguards.ts | 51 ++++--- .../avalanche-sign-transaction.test.ts | 18 ++- .../get-transaction-detail-sections.test.ts | 129 +++++++++++++----- .../utils/get-transaction-detail-sections.ts | 19 ++- .../chain-detail-section.ts | 57 +------- .../value-details-section.test.ts | 108 +++++++++++++++ .../value-details-section.ts | 45 ++++++ packages/types/package.json | 2 +- packages/types/src/rpc.ts | 21 ++- packages/types/src/staking.ts | 49 ++++--- pnpm-lock.yaml | 115 ++++++++++++++-- 15 files changed, 496 insertions(+), 157 deletions(-) create mode 100644 packages/avalanche-module/src/utils/transaction-detail-sections/value-details-section.test.ts create mode 100644 packages/avalanche-module/src/utils/transaction-detail-sections/value-details-section.ts diff --git a/packages-internal/utils/src/index.ts b/packages-internal/utils/src/index.ts index 354812e3..f24063a5 100644 --- a/packages-internal/utils/src/index.ts +++ b/packages-internal/utils/src/index.ts @@ -1,6 +1,14 @@ 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, +} from './utils/detail-item'; export { retry, RetryBackoffPolicy } from './utils/retry'; export { fetchAndVerify } from './utils/fetch-and-verify'; export { getCoreHeaders } from './utils/get-core-headers'; diff --git a/packages-internal/utils/src/utils/detail-item.ts b/packages-internal/utils/src/utils/detail-item.ts index 3ae24978..e830fe36 100644 --- a/packages-internal/utils/src/utils/detail-item.ts +++ b/packages-internal/utils/src/utils/detail-item.ts @@ -12,6 +12,8 @@ import { type AddressListItem, type NetworkItemValue, type NetworkItem, + type Transfer, + type TransferListItem, } from '@avalabs/vm-module-types'; export const fundsRecipientItem = ( @@ -87,3 +89,9 @@ 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, +}); diff --git a/packages/avalanche-module/package.json b/packages/avalanche-module/package.json index 61c6cd57..8bf59def 100644 --- a/packages/avalanche-module/package.json +++ b/packages/avalanche-module/package.json @@ -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.56e4cf95", "@avalabs/crypto-sdk": "1.0.0", "@avalabs/glacier-sdk": "3.1.0-alpha.97", "@avalabs/types": "3.1.0-alpha.97", diff --git a/packages/avalanche-module/src/handlers/avalanche-send-transaction/avalanche-send-transaction.test.ts b/packages/avalanche-module/src/handlers/avalanche-send-transaction/avalanche-send-transaction.test.ts index d8390c57..2c415cd9 100644 --- a/packages/avalanche-module/src/handlers/avalanche-send-transaction/avalanche-send-transaction.test.ts +++ b/packages/avalanche-module/src/handlers/avalanche-send-transaction/avalanche-send-transaction.test.ts @@ -17,6 +17,15 @@ jest.mock('@internal/utils/src/utils/retry', () => ({ retry: jest.fn(), })); +const emptyValueDetails = { + outputs: [], + inputAmounts: {}, + outputAmounts: {}, + totalAvaxInput: 0n, + totalAvaxOutput: 0n, + totalAvaxBurned: 0n, +}; + const utxosMock = [{ utxoId: '1' }, { utxoId: '2' }]; const mockOnTransactionConfirmed = jest.fn(); @@ -197,6 +206,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])]); @@ -219,6 +229,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])]); @@ -296,7 +307,7 @@ describe('avalanche_sendTransaction handler', () => { signingData: { type: 'avalanche_sendTransaction', unsignedTxJson: '{"foo":"bar"}', - data: { type: 'import' }, + data: { ...emptyValueDetails, type: 'import' }, vm: 'AVM', }, }); @@ -313,6 +324,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); @@ -373,7 +385,7 @@ describe('avalanche_sendTransaction handler', () => { signingData: { type: 'avalanche_sendTransaction', unsignedTxJson: '{"foo":"bar"}', - data: { type: 'import' }, + data: { ...emptyValueDetails, type: 'import' }, vm: 'EVM', }, }); @@ -406,6 +418,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])]); @@ -430,6 +443,7 @@ describe('avalanche_sendTransaction handler', () => { jest.clearAllMocks(); (Avalanche.parseAvalancheTx as jest.Mock).mockReturnValueOnce({ + ...emptyValueDetails, type: 'import', }); @@ -519,6 +533,7 @@ describe('avalanche_sendTransaction handler', () => { jest.clearAllMocks(); (Avalanche.parseAvalancheTx as jest.Mock).mockReturnValueOnce({ + ...emptyValueDetails, type: 'import', }); }); diff --git a/packages/avalanche-module/src/handlers/avalanche-send-transaction/typeguards.ts b/packages/avalanche-module/src/handlers/avalanche-send-transaction/typeguards.ts index a4fe6ae4..bf2351bc 100644 --- a/packages/avalanche-module/src/handlers/avalanche-send-transaction/typeguards.ts +++ b/packages/avalanche-module/src/handlers/avalanche-send-transaction/typeguards.ts @@ -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 = TxValueDetails & T; + +export const isAddPermissionlessDelegatorTx = (tx: TxDetails): tx is WithValueDetails => tx.type === TxType.AddPermissionlessDelegator; -export const isAddPermissionlessValidatorTx = (tx: TxDetails): tx is AddPermissionlessValidatorTx => +export const isAddPermissionlessValidatorTx = (tx: TxDetails): tx is WithValueDetails => 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 => tx.type === TxType.Export; +export const isImportTx = (tx: TxDetails): tx is WithValueDetails => tx.type === TxType.Import; +export const isBaseTx = (tx: TxDetails): tx is WithValueDetails => tx.type === TxType.Base; +export const isAddSubnetValidatorTx = (tx: TxDetails): tx is WithValueDetails => 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 => tx.type === TxType.CreateChain; +export const isCreateSubnetTx = (tx: TxDetails): tx is WithValueDetails => + tx.type === TxType.CreateSubnet; +export const isRemoveSubnetValidatorTx = (tx: TxDetails): tx is WithValueDetails => 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 => + tx.type === TxType.ConvertSubnetToL1; +export const isDisableL1ValidatorTx = (tx: TxDetails): tx is WithValueDetails => tx.type === TxType.DisableL1Validator; -export const isIncreaseL1ValidatorBalanceTx = (tx: TxDetails): tx is IncreaseL1ValidatorBalanceTx => +export const isIncreaseL1ValidatorBalanceTx = (tx: TxDetails): tx is WithValueDetails => tx.type === TxType.IncreaseL1ValidatorBalance; -export const isRegisterL1ValidatorTx = (tx: TxDetails): tx is RegisterL1ValidatorTx => +export const isRegisterL1ValidatorTx = (tx: TxDetails): tx is WithValueDetails => tx.type === TxType.RegisterL1Validator; -export const isSetL1ValidatorWeightTx = (tx: TxDetails): tx is SetL1ValidatorWeightTx => +export const isSetL1ValidatorWeightTx = (tx: TxDetails): tx is WithValueDetails => tx.type === TxType.SetL1ValidatorWeight; -export const isAddAutoRenewedValidatorTx = (tx: TxDetails): tx is AddAutoRenewedValidatorTx => +export const isAddAutoRenewedValidatorTx = (tx: TxDetails): tx is WithValueDetails => tx.type === TxType.AddAutoRenewedValidator; -export const isSetAutoRenewedValidatorConfigTx = (tx: TxDetails): tx is SetAutoRenewedValidatorConfigTx => - tx.type === TxType.SetAutoRenewedValidatorConfig; +export const isSetAutoRenewedValidatorConfigTx = ( + tx: TxDetails, +): tx is WithValueDetails => tx.type === TxType.SetAutoRenewedValidatorConfig; -export const isStakingDetails = (tx: TxDetails): tx is StakingDetails => +export const isStakingDetails = (tx: TxDetails): tx is WithValueDetails => 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 => + isExportTx(tx) || isImportTx(tx); +export const isChainDetails = (tx: TxDetails): tx is WithValueDetails => isBaseTx(tx); +export const isBlockchainDetails = (tx: TxDetails): tx is WithValueDetails => isCreateChainTx(tx); +export const isSubnetDetails = (tx: TxDetails): tx is WithValueDetails => isCreateSubnetTx(tx); diff --git a/packages/avalanche-module/src/handlers/avalanche-sign-transaction/avalanche-sign-transaction.test.ts b/packages/avalanche-module/src/handlers/avalanche-sign-transaction/avalanche-sign-transaction.test.ts index 5564704c..5c17835b 100644 --- a/packages/avalanche-module/src/handlers/avalanche-sign-transaction/avalanche-sign-transaction.test.ts +++ b/packages/avalanche-module/src/handlers/avalanche-sign-transaction/avalanche-sign-transaction.test.ts @@ -2,12 +2,14 @@ import { info, PVM, UnsignedTx, utils } from '@avalabs/avalanchejs'; import { AppName, NetworkVMType, RpcMethod, TxType } from '@avalabs/vm-module-types'; import { Avalanche } from '@avalabs/core-wallets-sdk'; import { avalancheSignTransaction } from './avalanche-sign-transaction'; +import { getAddressesByIndices } from '../avalanche-send-transaction/utils/get-addresses-by-indices'; import { rpcErrors } from '@metamask/rpc-errors'; import { Network as GlacierNetwork } from '@avalabs/glacier-sdk'; import type { GetUpgradesInfoResponse } from '@avalabs/avalanchejs/dist/info/model'; jest.mock('@avalabs/avalanchejs'); jest.mock('@avalabs/core-wallets-sdk'); +jest.mock('../avalanche-send-transaction/utils/get-addresses-by-indices'); const mockRequestApproval = jest.fn().mockImplementation(() => ({ success: true })); const mockApprovalController = { @@ -17,6 +19,15 @@ const mockApprovalController = { onTransactionConfirmed: jest.fn(), onTransactionReverted: jest.fn(), }; +const emptyValueDetails = { + outputs: [], + inputAmounts: {}, + outputAmounts: {}, + totalAvaxInput: 0n, + totalAvaxOutput: 0n, + totalAvaxBurned: 0n, +}; + const utxosMock = [{ utxoId: '1' }, { utxoId: '2' }]; const mockNetwork = { chainId: 123, @@ -83,12 +94,14 @@ describe('avalanche-sign-transaction', () => { }; beforeEach(() => { jest.resetAllMocks(); + (getAddressesByIndices as jest.Mock).mockResolvedValue([]); jest.spyOn(info.InfoApi.prototype, 'getUpgradesInfo').mockResolvedValue({} as GetUpgradesInfoResponse); (UnsignedTx.fromJSON as jest.Mock).mockReturnValue(unsignedTxMock); (Avalanche.getVmByChainAlias as jest.Mock).mockReturnValue(PVM); (Avalanche.createAvalancheUnsignedTx as jest.Mock).mockReturnValue(unsignedTxMock); (Avalanche.parseAvalancheTx as jest.Mock).mockReturnValue({ + ...emptyValueDetails, type: TxType.AddPermissionlessDelegator, start: '0', end: '1000', @@ -186,7 +199,10 @@ describe('avalanche-sign-transaction', () => { it('returns error if the tx type is unknown', async () => { const request = createRequest({ transactionHex: '0x00001', chainAlias: 'P', from: '123' }); - (Avalanche.parseAvalancheTx as jest.Mock).mockReturnValue({ type: TxType.Unknown }); + (Avalanche.parseAvalancheTx as jest.Mock).mockReturnValue({ + ...emptyValueDetails, + type: TxType.Unknown, + }); const result = await avalancheSignTransaction({ ...avalancheSignTransactionParams, diff --git a/packages/avalanche-module/src/utils/get-transaction-detail-sections.test.ts b/packages/avalanche-module/src/utils/get-transaction-detail-sections.test.ts index b68bf355..0ee53ffa 100644 --- a/packages/avalanche-module/src/utils/get-transaction-detail-sections.test.ts +++ b/packages/avalanche-module/src/utils/get-transaction-detail-sections.test.ts @@ -1,6 +1,30 @@ -import { NetworkVMType, TxType, type Network, type NetworkToken, type TxDetails } from '@avalabs/vm-module-types'; +import { + DetailItemType, + type Network, + type NetworkToken, + NetworkVMType, + type TxDetails, + TxType, +} from '@avalabs/vm-module-types'; import { getTransactionDetailSections } from './get-transaction-detail-sections'; +const emptyValueDetails = { + outputs: [], + inputAmounts: {}, + outputAmounts: {}, + totalAvaxInput: 0n, + totalAvaxOutput: 0n, + totalAvaxBurned: 0n, +}; + +const fundsOutTransfers = (txDetails: TxDetails, symbol: string) => { + const [item] = + getTransactionDetailSections(txDetails, symbol)?.find((section) => section.title === 'Transaction Outputs') + ?.items ?? []; + + return typeof item === 'string' || item?.type !== DetailItemType.TRANSFER_LIST ? undefined : item.value; +}; + const networkToken: NetworkToken = { decimals: 18, symbol: 'AVAX', @@ -22,6 +46,7 @@ const mockAccount = 'P-avax1mockaccount'; describe('getTransactionDetailSections - Detailed Tests', () => { it('should handle chain details', () => { const txDetails: TxDetails = { + ...emptyValueDetails, type: TxType.Base, chain: NetworkVMType.AVM, outputs: [ @@ -30,6 +55,7 @@ describe('getTransactionDetailSections - Detailed Tests', () => { owners: ['0xOwner1'], threshold: 1n, locktime: 1n, + stakeableLocktime: 0n, isAvax: true, assetId: '0xAssetID', }, @@ -51,31 +77,35 @@ describe('getTransactionDetailSections - Detailed Tests', () => { ], }, { - title: 'Balance Change', + title: 'Network Fee', items: [ { - label: 'To', - value: '0xOwner1', - type: 'address', - }, - { - label: 'Amount', - value: 100n, + label: 'Fee Amount', + value: 1n, type: 'currency', - symbol: 'AVAX', maxDecimals: 9, + symbol: 'AVAX', }, ], }, { - title: 'Network Fee', + title: 'Transaction Outputs', items: [ { - label: 'Fee Amount', - value: 1n, - type: 'currency', - maxDecimals: 9, - symbol: 'AVAX', + label: 'Transaction Outputs', + type: 'transferList', + value: [ + { + addresses: ['0xOwner1'], + amount: 100n, + assetId: '0xAssetID', + symbol: 'AVAX', + decimals: 9, + threshold: 1, + lockedUntil: 1, + stakeableLockedUntil: 0, + }, + ], }, ], }, @@ -85,6 +115,7 @@ describe('getTransactionDetailSections - Detailed Tests', () => { it('labels a non-AVAX output with the asset the chain described', () => { const txDetails: TxDetails = { + ...emptyValueDetails, type: TxType.Base, chain: NetworkVMType.AVM, outputs: [ @@ -93,6 +124,7 @@ describe('getTransactionDetailSections - Detailed Tests', () => { owners: ['0xOwner1'], threshold: 1n, locktime: 0n, + stakeableLocktime: 0n, isAvax: false, assetId: '0xAssetID', assetDescription: { assetID: '0xAssetID', name: 'Some Token', symbol: 'TKN', denomination: 2 }, @@ -101,15 +133,15 @@ describe('getTransactionDetailSections - Detailed Tests', () => { txFee: 1n, }; - const items = getTransactionDetailSections(txDetails, networkToken.symbol)?.[1]?.items; + const transfers = fundsOutTransfers(txDetails, networkToken.symbol); // Previously rendered as AVAX at AVAX's scale. - expect(items).toContainEqual({ label: 'Amount', value: 100n, type: 'currency', maxDecimals: 2, symbol: 'TKN' }); - expect(items).toContainEqual({ label: 'Asset', value: 'Some Token', type: 'text', alignment: 'horizontal' }); + expect(transfers?.[0]).toMatchObject({ amount: 100n, decimals: 2, symbol: 'TKN', assetName: 'Some Token' }); }); it('shows the raw amount and asset id for an undescribed non-AVAX output', () => { const txDetails: TxDetails = { + ...emptyValueDetails, type: TxType.Base, chain: NetworkVMType.AVM, outputs: [ @@ -118,6 +150,7 @@ describe('getTransactionDetailSections - Detailed Tests', () => { owners: ['0xOwner1'], threshold: 1n, locktime: 0n, + stakeableLocktime: 0n, isAvax: false, assetId: '0xAssetID', }, @@ -125,36 +158,42 @@ describe('getTransactionDetailSections - Detailed Tests', () => { txFee: 1n, }; - const items = getTransactionDetailSections(txDetails, networkToken.symbol)?.[1]?.items; + const transfers = fundsOutTransfers(txDetails, networkToken.symbol); - expect(items).toContainEqual({ - label: 'Amount', - value: '100 (smallest unit)', - type: 'text', - alignment: 'horizontal', - }); - expect(items).toContainEqual({ label: 'Asset', value: '0xAssetID', type: 'text', alignment: 'vertical' }); - expect(items).not.toContainEqual(expect.objectContaining({ symbol: networkToken.symbol, label: 'Amount' })); + // Without a symbol the client shows the raw amount against the asset id, rather than a + // number that would only look like a familiar unit. + expect(transfers?.[0]).toMatchObject({ amount: 100n, assetId: '0xAssetID' }); + expect(transfers?.[0]?.symbol).toBeUndefined(); + expect(transfers?.[0]?.decimals).toBeUndefined(); }); - it('flags an output that stays locked, and ignores a locktime already in the past', () => { - const buildTx = (locktime: bigint): TxDetails => ({ + it('reports both locks on an output, which restrict it in different ways', () => { + const txDetails: TxDetails = { + ...emptyValueDetails, type: TxType.Base, chain: NetworkVMType.AVM, - outputs: [{ amount: 100n, owners: ['0xOwner1'], threshold: 1n, locktime, isAvax: true, assetId: '0xAssetID' }], + outputs: [ + { + amount: 100n, + owners: ['0xOwner1'], + threshold: 1n, + locktime: 1700n, + stakeableLocktime: 9900n, + isAvax: true, + assetId: '0xAssetID', + }, + ], txFee: 1n, - }); + }; - const future = BigInt(Math.floor(Date.now() / 1000) + 3600); - const lockedItems = getTransactionDetailSections(buildTx(future), networkToken.symbol)?.[1]?.items; - const pastItems = getTransactionDetailSections(buildTx(1n), networkToken.symbol)?.[1]?.items; + const transfers = fundsOutTransfers(txDetails, networkToken.symbol); - expect(lockedItems).toContainEqual(expect.objectContaining({ label: 'Locked until' })); - expect(pastItems).not.toContainEqual(expect.objectContaining({ label: 'Locked until' })); + expect(transfers?.[0]).toMatchObject({ lockedUntil: 1700, stakeableLockedUntil: 9900 }); }); it('should handle export transactions', () => { const txDetails: TxDetails = { + ...emptyValueDetails, amount: 100n, chain: NetworkVMType.AVM, destination: NetworkVMType.PVM, @@ -215,6 +254,7 @@ describe('getTransactionDetailSections - Detailed Tests', () => { it('should handle import transactions', () => { const txDetails: TxDetails = { + ...emptyValueDetails, amount: 100n, chain: NetworkVMType.AVM, source: NetworkVMType.PVM, @@ -275,6 +315,7 @@ describe('getTransactionDetailSections - Detailed Tests', () => { it('should handle subnet details', () => { const txDetails: TxDetails = { + ...emptyValueDetails, type: TxType.CreateSubnet, threshold: 2, controlKeys: ['0xKey1', '0xKey2'], @@ -317,6 +358,7 @@ describe('getTransactionDetailSections - Detailed Tests', () => { it('should handle staking transactions for permissionless delegators', () => { const txDetails: TxDetails = { + ...emptyValueDetails, type: TxType.AddPermissionlessDelegator, nodeID: 'NodeID', subnetID: 'SubnetID', @@ -387,6 +429,7 @@ describe('getTransactionDetailSections - Detailed Tests', () => { it('should handle staking transactions for permissionless validators', () => { const txDetails: TxDetails = { + ...emptyValueDetails, type: TxType.AddPermissionlessValidator, nodeID: 'NodeID', subnetID: 'SubnetID', @@ -464,6 +507,7 @@ describe('getTransactionDetailSections - Detailed Tests', () => { it('should handle AddSubnetValidator transactions', () => { const txDetails: TxDetails = { + ...emptyValueDetails, type: TxType.AddSubnetValidator, nodeID: 'NodeID', start: '1691234567', @@ -518,6 +562,7 @@ describe('getTransactionDetailSections - Detailed Tests', () => { it('should handle RemoveSubnetValidator transactions', () => { const txDetails: TxDetails = { + ...emptyValueDetails, type: TxType.RemoveSubnetValidator, nodeID: 'NodeID', subnetID: 'SubnetID', @@ -576,6 +621,7 @@ describe('getTransactionDetailSections - Detailed Tests', () => { const expectedFormattedGenesis = JSON.stringify(genesisJson, null, 2); const txDetails: TxDetails = { + ...emptyValueDetails, type: TxType.CreateChain, chainID: 'chainID', chainName: 'chainName', @@ -636,6 +682,7 @@ describe('getTransactionDetailSections - Detailed Tests', () => { const invalidGenesisData = 'invalid-json-string'; const txDetails: TxDetails = { + ...emptyValueDetails, type: TxType.CreateChain, chainID: 'chainID', chainName: 'chainName', @@ -693,6 +740,7 @@ describe('getTransactionDetailSections - Detailed Tests', () => { it('should handle convert subnet l1 validator details', () => { const txDetails: TxDetails = { + ...emptyValueDetails, chain: NetworkVMType.PVM, totalAvaxBurned: 1n, totalAvaxOutput: 1n, @@ -790,6 +838,7 @@ describe('getTransactionDetailSections - Detailed Tests', () => { it('should handle disable l1 validator details', () => { const txDetails: TxDetails = { + ...emptyValueDetails, chain: NetworkVMType.PVM, totalAvaxBurned: 1n, totalAvaxOutput: 1n, @@ -830,6 +879,7 @@ describe('getTransactionDetailSections - Detailed Tests', () => { it('should handle register l1 validator details', () => { const txDetails: TxDetails = { + ...emptyValueDetails, chain: NetworkVMType.PVM, totalAvaxBurned: 1n, totalAvaxOutput: 1n, @@ -872,6 +922,7 @@ describe('getTransactionDetailSections - Detailed Tests', () => { it('should handle set l1 validator weight details', () => { const txDetails: TxDetails = { + ...emptyValueDetails, chain: NetworkVMType.PVM, totalAvaxBurned: 1n, totalAvaxOutput: 1n, @@ -901,6 +952,7 @@ describe('getTransactionDetailSections - Detailed Tests', () => { it('should handle AddAutoRenewedValidator transactions (ACP-236 ppm conversion)', () => { const txDetails: TxDetails = { + ...emptyValueDetails, type: TxType.AddAutoRenewedValidator, nodeID: 'NodeID', stake: 50n, @@ -948,6 +1000,7 @@ describe('getTransactionDetailSections - Detailed Tests', () => { 'AddAutoRenewedValidator: autoCompoundRewardShares=$raw renders as $expected (ppm → percent)', ({ raw, expected }) => { const txDetails: TxDetails = { + ...emptyValueDetails, type: TxType.AddAutoRenewedValidator, nodeID: 'NodeID', stake: 1n, @@ -973,6 +1026,7 @@ describe('getTransactionDetailSections - Detailed Tests', () => { it('should handle SetAutoRenewedValidatorConfig transactions (ACP-236 ppm conversion)', () => { const txDetails: TxDetails = { + ...emptyValueDetails, type: TxType.SetAutoRenewedValidatorConfig, txId: 'ValidatorTxId', autoCompoundRewardShares: 1_000_000, @@ -1000,6 +1054,7 @@ describe('getTransactionDetailSections - Detailed Tests', () => { it('should handle increase l1 validator balance details', () => { const txDetails: TxDetails = { + ...emptyValueDetails, chain: NetworkVMType.PVM, totalAvaxBurned: 1n, totalAvaxOutput: 1n, diff --git a/packages/avalanche-module/src/utils/get-transaction-detail-sections.ts b/packages/avalanche-module/src/utils/get-transaction-detail-sections.ts index f435d50f..f66b2e45 100644 --- a/packages/avalanche-module/src/utils/get-transaction-detail-sections.ts +++ b/packages/avalanche-module/src/utils/get-transaction-detail-sections.ts @@ -17,6 +17,7 @@ import { isSetL1ValidatorWeightTx, isChainDetails, } from '../handlers/avalanche-send-transaction/typeguards'; +import { valueDetailsSection } from './transaction-detail-sections/value-details-section'; import { addAutoRenewedValidatorDetailSection, convertSubnetToL1DetailSection, @@ -43,7 +44,7 @@ export type GetTransactionDetailSectionsContext = { recipients?: string[]; }; -export const getTransactionDetailSections = ( +const _getDetailSectionsByType = ( txDetails: TxDetails, symbol: string, context?: GetTransactionDetailSectionsContext, @@ -100,3 +101,19 @@ export const getTransactionDetailSections = ( return setAutoRenewedValidatorConfigDetailSection(txDetails, symbol); } }; + +export const getTransactionDetailSections = ( + txDetails: TxDetails, + symbol: string, + context?: GetTransactionDetailSectionsContext, +) => { + const detailSections = _getDetailSectionsByType(txDetails, symbol, context); + + if (detailSections === undefined) { + return undefined; + } + + const valueSection = valueDetailsSection(txDetails, symbol); + + return valueSection ? [...detailSections, valueSection] : detailSections; +}; diff --git a/packages/avalanche-module/src/utils/transaction-detail-sections/chain-detail-section.ts b/packages/avalanche-module/src/utils/transaction-detail-sections/chain-detail-section.ts index f14c312a..84d42d15 100644 --- a/packages/avalanche-module/src/utils/transaction-detail-sections/chain-detail-section.ts +++ b/packages/avalanche-module/src/utils/transaction-detail-sections/chain-detail-section.ts @@ -1,68 +1,17 @@ -import type { BaseTx, DetailItem, DetailSection } from '@avalabs/vm-module-types'; -import { addressItem, currencyItem, textItem } from '@internal/utils'; +import type { BaseTx, DetailSection } from '@avalabs/vm-module-types'; +import { currencyItem, textItem } from '@internal/utils'; import { AvalancheChainStrings, AVAX_NONEVM_DENOMINATION } from '../../constants'; import { PVM } from '@avalabs/avalanchejs'; -type Output = BaseTx['outputs'][number]; - -/** - * Renders the amount in the asset the output actually moves. - * - * The X-Chain carries arbitrary Avalanche Native Tokens, and every output was previously - * labelled with the network token's symbol and denomination - so a transfer of some other - * asset was shown to the user as if it were AVAX, at AVAX's scale. When the asset is not the - * network token and the chain did not describe it, the raw amount and the asset id are shown - * instead of a number that would only look like a familiar unit. - */ -const amountItems = (output: Output, symbol: string): DetailItem[] => { - if (output.isAvax) { - return [currencyItem('Amount', output.amount, AVAX_NONEVM_DENOMINATION, symbol)]; - } - - if (output.assetDescription) { - return [ - currencyItem('Amount', output.amount, output.assetDescription.denomination, output.assetDescription.symbol), - textItem('Asset', output.assetDescription.name), - ]; - } - - return [textItem('Amount', `${output.amount} (smallest unit)`), textItem('Asset', output.assetId, 'vertical')]; -}; - export const chainDetailSection = (tx: BaseTx, symbol: string) => { const details: DetailSection[] = []; - const { txFee, chain, outputs, memo } = tx; + const { txFee, chain, memo } = tx; details.push({ title: 'Chain Details', items: [textItem('Active chain', `Avalanche ${AvalancheChainStrings[chain]}`)], }); - outputs.forEach((output, index) => { - const balanceChangeItems: DetailItem[] = output.owners.flatMap((ownerAddress) => [ - addressItem('To', ownerAddress), - ...amountItems(output, symbol), - ]); - - if (output.owners.length > 1) { - balanceChangeItems.push(textItem('Threshold', output.threshold.toString())); - } - - // A locked output cannot be spent until its locktime passes, which is not something the - // amount alone conveys. A locktime already in the past places no restriction on the - // funds, so it is left out rather than shown as a condition. - const lockedUntilMs = Number(output.locktime) * 1000; - - if (lockedUntilMs > Date.now()) { - balanceChangeItems.push(textItem('Locked until', new Date(lockedUntilMs).toUTCString())); - } - - details.push({ - title: index === 0 ? 'Balance Change' : undefined, - items: balanceChangeItems, - }); - }); - if (chain !== PVM && !!memo) { details.push({ title: 'Memo', diff --git a/packages/avalanche-module/src/utils/transaction-detail-sections/value-details-section.test.ts b/packages/avalanche-module/src/utils/transaction-detail-sections/value-details-section.test.ts new file mode 100644 index 00000000..f227ac3c --- /dev/null +++ b/packages/avalanche-module/src/utils/transaction-detail-sections/value-details-section.test.ts @@ -0,0 +1,108 @@ +import { DetailItemType, type TxOutput, type TxValueDetails } from '@avalabs/vm-module-types'; + +import { valueDetailsSection } from './value-details-section'; + +const createOutput = (overrides: Partial = {}): TxOutput => ({ + assetId: 'avaxAssetId', + amount: 100n, + owners: ['X-fuji1recipient'], + locktime: 0n, + stakeableLocktime: 0n, + threshold: 1n, + isAvax: true, + ...overrides, +}); + +const valueDetails = (overrides: Partial = {}): TxValueDetails => ({ + outputs: [], + inputAmounts: {}, + outputAmounts: {}, + totalAvaxInput: 0n, + totalAvaxOutput: 0n, + totalAvaxBurned: 0n, + ...overrides, +}); + +const getTransfer = (tx: TxValueDetails) => { + const [item] = valueDetailsSection(tx, 'AVAX')?.items ?? []; + + return typeof item === 'string' || item?.type !== DetailItemType.TRANSFER_LIST ? undefined : item.value; +}; + +describe('valueDetailsSection', () => { + it('returns the outputs as a list', () => { + const section = valueDetailsSection( + valueDetails({ outputs: [createOutput(), createOutput({ owners: ['X-fuji1other'], amount: 7n })] }), + 'AVAX', + ); + + expect(section).toEqual({ + title: 'Transaction Outputs', + items: [expect.objectContaining({ type: DetailItemType.TRANSFER_LIST })], + }); + expect( + getTransfer(valueDetails({ outputs: [createOutput(), createOutput({ owners: ['X-fuji1other'], amount: 7n })] })), + ).toEqual([ + expect.objectContaining({ addresses: ['X-fuji1recipient'], amount: 100n }), + expect.objectContaining({ addresses: ['X-fuji1other'], amount: 7n }), + ]); + }); + + it('returns undefined when there are no outputs', () => { + expect(valueDetailsSection(valueDetails(), 'AVAX')).toBeUndefined(); + }); + + it('returns the proper AVAX details', () => { + expect(getTransfer(valueDetails({ outputs: [createOutput()] }))?.[0]).toMatchObject({ + symbol: 'AVAX', + decimals: 9, + }); + }); + + it('returns the proper non-AVAX asset details', () => { + const transfers = getTransfer( + valueDetails({ + outputs: [ + createOutput({ + isAvax: false, + assetId: 'tokenId', + assetDescription: { assetID: 'tokenId', name: 'Some Token', symbol: 'TKN', denomination: 2 }, + }), + ], + }), + ); + + expect(transfers?.[0]).toMatchObject({ symbol: 'TKN', decimals: 2, assetName: 'Some Token' }); + }); + + it('returns assetId even when the currency details are unknown', () => { + const transfers = getTransfer(valueDetails({ outputs: [createOutput({ isAvax: false, assetId: 'tokenId' })] })); + + expect(transfers?.[0]?.symbol).toBeUndefined(); + expect(transfers?.[0]?.assetId).toEqual('tokenId'); + }); + + it('returns the proper lock details', () => { + const transfers = getTransfer( + valueDetails({ outputs: [createOutput({ locktime: 1700n, stakeableLocktime: 9900n })] }), + ); + + expect(transfers?.[0]).toMatchObject({ lockedUntil: 1700, stakeableLockedUntil: 9900 }); + }); + + it('returns the proper threshold details', () => { + const shared = getTransfer( + valueDetails({ outputs: [createOutput({ owners: ['X-fuji1a', 'X-fuji1b'], threshold: 2n })] }), + ); + const sole = getTransfer(valueDetails({ outputs: [createOutput()] })); + + expect(shared?.[0]?.threshold).toEqual(2); + expect(sole?.[0]?.threshold).toEqual(1); + }); + + it('returns outputs with no owners', () => { + const transfers = getTransfer(valueDetails({ outputs: [createOutput({ owners: [] })] })); + + expect(transfers?.[0]).toMatchObject({ addresses: [], amount: 100n }); + }); +}); diff --git a/packages/avalanche-module/src/utils/transaction-detail-sections/value-details-section.ts b/packages/avalanche-module/src/utils/transaction-detail-sections/value-details-section.ts new file mode 100644 index 00000000..8ba4f7c2 --- /dev/null +++ b/packages/avalanche-module/src/utils/transaction-detail-sections/value-details-section.ts @@ -0,0 +1,45 @@ +import type { DetailSection, Transfer, TxOutput, TxValueDetails } from '@avalabs/vm-module-types'; +import { transferListItem } from '@internal/utils'; + +import { AVAX_NONEVM_DENOMINATION } from '../../constants'; + +const TITLE = 'Transaction Outputs'; + +const _getAssetDetails = (output: TxOutput, symbol: string): Pick => { + if (output.isAvax) { + return { symbol, decimals: AVAX_NONEVM_DENOMINATION }; + } + + if (output.assetDescription) { + return { + symbol: output.assetDescription.symbol, + decimals: output.assetDescription.denomination, + assetName: output.assetDescription.name, + }; + } + + return {}; +}; + +const _getTransferDetails = (output: TxOutput, symbol: string): Transfer => ({ + addresses: output.owners, + amount: output.amount, + assetId: output.assetId, + ..._getAssetDetails(output, symbol), + threshold: Number(output.threshold), + lockedUntil: Number(output.locktime), + stakeableLockedUntil: Number(output.stakeableLocktime), +}); + +export const valueDetailsSection = (tx: TxValueDetails, symbol: string): DetailSection | undefined => + tx.outputs.length === 0 + ? undefined + : { + title: TITLE, + items: [ + transferListItem( + TITLE, + tx.outputs.map((output) => _getTransferDetails(output, symbol)), + ), + ], + }; diff --git a/packages/types/package.json b/packages/types/package.json index 8ff4e652..e2c12fc9 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -20,7 +20,7 @@ "lint": "eslint \"src/**/*.ts\"" }, "dependencies": { - "@avalabs/core-wallets-sdk": "3.1.0-alpha.97", + "@avalabs/core-wallets-sdk": "3.1.0-canary.56e4cf95", "@avalabs/glacier-sdk": "3.1.0-alpha.97", "@metamask/rpc-errors": "6.3.0", "bitcoinjs-lib": "5.2.0", diff --git a/packages/types/src/rpc.ts b/packages/types/src/rpc.ts index 057fe6ca..fa9a0239 100644 --- a/packages/types/src/rpc.ts +++ b/packages/types/src/rpc.ts @@ -103,6 +103,7 @@ export enum DetailItemType { DATE = 'date', LINK = 'link', NETWORK = 'network', + TRANSFER_LIST = 'transferList', } // It's very similar as CurrencyItem, but we want the client apps @@ -170,6 +171,23 @@ export type NetworkItem = BaseDetailItem & { value: NetworkItemValue; }; +export type Transfer = { + addresses: string[]; + amount: bigint; + assetId: string; + symbol?: string; + decimals?: number; + assetName?: string; + threshold?: number; + lockedUntil?: number; + stakeableLockedUntil?: number; +}; + +export type TransferListItem = BaseDetailItem & { + type: DetailItemType.TRANSFER_LIST; + value: Transfer[]; +}; + export type DetailItem = | string | TextItem @@ -181,7 +199,8 @@ export type DetailItem = | DateItem | LinkItem | FundsRecipientItem - | NetworkItem; + | NetworkItem + | TransferListItem; export type DisplayData = { title: string; diff --git a/packages/types/src/staking.ts b/packages/types/src/staking.ts index 81f2a446..f03a0e74 100644 --- a/packages/types/src/staking.ts +++ b/packages/types/src/staking.ts @@ -1,12 +1,32 @@ import type { NetworkVMType } from './common'; -export type TxDetails = - | StakingDetails - | ExportImportTxDetails - | ChainDetails - | BlockchainDetails - | SubnetDetails - | L1Details; +export type TxDetails = TxValueDetails & + (StakingDetails | ExportImportTxDetails | ChainDetails | BlockchainDetails | SubnetDetails | L1Details); + +export type TxValueDetails = { + outputs: TxOutput[]; + inputAmounts: Record; + outputAmounts: Record; + totalAvaxInput: bigint; + totalAvaxOutput: bigint; + totalAvaxBurned: bigint; +}; + +export type TxOutput = { + assetId: string; + amount: bigint; + owners: string[]; + locktime: bigint; + stakeableLocktime: bigint; + threshold: bigint; + isAvax: boolean; + assetDescription?: { + assetID: string; + name: string; + symbol: string; + denomination: number; + }; +}; export type StakingDetails = | AddPermissionlessDelegatorTx @@ -70,20 +90,7 @@ export interface ImportTx { export interface BaseTx { type: TxType.Base; chain: VM; - outputs: { - assetId: string; - locktime: bigint; - threshold: bigint; - amount: bigint; - assetDescription?: { - assetID: string; - name: string; - symbol: string; - denomination: number; - }; - owners: string[]; - isAvax: boolean; - }[]; + outputs: TxOutput[]; memo?: string; txFee: bigint; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5c5f4a25..65c18f0f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -157,8 +157,8 @@ importers: specifier: 3.1.0-alpha.97 version: 3.1.0-alpha.97(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) '@avalabs/core-wallets-sdk': - specifier: 3.1.0-alpha.97 - version: 3.1.0-alpha.97(@solana/sysvars@2.1.0(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2) + specifier: 3.1.0-canary.56e4cf95 + version: 3.1.0-canary.56e4cf95(@solana/sysvars@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) '@avalabs/crypto-sdk': specifier: 1.0.0 version: 1.0.0 @@ -499,10 +499,10 @@ importers: version: 1.2.4 '@solana-program/system': specifier: 0.7.0 - version: 0.7.0(@solana/kit@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))) + version: 0.7.0(@solana/kit@2.1.0(typescript@5.8.2)) '@solana-program/token': specifier: 0.5.1 - version: 0.5.1(@solana/kit@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))) + version: 0.5.1(@solana/kit@2.1.0(typescript@5.8.2)) '@solana/kit': specifier: 2.1.0 version: 2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) @@ -550,8 +550,8 @@ importers: packages/types: dependencies: '@avalabs/core-wallets-sdk': - specifier: 3.1.0-alpha.97 - version: 3.1.0-alpha.97(@solana/sysvars@2.1.0(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2) + specifier: 3.1.0-canary.56e4cf95 + version: 3.1.0-canary.56e4cf95(@solana/sysvars@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) '@avalabs/glacier-sdk': specifier: 3.1.0-alpha.97 version: 3.1.0-alpha.97 @@ -611,6 +611,9 @@ packages: '@avalabs/core-chains-sdk@3.1.0-alpha.97': resolution: {integrity: sha512-1UPEU+6zq18ILzWrzPOjUUcU3jwDbASfeYUOBFm6kD6KPOqwmWZJIhV92JiTFUrQsY9j805JTdrNbvAnOspuTQ==} + '@avalabs/core-chains-sdk@3.1.0-canary.56e4cf95': + resolution: {integrity: sha512-a9bYDKHjkE22Baa6nCHERBaXjPt1yJ+aO5l5u2c4wL1QAnsGBBWuGkRQYC24QUA0o9y5xmzHvRjYn87fbcax+Q==} + '@avalabs/core-coingecko-sdk@3.1.0-alpha.97': resolution: {integrity: sha512-9UQTcJ80/wehUkJ6fNqLiTPHaVPae7WKLu1SHeg4JrMW29bU3YKMogqEN6qAYxlha5i8/+1joPwrpglnU8Ai5w==} @@ -624,11 +627,23 @@ packages: bn.js: ^5.2.1 ethers: ^6.7.1 + '@avalabs/core-utils-sdk@3.1.0-canary.56e4cf95': + resolution: {integrity: sha512-MNzGtjBmZf31qf27E9cTcNfigmbzpcsbNCrrf5XcuFlPWMAsFZ4VY/vyU5bZW+twTGyvHZKUJ3z48h2O0D6QEg==} + peerDependencies: + big.js: ^6.2.1 + bn.js: ^5.2.1 + ethers: ^6.7.1 + '@avalabs/core-wallets-sdk@3.1.0-alpha.97': resolution: {integrity: sha512-s7IPr6dSw1vDbRXVKjF4rFmolKIeVUe26x0S78T1sT1tw6m6UBBgYWR1LgPQkYn1RuG6N5jV2MuJPxWcUKbL+g==} peerDependencies: ethers: ^6.7.1 + '@avalabs/core-wallets-sdk@3.1.0-canary.56e4cf95': + resolution: {integrity: sha512-ic5+8aH2SkjCKidzecGdpJ4PXsivsdhEZPfHUtZ2EHisyetxk6/0/4RDRClkgvvMWlyqG6cr1F2OqbWz+15VrA==} + peerDependencies: + ethers: ^6.7.1 + '@avalabs/crypto-sdk@1.0.0': resolution: {integrity: sha512-2klBU8O8FhNKnQs511eJjCWOzM1XcWsSmK1KtXrGl4qAMhHnat8/Uvs0KQHphIZfCGGkOFe25LrekacP5p6Kpg==} engines: {node: '>=20.18.0'} @@ -644,6 +659,9 @@ packages: '@avalabs/glacier-sdk@3.1.0-alpha.97': resolution: {integrity: sha512-6Kd/j6iKT9LpO8AHWqZJYVtr+EInfsnVJJLQ97RFqY5Q60QoPRfidDtWlxC19Oqv3nHrgreiKAsRtwLSllPNDA==} + '@avalabs/glacier-sdk@3.1.0-canary.56e4cf95': + resolution: {integrity: sha512-PipkLPcg1DcDMi497nX24i6SgLViv8Pf8YNibkgXpbXXTcItSQGmJfXwHLRi81DNaTV6umtMdgrAkJpou5oqFA==} + '@avalabs/hw-app-avalanche@1.1.1': resolution: {integrity: sha512-c/naZTx763fXn33NgSS9NN02VyoMbxqKPViO3509xgrzlD8tEEx41xigwA5Jn5YO1Q2U9HhhUVr0vyCq9FLoLw==} @@ -6446,6 +6464,16 @@ snapshots: - node-fetch - supports-color + '@avalabs/core-chains-sdk@3.1.0-canary.56e4cf95(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)': + dependencies: + '@avalabs/core-utils-sdk': 3.1.0-canary.56e4cf95(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) + transitivePeerDependencies: + - big.js + - bn.js + - ethers + - node-fetch + - supports-color + '@avalabs/core-coingecko-sdk@3.1.0-alpha.97(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)': dependencies: '@avalabs/core-utils-sdk': 3.1.0-alpha.97(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) @@ -6498,6 +6526,17 @@ snapshots: - node-fetch - supports-color + '@avalabs/core-utils-sdk@3.1.0-canary.56e4cf95(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)': + dependencies: + '@hpke/core': 1.2.5 + big.js: 6.2.1 + bn.js: 5.2.1 + ethers: 6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10) + is-ipfs: 6.0.2(node-fetch@2.7.0) + transitivePeerDependencies: + - node-fetch + - supports-color + '@avalabs/core-wallets-sdk@3.1.0-alpha.97(@solana/sysvars@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))': dependencies: '@avalabs/avalanchejs': 5.1.1-alpha.4 @@ -6513,9 +6552,9 @@ snapshots: '@noble/curves': 1.6.0 '@noble/hashes': 1.7.1 '@scure/base': 1.2.4 - '@solana-program/system': 0.7.0(@solana/kit@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))) - '@solana-program/token': 0.5.1(@solana/kit@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))) - '@solana-program/token-2022': 0.4.2(@solana/kit@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)))(@solana/sysvars@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)) + '@solana-program/system': 0.7.0(@solana/kit@2.1.0(typescript@5.8.2)) + '@solana-program/token': 0.5.1(@solana/kit@2.1.0(typescript@5.8.2)) + '@solana-program/token-2022': 0.4.2(@solana/kit@2.1.0(typescript@5.8.2))(@solana/sysvars@2.1.0(typescript@5.8.2)) '@solana/kit': 2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) bip174: 2.1.1 bip32: 2.0.6 @@ -6557,9 +6596,53 @@ snapshots: '@noble/curves': 1.6.0 '@noble/hashes': 1.7.1 '@scure/base': 1.2.4 - '@solana-program/system': 0.7.0(@solana/kit@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))) - '@solana-program/token': 0.5.1(@solana/kit@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))) - '@solana-program/token-2022': 0.4.2(@solana/kit@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)))(@solana/sysvars@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)) + '@solana-program/system': 0.7.0(@solana/kit@2.1.0(typescript@5.8.2)) + '@solana-program/token': 0.5.1(@solana/kit@2.1.0(typescript@5.8.2)) + '@solana-program/token-2022': 0.4.2(@solana/kit@2.1.0(typescript@5.8.2))(@solana/sysvars@2.1.0(typescript@5.8.2)) + '@solana/kit': 2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) + bip174: 2.1.1 + bip32: 2.0.6 + bip32-path: 0.4.2 + bip39: 3.1.0 + bitcoinjs-lib: 5.2.0 + coinselect: 3.1.13 + create-hash: 1.2.0 + ethers: 6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10) + hdkey: 2.0.1 + ledger-bitcoin: 0.2.3 + micro-key-producer: 0.7.5 + xss: 1.0.14 + transitivePeerDependencies: + - '@solana/sysvars' + - big.js + - bn.js + - debug + - fastestsmallesttextencoderdecoder + - node-fetch + - react + - react-redux + - supports-color + - typescript + - ws + + '@avalabs/core-wallets-sdk@3.1.0-canary.56e4cf95(@solana/sysvars@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))': + dependencies: + '@avalabs/avalanchejs': 5.1.1-alpha.4 + '@avalabs/core-chains-sdk': 3.1.0-canary.56e4cf95(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) + '@avalabs/core-utils-sdk': 3.1.0-canary.56e4cf95(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) + '@avalabs/glacier-sdk': 3.1.0-canary.56e4cf95 + '@avalabs/hw-app-avalanche': 1.1.1(react@18.3.1) + '@ledgerhq/hw-app-btc': 10.11.0 + '@ledgerhq/hw-app-eth': 6.45.18(react@18.3.1) + '@ledgerhq/hw-app-solana': 7.5.3 + '@ledgerhq/hw-transport': 6.31.10 + '@metamask/eth-sig-util': 7.0.2 + '@noble/curves': 1.6.0 + '@noble/hashes': 1.7.1 + '@scure/base': 1.2.4 + '@solana-program/system': 0.7.0(@solana/kit@2.1.0(typescript@5.8.2)) + '@solana-program/token': 0.5.1(@solana/kit@2.1.0(typescript@5.8.2)) + '@solana-program/token-2022': 0.4.2(@solana/kit@2.1.0(typescript@5.8.2))(@solana/sysvars@2.1.0(typescript@5.8.2)) '@solana/kit': 2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) bip174: 2.1.1 bip32: 2.0.6 @@ -6590,6 +6673,8 @@ snapshots: '@avalabs/glacier-sdk@3.1.0-alpha.97': {} + '@avalabs/glacier-sdk@3.1.0-canary.56e4cf95': {} + '@avalabs/hw-app-avalanche@1.1.1(react@18.3.1)': dependencies: '@ledgerhq/hw-app-eth': 6.45.18(react@18.3.1) @@ -8716,16 +8801,16 @@ snapshots: '@socket.io/component-emitter@3.1.2': {} - '@solana-program/system@0.7.0(@solana/kit@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)))': + '@solana-program/system@0.7.0(@solana/kit@2.1.0(typescript@5.8.2))': dependencies: '@solana/kit': 2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) - '@solana-program/token-2022@0.4.2(@solana/kit@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)))(@solana/sysvars@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2))': + '@solana-program/token-2022@0.4.2(@solana/kit@2.1.0(typescript@5.8.2))(@solana/sysvars@2.1.0(typescript@5.8.2))': dependencies: '@solana/kit': 2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) '@solana/sysvars': 2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2) - '@solana-program/token@0.5.1(@solana/kit@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)))': + '@solana-program/token@0.5.1(@solana/kit@2.1.0(typescript@5.8.2))': dependencies: '@solana/kit': 2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) From d9fbae6a90672d1804b4144b8308da7188d021e6 Mon Sep 17 00:00:00 2001 From: bferenc Date: Wed, 23 Sep 2026 13:19:25 +0200 Subject: [PATCH 2/5] chore: update deps --- packages/bitcoin-module/package.json | 2 +- packages/evm-module/package.json | 2 +- packages/hypercore-module/package.json | 2 +- packages/svm-module/package.json | 2 +- pnpm-lock.yaml | 118 ++++++++++--------------- 5 files changed, 49 insertions(+), 77 deletions(-) diff --git a/packages/bitcoin-module/package.json b/packages/bitcoin-module/package.json index 9cfd6e37..395405d2 100644 --- a/packages/bitcoin-module/package.json +++ b/packages/bitcoin-module/package.json @@ -25,7 +25,7 @@ "dependencies": { "@avalabs/core-coingecko-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.56e4cf95", "@avalabs/crypto-sdk": "1.0.0", "@avalabs/vm-module-types": "workspace:*", "@metamask/rpc-errors": "6.3.0", diff --git a/packages/evm-module/package.json b/packages/evm-module/package.json index ffd72e44..26453eb1 100644 --- a/packages/evm-module/package.json +++ b/packages/evm-module/package.json @@ -28,7 +28,7 @@ "@avalabs/core-etherscan-sdk": "3.1.0-alpha.97", "@avalabs/core-utils-sdk": "3.1.0-alpha.97", "@avalabs/core-chains-sdk": "3.1.0-alpha.97", - "@avalabs/core-wallets-sdk": "3.1.0-alpha.97", + "@avalabs/core-wallets-sdk": "3.1.0-canary.56e4cf95", "@avalabs/crypto-sdk": "1.0.0", "@avalabs/glacier-sdk": "3.1.0-alpha.97", "@avalabs/types": "3.1.0-alpha.97", diff --git a/packages/hypercore-module/package.json b/packages/hypercore-module/package.json index 3403b1e2..01736c62 100644 --- a/packages/hypercore-module/package.json +++ b/packages/hypercore-module/package.json @@ -23,7 +23,7 @@ "test:watch": "jest --watch" }, "dependencies": { - "@avalabs/core-wallets-sdk": "3.1.0-alpha.97", + "@avalabs/core-wallets-sdk": "3.1.0-canary.56e4cf95", "@avalabs/crypto-sdk": "1.0.0", "@avalabs/vm-module-types": "workspace:*", "@metamask/rpc-errors": "6.3.0", diff --git a/packages/svm-module/package.json b/packages/svm-module/package.json index 356a3131..78835390 100644 --- a/packages/svm-module/package.json +++ b/packages/svm-module/package.json @@ -25,7 +25,7 @@ "@avalabs/core-chains-sdk": "3.1.0-alpha.97", "@avalabs/core-coingecko-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.56e4cf95", "@avalabs/crypto-sdk": "1.0.0", "@avalabs/vm-module-types": "workspace:*", "@blockaid/client": "0.48.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 65c18f0f..e47f09c7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -158,7 +158,7 @@ importers: version: 3.1.0-alpha.97(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) '@avalabs/core-wallets-sdk': specifier: 3.1.0-canary.56e4cf95 - version: 3.1.0-canary.56e4cf95(@solana/sysvars@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) + version: 3.1.0-canary.56e4cf95(@solana/sysvars@2.1.0(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2) '@avalabs/crypto-sdk': specifier: 1.0.0 version: 1.0.0 @@ -221,8 +221,8 @@ importers: specifier: 3.1.0-alpha.97 version: 3.1.0-alpha.97(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) '@avalabs/core-wallets-sdk': - specifier: 3.1.0-alpha.97 - version: 3.1.0-alpha.97(@solana/sysvars@2.1.0(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2) + specifier: 3.1.0-canary.56e4cf95 + version: 3.1.0-canary.56e4cf95(@solana/sysvars@2.1.0(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2) '@avalabs/crypto-sdk': specifier: 1.0.0 version: 1.0.0 @@ -288,8 +288,8 @@ importers: specifier: 3.1.0-alpha.97 version: 3.1.0-alpha.97(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) '@avalabs/core-wallets-sdk': - specifier: 3.1.0-alpha.97 - version: 3.1.0-alpha.97(@solana/sysvars@2.1.0(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2) + specifier: 3.1.0-canary.56e4cf95 + version: 3.1.0-canary.56e4cf95(@solana/sysvars@2.1.0(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2) '@avalabs/crypto-sdk': specifier: 1.0.0 version: 1.0.0 @@ -422,8 +422,8 @@ importers: packages/hypercore-module: dependencies: '@avalabs/core-wallets-sdk': - specifier: 3.1.0-alpha.97 - version: 3.1.0-alpha.97(@solana/sysvars@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) + specifier: 3.1.0-canary.56e4cf95 + version: 3.1.0-canary.56e4cf95(@solana/sysvars@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) '@avalabs/crypto-sdk': specifier: 1.0.0 version: 1.0.0 @@ -480,8 +480,8 @@ importers: specifier: 3.1.0-alpha.97 version: 3.1.0-alpha.97(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) '@avalabs/core-wallets-sdk': - specifier: 3.1.0-alpha.97 - version: 3.1.0-alpha.97(@solana/sysvars@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) + specifier: 3.1.0-canary.56e4cf95 + version: 3.1.0-canary.56e4cf95(@solana/sysvars@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) '@avalabs/crypto-sdk': specifier: 1.0.0 version: 1.0.0 @@ -499,10 +499,10 @@ importers: version: 1.2.4 '@solana-program/system': specifier: 0.7.0 - version: 0.7.0(@solana/kit@2.1.0(typescript@5.8.2)) + version: 0.7.0(@solana/kit@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))) '@solana-program/token': specifier: 0.5.1 - version: 0.5.1(@solana/kit@2.1.0(typescript@5.8.2)) + version: 0.5.1(@solana/kit@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))) '@solana/kit': specifier: 2.1.0 version: 2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) @@ -551,7 +551,7 @@ importers: dependencies: '@avalabs/core-wallets-sdk': specifier: 3.1.0-canary.56e4cf95 - version: 3.1.0-canary.56e4cf95(@solana/sysvars@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) + version: 3.1.0-canary.56e4cf95(@solana/sysvars@2.1.0(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2) '@avalabs/glacier-sdk': specifier: 3.1.0-alpha.97 version: 3.1.0-alpha.97 @@ -634,11 +634,6 @@ packages: bn.js: ^5.2.1 ethers: ^6.7.1 - '@avalabs/core-wallets-sdk@3.1.0-alpha.97': - resolution: {integrity: sha512-s7IPr6dSw1vDbRXVKjF4rFmolKIeVUe26x0S78T1sT1tw6m6UBBgYWR1LgPQkYn1RuG6N5jV2MuJPxWcUKbL+g==} - peerDependencies: - ethers: ^6.7.1 - '@avalabs/core-wallets-sdk@3.1.0-canary.56e4cf95': resolution: {integrity: sha512-ic5+8aH2SkjCKidzecGdpJ4PXsivsdhEZPfHUtZ2EHisyetxk6/0/4RDRClkgvvMWlyqG6cr1F2OqbWz+15VrA==} peerDependencies: @@ -6474,6 +6469,16 @@ snapshots: - node-fetch - supports-color + '@avalabs/core-chains-sdk@3.1.0-canary.56e4cf95(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)': + dependencies: + '@avalabs/core-utils-sdk': 3.1.0-canary.56e4cf95(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) + transitivePeerDependencies: + - big.js + - bn.js + - ethers + - node-fetch + - supports-color + '@avalabs/core-coingecko-sdk@3.1.0-alpha.97(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)': dependencies: '@avalabs/core-utils-sdk': 3.1.0-alpha.97(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) @@ -6537,56 +6542,23 @@ snapshots: - node-fetch - supports-color - '@avalabs/core-wallets-sdk@3.1.0-alpha.97(@solana/sysvars@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))': + '@avalabs/core-utils-sdk@3.1.0-canary.56e4cf95(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)': dependencies: - '@avalabs/avalanchejs': 5.1.1-alpha.4 - '@avalabs/core-chains-sdk': 3.1.0-alpha.97(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) - '@avalabs/core-utils-sdk': 3.1.0-alpha.97(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) - '@avalabs/glacier-sdk': 3.1.0-alpha.97 - '@avalabs/hw-app-avalanche': 1.1.1(react@18.3.1) - '@ledgerhq/hw-app-btc': 10.11.0 - '@ledgerhq/hw-app-eth': 6.45.18(react@18.3.1) - '@ledgerhq/hw-app-solana': 7.5.3 - '@ledgerhq/hw-transport': 6.31.10 - '@metamask/eth-sig-util': 7.0.2 - '@noble/curves': 1.6.0 - '@noble/hashes': 1.7.1 - '@scure/base': 1.2.4 - '@solana-program/system': 0.7.0(@solana/kit@2.1.0(typescript@5.8.2)) - '@solana-program/token': 0.5.1(@solana/kit@2.1.0(typescript@5.8.2)) - '@solana-program/token-2022': 0.4.2(@solana/kit@2.1.0(typescript@5.8.2))(@solana/sysvars@2.1.0(typescript@5.8.2)) - '@solana/kit': 2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) - bip174: 2.1.1 - bip32: 2.0.6 - bip32-path: 0.4.2 - bip39: 3.1.0 - bitcoinjs-lib: 5.2.0 - coinselect: 3.1.13 - create-hash: 1.2.0 + '@hpke/core': 1.2.5 + big.js: 6.2.1 + bn.js: 5.2.2 ethers: 6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10) - hdkey: 2.0.1 - ledger-bitcoin: 0.2.3 - micro-key-producer: 0.7.5 - xss: 1.0.14 + is-ipfs: 6.0.2(node-fetch@2.7.0) transitivePeerDependencies: - - '@solana/sysvars' - - big.js - - bn.js - - debug - - fastestsmallesttextencoderdecoder - node-fetch - - react - - react-redux - supports-color - - typescript - - ws - '@avalabs/core-wallets-sdk@3.1.0-alpha.97(@solana/sysvars@2.1.0(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2)': + '@avalabs/core-wallets-sdk@3.1.0-canary.56e4cf95(@solana/sysvars@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))': dependencies: '@avalabs/avalanchejs': 5.1.1-alpha.4 - '@avalabs/core-chains-sdk': 3.1.0-alpha.97(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) - '@avalabs/core-utils-sdk': 3.1.0-alpha.97(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) - '@avalabs/glacier-sdk': 3.1.0-alpha.97 + '@avalabs/core-chains-sdk': 3.1.0-canary.56e4cf95(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) + '@avalabs/core-utils-sdk': 3.1.0-canary.56e4cf95(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) + '@avalabs/glacier-sdk': 3.1.0-canary.56e4cf95 '@avalabs/hw-app-avalanche': 1.1.1(react@18.3.1) '@ledgerhq/hw-app-btc': 10.11.0 '@ledgerhq/hw-app-eth': 6.45.18(react@18.3.1) @@ -6596,9 +6568,9 @@ snapshots: '@noble/curves': 1.6.0 '@noble/hashes': 1.7.1 '@scure/base': 1.2.4 - '@solana-program/system': 0.7.0(@solana/kit@2.1.0(typescript@5.8.2)) - '@solana-program/token': 0.5.1(@solana/kit@2.1.0(typescript@5.8.2)) - '@solana-program/token-2022': 0.4.2(@solana/kit@2.1.0(typescript@5.8.2))(@solana/sysvars@2.1.0(typescript@5.8.2)) + '@solana-program/system': 0.7.0(@solana/kit@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))) + '@solana-program/token': 0.5.1(@solana/kit@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))) + '@solana-program/token-2022': 0.4.2(@solana/kit@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)))(@solana/sysvars@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)) '@solana/kit': 2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) bip174: 2.1.1 bip32: 2.0.6 @@ -6625,7 +6597,7 @@ snapshots: - typescript - ws - '@avalabs/core-wallets-sdk@3.1.0-canary.56e4cf95(@solana/sysvars@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))': + '@avalabs/core-wallets-sdk@3.1.0-canary.56e4cf95(@solana/sysvars@2.1.0(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2)': dependencies: '@avalabs/avalanchejs': 5.1.1-alpha.4 '@avalabs/core-chains-sdk': 3.1.0-canary.56e4cf95(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) @@ -6640,9 +6612,9 @@ snapshots: '@noble/curves': 1.6.0 '@noble/hashes': 1.7.1 '@scure/base': 1.2.4 - '@solana-program/system': 0.7.0(@solana/kit@2.1.0(typescript@5.8.2)) - '@solana-program/token': 0.5.1(@solana/kit@2.1.0(typescript@5.8.2)) - '@solana-program/token-2022': 0.4.2(@solana/kit@2.1.0(typescript@5.8.2))(@solana/sysvars@2.1.0(typescript@5.8.2)) + '@solana-program/system': 0.7.0(@solana/kit@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))) + '@solana-program/token': 0.5.1(@solana/kit@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))) + '@solana-program/token-2022': 0.4.2(@solana/kit@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)))(@solana/sysvars@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)) '@solana/kit': 2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) bip174: 2.1.1 bip32: 2.0.6 @@ -8801,16 +8773,16 @@ snapshots: '@socket.io/component-emitter@3.1.2': {} - '@solana-program/system@0.7.0(@solana/kit@2.1.0(typescript@5.8.2))': + '@solana-program/system@0.7.0(@solana/kit@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)))': dependencies: '@solana/kit': 2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) - '@solana-program/token-2022@0.4.2(@solana/kit@2.1.0(typescript@5.8.2))(@solana/sysvars@2.1.0(typescript@5.8.2))': + '@solana-program/token-2022@0.4.2(@solana/kit@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)))(@solana/sysvars@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2))': dependencies: '@solana/kit': 2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) '@solana/sysvars': 2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2) - '@solana-program/token@0.5.1(@solana/kit@2.1.0(typescript@5.8.2))': + '@solana-program/token@0.5.1(@solana/kit@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)))': dependencies: '@solana/kit': 2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) @@ -9238,7 +9210,7 @@ snapshots: '@types/connect@3.4.38': dependencies: - '@types/node': 12.20.55 + '@types/node': 20.12.13 '@types/debug@4.1.12': dependencies: @@ -9328,7 +9300,7 @@ snapshots: '@types/ws@7.4.7': dependencies: - '@types/node': 12.20.55 + '@types/node': 20.12.13 '@types/ws@8.18.1': dependencies: @@ -11563,7 +11535,7 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 25.0.6 + '@types/node': 20.12.13 chalk: 4.1.2 co: 4.6.0 dedent: 1.7.1 @@ -11671,7 +11643,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 25.0.6 + '@types/node': 20.12.13 jest-mock: 29.7.0 jest-util: 29.7.0 From c21af17da2b3a1caa515a13c245c8c0e6c11a45f Mon Sep 17 00:00:00 2001 From: bferenc Date: Wed, 23 Sep 2026 13:23:59 +0200 Subject: [PATCH 3/5] docs(changeset): add value details to avalanche tx approval screens --- .changeset/large-rockets-spend.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .changeset/large-rockets-spend.md diff --git a/.changeset/large-rockets-spend.md b/.changeset/large-rockets-spend.md new file mode 100644 index 00000000..d745285f --- /dev/null +++ b/.changeset/large-rockets-spend.md @@ -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 From eff94cede85dc5c07698b3a5fd6d1be6747b22b2 Mon Sep 17 00:00:00 2001 From: bferenc Date: Thu, 24 Sep 2026 15:52:31 +0200 Subject: [PATCH 4/5] feat: resurrect getExcessiveBurnAlert --- packages/avalanche-module/package.json | 2 +- .../avalanche-send-transaction.test.ts | 49 ++++++++- .../avalanche-send-transaction.ts | 3 + .../avalanche-sign-transaction.test.ts | 45 +++++++- .../avalanche-sign-transaction.ts | 3 + .../utils/get-excessive-burn-alert.test.ts | 27 +++++ .../src/utils/get-excessive-burn-alert.ts | 13 +++ .../get-transaction-detail-sections.test.ts | 1 + .../utils/get-transaction-detail-sections.ts | 8 +- .../amount-details-sections.test.ts | 100 ++++++++++++++++++ .../amount-details-sections.ts | 56 ++++++++++ .../value-details-section.test.ts | 1 + packages/bitcoin-module/package.json | 2 +- packages/evm-module/package.json | 2 +- packages/hypercore-module/package.json | 2 +- packages/svm-module/package.json | 2 +- packages/types/package.json | 2 +- packages/types/src/staking.ts | 1 + pnpm-lock.yaml | 70 ++++++------ 19 files changed, 345 insertions(+), 44 deletions(-) create mode 100644 packages/avalanche-module/src/utils/get-excessive-burn-alert.test.ts create mode 100644 packages/avalanche-module/src/utils/get-excessive-burn-alert.ts create mode 100644 packages/avalanche-module/src/utils/transaction-detail-sections/amount-details-sections.test.ts create mode 100644 packages/avalanche-module/src/utils/transaction-detail-sections/amount-details-sections.ts diff --git a/packages/avalanche-module/package.json b/packages/avalanche-module/package.json index 8bf59def..0cb6c7aa 100644 --- a/packages/avalanche-module/package.json +++ b/packages/avalanche-module/package.json @@ -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-canary.56e4cf95", + "@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", diff --git a/packages/avalanche-module/src/handlers/avalanche-send-transaction/avalanche-send-transaction.test.ts b/packages/avalanche-module/src/handlers/avalanche-send-transaction/avalanche-send-transaction.test.ts index 2c415cd9..e33ac9d1 100644 --- a/packages/avalanche-module/src/handlers/avalanche-send-transaction/avalanche-send-transaction.test.ts +++ b/packages/avalanche-module/src/handlers/avalanche-send-transaction/avalanche-send-transaction.test.ts @@ -1,6 +1,13 @@ 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'; @@ -8,6 +15,7 @@ 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'); @@ -24,6 +32,7 @@ const emptyValueDetails = { totalAvaxInput: 0n, totalAvaxOutput: 0n, totalAvaxBurned: 0n, + isValidAvaxBurnedAmount: true, }; const utxosMock = [{ utxoId: '1' }, { utxoId: '2' }]; @@ -56,6 +65,7 @@ const mockGetProvider = getProvider as jest.MockedFunction; const mockProvider = { issueTxHex: issueTxHexMock, getApiP: mockGetApiP, + getContext: () => ({ avaxAssetID: AVAX_ASSET_ID }), evmRpc: { waitForTransaction: mockWaitForTransaction, }, @@ -409,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), diff --git a/packages/avalanche-module/src/handlers/avalanche-send-transaction/avalanche-send-transaction.ts b/packages/avalanche-module/src/handlers/avalanche-send-transaction/avalanche-send-transaction.ts index acc2c07e..4191ed6f 100644 --- a/packages/avalanche-module/src/handlers/avalanche-send-transaction/avalanche-send-transaction.ts +++ b/packages/avalanche-module/src/handlers/avalanche-send-transaction/avalanche-send-transaction.ts @@ -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'; @@ -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 @@ -167,6 +169,7 @@ export const avalancheSendTransaction = async ({ }, details, networkFeeSelector: false, + alert: getExcessiveBurnAlert(txDetails), }; // prompt user for approval diff --git a/packages/avalanche-module/src/handlers/avalanche-sign-transaction/avalanche-sign-transaction.test.ts b/packages/avalanche-module/src/handlers/avalanche-sign-transaction/avalanche-sign-transaction.test.ts index 5c17835b..a9245caa 100644 --- a/packages/avalanche-module/src/handlers/avalanche-sign-transaction/avalanche-sign-transaction.test.ts +++ b/packages/avalanche-module/src/handlers/avalanche-sign-transaction/avalanche-sign-transaction.test.ts @@ -1,8 +1,9 @@ import { info, PVM, UnsignedTx, utils } from '@avalabs/avalanchejs'; -import { AppName, NetworkVMType, RpcMethod, TxType } from '@avalabs/vm-module-types'; +import { AlertType, AppName, NetworkVMType, RpcMethod, TxType } from '@avalabs/vm-module-types'; import { Avalanche } from '@avalabs/core-wallets-sdk'; import { avalancheSignTransaction } from './avalanche-sign-transaction'; import { getAddressesByIndices } from '../avalanche-send-transaction/utils/get-addresses-by-indices'; +import { getProvider } from '../../utils/get-provider'; import { rpcErrors } from '@metamask/rpc-errors'; import { Network as GlacierNetwork } from '@avalabs/glacier-sdk'; import type { GetUpgradesInfoResponse } from '@avalabs/avalanchejs/dist/info/model'; @@ -10,6 +11,9 @@ import type { GetUpgradesInfoResponse } from '@avalabs/avalanchejs/dist/info/mod jest.mock('@avalabs/avalanchejs'); jest.mock('@avalabs/core-wallets-sdk'); jest.mock('../avalanche-send-transaction/utils/get-addresses-by-indices'); +jest.mock('../../utils/get-provider'); + +const AVAX_ASSET_ID = 'avaxAssetId'; const mockRequestApproval = jest.fn().mockImplementation(() => ({ success: true })); const mockApprovalController = { @@ -26,6 +30,7 @@ const emptyValueDetails = { totalAvaxInput: 0n, totalAvaxOutput: 0n, totalAvaxBurned: 0n, + isValidAvaxBurnedAmount: true, }; const utxosMock = [{ utxoId: '1' }, { utxoId: '2' }]; @@ -95,6 +100,9 @@ describe('avalanche-sign-transaction', () => { beforeEach(() => { jest.resetAllMocks(); (getAddressesByIndices as jest.Mock).mockResolvedValue([]); + (getProvider as jest.MockedFunction).mockResolvedValue({ + getContext: () => ({ avaxAssetID: AVAX_ASSET_ID }), + } as unknown as Avalanche.JsonRpcProvider); jest.spyOn(info.InfoApi.prototype, 'getUpgradesInfo').mockResolvedValue({} as GetUpgradesInfoResponse); (UnsignedTx.fromJSON as jest.Mock).mockReturnValue(unsignedTxMock); @@ -253,6 +261,41 @@ describe('avalanche-sign-transaction', () => { }); }); + it('returns burn amount checker warning properly when isValidAvaxBurnedAmount is false', async () => { + (Avalanche.parseAvalancheTx as jest.Mock).mockReturnValue({ + ...emptyValueDetails, + isValidAvaxBurnedAmount: false, + type: TxType.AddPermissionlessDelegator, + start: '0', + end: '1000', + }); + mockRequestApproval.mockResolvedValue({ signedData: 'signedData' }); + + await avalancheSignTransaction({ + ...avalancheSignTransactionParams, + request: createRequest({ transactionHex: '0x00001', chainAlias: 'P', from: '123' }), + }); + + expect(mockRequestApproval).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 () => { + mockRequestApproval.mockResolvedValue({ signedData: 'signedData' }); + + await avalancheSignTransaction({ + ...avalancheSignTransactionParams, + request: createRequest({ transactionHex: '0x00001', chainAlias: 'P', from: '123' }), + }); + + expect(mockRequestApproval).toHaveBeenCalledWith( + expect.objectContaining({ displayData: expect.objectContaining({ alert: undefined }) }), + ); + }); + it('works with EVM export transactions', async () => { const request = createRequest({ transactionHex: '0x00001', chainAlias: 'C', from: 'C-avax1234567890' }); mockRequestApproval.mockResolvedValue({ signedData: 'signedData' }); diff --git a/packages/avalanche-module/src/handlers/avalanche-sign-transaction/avalanche-sign-transaction.ts b/packages/avalanche-module/src/handlers/avalanche-sign-transaction/avalanche-sign-transaction.ts index 3ef3d12f..d311c8a5 100644 --- a/packages/avalanche-module/src/handlers/avalanche-sign-transaction/avalanche-sign-transaction.ts +++ b/packages/avalanche-module/src/handlers/avalanche-sign-transaction/avalanche-sign-transaction.ts @@ -19,6 +19,7 @@ import { parseTxDetails } from '../../utils/parse-tx-details'; import { resolveUtxos } from '../../utils/resolve-utxos'; import { getCrossChainRecipients } from '../../utils/get-cross-chain-recipients'; import { getTransactionDetailSections } from '../../utils/get-transaction-detail-sections'; +import { getExcessiveBurnAlert } from '../../utils/get-excessive-burn-alert'; import { parseRequestParams } from './schemas/parse-request-params/parse-request-params'; import { getUnsignedOrPartiallySignedTx } from './util/get-unsigned-or-partially-signed-tx'; @@ -135,6 +136,7 @@ export const avalancheSignTransaction = async ({ network, signerAccount, recipients: getCrossChainRecipients(unsignedOrPartiallySignedTx.getTx(), txDetails, isTestnet), + avaxAssetId: provider.getContext().avaxAssetID, }); // Throw an error if we can't parse the transaction details @@ -157,6 +159,7 @@ export const avalancheSignTransaction = async ({ logoUri: network.logoUri, }, details, + alert: getExcessiveBurnAlert(txDetails), }; // prompt user for approval diff --git a/packages/avalanche-module/src/utils/get-excessive-burn-alert.test.ts b/packages/avalanche-module/src/utils/get-excessive-burn-alert.test.ts new file mode 100644 index 00000000..c10172a0 --- /dev/null +++ b/packages/avalanche-module/src/utils/get-excessive-burn-alert.test.ts @@ -0,0 +1,27 @@ +import { AlertType, type TxValueDetails } from '@avalabs/vm-module-types'; + +import { getExcessiveBurnAlert } from './get-excessive-burn-alert'; + +const valueDetails = (overrides: Partial = {}): TxValueDetails => ({ + outputs: [], + inputAmounts: {}, + outputAmounts: {}, + totalAvaxInput: 0n, + totalAvaxOutput: 0n, + totalAvaxBurned: 0n, + isValidAvaxBurnedAmount: true, + ...overrides, +}); + +describe('getExcessiveBurnAlert', () => { + it('returns burn amount checker warning properly when isValidAvaxBurnedAmount is false', () => { + const alert = getExcessiveBurnAlert(valueDetails({ isValidAvaxBurnedAmount: false })); + + expect(alert).toMatchObject({ type: AlertType.WARNING }); + expect(alert?.details.title).toEqual('Caution!'); + }); + + it('does not return burn amount checker warning when isValidAvaxBurnedAmount is true', () => { + expect(getExcessiveBurnAlert(valueDetails({ isValidAvaxBurnedAmount: true }))).toBeUndefined(); + }); +}); diff --git a/packages/avalanche-module/src/utils/get-excessive-burn-alert.ts b/packages/avalanche-module/src/utils/get-excessive-burn-alert.ts new file mode 100644 index 00000000..d8230ca4 --- /dev/null +++ b/packages/avalanche-module/src/utils/get-excessive-burn-alert.ts @@ -0,0 +1,13 @@ +import { type Alert, AlertType, type TxValueDetails } from '@avalabs/vm-module-types'; + +export const getExcessiveBurnAlert = (tx: TxValueDetails): Alert | undefined => + tx.isValidAvaxBurnedAmount + ? undefined + : { + type: AlertType.WARNING, + details: { + title: 'Caution!', + description: + 'The inputs of this transaction are greater than the outputs. Approving it will burn the difference, which cannot be recovered.', + }, + }; diff --git a/packages/avalanche-module/src/utils/get-transaction-detail-sections.test.ts b/packages/avalanche-module/src/utils/get-transaction-detail-sections.test.ts index 0ee53ffa..7e5cd48c 100644 --- a/packages/avalanche-module/src/utils/get-transaction-detail-sections.test.ts +++ b/packages/avalanche-module/src/utils/get-transaction-detail-sections.test.ts @@ -15,6 +15,7 @@ const emptyValueDetails = { totalAvaxInput: 0n, totalAvaxOutput: 0n, totalAvaxBurned: 0n, + isValidAvaxBurnedAmount: true, }; const fundsOutTransfers = (txDetails: TxDetails, symbol: string) => { diff --git a/packages/avalanche-module/src/utils/get-transaction-detail-sections.ts b/packages/avalanche-module/src/utils/get-transaction-detail-sections.ts index f66b2e45..b6541245 100644 --- a/packages/avalanche-module/src/utils/get-transaction-detail-sections.ts +++ b/packages/avalanche-module/src/utils/get-transaction-detail-sections.ts @@ -17,6 +17,7 @@ import { isSetL1ValidatorWeightTx, isChainDetails, } from '../handlers/avalanche-send-transaction/typeguards'; +import { amountDetailsSections } from './transaction-detail-sections/amount-details-sections'; import { valueDetailsSection } from './transaction-detail-sections/value-details-section'; import { addAutoRenewedValidatorDetailSection, @@ -42,6 +43,7 @@ export type GetTransactionDetailSectionsContext = { signerAccount: string; /** Addresses receiving the funds of a cross-chain transfer - see getExportRecipients. */ recipients?: string[]; + avaxAssetId?: string; }; const _getDetailSectionsByType = ( @@ -115,5 +117,9 @@ export const getTransactionDetailSections = ( const valueSection = valueDetailsSection(txDetails, symbol); - return valueSection ? [...detailSections, valueSection] : detailSections; + return [ + ...detailSections, + ...(valueSection ? [valueSection] : []), + ...amountDetailsSections(txDetails, symbol, context?.avaxAssetId), + ]; }; diff --git a/packages/avalanche-module/src/utils/transaction-detail-sections/amount-details-sections.test.ts b/packages/avalanche-module/src/utils/transaction-detail-sections/amount-details-sections.test.ts new file mode 100644 index 00000000..3b05b7f7 --- /dev/null +++ b/packages/avalanche-module/src/utils/transaction-detail-sections/amount-details-sections.test.ts @@ -0,0 +1,100 @@ +import { DetailItemType, type TxOutput, type TxValueDetails } from '@avalabs/vm-module-types'; +import { amountDetailsSections } from './amount-details-sections'; + +const AVAX_ASSET_ID = 'avaxAssetId'; + +const createOutput = (overrides: Partial = {}): TxOutput => ({ + assetId: AVAX_ASSET_ID, + amount: 100n, + owners: ['X-fuji1recipient'], + locktime: 0n, + stakeableLocktime: 0n, + threshold: 1n, + isAvax: true, + ...overrides, +}); + +const valueDetails = (overrides: Partial = {}): TxValueDetails => ({ + outputs: [], + inputAmounts: {}, + outputAmounts: {}, + totalAvaxInput: 0n, + totalAvaxOutput: 0n, + totalAvaxBurned: 0n, + isValidAvaxBurnedAmount: true, + ...overrides, +}); + +const getSection = (tx: TxValueDetails) => amountDetailsSections(tx, 'AVAX', AVAX_ASSET_ID); + +describe('amountDetailsSections', () => { + it('returns a section for input and output amounts', () => { + const sections = getSection( + valueDetails({ + inputAmounts: { [AVAX_ASSET_ID]: 3n }, + outputAmounts: { [AVAX_ASSET_ID]: 2n }, + }), + ); + + expect(sections.map(({ title }) => title)).toEqual(['Input amounts', 'Output amounts']); + }); + + it('returns no sections when the transaction does not have any transfers', () => { + expect(getSection(valueDetails())).toEqual([]); + }); + + it('returns all assets and their amounts', () => { + const [inputs] = getSection( + valueDetails({ + inputAmounts: { [AVAX_ASSET_ID]: 3n, tokenId: 7n, otherId: 9n }, + }), + ); + + expect(inputs?.items).toHaveLength(3); + }); + + it('uses the proper denomination for the network token', () => { + const [inputs] = getSection(valueDetails({ inputAmounts: { [AVAX_ASSET_ID]: 3n } })); + + expect(inputs?.items[0]).toEqual({ + label: 'AVAX', + type: DetailItemType.CURRENCY, + value: 3n, + maxDecimals: 9, + symbol: 'AVAX', + }); + }); + + it('uses the proper description for non-AVAX assets', () => { + const [inputs] = getSection( + valueDetails({ + inputAmounts: { tokenId: 7n }, + outputs: [ + createOutput({ + isAvax: false, + assetId: 'tokenId', + assetDescription: { assetID: 'tokenId', name: 'Some Token', symbol: 'TKN', denomination: 2 }, + }), + ], + }), + ); + + expect(inputs?.items[0]).toMatchObject({ + label: 'Some Token', + type: DetailItemType.CURRENCY, + value: 7n, + maxDecimals: 2, + symbol: 'TKN', + }); + }); + + it('returns the amount only for assets without a description', () => { + const [inputs] = getSection(valueDetails({ inputAmounts: { tokenId: 7n } })); + + expect(inputs?.items[0]).toMatchObject({ + label: 'tokenId', + type: DetailItemType.TEXT, + value: '7', + }); + }); +}); diff --git a/packages/avalanche-module/src/utils/transaction-detail-sections/amount-details-sections.ts b/packages/avalanche-module/src/utils/transaction-detail-sections/amount-details-sections.ts new file mode 100644 index 00000000..200bd03e --- /dev/null +++ b/packages/avalanche-module/src/utils/transaction-detail-sections/amount-details-sections.ts @@ -0,0 +1,56 @@ +import type { DetailItem, DetailSection, TxOutput, TxValueDetails } from '@avalabs/vm-module-types'; +import { currencyItem, textItem } from '@internal/utils'; +import { AVAX_NONEVM_DENOMINATION } from '../../constants'; + +const INPUT_AMOUNTS = 'Input amounts'; +const OUTPUT_AMOUNTS = 'Output amounts'; + +type AssetDescription = NonNullable; + +const _getDescribedAssets = (outputs: TxOutput[]): Map => + outputs.reduce((assets, output) => { + if (output.assetDescription) { + assets.set(output.assetId, output.assetDescription); + } + + return assets; + }, new Map()); + +const _getAmountItem = ( + assetId: string, + amount: bigint, + symbol: string, + avaxAssetId: string | undefined, + assets: Map, +): DetailItem => { + if (assetId === avaxAssetId) { + return currencyItem(symbol, amount, AVAX_NONEVM_DENOMINATION, symbol); + } + + const asset = assets.get(assetId); + + if (asset) { + return currencyItem(asset.name, amount, asset.denomination, asset.symbol); + } + + return textItem(assetId, amount.toString(), 'vertical'); +}; + +export const amountDetailsSections = (tx: TxValueDetails, symbol: string, avaxAssetId?: string): DetailSection[] => { + const assets = _getDescribedAssets(tx.outputs); + + const section = (title: string, amounts: Record): DetailSection[] => { + const entries = Object.entries(amounts); + + return entries.length === 0 + ? [] + : [ + { + title, + items: entries.map(([assetId, amount]) => _getAmountItem(assetId, amount, symbol, avaxAssetId, assets)), + }, + ]; + }; + + return [...section(INPUT_AMOUNTS, tx.inputAmounts), ...section(OUTPUT_AMOUNTS, tx.outputAmounts)]; +}; diff --git a/packages/avalanche-module/src/utils/transaction-detail-sections/value-details-section.test.ts b/packages/avalanche-module/src/utils/transaction-detail-sections/value-details-section.test.ts index f227ac3c..f8cb3c3c 100644 --- a/packages/avalanche-module/src/utils/transaction-detail-sections/value-details-section.test.ts +++ b/packages/avalanche-module/src/utils/transaction-detail-sections/value-details-section.test.ts @@ -20,6 +20,7 @@ const valueDetails = (overrides: Partial = {}): TxValueDetails = totalAvaxInput: 0n, totalAvaxOutput: 0n, totalAvaxBurned: 0n, + isValidAvaxBurnedAmount: true, ...overrides, }); diff --git a/packages/bitcoin-module/package.json b/packages/bitcoin-module/package.json index 395405d2..3e110e35 100644 --- a/packages/bitcoin-module/package.json +++ b/packages/bitcoin-module/package.json @@ -25,7 +25,7 @@ "dependencies": { "@avalabs/core-coingecko-sdk": "3.1.0-alpha.97", "@avalabs/core-utils-sdk": "3.1.0-alpha.97", - "@avalabs/core-wallets-sdk": "3.1.0-canary.56e4cf95", + "@avalabs/core-wallets-sdk": "3.1.0-canary.303fde90", "@avalabs/crypto-sdk": "1.0.0", "@avalabs/vm-module-types": "workspace:*", "@metamask/rpc-errors": "6.3.0", diff --git a/packages/evm-module/package.json b/packages/evm-module/package.json index 26453eb1..92c647a0 100644 --- a/packages/evm-module/package.json +++ b/packages/evm-module/package.json @@ -28,7 +28,7 @@ "@avalabs/core-etherscan-sdk": "3.1.0-alpha.97", "@avalabs/core-utils-sdk": "3.1.0-alpha.97", "@avalabs/core-chains-sdk": "3.1.0-alpha.97", - "@avalabs/core-wallets-sdk": "3.1.0-canary.56e4cf95", + "@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", diff --git a/packages/hypercore-module/package.json b/packages/hypercore-module/package.json index 01736c62..7d36b909 100644 --- a/packages/hypercore-module/package.json +++ b/packages/hypercore-module/package.json @@ -23,7 +23,7 @@ "test:watch": "jest --watch" }, "dependencies": { - "@avalabs/core-wallets-sdk": "3.1.0-canary.56e4cf95", + "@avalabs/core-wallets-sdk": "3.1.0-canary.303fde90", "@avalabs/crypto-sdk": "1.0.0", "@avalabs/vm-module-types": "workspace:*", "@metamask/rpc-errors": "6.3.0", diff --git a/packages/svm-module/package.json b/packages/svm-module/package.json index 78835390..7befca98 100644 --- a/packages/svm-module/package.json +++ b/packages/svm-module/package.json @@ -25,7 +25,7 @@ "@avalabs/core-chains-sdk": "3.1.0-alpha.97", "@avalabs/core-coingecko-sdk": "3.1.0-alpha.97", "@avalabs/core-utils-sdk": "3.1.0-alpha.97", - "@avalabs/core-wallets-sdk": "3.1.0-canary.56e4cf95", + "@avalabs/core-wallets-sdk": "3.1.0-canary.303fde90", "@avalabs/crypto-sdk": "1.0.0", "@avalabs/vm-module-types": "workspace:*", "@blockaid/client": "0.48.0", diff --git a/packages/types/package.json b/packages/types/package.json index e2c12fc9..a8b2a2f6 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -20,7 +20,7 @@ "lint": "eslint \"src/**/*.ts\"" }, "dependencies": { - "@avalabs/core-wallets-sdk": "3.1.0-canary.56e4cf95", + "@avalabs/core-wallets-sdk": "3.1.0-canary.303fde90", "@avalabs/glacier-sdk": "3.1.0-alpha.97", "@metamask/rpc-errors": "6.3.0", "bitcoinjs-lib": "5.2.0", diff --git a/packages/types/src/staking.ts b/packages/types/src/staking.ts index f03a0e74..53909b9b 100644 --- a/packages/types/src/staking.ts +++ b/packages/types/src/staking.ts @@ -10,6 +10,7 @@ export type TxValueDetails = { totalAvaxInput: bigint; totalAvaxOutput: bigint; totalAvaxBurned: bigint; + isValidAvaxBurnedAmount: boolean; }; export type TxOutput = { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e47f09c7..d97a9b8f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -157,8 +157,8 @@ importers: specifier: 3.1.0-alpha.97 version: 3.1.0-alpha.97(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) '@avalabs/core-wallets-sdk': - specifier: 3.1.0-canary.56e4cf95 - version: 3.1.0-canary.56e4cf95(@solana/sysvars@2.1.0(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2) + specifier: 3.1.0-canary.303fde90 + version: 3.1.0-canary.303fde90(@solana/sysvars@2.1.0(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2) '@avalabs/crypto-sdk': specifier: 1.0.0 version: 1.0.0 @@ -221,8 +221,8 @@ importers: specifier: 3.1.0-alpha.97 version: 3.1.0-alpha.97(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) '@avalabs/core-wallets-sdk': - specifier: 3.1.0-canary.56e4cf95 - version: 3.1.0-canary.56e4cf95(@solana/sysvars@2.1.0(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2) + specifier: 3.1.0-canary.303fde90 + version: 3.1.0-canary.303fde90(@solana/sysvars@2.1.0(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2) '@avalabs/crypto-sdk': specifier: 1.0.0 version: 1.0.0 @@ -288,8 +288,8 @@ importers: specifier: 3.1.0-alpha.97 version: 3.1.0-alpha.97(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) '@avalabs/core-wallets-sdk': - specifier: 3.1.0-canary.56e4cf95 - version: 3.1.0-canary.56e4cf95(@solana/sysvars@2.1.0(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2) + specifier: 3.1.0-canary.303fde90 + version: 3.1.0-canary.303fde90(@solana/sysvars@2.1.0(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2) '@avalabs/crypto-sdk': specifier: 1.0.0 version: 1.0.0 @@ -422,8 +422,8 @@ importers: packages/hypercore-module: dependencies: '@avalabs/core-wallets-sdk': - specifier: 3.1.0-canary.56e4cf95 - version: 3.1.0-canary.56e4cf95(@solana/sysvars@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) + specifier: 3.1.0-canary.303fde90 + version: 3.1.0-canary.303fde90(@solana/sysvars@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) '@avalabs/crypto-sdk': specifier: 1.0.0 version: 1.0.0 @@ -480,8 +480,8 @@ importers: specifier: 3.1.0-alpha.97 version: 3.1.0-alpha.97(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) '@avalabs/core-wallets-sdk': - specifier: 3.1.0-canary.56e4cf95 - version: 3.1.0-canary.56e4cf95(@solana/sysvars@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) + specifier: 3.1.0-canary.303fde90 + version: 3.1.0-canary.303fde90(@solana/sysvars@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) '@avalabs/crypto-sdk': specifier: 1.0.0 version: 1.0.0 @@ -550,8 +550,8 @@ importers: packages/types: dependencies: '@avalabs/core-wallets-sdk': - specifier: 3.1.0-canary.56e4cf95 - version: 3.1.0-canary.56e4cf95(@solana/sysvars@2.1.0(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2) + specifier: 3.1.0-canary.303fde90 + version: 3.1.0-canary.303fde90(@solana/sysvars@2.1.0(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2) '@avalabs/glacier-sdk': specifier: 3.1.0-alpha.97 version: 3.1.0-alpha.97 @@ -611,8 +611,8 @@ packages: '@avalabs/core-chains-sdk@3.1.0-alpha.97': resolution: {integrity: sha512-1UPEU+6zq18ILzWrzPOjUUcU3jwDbASfeYUOBFm6kD6KPOqwmWZJIhV92JiTFUrQsY9j805JTdrNbvAnOspuTQ==} - '@avalabs/core-chains-sdk@3.1.0-canary.56e4cf95': - resolution: {integrity: sha512-a9bYDKHjkE22Baa6nCHERBaXjPt1yJ+aO5l5u2c4wL1QAnsGBBWuGkRQYC24QUA0o9y5xmzHvRjYn87fbcax+Q==} + '@avalabs/core-chains-sdk@3.1.0-canary.303fde90': + resolution: {integrity: sha512-aaeeYCg0zwPXonGJpkuJVaQTx7ugOBXrNo28I5fkxhvjcNr8PoAfVPbqvZL1H1aYZdWuttNUMFXIHC5LJP9C3g==} '@avalabs/core-coingecko-sdk@3.1.0-alpha.97': resolution: {integrity: sha512-9UQTcJ80/wehUkJ6fNqLiTPHaVPae7WKLu1SHeg4JrMW29bU3YKMogqEN6qAYxlha5i8/+1joPwrpglnU8Ai5w==} @@ -627,15 +627,15 @@ packages: bn.js: ^5.2.1 ethers: ^6.7.1 - '@avalabs/core-utils-sdk@3.1.0-canary.56e4cf95': - resolution: {integrity: sha512-MNzGtjBmZf31qf27E9cTcNfigmbzpcsbNCrrf5XcuFlPWMAsFZ4VY/vyU5bZW+twTGyvHZKUJ3z48h2O0D6QEg==} + '@avalabs/core-utils-sdk@3.1.0-canary.303fde90': + resolution: {integrity: sha512-b+FrtKY6ft4/EXJI5GQCPiK0WKbGXIfYfuwXnsXWRwywUUKdOruWqfCnW7BKtBFxDBilcnBoxT+yXI/CTfDLyw==} peerDependencies: big.js: ^6.2.1 bn.js: ^5.2.1 ethers: ^6.7.1 - '@avalabs/core-wallets-sdk@3.1.0-canary.56e4cf95': - resolution: {integrity: sha512-ic5+8aH2SkjCKidzecGdpJ4PXsivsdhEZPfHUtZ2EHisyetxk6/0/4RDRClkgvvMWlyqG6cr1F2OqbWz+15VrA==} + '@avalabs/core-wallets-sdk@3.1.0-canary.303fde90': + resolution: {integrity: sha512-i8YmrAT7LpjFXcIwf1QEZOICvcJVbvIBFaQevn8Zr+ktkGsF7oIZt4ZTInHDYm6dfsLBuqqfz7tdWM+rnkVE4w==} peerDependencies: ethers: ^6.7.1 @@ -654,8 +654,8 @@ packages: '@avalabs/glacier-sdk@3.1.0-alpha.97': resolution: {integrity: sha512-6Kd/j6iKT9LpO8AHWqZJYVtr+EInfsnVJJLQ97RFqY5Q60QoPRfidDtWlxC19Oqv3nHrgreiKAsRtwLSllPNDA==} - '@avalabs/glacier-sdk@3.1.0-canary.56e4cf95': - resolution: {integrity: sha512-PipkLPcg1DcDMi497nX24i6SgLViv8Pf8YNibkgXpbXXTcItSQGmJfXwHLRi81DNaTV6umtMdgrAkJpou5oqFA==} + '@avalabs/glacier-sdk@3.1.0-canary.303fde90': + resolution: {integrity: sha512-mU1Dci+wiUl446wZDyXkTK8+yUrBofZbH4HF2CrgAEONIHoqCkAndc5ViyNcWJfGO9+jSYOrsSW1M1VMV+Q6kg==} '@avalabs/hw-app-avalanche@1.1.1': resolution: {integrity: sha512-c/naZTx763fXn33NgSS9NN02VyoMbxqKPViO3509xgrzlD8tEEx41xigwA5Jn5YO1Q2U9HhhUVr0vyCq9FLoLw==} @@ -6459,9 +6459,9 @@ snapshots: - node-fetch - supports-color - '@avalabs/core-chains-sdk@3.1.0-canary.56e4cf95(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)': + '@avalabs/core-chains-sdk@3.1.0-canary.303fde90(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)': dependencies: - '@avalabs/core-utils-sdk': 3.1.0-canary.56e4cf95(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) + '@avalabs/core-utils-sdk': 3.1.0-canary.303fde90(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) transitivePeerDependencies: - big.js - bn.js @@ -6469,9 +6469,9 @@ snapshots: - node-fetch - supports-color - '@avalabs/core-chains-sdk@3.1.0-canary.56e4cf95(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)': + '@avalabs/core-chains-sdk@3.1.0-canary.303fde90(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)': dependencies: - '@avalabs/core-utils-sdk': 3.1.0-canary.56e4cf95(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) + '@avalabs/core-utils-sdk': 3.1.0-canary.303fde90(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) transitivePeerDependencies: - big.js - bn.js @@ -6531,7 +6531,7 @@ snapshots: - node-fetch - supports-color - '@avalabs/core-utils-sdk@3.1.0-canary.56e4cf95(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)': + '@avalabs/core-utils-sdk@3.1.0-canary.303fde90(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)': dependencies: '@hpke/core': 1.2.5 big.js: 6.2.1 @@ -6542,7 +6542,7 @@ snapshots: - node-fetch - supports-color - '@avalabs/core-utils-sdk@3.1.0-canary.56e4cf95(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)': + '@avalabs/core-utils-sdk@3.1.0-canary.303fde90(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)': dependencies: '@hpke/core': 1.2.5 big.js: 6.2.1 @@ -6553,12 +6553,12 @@ snapshots: - node-fetch - supports-color - '@avalabs/core-wallets-sdk@3.1.0-canary.56e4cf95(@solana/sysvars@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))': + '@avalabs/core-wallets-sdk@3.1.0-canary.303fde90(@solana/sysvars@2.1.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2)(ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))': dependencies: '@avalabs/avalanchejs': 5.1.1-alpha.4 - '@avalabs/core-chains-sdk': 3.1.0-canary.56e4cf95(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) - '@avalabs/core-utils-sdk': 3.1.0-canary.56e4cf95(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) - '@avalabs/glacier-sdk': 3.1.0-canary.56e4cf95 + '@avalabs/core-chains-sdk': 3.1.0-canary.303fde90(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) + '@avalabs/core-utils-sdk': 3.1.0-canary.303fde90(big.js@6.2.1)(bn.js@5.2.2)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) + '@avalabs/glacier-sdk': 3.1.0-canary.303fde90 '@avalabs/hw-app-avalanche': 1.1.1(react@18.3.1) '@ledgerhq/hw-app-btc': 10.11.0 '@ledgerhq/hw-app-eth': 6.45.18(react@18.3.1) @@ -6597,12 +6597,12 @@ snapshots: - typescript - ws - '@avalabs/core-wallets-sdk@3.1.0-canary.56e4cf95(@solana/sysvars@2.1.0(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2)': + '@avalabs/core-wallets-sdk@3.1.0-canary.303fde90(@solana/sysvars@2.1.0(typescript@5.8.2))(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0)(react@18.3.1)(typescript@5.8.2)': dependencies: '@avalabs/avalanchejs': 5.1.1-alpha.4 - '@avalabs/core-chains-sdk': 3.1.0-canary.56e4cf95(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) - '@avalabs/core-utils-sdk': 3.1.0-canary.56e4cf95(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) - '@avalabs/glacier-sdk': 3.1.0-canary.56e4cf95 + '@avalabs/core-chains-sdk': 3.1.0-canary.303fde90(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) + '@avalabs/core-utils-sdk': 3.1.0-canary.303fde90(big.js@6.2.1)(bn.js@5.2.1)(ethers@6.13.5(bufferutil@4.1.0)(utf-8-validate@5.0.10))(node-fetch@2.7.0) + '@avalabs/glacier-sdk': 3.1.0-canary.303fde90 '@avalabs/hw-app-avalanche': 1.1.1(react@18.3.1) '@ledgerhq/hw-app-btc': 10.11.0 '@ledgerhq/hw-app-eth': 6.45.18(react@18.3.1) @@ -6645,7 +6645,7 @@ snapshots: '@avalabs/glacier-sdk@3.1.0-alpha.97': {} - '@avalabs/glacier-sdk@3.1.0-canary.56e4cf95': {} + '@avalabs/glacier-sdk@3.1.0-canary.303fde90': {} '@avalabs/hw-app-avalanche@1.1.1(react@18.3.1)': dependencies: From 89a02d4fdbfeffb9f494823502514296a07a29a4 Mon Sep 17 00:00:00 2001 From: bferenc Date: Fri, 25 Sep 2026 13:27:03 +0200 Subject: [PATCH 5/5] feat: add collapsible group section for value details --- packages-internal/utils/src/index.ts | 1 + .../utils/src/utils/detail-item.ts | 8 ++ .../get-transaction-detail-sections.test.ts | 100 ++++++++++++++---- .../utils/get-transaction-detail-sections.ts | 16 ++- packages/types/src/rpc.ts | 9 +- 5 files changed, 109 insertions(+), 25 deletions(-) diff --git a/packages-internal/utils/src/index.ts b/packages-internal/utils/src/index.ts index f24063a5..8a7c29e7 100644 --- a/packages-internal/utils/src/index.ts +++ b/packages-internal/utils/src/index.ts @@ -8,6 +8,7 @@ export { dataItem, dateItem, transferListItem, + collapsibleGroupItem, } from './utils/detail-item'; export { retry, RetryBackoffPolicy } from './utils/retry'; export { fetchAndVerify } from './utils/fetch-and-verify'; diff --git a/packages-internal/utils/src/utils/detail-item.ts b/packages-internal/utils/src/utils/detail-item.ts index e830fe36..f4a36375 100644 --- a/packages-internal/utils/src/utils/detail-item.ts +++ b/packages-internal/utils/src/utils/detail-item.ts @@ -14,6 +14,8 @@ import { type NetworkItem, type Transfer, type TransferListItem, + type CollapsibleGroupItem, + type DetailSection, } from '@avalabs/vm-module-types'; export const fundsRecipientItem = ( @@ -95,3 +97,9 @@ export const transferListItem = (label: string, value: Transfer[]): TransferList type: DetailItemType.TRANSFER_LIST, value, }); + +export const collapsibleGroupItem = (label: string, value: DetailSection[]): CollapsibleGroupItem => ({ + label, + type: DetailItemType.COLLAPSIBLE_GROUP, + value, +}); diff --git a/packages/avalanche-module/src/utils/get-transaction-detail-sections.test.ts b/packages/avalanche-module/src/utils/get-transaction-detail-sections.test.ts index 7e5cd48c..e5051cfd 100644 --- a/packages/avalanche-module/src/utils/get-transaction-detail-sections.test.ts +++ b/packages/avalanche-module/src/utils/get-transaction-detail-sections.test.ts @@ -18,10 +18,16 @@ const emptyValueDetails = { isValidAvaxBurnedAmount: true, }; -const fundsOutTransfers = (txDetails: TxDetails, symbol: string) => { +const getValueDetailsSection = (txDetails: TxDetails, symbol: string) => { + const items = getTransactionDetailSections(txDetails, symbol)?.flatMap((section) => section.items) ?? []; + const group = items.find((item) => typeof item !== 'string' && item.type === DetailItemType.COLLAPSIBLE_GROUP); + + return group && typeof group !== 'string' && group.type === DetailItemType.COLLAPSIBLE_GROUP ? group.value : []; +}; + +const getTransactionOutputsSection = (txDetails: TxDetails, symbol: string) => { const [item] = - getTransactionDetailSections(txDetails, symbol)?.find((section) => section.title === 'Transaction Outputs') - ?.items ?? []; + getValueDetailsSection(txDetails, symbol).find((section) => section.title === 'Transaction Outputs')?.items ?? []; return typeof item === 'string' || item?.type !== DetailItemType.TRANSFER_LIST ? undefined : item.value; }; @@ -90,21 +96,31 @@ describe('getTransactionDetailSections - Detailed Tests', () => { ], }, { - title: 'Transaction Outputs', items: [ { - label: 'Transaction Outputs', - type: 'transferList', + label: 'Transfer details', + type: 'collapsibleGroup', value: [ { - addresses: ['0xOwner1'], - amount: 100n, - assetId: '0xAssetID', - symbol: 'AVAX', - decimals: 9, - threshold: 1, - lockedUntil: 1, - stakeableLockedUntil: 0, + title: 'Transaction Outputs', + items: [ + { + label: 'Transaction Outputs', + type: 'transferList', + value: [ + { + addresses: ['0xOwner1'], + amount: 100n, + assetId: '0xAssetID', + symbol: 'AVAX', + decimals: 9, + threshold: 1, + lockedUntil: 1, + stakeableLockedUntil: 0, + }, + ], + }, + ], }, ], }, @@ -114,7 +130,51 @@ describe('getTransactionDetailSections - Detailed Tests', () => { expect(details).toEqual(expectedDetails); }); - it('labels a non-AVAX output with the asset the chain described', () => { + it('groups value detail sections into a collapsible section', () => { + const txDetails: TxDetails = { + ...emptyValueDetails, + type: TxType.Base, + chain: NetworkVMType.AVM, + inputAmounts: { '0xAssetID': 3n }, + outputAmounts: { '0xAssetID': 2n }, + outputs: [ + { + amount: 100n, + owners: ['0xOwner1'], + threshold: 1n, + locktime: 0n, + stakeableLocktime: 0n, + isAvax: true, + assetId: '0xAssetID', + }, + ], + txFee: 1n, + }; + + const sections = getTransactionDetailSections(txDetails, networkToken.symbol) ?? []; + + // The three belong to one collapsible item rather than sitting loose beside Chain Details + // and Network Fee, so a client can show and hide them together. + expect(sections.map(({ title }) => title)).toEqual(['Chain Details', 'Network Fee', undefined]); + expect(getValueDetailsSection(txDetails, networkToken.symbol).map(({ title }) => title)).toEqual([ + 'Transaction Outputs', + 'Input amounts', + 'Output amounts', + ]); + }); + + it('skips collapsible group when there are no value details', () => { + const txDetails: TxDetails = { + ...emptyValueDetails, + type: TxType.Base, + chain: NetworkVMType.AVM, + txFee: 1n, + }; + + expect(getValueDetailsSection(txDetails, networkToken.symbol)).toEqual([]); + }); + + it('returns the proper details for a non-AVAX output', () => { const txDetails: TxDetails = { ...emptyValueDetails, type: TxType.Base, @@ -134,13 +194,13 @@ describe('getTransactionDetailSections - Detailed Tests', () => { txFee: 1n, }; - const transfers = fundsOutTransfers(txDetails, networkToken.symbol); + const transfers = getTransactionOutputsSection(txDetails, networkToken.symbol); // Previously rendered as AVAX at AVAX's scale. expect(transfers?.[0]).toMatchObject({ amount: 100n, decimals: 2, symbol: 'TKN', assetName: 'Some Token' }); }); - it('shows the raw amount and asset id for an undescribed non-AVAX output', () => { + it('returns the assetId for an undescribed non-AVAX output', () => { const txDetails: TxDetails = { ...emptyValueDetails, type: TxType.Base, @@ -159,7 +219,7 @@ describe('getTransactionDetailSections - Detailed Tests', () => { txFee: 1n, }; - const transfers = fundsOutTransfers(txDetails, networkToken.symbol); + const transfers = getTransactionOutputsSection(txDetails, networkToken.symbol); // Without a symbol the client shows the raw amount against the asset id, rather than a // number that would only look like a familiar unit. @@ -168,7 +228,7 @@ describe('getTransactionDetailSections - Detailed Tests', () => { expect(transfers?.[0]?.decimals).toBeUndefined(); }); - it('reports both locks on an output, which restrict it in different ways', () => { + it('returns the proper lock times for each output', () => { const txDetails: TxDetails = { ...emptyValueDetails, type: TxType.Base, @@ -187,7 +247,7 @@ describe('getTransactionDetailSections - Detailed Tests', () => { txFee: 1n, }; - const transfers = fundsOutTransfers(txDetails, networkToken.symbol); + const transfers = getTransactionOutputsSection(txDetails, networkToken.symbol); expect(transfers?.[0]).toMatchObject({ lockedUntil: 1700, stakeableLockedUntil: 9900 }); }); diff --git a/packages/avalanche-module/src/utils/get-transaction-detail-sections.ts b/packages/avalanche-module/src/utils/get-transaction-detail-sections.ts index b6541245..62e1cf41 100644 --- a/packages/avalanche-module/src/utils/get-transaction-detail-sections.ts +++ b/packages/avalanche-module/src/utils/get-transaction-detail-sections.ts @@ -1,4 +1,5 @@ -import { type Network, type TxDetails } from '@avalabs/vm-module-types'; +import { type DetailSection, type Network, type TxDetails } from '@avalabs/vm-module-types'; +import { collapsibleGroupItem } from '@internal/utils'; import { isAddAutoRenewedValidatorTx, isAddPermissionlessDelegatorTx, @@ -108,18 +109,25 @@ export const getTransactionDetailSections = ( txDetails: TxDetails, symbol: string, context?: GetTransactionDetailSectionsContext, -) => { +): DetailSection[] | undefined => { const detailSections = _getDetailSectionsByType(txDetails, symbol, context); if (detailSections === undefined) { return undefined; } + // attempt to add value details section to all avalanche transactions const valueSection = valueDetailsSection(txDetails, symbol); - return [ - ...detailSections, + const valueDetails: DetailSection[] = [ ...(valueSection ? [valueSection] : []), ...amountDetailsSections(txDetails, symbol, context?.avaxAssetId), ]; + + if (valueDetails.length === 0) { + return detailSections; + } + + // value details are attached in a collapsible group to the end of the detail sections + return [...detailSections, { items: [collapsibleGroupItem('Transfer details', valueDetails)] }]; }; diff --git a/packages/types/src/rpc.ts b/packages/types/src/rpc.ts index fa9a0239..a65c6650 100644 --- a/packages/types/src/rpc.ts +++ b/packages/types/src/rpc.ts @@ -104,6 +104,7 @@ export enum DetailItemType { LINK = 'link', NETWORK = 'network', TRANSFER_LIST = 'transferList', + COLLAPSIBLE_GROUP = 'collapsibleGroup', } // It's very similar as CurrencyItem, but we want the client apps @@ -188,6 +189,11 @@ export type TransferListItem = BaseDetailItem & { value: Transfer[]; }; +export type CollapsibleGroupItem = BaseDetailItem & { + type: DetailItemType.COLLAPSIBLE_GROUP; + value: DetailSection[]; +}; + export type DetailItem = | string | TextItem @@ -200,7 +206,8 @@ export type DetailItem = | LinkItem | FundsRecipientItem | NetworkItem - | TransferListItem; + | TransferListItem + | CollapsibleGroupItem; export type DisplayData = { title: string;