import { isAddress, parseUnits, ZeroAddress } from 'ethers'; export const CHAIN = { chainId: '0x1237', chainName: 'Robinhood Chain', nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, rpcUrls: ['https://rpc.mainnet.chain.robinhood.com'], blockExplorerUrls: ['https://robinhoodchain.blockscout.com'] }; export function vestedAt(s, now = Math.floor(Date.now() / 1000)) { const total = BigInt(s.total); if (now < s.cliff || now <= s.start) return 0n; if (now >= s.end) return total; return total * BigInt(now - s.start) / BigInt(s.end - s.start); } export function statusOf(s, now = Math.floor(Date.now() / 1000)) { if (BigInt(s.released) >= BigInt(s.total)) return 'complete'; return now < s.cliff || now < s.start ? 'locked' : 'active'; } export function validateSchedule(input, decimals, now = Math.floor(Date.now() / 1000)) { if (!isAddress(input.beneficiary) || input.beneficiary === ZeroAddress) throw new Error('Enter a valid, non-zero beneficiary address.'); if (!isAddress(input.token) || input.token === ZeroAddress) throw new Error('Enter a valid token contract address.'); if (!/^\d+(\.\d+)?$/.test(input.amount)) throw new Error('Enter a positive token amount.'); let amount; try { amount = parseUnits(input.amount, decimals); } catch { throw new Error(`This token supports at most ${decimals} decimal places.`); } if (amount <= 0n || amount >= 2n ** 256n) throw new Error('The amount is outside the supported range.'); if (![input.start, input.cliff, input.end].every(Number.isSafeInteger)) throw new Error('Enter all three dates.'); if (input.start < now + 30) throw new Error('Start must be at least 30 seconds in the future.'); if (input.end <= input.start) throw new Error('End must be after the start.'); if (input.cliff < input.start || input.cliff > input.end) throw new Error('Cliff must fall between the start and end.'); return { ...input, total: amount.toString(), decimals }; }