Завдання
Circle, яка випускає USDC, відкрила бриф на DoraHacks: 20 мікрогрантів по 500 USDC за живі проєкти у своїй новій мережі Arc. Дві умови: проєкт задеплоєно в Arc mainnet і код відкритий.
Ми хотіли перевірити, чи доводимо смарт-контракт до продакшену за день. Не макет, а контракт з тестами, перевіреним вихідним кодом і справжніми грошима, які через нього пройшли. І отримати маленький, але закінчений продукт у портфоліо: сайт, контракт, бот і докази в блокчейні.
Проблема, яку розвʼязували, проста. Дизайнер намалював логотип для кафе в Берліні і хоче отримати 25 USDC. Звичайний шлях у крипті: прислати клієнту адресу гаманця, пояснити мережу, а клієнту ще знайти монету на комісію. Клієнт, який платить уперше, на цьому кроці часто здається. В Arc комісія платиться в USDC. Якщо у клієнта є 25 USDC, він може оплатити рахунок на 25 USDC, і все.
Навіщо і яка ідея
Ідею вибирав консиліум із пʼяти AI-порадників: скептик, архітектор, візіонер, людина збоку, прагматик. Вердикт: сервіс посилань на оплату з нейтральною назвою, не «Arc Pay», бо це звучить як продукт самої Circle. Викинути все зайве: обмінник валют, x402, AI, свопи. Скептик тоді попередив, що платіжних посилань серед заявок буде багато. Він мав рацію, про це нижче.
Три частини. Головна сторінка: сума, валюта (USDC або EURC), призначення платежу, кнопка «підключити гаманець і створити». Сторінка рахунку за посиланням ?i=номер: видно всім, навіть без гаманця, зі статусом, сумою, хто і коли заплатив, і посиланням на транзакцію. Telegram-бот: продавець пише /watch <адреса>, і бот пише, коли на цю адресу створено рахунок і коли його оплачено. Бот тільки читає мережу, ключів у нього немає.
Кожна дія від імені Дмитра на DoraHacks (публікація питання, відправка заявки, згода з умовами) робилась тільки після його прямого «так».
Хід роботи
| Коли | Що відбувалося |
|---|---|
| Ранок | Консиліум по брифу. Вердикт: посилання на оплату, нейтральна назва, нічого зайвого |
| Ранок | Контракт і 13 тестів, сайт, бот. Чистий гаманець для деплою, чекаємо поповнення |
| 08:50 | Реєстрація на DoraHacks, питання в Q&A про виплати для України |
| 09:50 | Гаманець поповнено на 2,99 USDC. Деплой за 0,034 USDC, рахунок #1 створено і оплачено, код перевірено на Sourcify. Сайт на Vercel, репозиторій відкрито |
| 09:51 | Бот запущено на сервері |
| 10:00 | Автоматичне security-ревʼю знайшло призначення платежу без екранування в повідомленні бота. Виправлено |
| 10:05 | Наскрізна перевірка: рахунок #2 створено і оплачено. Бот спочатку мовчав, полагодили, прийшли обидва сповіщення |
| 10:20 | Заявка на DoraHacks: профіль, опис, анкета з 10 питань. Відправлено по слову Дмитра |
| 12:40 | Розвідка: 334 заявки на 20 місць. Доробка: жива оплата в EURC, відео на 61 секунду |
| 12:55 | Сторінка прискорена з 5+ до 1 секунди, відео і EURC додано в заявку |
Усього 5 комітів, близько 900 рядків коду разом із тестами і скриптами. Робочих днів: один.
Чим робили
Контракт TallyInvoices.sol, 124 рядки на Solidity. У рахунку три стани: відкритий, оплачений, скасований. Скасувати може тільки продавець. Гроші не затримуються на контракті: у тій самій транзакції вони йдуть продавцю. Стан рахунку міняється до переказу, тому повторний вхід знаходить рахунок уже оплаченим. Адреса токена без коду відхиляється, інакше рахунок можна було б «оплатити» викликом, який нічого не переказує. Неправильна сума, повторна оплата і надто довге призначення відкочуються.
Тести у Foundry, 13 штук: серед них атака повторним входом, продавець, який відмовляється приймати гроші, і fuzz-тест суми на 256 прогонів. Вихідний код перевірено на Sourcify з точним збігом байткоду.
Сайт: одна статична сторінка на Vercel, viem зібрано в один файл. Читання мережі йде через свій проксі /api/rpc, який пропускає тільки 10 методів читання і не більше 20 викликів у пачці. Записувати через нього не можна. Бот на Python з web3 і SQLite для підписок, опитує мережу кожні 3 секунди, крутиться службою на нашому сервері.
Ключі: контракт задеплоєно з окремого чистого гаманця, створеного під цей проєкт. Підпис транзакцій локально, ключі в файлах з правами тільки для власника, в репозиторій не потрапляють. Токен бота так само. Сервіс не тримає нічиїх грошей.
Результат
Три справжні оплати в Arc mainnet. Рахунок #1 на 0,50 USDC створено і оплачено з іншого гаманця з різницею в 6 секунд, у продавця рівно +0,50, на контракті 0. Рахунок #2 на 0,25 USDC: бот прислав обидва сповіщення, про створення і про оплату. Рахунок #3 на 0,50 EURC оплачено двома підписами (дозвіл на списання і оплата), бо EURC в Arc звичайний токен ERC-20. У продавця рівно +0,50 EURC.
Комісія за одну оплату у покупця близько 0,0014 USDC, деплой контракту коштував 0,034 USDC. Хеші всіх транзакцій є в README репозиторію. Оплачений рахунок відкривається без гаманця: tally-invoices.vercel.app/?i=1.
Швидкість: сторінка рахунку в чистому браузері відкривалась понад 5 секунд, після збірки viem локально 0,9-1,1 секунди за заміром Playwright.
Слабкі місця кажемо прямо. Реальних користувачів немає, це демо з нашими власними оплатами: лічильник на сайті показував 3 створені рахунки, всі наші. Відриву від інших заявок по функціях теж немає, продукт свідомо маленький.
Помилки і що винесли
"anonymous": false в описі подій контракту, без нього падає з KeyError. На тестах не спливло, спливло тільки на живому прогоні. Додали поле, бот сам дочитав пропущені блоки і прислав обидва сповіщення. Урок: бота перевіряти живою оплатою до подачі, а не по коду..vercel.app, довелось перейти в markdown і перевірити всі 8 посилань у превʼю. Випадаючі списки лишались невидимими і перекривали сусідні поля, поле відео не приймало звичайний ввід. Урок: після такої форми звіряти збережене через API, а не на око. Опис і відео звірили запитом до їхнього API.




