@ledgerhq/coin-tezos
Version:
109 lines • 4.53 kB
JavaScript
// SPDX-FileCopyrightText: © 2026 LEDGER SAS
// SPDX-License-Identifier: Apache-2.0
import { log } from '@ledgerhq/logs';
import { hasManagerKey } from '../network/types';
import { createTzktApi } from '../network/tzkt';
import { partitionNativeBalance } from '../utils';
import { STAKING_UID_PREFIX } from './positionUid';
export { isDelegationPosition, isFinalizablePosition, isStakePosition, isUnstakingPosition, } from './positionUid';
export function fetchUnstakeRequests(config, address, account) {
if (!hasManagerKey(account))
return Promise.resolve([]);
return (account.unstakedBalance ?? 0) > 0
? createTzktApi(config).getUnstakeRequests(address)
: Promise.resolve([]);
}
function unstakeRequestToStake(address, req) {
// TzKT's status filter is unreliable; never build a position for a finalized request.
if (req.status !== 'pending' && req.status !== 'finalizable') {
log('coin:tezos', 'unstakeRequestToStake: dropping non-active unstake request', {
requestId: req.id,
status: req.status,
});
return null;
}
if (req.actualAmount <= 0) {
log('coin:tezos', 'unstakeRequestToStake: dropping non-positive unstake request', {
requestId: req.id,
actualAmount: req.actualAmount,
});
return null;
}
const createdAt = new Date(req.firstTime);
if (!Number.isFinite(createdAt.getTime())) {
log('coin:tezos', 'unstakeRequestToStake: dropping unstake request with invalid firstTime', {
requestId: req.id,
firstTime: req.firstTime,
});
return null;
}
const isFinalizable = req.status === 'finalizable';
const prefix = isFinalizable ? STAKING_UID_PREFIX.finalizable : STAKING_UID_PREFIX.unstaking;
return {
uid: `${prefix}${req.id}`,
address,
...(req.baker?.address && { delegate: req.baker.address }),
state: isFinalizable ? 'inactive' : 'deactivating',
createdAt,
asset: { type: 'native' },
amount: BigInt(req.actualAmount),
actions: [],
};
}
export function buildStakesForAccount(address, account, unstakeRequests) {
const balance = BigInt(account.balance ?? 0);
const stakedBalance = BigInt(account.stakedBalance ?? 0);
// Account-level unstaked total (the source validateIntent's gate uses), not the summed per-request
// positions below — so spendable stays gate-consistent even if a request is dropped or its fetch fails.
const unstakedBalance = BigInt(account.unstakedBalance ?? 0);
const delegateAddress = account.delegate?.address;
const unstakeStakes = [];
for (const req of unstakeRequests) {
const stake = unstakeRequestToStake(address, req);
if (stake)
unstakeStakes.push(stake);
}
const stakes = [];
if (delegateAddress) {
const { spendable: delegated, locked } = partitionNativeBalance(balance, stakedBalance, unstakedBalance);
if (locked < stakedBalance + unstakedBalance) {
log('coin:tezos', 'buildStakesForAccount: balance < staked + unstaked, clamping to 0', {
address,
balance: balance.toString(),
stakedBalance: stakedBalance.toString(),
unstakedBalance: unstakedBalance.toString(),
});
}
stakes.push({
uid: `${STAKING_UID_PREFIX.delegation}${address}`,
address,
delegate: delegateAddress,
state: 'active',
asset: { type: 'native' },
amount: delegated,
actions: [],
});
}
if (stakedBalance > 0n) {
stakes.push({
uid: `${STAKING_UID_PREFIX.stake}${address}`,
address,
...(delegateAddress && { delegate: delegateAddress }),
state: 'active',
asset: { type: 'native' },
amount: stakedBalance,
actions: [],
});
}
stakes.push(...unstakeStakes);
return stakes;
}
export async function getStakes(context, address, _cursor) {
const config = await context.config();
const accountInfo = await createTzktApi(config).getAccountByAddress(address);
if (!hasManagerKey(accountInfo))
return { items: [] };
const unstakeRequests = await fetchUnstakeRequests(config, address, accountInfo);
return { items: buildStakesForAccount(address, accountInfo, unstakeRequests) };
}
//# sourceMappingURL=getStakes.js.map