Інтеграція з Sablier (лінійні платежі)
Стандартний вестинг через смарт-контракт — це cliff + linear unlock: контракт зберігає токени, кожні N секунд розблокує частину. Самописні реалізації роками містять одну ту ж помилку: розрахунок vestedAmount через block.timestamp без врахування затримки мемпулом. Користувач робить claim, бачить очікувану суму в інтерфейсі, відправляє транзакцію — поки вона чекає включення в блок, проходить ще 30 секунд та фактично отримана сума відрізняється. Sablier вирішує цю задачу через зрілий, проаудирований протокол з детермінованим потоком.
Архітектура Sablier v2
Sablier v2 складається з двох основних контрактів:
SablierV2LockupLinear — лінійні потоки від точки A до точки B. Підтримує cliff period (період до початку unlock). Стріміння йде секунда в секунду з моменту start time.
SablierV2LockupDynamic — динамічні потоки з кастомними сегментами. Можна описати довільну криву вестингу: експоненціальну, stepped, будь-яку комбінацію.
Кожен потік — це NFT (ERC-721). Це ключове рішення Sablier: стрім можна передати іншому адресу (transfer stream = transfer право на майбутні платежі), використовувати як залог у lending-протоколі або відображати в NFT-маркетплейсі. Sender може відозвати потік (якщо створений як cancelable) — залишкові токени повертаються.
Створення потоків через контракт
Інтеграція на рівні смарт-контракту — для dApp, який створює потоки програмно (наприклад, DAO виплачує гранти, протокол починає rewards через Sablier замість custom vesting).
import { ISablierV2LockupLinear } from "@sablier/v2-core/interfaces/ISablierV2LockupLinear.sol";
import { LockupLinear, Broker } from "@sablier/v2-core/types/DataTypes.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract VestingManager {
ISablierV2LockupLinear public immutable sablier;
IERC20 public immutable token;
constructor(address _sablier, address _token) {
sablier = ISablierV2LockupLinear(_sablier);
token = IERC20(_token);
}
function createVestingStream(
address recipient,
uint128 totalAmount,
uint40 cliffDuration, // секунди
uint40 totalDuration // секунди
) external returns (uint256 streamId) {
token.approve(address(sablier), totalAmount);
LockupLinear.CreateWithDurations memory params = LockupLinear.CreateWithDurations({
sender: address(this),
recipient: recipient,
totalAmount: totalAmount,
asset: token,
cancelable: true, // DAO може відозвати
transferable: false, // NFT не можна передати (опціонально)
durations: LockupLinear.Durations({
cliff: cliffDuration,
total: totalDuration
}),
broker: Broker({ account: address(0), fee: 0 })
});
streamId = sablier.createWithDurations(params);
}
}
Важливо: totalAmount — це gross amount. Якщо у токена є tax, потрібно враховувати deflationary transfer та передавати totalAmount з запасом, або використовувати transferFrom з перевіркою фактичного received amount.
Інтерфейс управління потоками
Для користувача потрібні:
- Список активних стрімів — поточний прогрес, вже виплачено, доступно до клейму, залишилось
- Withdraw дія — клейм доступних токенів
- Cancel дія (для sender) — відзив стрімса
- Візуалізація прогресу — timeline з cliff та linear фазами
Дані для відображення
Sablier надає subgraph (The Graph) для всіх основних мереж. Запит активних стрімів для адреси:
query GetStreams($recipient: String!) {
streams(
where: { recipient: $recipient, status: STREAMING }
orderBy: startTime
orderDirection: desc
) {
id
asset { symbol, decimals }
depositAmount
withdrawnAmount
startTime
endTime
cliffTime
cancelable
transferable
}
}
Поточний доступний баланс — streamedAmount - withdrawnAmount — рахується через on-chain виклик streamedAmountOf(streamId). Subgraph дає snapshot на момент останньої события, live баланс потрібно читати з контракту.
Прогрес-бар з математикою
Для linear стрімса без cliff прогрес = (now - startTime) / (endTime - startTime). З cliff — до cliff показуємо 0%, після cliff лінійна інтерполяція. Для dynamic стрімів потрібно інтерполювати за сегментами — SDK Sablier надає computeStreamedAmount helper.
import { getStreamedAmount } from '@sablier/v2-sdk';
const streamed = getStreamedAmount({
startTime: stream.startTime,
endTime: stream.endTime,
depositAmount: BigInt(stream.depositAmount),
cliffTime: stream.cliffTime,
currentTime: BigInt(Math.floor(Date.now() / 1000)),
segments: stream.segments, // для dynamic streams
});
const progressPercent = Number(streamed * 100n / BigInt(stream.depositAmount));
Мережі та деплоїди
Sablier v2 задеплоєн на: Ethereum mainnet, Arbitrum, Optimism, Polygon, Avalanche, BNB Chain, Base, Gnosis. Адреси контрактів стабільні — детермінований деплой через Create2 з однаковими адресами на всіх мережах (крім Ethereum mainnet, де адреси дещо відрізняються).
Типові use cases
- Team vesting: 4-year vesting з 1-year cliff для команди та інвесторів
- Grant streaming: DAO стрімить гранти по місяцям замість upfront виплат
- Salary in crypto: безперервний потік USDC як зарплата
- Protocol rewards: заміна custom reward-контракту на Sablier streams
Часові орієнтири
Базова інтеграція (створення стрімів з контракту + UI перегляду) — 3-4 дні. Повнофункціональний інтерфейс з управлінням, візуалізацією та мультисетевою підтримкою — 5-7 днів. Вартість розраховується індивідуально.







