Splitting an encrypted balance — say, transferring 1/3 of Mira's vesting to a new recipient — looks easy in plaintext: multiply by 1/3. Inside FHEVM, the contract can't see the operands. It can compute FHE.mul(handle, plaintext), but only on integer plaintexts. So the SDK encodes the ratio as a fixed-denominator scaled numerator and lets the contract do the on-chain multiply.
The denominator#
FHE_SPLIT_DENOMINATOR is a public constant (currently 10^18). Any ratio gets scaled by this denominator and rounded down before reaching the contract. The contract performs FHE.mul(handle, scaledNum) / FHE_SPLIT_DENOMINATOR inside its own computation.
The helper#
import { scaleRatio, FHE_SPLIT_DENOMINATOR } from "@tokenops/sdk/fhe";
// 33.3...% split: 1/3 of the original vesting moves to the new recipient.
const ratio = scaleRatio({ numerator: 1n, denominator: 3n });
// ratio.scaledNumerator === FHE_SPLIT_DENOMINATOR / 3 (rounded down)
await manager.splitVesting({
vestingId,
newRecipient,
ratio: ratio.scaledNumerator,
});When ratios fail#
Ratios > 1 throw at the SDK boundary — scaleRatio({ numerator: 4n, denominator: 3n }) is a programming error, not a runtime decision. The split-vesting contract also rejects ratios > 1e18 to refuse over-allocation even if the caller bypasses the SDK.