UNPKG

@ledgerhq/coin-tezos

Version:
198 lines (160 loc) 7.06 kB
// SPDX-FileCopyrightText: © 2026 LEDGER SAS // SPDX-License-Identifier: Apache-2.0 import { validatePublicKey, ValidationResult } from '@taquito/utils' import { computeMaxStakeAmount, normalizePublicKeyForAddress, parseTezosTokenAsset, partitionNativeBalance, resolveTezosOperationMode, STAKE_USE_ALL_RESERVE_MUTEZ, } from './utils' describe('parseTezosTokenAsset', () => { it('returns null for native asset', () => { expect(parseTezosTokenAsset({ type: 'native' })).toBeNull() }) it('returns null when asset is undefined', () => { expect(parseTezosTokenAsset(undefined)).toBeNull() }) it('parses KT1-only reference as token id 0', () => { expect( parseTezosTokenAsset({ type: 'token', assetReference: 'KT1CpeSQKdkhWi4pinYcseCFKmDhs5M74BkU', }) ).toEqual({ contractAddress: 'KT1CpeSQKdkhWi4pinYcseCFKmDhs5M74BkU', tokenId: 0, }) }) it('parses contract:tokenId reference', () => { expect( parseTezosTokenAsset({ type: 'token', assetReference: 'KT1CpeSQKdkhWi4pinYcseCFKmDhs5M74BkU:42', }) ).toEqual({ contractAddress: 'KT1CpeSQKdkhWi4pinYcseCFKmDhs5M74BkU', tokenId: 42, }) }) it('returns null for invalid token id suffix', () => { expect( parseTezosTokenAsset({ type: 'token', assetReference: 'KT1CpeSQKdkhWi4pinYcseCFKmDhs5M74BkU:abc', }) ).toBeNull() }) it('returns null when reference does not start with KT1', () => { expect(parseTezosTokenAsset({ type: 'token', assetReference: 'not-a-contract' })).toBeNull() }) }) describe('resolveTezosOperationMode', () => { it('returns send_token for send intent with token asset', () => { expect( resolveTezosOperationMode('send', { type: 'token', assetReference: 'KT1CpeSQKdkhWi4pinYcseCFKmDhs5M74BkU:0', }) ).toBe('send_token') }) it('returns send for send intent with native asset', () => { expect(resolveTezosOperationMode('send', { type: 'native' })).toBe('send') }) it('maps staking intents to real staking modes regardless of asset', () => { expect( resolveTezosOperationMode('stake', { type: 'token', assetReference: 'KT1CpeSQKdkhWi4pinYcseCFKmDhs5M74BkU:0', }) ).toBe('stake') expect(resolveTezosOperationMode('unstake', { type: 'native' })).toBe('unstake') expect(resolveTezosOperationMode('finalize_unstake', { type: 'native' })).toBe( 'finalize_unstake' ) }) it('maps delegation intents to delegation modes', () => { expect(resolveTezosOperationMode('delegate', { type: 'native' })).toBe('delegate') expect(resolveTezosOperationMode('undelegate', { type: 'native' })).toBe('undelegate') }) }) describe('normalizePublicKeyForAddress', () => { it('should normalize hex public key for tz2 address (secp256k1) to base58 format', () => { const tz2Address = 'tz2F4XnSd1wjwWsthemvZQjoPER7NVSt35k3' const hexPublicKey = '03576c19462a7d0cc3d121b1b00e92258b5f71d643c99a599fc1683f03abb7a1c2' const expectedPublicKey = 'sppk7but7h93Ws1XhAPvdBcttVmoBDGHxdpaU8dPy5549f3eLJFAjag' // When: normalize the hex public key to base58 format const result = normalizePublicKeyForAddress(hexPublicKey, tz2Address) // Then: should return the correct base58 encoded public key expect(result).toBe(expectedPublicKey) }) it('should normalize a 33-byte Ledger ED25519 payload (prefix byte + raw key) to a valid edpk', () => { const tz1Address = 'tz1SVgE7L6xzEY2UpQ4UvtPkRDDMwSmcqVqr' const ledgerEd25519Hex = '02b053b74eab7e21c5a8fc1d945ee9fa6dbe2fedb6d47a43299ff33b7f28ea179b' const result = normalizePublicKeyForAddress(ledgerEd25519Hex, tz1Address) expect(result!.startsWith('edpk')).toBe(true) expect(validatePublicKey(result!)).toBe(ValidationResult.VALID) }) it('should normalize a 32-byte raw ED25519 key to a valid edpk', () => { const tz1Address = 'tz1SVgE7L6xzEY2UpQ4UvtPkRDDMwSmcqVqr' const rawEd25519Hex = 'b053b74eab7e21c5a8fc1d945ee9fa6dbe2fedb6d47a43299ff33b7f28ea179b' const result = normalizePublicKeyForAddress(rawEd25519Hex, tz1Address) expect(result!.startsWith('edpk')).toBe(true) expect(validatePublicKey(result!)).toBe(ValidationResult.VALID) }) it('should refuse to encode a malformed tz1 input (e.g. a non-hex b58 string parsed to 0 bytes)', () => { const tz1Address = 'tz1SVgE7L6xzEY2UpQ4UvtPkRDDMwSmcqVqr' const garbage = '3s7xnj2xf2q7au3rrpg7j9k5zf3djfqgbtrswxf8d69vh94hfdtsbpni' const result = normalizePublicKeyForAddress(garbage, tz1Address) expect(result).toBeUndefined() }) it('should refuse to encode a 65-byte SEC1 input for a tz1 (ED25519) address', () => { const tz1Address = 'tz1SVgE7L6xzEY2UpQ4UvtPkRDDMwSmcqVqr' const sec1Hex = '04' + 'ab'.repeat(64) // 65 bytes const result = normalizePublicKeyForAddress(sec1Hex, tz1Address) expect(result).toBeUndefined() }) }) describe('computeMaxStakeAmount', () => { it('subtracts already-staked funds, fees and the reserve', () => { expect(computeMaxStakeAmount(1_000_000n, 300_000n, 0n, 1000n)).toBe( 700_000n - 1000n - STAKE_USE_ALL_RESERVE_MUTEZ ) }) it('uses the full balance when nothing is staked', () => { expect(computeMaxStakeAmount(1_000_000n, 0n, 0n, 1000n)).toBe( 1_000_000n - 1000n - STAKE_USE_ALL_RESERVE_MUTEZ ) }) it('also subtracts unstaked-frozen funds (not re-stakeable until finalized)', () => { expect(computeMaxStakeAmount(1_000_000n, 300_000n, 200_000n, 1000n)).toBe( 500_000n - 1000n - STAKE_USE_ALL_RESERVE_MUTEZ ) }) it('clamps to 0 when staked balance covers the whole balance', () => { expect(computeMaxStakeAmount(300_000n, 300_000n, 0n, 1000n)).toBe(0n) expect(computeMaxStakeAmount(200_000n, 300_000n, 0n, 1000n)).toBe(0n) }) it('clamps to 0 when the liquid balance cannot cover fees and reserve', () => { expect(computeMaxStakeAmount(305_000n, 300_000n, 0n, 1000n)).toBe(0n) }) }) describe('partitionNativeBalance', () => { it('splits total into spendable and locked (staked + unstaked)', () => { expect(partitionNativeBalance(100n, 30n, 10n)).toEqual({ spendable: 60n, locked: 40n }) }) it('treats the whole balance as spendable when nothing is frozen', () => { expect(partitionNativeBalance(100n, 0n, 0n)).toEqual({ spendable: 100n, locked: 0n }) }) it('clamps locked to total (spendable to 0) when frozen funds exceed balance', () => { expect(partitionNativeBalance(50n, 80n, 10n)).toEqual({ spendable: 0n, locked: 50n }) }) it('returns zero spendable when frozen funds exactly equal the balance', () => { expect(partitionNativeBalance(100n, 70n, 30n)).toEqual({ spendable: 0n, locked: 100n }) }) it('locks staked-only and unstaked-only amounts', () => { expect(partitionNativeBalance(100n, 40n, 0n)).toEqual({ spendable: 60n, locked: 40n }) expect(partitionNativeBalance(100n, 0n, 25n)).toEqual({ spendable: 75n, locked: 25n }) }) })