import { Contract, isAddress, keccak256 } from 'ethers'; import { CHAIN, vestedAt, statusOf } from './vesting.js'; import { readTokenMetadata } from './token-metadata.js'; export function publicLink(origin, workspace, schedule = '') { const url = new URL('/vesting.html', origin); url.searchParams.set('workspace', workspace); if (schedule) url.searchParams.set('schedule', String(schedule)); return url.href; } export async function readPublicSchedule(provider, artifact, workspace, id) { if (!isAddress(workspace)) throw Error('Enter a valid workspace contract address, not a wallet or token address.'); if (!/^[1-9]\d*$/.test(String(id)) || BigInt(id) >= 2n ** 256n) throw Error('Enter a positive schedule ID.'); const network = await provider.getNetwork(); if (network.chainId !== BigInt(CHAIN.chainId)) throw Error('The public RPC is on the wrong network.'); const block = await provider.getBlock('latest'); if (!block) throw Error('The latest block is unavailable. Please retry.'); const code = await provider.getCode(workspace, block.number); if (code === '0x' || keccak256(code) !== keccak256(artifact.runtimeBytecode)) throw Error('No matching VEYLOCK workspace found at this address on Robinhood mainnet.'); const contract = new Contract(workspace, artifact.abi, provider); const s = await contract.schedules(id, { blockTag: block.number }); if (s.total === 0n) throw Error('Schedule not found in this workspace. Check the schedule ID.'); const token = new Contract(s.token, ['function decimals() view returns(uint8)', 'function symbol() view returns(string)'], provider); const metadata = await readTokenMetadata({ decimals: () => token.decimals({ blockTag: block.number }), symbol: () => token.symbol({ blockTag: block.number }) }, s.token); const row = { id: String(id), workspace, token: s.token, creator: s.creator, beneficiary: s.beneficiary, total: s.total.toString(), released: s.released.toString(), start: Number(s.start), cliff: Number(s.cliff), end: Number(s.end), ...metadata }; const vested = vestedAt(row, Number(block.timestamp)); return { ...row, vested: String(vested), claimable: String(vested - s.released), status: statusOf(row, Number(block.timestamp)), blockNumber: block.number, blockTime: Number(block.timestamp) }; }