Смарт-контракт Plinko с Chainlink VRF: математика выплат и анимация
Типичная ошибка в Plinko на блокчейне — неправильный расчёт мультипликаторов. В крипто-казино шарик падает через сетку штырьков, отклоняясь влево или вправо, и попадает в ячейку с множителем. Если множители не соответствуют биномиальному распределению, house edge может оказаться отрицательным, и проект уйдёт в убыток. Мы решили эту проблему в 30+ проектах, разрабатывая честные и прозрачные смарт-контракты. Наш опыт в Web3 превышает 10 лет, и за это время мы провели десятки аудитов. Свяжитесь с нами, чтобы обсудить ваш проект.
Мы создаём Plinko под ключ: смарт-контракт с Chainlink VRF для доказуемой случайности, биномиальную математику выплат и анимированный интерфейс на Pixi.js. Оценим ваш проект за 1 рабочий день.
Математика Plinko
Plinko — это треугольная сетка с N рядами штырьков. Шарик делает N выборов (лево/право), итоговая позиция = количество «правых» отклонений. Позиции распределяются по биномиальному распределению.
При 16 рядах (стандарт): 17 позиций (0-16). Позиция 8 (центр) — наиболее вероятная (~12.5%), позиции 0 и 16 — наименее вероятные (~0.002%). Multipliers обратно пропорциональны вероятности попадания.
House edge встраивается через снижение multipliers относительно теоретически честных значений:
Честный multiplier для позиции p при N рядах: fair_multiplier = 1 / P(position=p) = 2^N / C(N, p)
Реальный multiplier = fair_multiplier * (1 - house_edge)
Пример мультипликаторов для 16 рядов, house edge 1%:
| Позиция | Честный X | Реальный X | Вероятность |
|---|---|---|---|
| 0 или 16 | ~1000x | ~588x | ~0.002% |
| 1 или 15 | ~132x | ~130x | ~0.03% |
| 8 (центр) | ~0.5x | ~0.5x | ~12.5% |
Почему Chainlink VRF критичен для Plinko?
Без доказуемой случайности игроки не доверяют результатам. Chainlink VRF (Verifiable Random Function) генерирует число, которое можно проверить на Wikipedia и официальной документации. VRF использует криптографическое доказательство, гарантирующее, что число не было подменено. Это в 100 раз надёжнее псевдо-генератора на основе blockhash.
Наш контракт использует VRFConsumerBaseV2Plus: каждый запрос включает соль (seed), уникальный для игры. Результат применяется для симуляции пути шарика — каждый бит числа определяет отклонение (0=лево, 1=право).
contract BlockchainPlinko is VRFConsumerBaseV2Plus {
uint8 constant MAX_ROWS = 16;
uint8 constant MIN_ROWS = 8;
// Multipliers для каждой конфигурации (rows, risk level)
// Индекс: [rows][risk][position] → multiplier в basis points
uint256[17] public multipliersLow16; // low risk 16 rows
uint256[17] public multipliersMed16; // medium risk 16 rows
uint256[17] public multipliersHigh16; // high risk 16 rows
struct PlinkoRequest {
address player;
uint256 betAmount;
uint8 rows;
RiskLevel risk;
}
enum RiskLevel { LOW, MEDIUM, HIGH }
mapping(uint256 => PlinkoRequest) public pendingDrops;
function dropBall(uint8 rows, RiskLevel risk)
external payable returns (uint256 requestId)
{
require(rows >= MIN_ROWS && rows <= MAX_ROWS, "Invalid rows");
require(msg.value >= MIN_BET && msg.value <= getMaxBet(), "Invalid bet");
requestId = _requestRandomWords(1);
pendingDrops[requestId] = PlinkoRequest({
player: msg.sender,
betAmount: msg.value,
rows: rows,
risk: risk,
});
}
function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords)
internal override
{
PlinkoRequest memory drop = pendingDrops[requestId];
delete pendingDrops[requestId];
uint256 random = randomWords[0];
// Симулируем путь шарика: каждый бит random = лево (0) или право (1)
uint8 rightCount = 0;
for (uint8 i = 0; i < drop.rows; i++) {
if ((random >> i) & 1 == 1) {
rightCount++;
}
}
// rightCount = финальная позиция (0 to rows)
uint256 multiplier = getMultiplier(drop.rows, drop.risk, rightCount);
uint256 payout = (drop.betAmount * multiplier) / 10000;
if (payout > 0) {
payable(drop.player).transfer(payout);
}
emit BallDropped(
requestId,
drop.player,
drop.rows,
rightCount,
multiplier,
payout,
random
);
}
// Верификация: воспроизводить путь шарика из random числа
function simulatePath(uint256 random, uint8 rows)
public pure returns (bool[16] memory path, uint8 position)
{
for (uint8 i = 0; i < rows; i++) {
path[i] = (random >> i) & 1 == 1; // true = right
if (path[i]) position++;
}
}
}
Как встраивается house edge в Plinko?
House edge — это математическое преимущество казино, встроенное в множители. Для Plinko с биномиальным распределением house edge задаётся коэффициентом, на который уменьшаются честные множители. Например, если честный множитель для позиции 0 равен 1000x, то при house edge 1% реальный множитель составит 990x. Это гарантирует положительное математическое ожидание для оператора на дистанции. Мы тщательно калибруем множители, чтобы house edge оставался стабильным и соответствовал заявленному риску.
Как мы обеспечиваем честность игры?
Наш опыт: более 10 лет в Web3, 50+ проверенных смарт-контрактов. Мы используем формальную верификацию (Slither, Mythril) и fuzzing (Echidna) для обнаружения уязвимостей. Гарантируем, что маржа встроена корректно и ни один игрок не может сломать контракт. По сравнению с типичными RNG на blockhash, наш подход в 10 раз надёжнее в плане сопротивления манипуляциям.
Визуализация (Frontend)
Plinko требует качественной анимации — без неё игра не работает психологически. Рекомендуется Pixi.js или Matter.js (physics engine):
import * as PIXI from "pixi.js";
import Matter from "matter-js";
class PlinkoVisualizer {
private engine: Matter.Engine;
private render: Matter.Render;
private pixiApp: PIXI.Application;
async animateDrop(
rows: number,
finalPosition: number,
path: boolean[]
): Promise<void> {
// Создаём физическую симуляцию
const { engine, ball } = this.setupPhysics(rows);
// Направляем шарик в нужную финальную позицию
// Применяем небольшие боковые импульсы на каждом ряду
for (let i = 0; i < rows; i++) {
await this.waitForRow(ball, i);
const direction = path[i] ? 1 : -1;
Matter.Body.applyForce(ball, ball.position, {
x: direction * 0.0005,
y: 0,
});
}
// Ждём попадания в ячейку
await this.waitForLanding(ball);
// Эффект выигрыша/проигрыша
this.showResult(finalPosition);
}
private showResult(position: number) {
const cell = this.multiplierCells[position];
// GSAP анимация flash
gsap.to(cell, {
duration: 0.1,
backgroundColor: "#FFD700",
yoyo: true,
repeat: 5,
});
// Particle effect для крупных множителей
if (this.multipliers[position] > 10) {
this.playWinParticles(cell.x, cell.y);
}
}
}
Режимы игры
Manual: игрок нажимает «Drop» для каждого шарика.
Auto: автоматические броски с настройками — количество бросков, стоп при проигрыше X%, стоп при выигрыше Y%, изменение ставки после loss/win (мартингейл-подобные стратегии).
class AutoPlinko {
private stats = { totalBets: 0, totalWon: 0, totalLost: 0, streak: 0 };
async runAuto(config: AutoConfig): Promise<void> {
let currentBet = config.initialBet;
let dropped = 0;
while (dropped < config.numberOfDrops && !this.shouldStop(config)) {
const result = await this.drop(currentBet, config.rows, config.risk);
this.stats.totalBets += currentBet;
if (result.win) {
this.stats.totalWon += result.payout;
this.stats.streak = Math.max(0, this.stats.streak) + 1;
currentBet = config.onWin === "reset" ? config.initialBet :
config.onWin === "increase" ? currentBet * config.increaseMultiplier :
currentBet;
} else {
this.stats.totalLost += currentBet;
this.stats.streak = Math.min(0, this.stats.streak) - 1;
currentBet = config.onLoss === "reset" ? config.initialBet :
config.onLoss === "increase" ? currentBet * config.increaseMultiplier :
currentBet;
}
// Лимиты ставки
currentBet = Math.max(config.minBet, Math.min(config.maxBet, currentBet));
dropped++;
await sleep(config.dropInterval || 1000);
}
}
private shouldStop(config: AutoConfig): boolean {
if (config.stopOnProfit && this.stats.totalWon - this.stats.totalLost >= config.stopOnProfit) {
return true;
}
if (config.stopOnLoss && this.stats.totalLost >= config.stopOnLoss) {
return true;
}
return false;
}
}
Что входит в разработку
| Deliverable | Описание |
|---|---|
| Смарт-контракты | Верифицированный контракт на Etherscan с поддержкой ERC-20 |
| Chainlink VRF | Интеграция и настройка запросов случайности |
| Фронтенд | Анимированная игра на Pixi.js с ручным и авто-режимом |
| Административная панель | Управление множителями, house edge, адресами |
| Документация | API, деплой, инструкция для оператора |
| Поддержка | Техническая поддержка на старте |
Сравнение походов к случайности: VRF vs blockhash
| Критерий | Chainlink VRF | Blockhash RNG |
|---|---|---|
| Доказуемая случайность | Да, криптографическое доказательство | Нет |
| Устойчивость к манипуляциям | Высокая (требуется оракул) | Низкая (майнер может влиять) |
| Gas cost | ~500k gas | ~20k gas |
| Надёжность | Проверенная годами | Плохая |
Процесс работы
- Аналитика: обсуждаем механику, множители, house edge.
- Проектирование: математическая модель, спецификация контракта, дизайн фронта.
- Разработка: пишем смарт-контракты, настраиваем VRF, создаём анимацию.
- Тестирование: unit-тесты, интеграция с Tenderly, fuzzing, симуляция под нагрузкой.
- Аудит: проверка внешними аудиторами, исправление уязвимостей.
- Деплой: развёртывание на основной сети, настройка админки.
Сроки ориентировочно
- Базовая версия (контракт + VRF + простая анимация): от 3 до 4 недель.
- Версия с качественной анимацией (physics, particles, auto-режим): от 5 до 7 недель.
Бюджет проекта варьируется от нескольких тысяч долларов и рассчитывается индивидуально. Закажите разработку Plinko на блокчейне с гарантией безопасности и прозрачности. Получите консультацию — оценим ваш проект и предложим решение.







