@ledgerhq/coin-tezos
Version:
127 lines (112 loc) • 4.34 kB
text/typescript
// SPDX-FileCopyrightText: © 2026 LEDGER SAS
// SPDX-License-Identifier: Apache-2.0
import type { TezosCoinConfig, TezosContext } from '../config'
import type { OtherBlockOperation } from '@ledgerhq/coin-module-framework/api/types'
import { getBlock } from './getBlock'
/**
* Integration tests for getBlock against Shadownet (current Tezos testnet of record;
* Ghostnet was deprecated in early 2026).
* https://shadownet.tzkt.io
*
* Block 3113219 was chosen because it contains exactly one simple XTZ transfer
* (1 mutez from a tz2 to a tz1), making the assertions deterministic.
*/
const shadownetConfig = (): TezosCoinConfig =>
({
status: { type: 'active' },
baker: { url: 'https://tezos-bakers.api.live.ledger.com' },
explorer: { url: 'https://api.shadownet.tzkt.io', maxTxQuery: 100 },
node: { url: 'https://rpc.shadownet.teztnets.com' },
fees: {
minGasLimit: 0,
minRevealGasLimit: 0,
minStorageLimit: 0,
minFees: 0,
minEstimatedFees: 0,
},
}) as TezosCoinConfig
const context: TezosContext = {
config: async () => shadownetConfig(),
logger: () => undefined,
}
describe('getBlock', () => {
it('should fetch and parse block 3113219 correctly', async () => {
const block = await getBlock(context, 3113219)
expect(block.info).toMatchObject({
hash: 'BLAf8kVQvpsgXNA914eLo3gJKauywozjuravm7cmdvZ9VEM4QyK',
height: 3113219,
time: new Date('2026-04-29T08:27:12Z'),
parent: {
height: 3113218,
hash: 'BLtMGf5iBPGq2c98iXWVX6Z5xK4V227Sm7cCTjyiQ9ufRJRgBmf',
},
})
expect(block.transactions).toHaveLength(1)
const tx0 = block.transactions[0]
expect(tx0.hash).toBe('onktfPphnYKAm7yJ2DLWPn3ZHKmx9U6sAiSYSwrpUUimL6ivbr5')
expect(tx0.failed).toBe(false)
// 1500 (transaction) + 332 (reveal batched in the same hash) = 1832
expect(tx0.fees).toBe(1832n)
expect(tx0.feesPayer).toBe('tz29GPjgeRQTRX6mcPQXkiuHnq7jbya1Abnq')
expect(tx0.operations).toEqual(
expect.arrayContaining([
{
type: 'transfer',
address: 'tz29GPjgeRQTRX6mcPQXkiuHnq7jbya1Abnq',
peer: 'tz1dKrT1h6d7wP8fEzMPptG6er7mLLeQjBBY',
asset: { type: 'native', name: 'XTZ' },
amount: -1n,
},
{
type: 'transfer',
address: 'tz1dKrT1h6d7wP8fEzMPptG6er7mLLeQjBBY',
peer: 'tz29GPjgeRQTRX6mcPQXkiuHnq7jbya1Abnq',
asset: { type: 'native', name: 'XTZ' },
amount: 1n,
},
{
type: 'other',
address: 'tz29GPjgeRQTRX6mcPQXkiuHnq7jbya1Abnq',
ledgerOpType: 'REVEAL',
counter: 14472417,
gasLimit: 581,
storageLimit: 0,
},
])
)
})
})
/**
* Shadownet integration tests for Paris-style staking operations.
*
* Pinned blocks come from a chain-of-custody on `tz1dKrT1...` (faucet-funded test address):
* - 3106279: `stake` 500 XTZ to TF Test Baker
* - 3106307: `unstake` 250 XTZ
* - finalize: pending (`.todo` until the consensus-rights-delay window passes)
*/
const SHADOWNET_STAKER = 'tz1dKrT1h6d7wP8fEzMPptG6er7mLLeQjBBY'
const SHADOWNET_BAKER = 'tz3Q67aMz7gSMiQRcW729sXSfuMtkyAHYfqc'
const STAKE_BLOCK = 3106279
const UNSTAKE_BLOCK = 3106307
describe('getBlock — Shadownet Paris staking ops', () => {
it.each([
[STAKE_BLOCK, 'STAKE', 500_000_000],
[UNSTAKE_BLOCK, 'UNSTAKE', 250_000_000],
])(
'block %s contains a staking op with ledgerOpType=%s and stakedAmount=%s',
async (height, expectedOpType, expectedAmount) => {
const block = await getBlock(context, height)
const stakingTx = block.transactions.find((tx) =>
tx.operations.some((op) => (op as Record<string, unknown>).ledgerOpType === expectedOpType)
)
if (!stakingTx) throw new Error(`No ${expectedOpType} op found in block ${height}`)
expect(stakingTx.feesPayer).toBe(SHADOWNET_STAKER)
const stakingOp = stakingTx.operations[0] as Record<string, unknown>
expect(stakingOp.type).toBe('other')
expect(stakingOp.ledgerOpType).toBe(expectedOpType)
expect(stakingOp.stakedAmount).toBe(expectedAmount)
expect(stakingOp).not.toHaveProperty('delegate')
}
)
it.todo('block <FINALIZE_BLOCK> contains a FINALIZE_UNSTAKE op (enable once it lands)')
})